Explanation, source code and interpretation of GCN source code comments

Explanation, source code and interpretation of GCN source code comments

1. utils.py

import numpy as np
import pickle as pkl
import networkx as nx
import scipy.sparse as sp
from scipy.sparse.linalg.eigen.arpack import eigsh
import sys


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_data(dataset_str):
    """
    Loads input data from gcn/data directory

    ind.dataset_str.x => the feature vectors of the training instances as scipy.sparse.csr.csr_matrix object;
    ind.dataset_str.tx => the feature vectors of the test instances as scipy.sparse.csr.csr_matrix object;
    ind.dataset_str.allx => the feature vectors of both labeled and unlabeled training instances
        (a superset of ind.dataset_str.x) as scipy.sparse.csr.csr_matrix object;
    ind.dataset_str.y => the one-hot labels of the labeled training instances as numpy.ndarray object;
    ind.dataset_str.ty => the one-hot labels of the test instances as numpy.ndarray object;
    ind.dataset_str.ally => the labels for instances in ind.dataset_str.allx as numpy.ndarray object;
    ind.dataset_str.graph => a dict in the format {index: [index_of_neighbor_nodes]} as collections.defaultdict object;
    ind.dataset_str.test.index => the indices of test instances in graph, for the inductive setting as list object. 归纳这些test结点

    All objects above must be saved using python pickle module.

    :param dataset_str: Dataset name
    :return: All data input files loaded (as well the training/test data).
    """
    names = ['x', 'y', 'tx', 'ty', 'allx', 'ally', 'graph']
    objects = []
    for i in range(len(names)):
        with open("data/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/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()
    """ example
        it works for numpy
        arr = np.array([2, 3, 4, 5, 6])
        idx_reorder = [4, 3, 2, 1, 0]  # the disorder indices of arr
        # we are going to order the arr in a sort indices
        idx_range = np.sort(idx_reorder)
        arr[idx_reorder] = arr[idx_range]
        print(arr)
        >>> [6 5 4 3 2]

        ----- lil matrix ----
        feature = sp.lil_matrix(np.array([[2], [3], [4], [5], [6]]))
        >>> (0, 0)        2
            (1, 0)        3
            (2, 0)        4
            (3, 0)        5
            (4, 0)        6
        idx_reorder = [4, 3, 2, 1, 0]  # the disorder indices of feature
        # we are going to order the feature in a sort indices
        idx_range = np.sort(idx_reorder)
        feature[idx_reorder, :] = feature[idx_range, :] # [4,3,2,1,0] => [0,1,2,3,4] 原本 4 位置的值被0替换掉
        >>> (0, 0)        6
            (1, 0)        5
            (2, 0)        4
            (3, 0)        3
            (4, 0)        2
    """
    features[test_idx_reorder, :] = features[test_idx_range,:] 
     # 把特征矩阵还原 和对应的邻接矩阵对应起来 因为之前是打乱的 
    adj = nx.adjacency_matrix(nx.from_dict_of_lists(graph))

    labels = np.vstack((ally, ty))
    labels[test_idx_reorder, :] = labels[test_idx_range, :]

    idx_test = test_idx_range.tolist() # [1708,2707]
    idx_train = range(len(y)) # [0,140)
    idx_val = range(len(y), len(y)+500) #[140,640)

    """ bool [True  True  True ... False False False]
        train_mask = [0,140)为True 其余为False
        val_mask = [140,640)为True 其余为False
        test_mask = [1708, 2707]为True 其余为False
    """
    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])

    y_train = np.zeros(labels.shape) 
    y_val = np.zeros(labels.shape)
    y_test = np.zeros(labels.shape)

    """ 替换False的位置为0 
        a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
        mask = np.zeros(len(a))
        idx = [1, 2]
        mask[idx] = 1
        mask = np.array(mask, dtype=np.bool)
        a_ = np.zeros(a.shape)
        a_[mask, :] = a[mask, :]
        print(a_mask)
        >>> [[0. 0. 0.]
             [4. 5. 6.]
             [7. 8. 9.]
             [0. 0. 0.]]
    """
    y_train[train_mask, :] = labels[train_mask, :] 
    y_val[val_mask, :] = labels[val_mask, :]
    y_test[test_mask, :] = labels[test_mask, :]

    return adj, features, y_train, y_val, y_test, train_mask, val_mask, test_mask


def sparse_to_tuple(sparse_mx):
    """Convert sparse matrix to tuple representation.
    将稀疏矩阵转换为元组形式(行列坐标,值,shape)"""
    def to_tuple(mx):
        if not sp.isspmatrix_coo(mx):  # 判断mx是否为coo_matrix
            mx = mx.tocoo()  # 返回稀疏矩阵coo_matrix形式
        coords = np.vstack((mx.row, mx.col)).transpose()  # 矩阵mx的行和列索引
        values = mx.data  # 矩阵mx的值
        shape = mx.shape  # 矩阵mx的形状
        """ example:
            a = [[1,0,2],
                 [0,0,3],
                 [5,0,0]]
            mx = coo_matrix(a)
            >>> (0, 0)        1
                (0, 2)        2
                (1, 2)        3
                (2, 0)        5
            mx.row => [0,0,1,2]
            mx.col => [0,2,2,0]
            np.vstack((mx.row,mx.col)).transpose()
            >>> [[0 0]
                 [0 2]
                 [1 2]
                 [2 0]]
            mx.data
            >>> [1 2 3 5]
            mx.shape
            >>> (3, 3)

            :return: tuple
            >>> (array([[0, 0], [0, 2], [1, 2], [2, 0]], dtype=int32), 
                 array([1, 2, 3, 5]), (3, 3))
        """
        return coords, values, shape

    if isinstance(sparse_mx, list):  # 判断sparse_mx是否为list类型
        for i in range(len(sparse_mx)):
            sparse_mx[i] = to_tuple(sparse_mx[i])
    else:
        sparse_mx = to_tuple(sparse_mx)

    return sparse_mx


# preprocess_features和normalize_adj的写法一样 都是用度矩阵来归一化

def preprocess_features(features):
    """Row-normalize feature matrix and convert to tuple representation"""
    rowsum = np.array(features.sum(1))  # 对每一行求和 axis=1是按列 axis=0是行
    """ example
        a = np.array([[1, 2, 4], [4, 6, 3]])
        np.sum(a, axis=1)
        >>> [7, 13]
    """
    r_inv = np.power(rowsum, -1).flatten()
    r_inv[np.isinf(r_inv)] = 0.
    r_mat_inv = sp.diags(r_inv)  # 稀疏对角矩阵
    # 这里的归一化类似于GCN里面的归一化 用矩阵的度来归一化 相当于就是除以这一行的和 妙!
    features = r_mat_inv.dot(features)
    return sparse_to_tuple(features)


def normalize_adj(adj):
    """Symmetrically normalize adjacency matrix.
    对称归一化,对应论文中的公式."""
    adj = sp.coo_matrix(adj)
    rowsum = np.array(adj.sum(1))
    d_inv_sqrt = np.power(rowsum, -0.5).flatten()
    d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0.
    d_mat_inv_sqrt = sp.diags(d_inv_sqrt)
    return adj.dot(d_mat_inv_sqrt).transpose().dot(d_mat_inv_sqrt).tocoo()


def preprocess_adj(adj):
    """Preprocessing of adjacency matrix for simple GCN model and conversion to tuple representation."""
    adj_normalized = normalize_adj(adj + sp.eye(adj.shape[0]))
    return sparse_to_tuple(adj_normalized)


def construct_feed_dict(features, support, labels, labels_mask, placeholders):
    """Construct feed dictionary."""

    """ example
        dic=dict()
        dic={'Name':'Liugx','age':20}
        >>> {'Name': 'Liugx', 'age': 20}
        dic.update({'sex':'man'}) 添加指定字典到dic中
        >>> {'Name': 'Liugx', 'age': 20, 'sex': 'man'} 
    """
    feed_dict = dict()
    feed_dict.update({placeholders['labels']: labels})
    feed_dict.update({placeholders['labels_mask']: labels_mask})
    feed_dict.update({placeholders['features']: features})
    feed_dict.update({placeholders['support'][i]: support[i]
                      for i in range(len(support))})
    feed_dict.update({placeholders['num_features_nonzero']: features[1].shape})
    return feed_dict


def chebyshev_polynomials(adj, k):
    """Calculate Chebyshev polynomials up to order k. Return a list of sparse matrices (tuple representation)."""
    print("Calculating Chebyshev polynomials up to order {}...".format(k))

    adj_normalized = normalize_adj(adj)
    laplacian = sp.eye(adj.shape[0]) - adj_normalized
    largest_eigval, _ = eigsh(laplacian, 1, which='LM')
    scaled_laplacian = (
        2. / largest_eigval[0]) * laplacian - sp.eye(adj.shape[0])

    t_k = list()
    t_k.append(sp.eye(adj.shape[0]))
    t_k.append(scaled_laplacian)

    def chebyshev_recurrence(t_k_minus_one, t_k_minus_two, scaled_lap):
        s_lap = sp.csr_matrix(scaled_lap, copy=True)
        return 2 * s_lap.dot(t_k_minus_one) - t_k_minus_two

    for i in range(2, k+1):
        t_k.append(chebyshev_recurrence(t_k[-1], t_k[-2], scaled_laplacian))

    return sparse_to_tuple(t_k)

2. The models.py
base class Model implements the build() method, which performs forward propagation. The specific propagation method is defined by the layer. The specific layers of each model are defined in the _build() method of the subclass.

from gcn.layers import *
from gcn.metrics import *

flags = tf.app.flags
FLAGS = flags.FLAGS


class Model(object):
    def __init__(self, **kwargs):
        allowed_kwargs = {'name', 'logging'}
        for kwarg in kwargs.keys():
            assert kwarg in allowed_kwargs, 'Invalid keyword argument: ' + kwarg
        name = kwargs.get('name')
        if not name:
            name = self.__class__.__name__.lower()
        self.name = name

        logging = kwargs.get('logging', False)
        self.logging = logging

        self.vars = {}
        self.placeholders = {}

        self.layers = []
        self.activations = []

        self.inputs = None
        self.outputs = None

        self.loss = 0
        self.accuracy = 0
        self.optimizer = None
        self.opt_op = None

    def _build(self):
        raise NotImplementedError

    def build(self):
        """ Wrapper for _build() """
        with tf.variable_scope(self.name):
            self._build()

        # Build sequential layer model
        self.activations.append(self.inputs)  # 初始化第一个元素为inputs features
        for layer in self.layers:
            hidden = layer(self.activations[-1])  # 这个hidden即为对应最后一个input的输出
            self.activations.append(hidden)
        self.outputs = self.activations[-1]

        # Store model variables for easy access
        variables = tf.get_collection(
            tf.GraphKeys.GLOBAL_VARIABLES, scope=self.name)
        self.vars = {var.name: var for var in variables}

        # Build metrics
        self._loss()
        self._accuracy()

        self.opt_op = self.optimizer.minimize(self.loss)

    def predict(self):
        pass  # 不做任何事情 用做占位语句 给后面的子类用的

    def _loss(self):
        raise NotImplementedError  # 子类一定要实现这个方法 否则就会报错

    def _accuracy(self):
        raise NotImplementedError  # 子类一定要实现这个方法 否则就会报错

    def save(self, sess=None):
        if not sess:
            raise AttributeError("TensorFlow session not provided.")
        saver = tf.train.Saver(self.vars)
        save_path = saver.save(sess, "tmp/%s.ckpt" % self.name)
        print("Model saved in file: %s" % save_path)

    def load(self, sess=None):
        if not sess:
            raise AttributeError("TensorFlow session not provided.")
        saver = tf.train.Saver(self.vars)
        save_path = "tmp/%s.ckpt" % self.name
        saver.restore(sess, save_path)
        print("Model restored from file: %s" % save_path)


class MLP(Model):
    def __init__(self, placeholders, input_dim, **kwargs):
        super(MLP, self).__init__(**kwargs)

        self.inputs = placeholders['features']
        self.input_dim = input_dim
        # self.input_dim = self.inputs.get_shape().as_list()[1]  # To be supported in future Tensorflow versions
        self.output_dim = placeholders['labels'].get_shape().as_list()[1]
        self.placeholders = placeholders

        self.optimizer = tf.train.AdamOptimizer(
            learning_rate=FLAGS.learning_rate)

        self.build()

    def _loss(self):
        # Weight decay loss
        for var in self.layers[0].vars.values():
            self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var)

        # Cross entropy error
        self.loss += masked_softmax_cross_entropy(self.outputs, self.placeholders['labels'],
                                                  self.placeholders['labels_mask'])

    def _accuracy(self):
        self.accuracy = masked_accuracy(self.outputs, self.placeholders['labels'],
                                        self.placeholders['labels_mask'])

    def _build(self):
        self.layers.append(Dense(input_dim=self.input_dim,
                                 output_dim=FLAGS.hidden1,
                                 placeholders=self.placeholders,
                                 act=tf.nn.relu,
                                 dropout=True,
                                 sparse_inputs=True,
                                 logging=self.logging))

        self.layers.append(Dense(input_dim=FLAGS.hidden1,
                                 output_dim=self.output_dim,
                                 placeholders=self.placeholders,
                                 act=lambda x: x,
                                 dropout=True,
                                 logging=self.logging))

    def predict(self):
        return tf.nn.softmax(self.outputs)


class GCN(Model):
    def __init__(self, placeholders, input_dim, **kwargs):
        super(GCN, self).__init__(**kwargs)

        self.inputs = placeholders['features']
        self.input_dim = input_dim
        # self.input_dim = self.inputs.get_shape().as_list()[1]  # To be supported in future Tensorflow versions
        self.output_dim = placeholders['labels'].get_shape().as_list()[1]
        self.placeholders = placeholders

        self.optimizer = tf.train.AdamOptimizer(
            learning_rate=FLAGS.learning_rate)

        self.build()

    def _loss(self):
        # Weight decay loss
        # l2损失加到总损失里面
        for var in self.layers[0]

 

The article comes from the Internet and respects the author’s originality. If there is any infringement, please contact the administrator to delete it.

Guess you like

Origin blog.csdn.net/fly_time2012/article/details/106531070