GCN源代码注释的解释,源码,解读

GCN源代码注释的解释,源码,解读

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. models.py
基类Model实现build()方法,该方法进行前向传播,具体的传播方法靠layer定义,每个模型具体的layers在子类的_build()方法中定义。

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]

文章来源互联网,尊重作者原创,如有侵权,请联系管理员删除。

猜你喜欢

转载自blog.csdn.net/fly_time2012/article/details/106531070