[NeurIPS 2023] Latent Graph Inference with Limited Supervision
import torch
import argparse
from citation_networks import load_citation_network_calculate_starved_nodes
import numpy as np


def Calculate_Number_of_Starved_Nodes(args):
    features, nfeats, labels, nclasses, train_mask, val_mask, test_mask, adj = load_citation_network_calculate_starved_nodes(args.dataset)

    #######  Citeseer 370  #######
    ## train_mask[120:370] = True
    ## val_mask[120:370] = False
    #######   Cora 390    #######
    ## train_mask[140:390] = True
    ## val_mask[140:390] = False

    adj = adj + np.eye(adj.shape[0])
    adj = torch.from_numpy(adj)
    adj1hop = adj[:, train_mask]
    adj1hop = adj1hop.sum(dim=1)
    adj1hop = adj1hop.numpy()
    connect_label = np.sum(adj1hop > 0)
    num_of_1hop_starved_nodes = adj1hop.shape[0] - connect_label
    notcon_label_index = np.where(adj1hop == 0)[0]

    adjpower2 = torch.mm(adj, adj)
    adjpower2np = adjpower2.numpy()
    index = np.argwhere(adjpower2np == 1)
    rows = index[:, 0]
    rows = np.unique(rows)
    label_adjpower2np = adjpower2np[:, train_mask]
    label_index = np.argwhere(label_adjpower2np == 1)
    label_rows = label_index[:, 0]
    label_rows = np.unique(label_rows)
    diff_rows = np.setdiff1d(rows, label_rows)
    interrows = np.intersect1d(notcon_label_index, diff_rows)
    num_of_2hop_starved_nodes = interrows.size

    adjpower3 = torch.mm(adjpower2, adj)
    adjpower3np = adjpower3.numpy()
    index3hop = np.argwhere(adjpower3np == 1)
    rows3ho = index3hop[:, 0]
    rows3ho = np.unique(rows3ho)
    label_adjpower3np = adjpower3np[:, train_mask]
    label_index3hop = np.argwhere(label_adjpower3np == 1)
    label_rows3hop = label_index3hop[:, 0]
    label_rows3hop = np.unique(label_rows3hop)
    diff_rows3hop = np.setdiff1d(rows3ho, label_rows3hop)
    interrows3hop = np.intersect1d(interrows, diff_rows3hop)
    num_of_3hop_starved_nodes = interrows3hop.size

    adjpower4 = torch.mm(adjpower3, adj)
    adjpower4np = adjpower4.numpy()
    index4hop = np.argwhere(adjpower4np == 1)
    rows4ho = index4hop[:, 0]
    rows4ho = np.unique(rows4ho)
    label_adjpower4np = adjpower4np[:, train_mask]
    label_index4hop = np.argwhere(label_adjpower4np == 1)
    label_rows4hop = label_index4hop[:, 0]
    label_rows4hop = np.unique(label_rows4hop)
    diff_rows4hop = np.setdiff1d(rows4ho, label_rows4hop)
    interrows4hop = np.intersect1d(interrows3hop, diff_rows4hop)
    num_of_4hop_starved_nodes = interrows4hop.size

    print("The number of 1-, 2-, 3-, and 4-hop starved nodes on " + args.dataset +
          " dataset are {:0d}, {:0d}, {:0d}, and {:0d}, respectively.".format(num_of_1hop_starved_nodes,
          num_of_2hop_starved_nodes, num_of_3hop_starved_nodes, num_of_4hop_starved_nodes))



if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument('-dataset', type=str, default='cora', help='See choices',
                        choices=['cora', 'citeseer', 'pubmed'])

    args = parser.parse_args()
    Calculate_Number_of_Starved_Nodes(args)

calculate_starved_nodes.py
Jianglin954/LGI-LS
# The MIT License

# Copyright (c) 2016 Thomas Kipf

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

import pickle as pkl
import sys
import warnings

import numpy as np
import scipy.sparse as sp
import torch

warnings.simplefilter("ignore")


def parse_index_file(filename):
    """Parse index file."""
    index = []
    for line in open(filename):
        index.append(int(line.strip()))
    return index


def sample_mask(idx, l):
    """Create mask."""
    mask = np.zeros(l)
    mask[idx] = 1
    return np.array(mask, dtype=np.bool)


def load_citation_network(dataset_str):
    names = ['x', 'y', 'tx', 'ty', 'allx', 'ally', 'graph']
    objects = []
    for i in range(len(names)):
        with open("data_tf/ind.{}.{}".format(dataset_str, names[i]), 'rb') as f:
            if sys.version_info > (3, 0):
                objects.append(pkl.load(f, encoding='latin1'))
            else:
                objects.append(pkl.load(f))
    x, y, tx, ty, allx, ally, graph = tuple(objects)

    test_idx_reorder = parse_index_file("data_tf/ind.{}.test.index".format(dataset_str))
    test_idx_range = np.sort(test_idx_reorder)

    if dataset_str == 'citeseer':
        test_idx_range_full = range(min(test_idx_reorder), max(test_idx_reorder) + 1)
        tx_extended = sp.lil_matrix((len(test_idx_range_full), x.shape[1]))
        tx_extended[test_idx_range - min(test_idx_range), :] = tx
        tx = tx_extended
        ty_extended = np.zeros((len(test_idx_range_full), y.shape[1]))
        ty_extended[test_idx_range - min(test_idx_range), :] = ty
        ty = ty_extended


    features = sp.vstack((allx, tx)).tolil()
    features[test_idx_reorder, :] = features[test_idx_range, :]

    labels = np.vstack((ally, ty))
    labels[test_idx_reorder, :] = labels[test_idx_range, :]
    idx_test = test_idx_range.tolist()
    idx_train = range(len(y))
    idx_val = range(len(y), len(y) + 500)

    train_mask = sample_mask(idx_train, labels.shape[0])
    val_mask = sample_mask(idx_val, labels.shape[0])
    test_mask = sample_mask(idx_test, labels.shape[0])

    features = torch.FloatTensor(features.todense())
    labels = torch.LongTensor(labels)
    train_mask = torch.BoolTensor(train_mask)
    val_mask = torch.BoolTensor(val_mask)
    test_mask = torch.BoolTensor(test_mask)

    nfeats = features.shape[1]
    for i in range(labels.shape[0]):
        sum_ = torch.sum(labels[i])
        if sum_ != 1:
            labels[i] = torch.tensor([1, 0, 0, 0, 0, 0])
    labels = (labels == 1).nonzero()[:, 1]
    nclasses = torch.max(labels).item() + 1

    return features, nfeats, labels, nclasses, train_mask, val_mask, test_mask


def load_citation_network_halftrain(dataset_str):
    names = ['x', 'y', 'tx', 'ty', 'allx', 'ally', 'graph']
    objects = []
    for i in range(len(names)):
        with open("data_tf/ind.{}.{}".format(dataset_str, names[i]), 'rb') as f:
            if sys.version_info > (3, 0):
                objects.append(pkl.load(f, encoding='latin1'))
            else:
                objects.append(pkl.load(f))
    x, y, tx, ty, allx, ally, graph = tuple(objects)
    test_idx_reorder = parse_index_file("data_tf/ind.{}.test.index".format(dataset_str))
    test_idx_range = np.sort(test_idx_reorder)

    if dataset_str == 'citeseer':
        test_idx_range_full = range(min(test_idx_reorder), max(test_idx_reorder) + 1)
        tx_extended = sp.lil_matrix((len(test_idx_range_full), x.shape[1]))
        tx_extended[test_idx_range - min(test_idx_range), :] = tx
        tx = tx_extended
        ty_extended = np.zeros((len(test_idx_range_full), y.shape[1]))
        ty_extended[test_idx_range - min(test_idx_range), :] = ty
        ty = ty_extended

    features = sp.vstack((allx, tx)).tolil()
    features[test_idx_reorder, :] = features[test_idx_range, :]

    labels = np.vstack((ally, ty))
    labels[test_idx_reorder, :] = labels[test_idx_range, :]
    idx_test = test_idx_range.tolist()
    idx_train = range(len(y))
    idx_val = range(len(y), len(y) + 500)

    train_mask = sample_mask(idx_train, labels.shape[0])
    val_mask = sample_mask(idx_val, labels.shape[0])
    test_mask = sample_mask(idx_test, labels.shape[0])

    features = torch.FloatTensor(features.todense())
    labels = torch.LongTensor(labels)
    train_mask = torch.BoolTensor(train_mask)
    val_mask = torch.BoolTensor(val_mask)
    test_mask = torch.BoolTensor(test_mask)

    nfeats = features.shape[1]
    for i in range(labels.shape[0]):
        sum_ = torch.sum(labels[i])
        if sum_ != 1:
            labels[i] = torch.tensor([1, 0, 0, 0, 0, 0])
    labels = (labels == 1).nonzero()[:, 1]
    nclasses = torch.max(labels).item() + 1

    if dataset_str == 'pubmed':
        colum_sum = torch.zeros((1, 3))
    elif dataset_str == 'cora':
        colum_sum = torch.zeros((1, 7))
    elif dataset_str == 'citeseer':
        colum_sum = torch.zeros((1, 6))

    for iii in range(y.shape[0]):
        colum_sum = colum_sum + y[iii, :]
        if colum_sum.max() > 10:
            colum_sum = colum_sum - y[iii, :]
            train_mask[iii] = 0

    return features, nfeats, labels, nclasses, train_mask, val_mask, test_mask





def load_citation_network_calculate_starved_nodes(dataset_str):
    names = ['x', 'y', 'tx', 'ty', 'allx', 'ally', 'graph']
    objects = []
    for i in range(len(names)):
        with open("data_tf/ind.{}.{}".format(dataset_str, names[i]), 'rb') as f:
            if sys.version_info > (3, 0):
                objects.append(pkl.load(f, encoding='latin1'))
            else:
                objects.append(pkl.load(f))
    x, y, tx, ty, allx, ally, graph = tuple(objects)

    num_vertices = len(graph)

    adjacency = [[0 for j in range(num_vertices)] for i in range(num_vertices)]

    for i in range(num_vertices):
        for j in graph[i]:
            adjacency[i][j] = 1

    adjacency = np.array(adjacency)



    test_idx_reorder = parse_index_file("data_tf/ind.{}.test.index".format(dataset_str))
    test_idx_range = np.sort(test_idx_reorder)

    if dataset_str == 'citeseer':
        # Fix citeseer dataset (there are some isolated nodes in the graph)
        # Find isolated nodes, add them as zero-vecs into the right position
        test_idx_range_full = range(min(test_idx_reorder), max(test_idx_reorder) + 1)
        tx_extended = sp.lil_matrix((len(test_idx_range_full), x.shape[1]))
        tx_extended[test_idx_range - min(test_idx_range), :] = tx
        tx = tx_extended
        ty_extended = np.zeros((len(test_idx_range_full), y.shape[1]))
        ty_extended[test_idx_range - min(test_idx_range), :] = ty
        ty = ty_extended


    features = sp.vstack((allx, tx)).tolil()
    features[test_idx_reorder, :] = features[test_idx_range, :]

    labels = np.vstack((ally, ty))
    labels[test_idx_reorder, :] = labels[test_idx_range, :]
    idx_test = test_idx_range.tolist()
    idx_train = range(len(y))
    idx_val = range(len(y), len(y) + 500)

    train_mask = sample_mask(idx_train, labels.shape[0])
    val_mask = sample_mask(idx_val, labels.shape[0])
    test_mask = sample_mask(idx_test, labels.shape[0])

    features = torch.FloatTensor(features.todense())
    labels = torch.LongTensor(labels)
    train_mask = torch.BoolTensor(train_mask)
    val_mask = torch.BoolTensor(val_mask)
    test_mask = torch.BoolTensor(test_mask)

    nfeats = features.shape[1]
    for i in range(labels.shape[0]):  
        sum_ = torch.sum(labels[i])
        if sum_ != 1:
            labels[i] = torch.tensor([1, 0, 0, 0, 0, 0])
    labels = (labels == 1).nonzero()[:, 1]
    nclasses = torch.max(labels).item() + 1

    return features, nfeats, labels, nclasses, train_mask, val_mask, test_mask, adjacency


citation_networks.py
Jianglin954/LGI-LS
import warnings
import torch

from citation_networks import load_citation_network, sample_mask

warnings.simplefilter("ignore")


def load_ogb_data(dataset_str):
    from ogb.nodeproppred.dataset_pyg import PygNodePropPredDataset
    dataset = PygNodePropPredDataset(dataset_str)

    data = dataset[0]
    features = data.x
    nfeats = data.num_features
    nclasses = dataset.num_classes
    labels = data.y

    split_idx = dataset.get_idx_split()

    train_mask = sample_mask(split_idx['train'], data.x.shape[0])
    val_mask = sample_mask(split_idx['valid'], data.x.shape[0])
    test_mask = sample_mask(split_idx['test'], data.x.shape[0])

    features = torch.FloatTensor(features)
    labels = torch.LongTensor(labels).view(-1)
    train_mask = torch.BoolTensor(train_mask)
    val_mask = torch.BoolTensor(val_mask)
    test_mask = torch.BoolTensor(test_mask)

    return features, nfeats, labels, nclasses, train_mask, val_mask, test_mask


def load_data(args):
    dataset_str = args.dataset

    if dataset_str.startswith('ogb'):
        return load_ogb_data(dataset_str)

    return load_citation_network(dataset_str)

data_loader.py
Jianglin954/LGI-LS
import torch
import torch.nn as nn

class GCNConv_dense(nn.Module):
    def __init__(self, input_size, output_size):
        super(GCNConv_dense, self).__init__()
        self.linear = nn.Linear(input_size, output_size)

    def init_para(self):
        self.linear.reset_parameters()

    def forward(self, input, A, sparse=False):
        hidden = self.linear(input)
        if sparse:
            output = torch.sparse.mm(A, hidden)
        else:
            output = torch.matmul(A, hidden)
        return output
layers.py
Jianglin954/LGI-LS
import argparse
import random
from data_loader import load_data
from citation_networks import load_citation_network_halftrain
from model import GCN
from utils import *

class Experiment:
    def __init__(self):
        super(Experiment, self).__init__()

    def get_loss_fixed_adj(self, model, mask, features, labels):
        logits = model(features)
        logp = F.log_softmax(logits, 1)
        loss = F.nll_loss(logp[mask], labels[mask], reduction='mean')
        accu = accuracy(logp[mask], labels[mask])
        return loss, accu

    def half_val_as_train(self, val_mask, train_mask):
        val_size = np.count_nonzero(val_mask)
        counter = 0
        for i in range(len(val_mask)):
            if val_mask[i] and counter < val_size / 2:
                counter += 1
                val_mask[i] = False
                train_mask[i] = True
        return val_mask, train_mask


    def GCN_KNN(self, args):

        if args.half_train:
            print("Using half of labeled nodes for training!")
            features, nfeats, labels, nclasses, train_mask, val_mask, test_mask = load_citation_network_halftrain(args.dataset)
        else:
            features, nfeats, labels, nclasses, train_mask, val_mask, test_mask = load_data(args)

        Adj = torch.from_numpy(nearest_neighbors(features, args.k, args.knn_metric)).cuda()
        Adj = normalize(Adj, args.normalization, args.sparse)

        if torch.cuda.is_available():
            features = features.cuda()

        if args.half_val_as_train:
            val_mask, train_mask = self.half_val_as_train(val_mask, train_mask)


        test_accu = []
        for trial in range(args.ntrials):
            model = GCN(in_channels=nfeats, hidden_channels=args.hidden, out_channels=nclasses, num_layers=args.nlayers,
                        dropout=args.dropout2, dropout_adj=args.dropout_adj2, Adj=Adj, sparse=args.sparse)
            optimizer = torch.optim.Adam(model.parameters(), lr=args.lr, weight_decay=args.w_decay)

            best_test_accu = 0
            counter = 0
            if torch.cuda.is_available():
                model = model.cuda()
                train_mask = train_mask.cuda()
                val_mask = val_mask.cuda()
                test_mask = test_mask.cuda()
                features = features.cuda()
                labels = labels.cuda()

            for epoch in range(1, args.epochs + 1):
                model.train()
                loss, accu = self.get_loss_fixed_adj(model, train_mask, features, labels)
                optimizer.zero_grad()
                loss.backward()

                optimizer.step()

                if epoch % 1 == 0:
                    with torch.no_grad():
                        model.eval()
                        test_loss_, test_accu_ = self.get_loss_fixed_adj(model, test_mask, features, labels)
                        if epoch % 100 == 0:
                            print("Epoch {:04d}: Test Loss {:.4f}, Test Accuracy {:.4f}".format(epoch, test_loss_, test_accu_))
                        if test_accu_ > best_test_accu:
                            counter = 0
                            best_test_accu = test_accu_
                        else:
                            counter += 1
                        if counter >= args.patience:
                            break

            with torch.no_grad():
                model.eval()
                print("*******************************")
                print("Trial {:02d}: test accuracy {:.4f}".format(trial, best_test_accu))
                print("*******************************")
                test_accu.append(best_test_accu.item())

        print(test_accu)
        print("std of test accuracy", np.std(test_accu) * 100)
        print("average of test accuracy", np.mean(test_accu) * 100)


    def GCN_KNN_U(self, args):

        if args.half_train:
            print("Using half of labeled nodes for training!")
            features, nfeats, labels, nclasses, train_mask, val_mask, test_mask = load_citation_network_halftrain(args.dataset)
        else:
            features, nfeats, labels, nclasses, train_mask, val_mask, test_mask = load_data(args)

        Adj = torch.from_numpy(nearest_neighbors(features, args.k, args.knn_metric)).cuda()



        if torch.cuda.is_available():
            features = features.cuda()

        if args.half_val_as_train:
            val_mask, train_mask = self.half_val_as_train(val_mask, train_mask)



        CUR_C = Adj.clone()[:, train_mask]
        row_mask = torch.sum(CUR_C, dim=1) > 0
        asy_similarities = cal_similarity_graph(features, features[train_mask, :])
        asy_similarities = top_k(asy_similarities, args.klabel)
        asy_similarities[train_mask, :] = 0.0   # Empirically, we found that train_mask works better!!
        # asy_similarities[row_mask, :] = 0.0


        Adj[:, train_mask] = Adj[:, train_mask] + args.alpha * asy_similarities
        Adj = normalize(Adj, args.normalization, args.sparse)

        test_accu = []
        for trial in range(args.ntrials):
            model = GCN(in_channels=nfeats, hidden_channels=args.hidden, out_channels=nclasses, num_layers=args.nlayers,
                        dropout=args.dropout2, dropout_adj=args.dropout_adj2, Adj=Adj, sparse=args.sparse)
            optimizer = torch.optim.Adam(model.parameters(), lr=args.lr, weight_decay=args.w_decay)

            best_test_accu = 0
            counter = 0
            if torch.cuda.is_available():
                model = model.cuda()
                train_mask = train_mask.cuda()
                val_mask = val_mask.cuda()
                test_mask = test_mask.cuda()
                features = features.cuda()
                labels = labels.cuda()

            for epoch in range(1, args.epochs + 1):
                model.train()
                loss, accu = self.get_loss_fixed_adj(model, train_mask, features, labels)
                optimizer.zero_grad()
                loss.backward()

                optimizer.step()

                if epoch % 1 == 0:
                    with torch.no_grad():
                        model.eval()
                        test_loss_, test_accu_ = self.get_loss_fixed_adj(model, test_mask, features, labels)
                        if epoch % 100 == 0:
                            print("Epoch {:04d}: Test Loss {:.4f}, Test Accuracy {:.4f}".format(epoch, test_loss_, test_accu_))
                        if test_accu_ > best_test_accu:
                            counter = 0
                            best_test_accu = test_accu_
                        else:
                            counter += 1
                        if counter >= args.patience:
                            break

            with torch.no_grad():
                model.eval()
                print("*******************************")
                print("Trial {:02d}: test accuracy {:.4f}".format(trial, best_test_accu))
                print("*******************************")
                test_accu.append(best_test_accu.item())

        print(test_accu)
        print("std of test accuracy", np.std(test_accu) * 100)
        print("average of test accuracy", np.mean(test_accu) * 100)



    def GCN_KNN_R(self, args):

        if args.half_train:
            print("Using half of labeled nodes for training!")
            features, nfeats, labels, nclasses, train_mask, val_mask, test_mask = load_citation_network_halftrain(args.dataset)
        else:
            features, nfeats, labels, nclasses, train_mask, val_mask, test_mask = load_data(args)

        Adj = torch.from_numpy(nearest_neighbors(features, args.k, args.knn_metric)).cuda()



        if torch.cuda.is_available():
            features = features.cuda()

        if args.half_val_as_train:
            val_mask, train_mask = self.half_val_as_train(val_mask, train_mask)

        asy_similarities = cal_similarity_graph(features, features[train_mask, :])
        asy_similarities = top_k(asy_similarities, args.klabel)

        Adj[:, train_mask] = Adj[:, train_mask] + args.alpha * asy_similarities
        Adj = normalize(Adj, args.normalization, args.sparse)

        test_accu = []
        for trial in range(args.ntrials):
            model = GCN(in_channels=nfeats, hidden_channels=args.hidden, out_channels=nclasses, num_layers=args.nlayers,
                        dropout=args.dropout2, dropout_adj=args.dropout_adj2, Adj=Adj, sparse=args.sparse)
            optimizer = torch.optim.Adam(model.parameters(), lr=args.lr, weight_decay=args.w_decay)

            best_test_accu = 0
            counter = 0
            if torch.cuda.is_available():
                model = model.cuda()
                train_mask = train_mask.cuda()
                val_mask = val_mask.cuda()
                test_mask = test_mask.cuda()
                features = features.cuda()
                labels = labels.cuda()

            for epoch in range(1, args.epochs + 1):
                model.train()
                loss, accu = self.get_loss_fixed_adj(model, train_mask, features, labels)
                optimizer.zero_grad()
                loss.backward()

                optimizer.step()

                if epoch % 1 == 0:
                    with torch.no_grad():
                        model.eval()
                        test_loss_, test_accu_ = self.get_loss_fixed_adj(model, test_mask, features, labels)
                        if epoch % 100 == 0:
                            print("Epoch {:04d}: Test Loss {:.4f}, Test Accuracy {:.4f}".format(epoch, test_loss_, test_accu_))
                        if test_accu_ > best_test_accu:
                            counter = 0
                            best_test_accu = test_accu_
                        else:
                            counter += 1
                        if counter >= args.patience:
                            break


            with torch.no_grad():
                model.eval()
                print("Trial {:02d}: test accuracy {:.4f}".format(trial, best_test_accu))
                test_accu.append(best_test_accu.item())

        print(test_accu)
        print("std of test accuracy", np.std(test_accu) * 100)
        print("average of test accuracy", np.mean(test_accu) * 100)




if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument('-dataset', type=str, default='pubmed', help='See choices',
                        choices=['cora', 'citeseer', 'pubmed', 'ogbn-arxiv'])
    parser.add_argument('-ntrials', type=int, default=5, help='Number of trials')
    parser.add_argument('-epochs', type=int, default=2000, help='Number of epochs to train.')
    parser.add_argument('-lr', type=float, default=0.01, help='Initial learning rate.')
    parser.add_argument('-w_decay', type=float, default=0.0005, help='Weight decay (L2 loss on parameters).')
    parser.add_argument('-hidden', type=int, default=32, help='Number of hidden units.')
    parser.add_argument('-nlayers', type=int, default=2, help='#layers')
    parser.add_argument('-k', type=int, default=15, help='k for initializing with knn')
    parser.add_argument('-knn_metric', type=str, default='cosine', help='See choices', choices=['cosine', 'minkowski'])
    parser.add_argument('-half_val_as_train', type=int, default=0, help='use first half of validation for training')
    parser.add_argument('-half_train', type=int, default=0, help='use half of labeled nodes for training')
    parser.add_argument('-normalization', type=str, default='sym')
    parser.add_argument('-sparse', type=int, default=0)
    parser.add_argument('-patience', type=int, default=3000, help='patience for early stopping')
    parser.add_argument('-method', type=str, default='GCN_KNN', help='See choices',
                        choices=['GCN_KNN', 'GCN_KNN_U', 'GCN_KNN_R'])
    experiment = Experiment()


    def set_seed(seed):
        random.seed(seed)
        np.random.seed(seed)
        torch.manual_seed(seed)
        if torch.cuda.is_available():
            torch.cuda.manual_seed(seed)
            torch.cuda.manual_seed_all(seed)
    set_seed(42)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = True

    parser.add_argument('-dropout2', type=float, default=0.5, help='Dropout rate in GCN.')
    parser.add_argument('-dropout_adj2', type=float, default=0., help='Dropout rate GCN.')
    parser.add_argument('-alpha', type=float, default=100, help='control the contribution of asy and sys similarity')
    parser.add_argument('-klabel', type=int, default=30, help='k_label for asymmetric similarity')
    args = parser.parse_args()

    if args.method == "GCN_KNN":
        experiment.GCN_KNN(args)
    elif args.method == "GCN_KNN_U":
        experiment.GCN_KNN_U(args)
    elif args.method == "GCN_KNN_R":
        experiment.GCN_KNN_R(args)
    print(args)
main.py
Jianglin954/LGI-LS
import torch.nn as nn
import torch.nn.functional as F
from layers import GCNConv_dense

class GCN(nn.Module):
    def __init__(self, in_channels, hidden_channels, out_channels, num_layers, dropout, dropout_adj, Adj, sparse):
        super(GCN, self).__init__()
        self.layers = nn.ModuleList()
        self.layers.append(GCNConv_dense(in_channels, hidden_channels))
        for i in range(num_layers - 2):
            self.layers.append(GCNConv_dense(hidden_channels, hidden_channels))
        self.layers.append(GCNConv_dense(hidden_channels, out_channels))

        self.dropout = dropout
        self.dropout_adj = nn.Dropout(p=dropout_adj)
        self.dropout_adj_p = dropout_adj
        self.Adj = Adj
        self.Adj.requires_grad = False
        self.sparse = sparse

    def forward(self, x):

        if self.sparse:
            Adj = self.Adj
            Adj.edata['w'] = F.dropout(Adj.edata['w'], p=self.dropout_adj_p, training=self.training)
        else:
            Adj = self.dropout_adj(self.Adj)

        for i, conv in enumerate(self.layers[:-1]):
            x = conv(x, Adj)
            x = F.relu(x)
            x = F.dropout(x, p=self.dropout, training=self.training)
        x = self.layers[-1](x, Adj)
        return x
model.py
Jianglin954/LGI-LS
import numpy as np
import scipy.sparse as sp
import torch
import torch.nn.functional as F
from sklearn.neighbors import kneighbors_graph

EOS = 1e-10

def apply_non_linearity(tensor, non_linearity, i):
    if non_linearity == 'elu':
        return F.elu(tensor * i - i) + 1
    elif non_linearity == 'relu':
        return F.relu(tensor)
    elif non_linearity == 'none':
        return tensor
    else:
        raise NameError('We dont support the non-linearity yet')


def get_random_mask(features, r, nr):
    nones = torch.sum(features > 0.0).float()
    nzeros = features.shape[0] * features.shape[1] - nones
    pzeros = nones / nzeros / r * nr
    probs = torch.zeros(features.shape).cuda()
    probs[features == 0.0] = pzeros
    probs[features > 0.0] = 1 / r
    mask = torch.bernoulli(probs)
    return mask


def get_random_mask_ogb(features, r):
    probs = torch.full(features.shape, 1 / r)
    mask = torch.bernoulli(probs)
    return mask


def accuracy(preds, labels):
    pred_class = torch.max(preds, 1)[1]
    return torch.sum(torch.eq(pred_class, labels)).float() / labels.shape[0]


def nearest_neighbors(X, k, metric):
    adj = kneighbors_graph(X, k, metric=metric)
    adj = np.array(adj.todense(), dtype=np.float32)
    adj += np.eye(adj.shape[0])
    return adj


def nearest_neighbors_sparse(X, k, metric):
    adj = kneighbors_graph(X, k, metric=metric)
    loop = np.arange(X.shape[0])
    [s_, d_, val] = sp.find(adj)
    s = np.concatenate((s_, loop))
    d = np.concatenate((d_, loop))

    return s, d


def nearest_neighbors_pre_exp(X, k, metric, i):
    adj = kneighbors_graph(X, k, metric=metric)
    adj = np.array(adj.todense(), dtype=np.float32)
    adj += np.eye(adj.shape[0])
    adj = adj * i - i
    return adj


def nearest_neighbors_pre_elu(X, k, metric, i):
    adj = kneighbors_graph(X, k, metric=metric)
    adj = np.array(adj.todense(), dtype=np.float32)
    adj += np.eye(adj.shape[0])
    adj = adj * i - i
    return adj


def normalize(adj, mode, sparse=False):
    if not sparse:
        if mode == "sym":
            inv_sqrt_degree = 1. / (torch.sqrt(adj.sum(dim=1, keepdim=False)) + EOS)
            return inv_sqrt_degree[:, None] * adj * inv_sqrt_degree[None, :]
        elif mode == "row":
            inv_degree = 1. / (adj.sum(dim=1, keepdim=False) + EOS)
            return inv_degree[:, None] * adj
        else:
            exit("wrong norm mode")
    else:
        adj = adj.coalesce()
        if mode == "sym":
            inv_sqrt_degree = 1. / (torch.sqrt(torch.sparse.sum(adj, dim=1).values()))
            D_value = inv_sqrt_degree[adj.indices()[0]] * inv_sqrt_degree[adj.indices()[1]]

        elif mode == "row":
            inv_degree = 1. / (torch.sparse.sum(adj, dim=1).values() + EOS)
            D_value = inv_degree[adj.indices()[0]]
        else:
            exit("wrong norm mode")
        new_values = adj.values() * D_value

        return torch.sparse.FloatTensor(adj.indices(), new_values, adj.size())


def symmetrize(adj):  # only for non-sparse
    return (adj + adj.T) / 2


def cal_similarity_graph(node_embeddings1, node_embeddings2):
    similarity_graph = torch.mm(node_embeddings1, node_embeddings2.t())
    return similarity_graph


def top_k(raw_graph, K):
    values, indices = raw_graph.topk(k=int(K), dim=-1)
    assert torch.max(indices) < raw_graph.shape[1]
    mask = torch.zeros(raw_graph.shape).cuda()
    mask[torch.arange(raw_graph.shape[0]).view(-1, 1), indices] = 1.

    mask.requires_grad = False
    sparse_graph = raw_graph * mask
    return sparse_graph


def knn_fast(X, k, b):
    X = F.normalize(X, dim=1, p=2)
    index = 0
    values = torch.zeros(X.shape[0] * (k + 1)).cuda()
    rows = torch.zeros(X.shape[0] * (k + 1)).cuda()
    cols = torch.zeros(X.shape[0] * (k + 1)).cuda()
    norm_row = torch.zeros(X.shape[0]).cuda()
    norm_col = torch.zeros(X.shape[0]).cuda()
    while index < X.shape[0]:
        if (index + b) > (X.shape[0]):
            end = X.shape[0]
        else:
            end = index + b
        sub_tensor = X[index:index + b]
        similarities = torch.mm(sub_tensor, X.t())
        vals, inds = similarities.topk(k=k + 1, dim=-1)
        values[index * (k + 1):(end) * (k + 1)] = vals.view(-1)
        cols[index * (k + 1):(end) * (k + 1)] = inds.view(-1)
        rows[index * (k + 1):(end) * (k + 1)] = torch.arange(index, end).view(-1, 1).repeat(1, k + 1).view(-1)
        norm_row[index: end] = torch.sum(vals, dim=1)
        norm_col.index_add_(-1, inds.view(-1), vals.view(-1))
        index += b
    norm = norm_row + norm_col
    rows = rows.long()
    cols = cols.long()
    values *= (torch.pow(norm[rows], -0.5) * torch.pow(norm[cols], -0.5))
    return rows, cols, values

utils.py
Jianglin954/LGI-LS