From 3b69ee1e39f3b6252a357ef5f9eb62bbd5069530 Mon Sep 17 00:00:00 2001 From: opeceipeno Date: Fri, 25 Feb 2022 10:22:50 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/aggregators.py | 163 +++++++------ graphsage/layers.py | 13 +- graphsage/models.py | 397 +++++++++++++++++++++++--------- graphsage/unsupervised_train.py | 34 ++- 4 files changed, 411 insertions(+), 196 deletions(-) diff --git a/graphsage/aggregators.py b/graphsage/aggregators.py index 7dbd2523..eb73a33e 100644 --- a/graphsage/aggregators.py +++ b/graphsage/aggregators.py @@ -3,14 +3,15 @@ from .layers import Layer, Dense from .inits import glorot, zeros + class MeanAggregator(Layer): """ Aggregates via mean followed by matmul and non-linearity. """ def __init__(self, input_dim, output_dim, neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, - name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, + name=None, concat=False, **kwargs): super(MeanAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -28,9 +29,9 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([neigh_input_dim, output_dim], - name='neigh_weights') + name='neigh_weights') self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -41,17 +42,20 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, self.output_dim = output_dim def _call(self, inputs): + self_vecs, neigh_vecs = inputs + # neigh_vecs = tf.nn.dropout(neigh_vecs, 1-self.dropout) self_vecs = tf.nn.dropout(self_vecs, 1-self.dropout) neigh_means = tf.reduce_mean(neigh_vecs, axis=1) - + # [nodes] x [out_dim] + # batch * output_dim from_neighs = tf.matmul(neigh_means, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + if not self.concat: output = tf.add_n([from_self, from_neighs]) else: @@ -60,9 +64,10 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - + return self.act(output) + class GCNAggregator(Layer): """ Aggregates via mean followed by matmul and non-linearity. @@ -70,7 +75,7 @@ class GCNAggregator(Layer): """ def __init__(self, input_dim, output_dim, neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(GCNAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -88,7 +93,7 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, with tf.variable_scope(self.name + name + '_vars'): self.vars['weights'] = glorot([neigh_input_dim, output_dim], - name='neigh_weights') + name='neigh_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -103,24 +108,25 @@ def _call(self, inputs): neigh_vecs = tf.nn.dropout(neigh_vecs, 1-self.dropout) self_vecs = tf.nn.dropout(self_vecs, 1-self.dropout) - means = tf.reduce_mean(tf.concat([neigh_vecs, - tf.expand_dims(self_vecs, axis=1)], axis=1), axis=1) - + means = tf.reduce_mean(tf.concat([neigh_vecs, + tf.expand_dims(self_vecs, axis=1)], axis=1), axis=1) + # [nodes] x [out_dim] output = tf.matmul(means, self.vars['weights']) # bias if self.bias: output += self.vars['bias'] - + return self.act(output) class MaxPoolingAggregator(Layer): """ Aggregates via max-pooling over MLP functions. """ + def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(MaxPoolingAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -143,18 +149,18 @@ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=No self.mlp_layers = [] self.mlp_layers.append(Dense(input_dim=neigh_input_dim, - output_dim=hidden_dim, - act=tf.nn.relu, - dropout=dropout, - sparse_inputs=False, - logging=self.logging)) + output_dim=hidden_dim, + act=tf.nn.relu, + dropout=dropout, + sparse_inputs=False, + logging=self.logging)) with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([hidden_dim, output_dim], - name='neigh_weights') - + name='neigh_weights') + self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -173,16 +179,18 @@ def _call(self, inputs): batch_size = dims[0] num_neighbors = dims[1] # [nodes * sampled neighbors] x [hidden_dim] - h_reshaped = tf.reshape(neigh_h, (batch_size * num_neighbors, self.neigh_input_dim)) + h_reshaped = tf.reshape( + neigh_h, (batch_size * num_neighbors, self.neigh_input_dim)) for l in self.mlp_layers: h_reshaped = l(h_reshaped) - neigh_h = tf.reshape(h_reshaped, (batch_size, num_neighbors, self.hidden_dim)) + neigh_h = tf.reshape( + h_reshaped, (batch_size, num_neighbors, self.hidden_dim)) neigh_h = tf.reduce_max(neigh_h, axis=1) - + from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + if not self.concat: output = tf.add_n([from_self, from_neighs]) else: @@ -191,14 +199,16 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - + return self.act(output) + class MeanPoolingAggregator(Layer): """ Aggregates via mean-pooling over MLP functions. """ + def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(MeanPoolingAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -221,18 +231,18 @@ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=No self.mlp_layers = [] self.mlp_layers.append(Dense(input_dim=neigh_input_dim, - output_dim=hidden_dim, - act=tf.nn.relu, - dropout=dropout, - sparse_inputs=False, - logging=self.logging)) + output_dim=hidden_dim, + act=tf.nn.relu, + dropout=dropout, + sparse_inputs=False, + logging=self.logging)) with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([hidden_dim, output_dim], - name='neigh_weights') - + name='neigh_weights') + self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -251,16 +261,18 @@ def _call(self, inputs): batch_size = dims[0] num_neighbors = dims[1] # [nodes * sampled neighbors] x [hidden_dim] - h_reshaped = tf.reshape(neigh_h, (batch_size * num_neighbors, self.neigh_input_dim)) + h_reshaped = tf.reshape( + neigh_h, (batch_size * num_neighbors, self.neigh_input_dim)) for l in self.mlp_layers: h_reshaped = l(h_reshaped) - neigh_h = tf.reshape(h_reshaped, (batch_size, num_neighbors, self.hidden_dim)) + neigh_h = tf.reshape( + h_reshaped, (batch_size, num_neighbors, self.hidden_dim)) neigh_h = tf.reduce_mean(neigh_h, axis=1) - + from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + if not self.concat: output = tf.add_n([from_self, from_neighs]) else: @@ -269,15 +281,16 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - + return self.act(output) class TwoMaxLayerPoolingAggregator(Layer): """ Aggregates via pooling over two MLP functions. """ + def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(TwoMaxLayerPoolingAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -302,25 +315,24 @@ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=No self.mlp_layers = [] self.mlp_layers.append(Dense(input_dim=neigh_input_dim, - output_dim=hidden_dim_1, - act=tf.nn.relu, - dropout=dropout, - sparse_inputs=False, - logging=self.logging)) + output_dim=hidden_dim_1, + act=tf.nn.relu, + dropout=dropout, + sparse_inputs=False, + logging=self.logging)) self.mlp_layers.append(Dense(input_dim=hidden_dim_1, - output_dim=hidden_dim_2, - act=tf.nn.relu, - dropout=dropout, - sparse_inputs=False, - logging=self.logging)) - + output_dim=hidden_dim_2, + act=tf.nn.relu, + dropout=dropout, + sparse_inputs=False, + logging=self.logging)) with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([hidden_dim_2, output_dim], - name='neigh_weights') - + name='neigh_weights') + self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -339,16 +351,18 @@ def _call(self, inputs): batch_size = dims[0] num_neighbors = dims[1] # [nodes * sampled neighbors] x [hidden_dim] - h_reshaped = tf.reshape(neigh_h, (batch_size * num_neighbors, self.neigh_input_dim)) + h_reshaped = tf.reshape( + neigh_h, (batch_size * num_neighbors, self.neigh_input_dim)) for l in self.mlp_layers: h_reshaped = l(h_reshaped) - neigh_h = tf.reshape(h_reshaped, (batch_size, num_neighbors, self.hidden_dim_2)) + neigh_h = tf.reshape( + h_reshaped, (batch_size, num_neighbors, self.hidden_dim_2)) neigh_h = tf.reduce_max(neigh_h, axis=1) - + from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + if not self.concat: output = tf.add_n([from_self, from_neighs]) else: @@ -357,14 +371,16 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - + return self.act(output) + class SeqAggregator(Layer): """ Aggregates via a standard LSTM. """ + def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(SeqAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -387,10 +403,10 @@ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=No with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([hidden_dim, output_dim], - name='neigh_weights') - + name='neigh_weights') + self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -416,15 +432,15 @@ def _call(self, inputs): with tf.variable_scope(self.name) as scope: try: rnn_outputs, rnn_states = tf.nn.dynamic_rnn( - self.cell, neigh_vecs, - initial_state=initial_state, dtype=tf.float32, time_major=False, - sequence_length=length) + self.cell, neigh_vecs, + initial_state=initial_state, dtype=tf.float32, time_major=False, + sequence_length=length) except ValueError: scope.reuse_variables() rnn_outputs, rnn_states = tf.nn.dynamic_rnn( - self.cell, neigh_vecs, - initial_state=initial_state, dtype=tf.float32, time_major=False, - sequence_length=length) + self.cell, neigh_vecs, + initial_state=initial_state, dtype=tf.float32, time_major=False, + sequence_length=length) batch_size = tf.shape(rnn_outputs)[0] max_len = tf.shape(rnn_outputs)[1] out_size = int(rnn_outputs.get_shape()[2]) @@ -434,7 +450,7 @@ def _call(self, inputs): from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + output = tf.add_n([from_self, from_neighs]) if not self.concat: @@ -445,6 +461,5 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - - return self.act(output) + return self.act(output) diff --git a/graphsage/layers.py b/graphsage/layers.py index ca2496d9..d1b7b37e 100644 --- a/graphsage/layers.py +++ b/graphsage/layers.py @@ -16,6 +16,7 @@ # global unique layer ID dictionary for layer name assignment _LAYER_UIDS = {} + def get_layer_uid(layer_name=''): """Helper function, assigns unique layer IDs.""" if layer_name not in _LAYER_UIDS: @@ -25,6 +26,7 @@ def get_layer_uid(layer_name=''): _LAYER_UIDS[layer_name] += 1 return _LAYER_UIDS[layer_name] + class Layer(object): """Base layer class. Defines basic API for all layer objects. Implementation inspired by keras (http://keras.io). @@ -72,8 +74,9 @@ def _log_vars(self): class Dense(Layer): """Dense layer.""" - def __init__(self, input_dim, output_dim, dropout=0., - act=tf.nn.relu, placeholders=None, bias=True, featureless=False, + + def __init__(self, input_dim, output_dim, dropout=0., + act=tf.nn.relu, placeholders=None, bias=True, featureless=False, sparse_inputs=False, **kwargs): super(Dense, self).__init__(**kwargs) @@ -92,9 +95,9 @@ def __init__(self, input_dim, output_dim, dropout=0., with tf.variable_scope(self.name + '_vars'): self.vars['weights'] = tf.get_variable('weights', shape=(input_dim, output_dim), - dtype=tf.float32, - initializer=tf.contrib.layers.xavier_initializer(), - regularizer=tf.contrib.layers.l2_regularizer(FLAGS.weight_decay)) + dtype=tf.float32, + initializer=tf.contrib.layers.xavier_initializer(), + regularizer=tf.contrib.layers.l2_regularizer(FLAGS.weight_decay)) if self.bias: self.vars['bias'] = zeros([output_dim], name='bias') diff --git a/graphsage/models.py b/graphsage/models.py index b3b9db45..018fc47c 100644 --- a/graphsage/models.py +++ b/graphsage/models.py @@ -17,6 +17,7 @@ # https://github.com/tkipf/gcn # which itself was very inspired by the keras package + class Model(object): def __init__(self, **kwargs): allowed_kwargs = {'name', 'logging', 'model_size'} @@ -30,8 +31,8 @@ def __init__(self, **kwargs): logging = kwargs.get('logging', False) self.logging = logging - self.vars = {} - self.placeholders = {} + self.vars = {} # 模型参数 + self.placeholders = {} # 预留的位置,存放输入数据 self.layers = [] self.activations = [] @@ -53,21 +54,23 @@ def build(self): self._build() # Build sequential layer model - self.activations.append(self.inputs) + self.activations.append(self.inputs) # 第一步,将输入数据加进激活层,作为第一层 for layer in self.layers: + # 逐层计算中间层,并将每一层的输出都放进activations中保存 hidden = layer(self.activations[-1]) self.activations.append(hidden) - self.outputs = self.activations[-1] + self.outputs = self.activations[-1] # 模型的输出即为最后一层 # Store model variables for easy access - variables = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope=self.name) + variables = tf.get_collection( + tf.GraphKeys.GLOBAL_VARIABLES, scope=self.name) # 获取全局的参数,并赋给vars self.vars = {var.name: var for var in variables} # Build metrics - self._loss() + self._loss() # 定义损失函数 self._accuracy() - self.opt_op = self.optimizer.minimize(self.loss) + self.opt_op = self.optimizer.minimize(self.loss) # 优化策略 def predict(self): pass @@ -78,14 +81,14 @@ def _loss(self): def _accuracy(self): raise NotImplementedError - def save(self, sess=None): + 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): + def load(self, sess=None): # 从本地文件读取模型 if not sess: raise AttributeError("TensorFlow session not provided.") saver = tf.train.Saver(self.vars) @@ -96,6 +99,7 @@ def load(self, sess=None): class MLP(Model): """ A standard multi-layer perceptron """ + def __init__(self, placeholders, dims, categorical=True, **kwargs): super(MLP, self).__init__(**kwargs) @@ -108,7 +112,8 @@ def __init__(self, placeholders, dims, categorical=True, **kwargs): self.inputs = placeholders['features'] self.labels = placeholders['labels'] - self.optimizer = tf.train.AdamOptimizer(learning_rate=FLAGS.learning_rate) + self.optimizer = tf.train.AdamOptimizer( + learning_rate=FLAGS.learning_rate) self.build() @@ -120,45 +125,49 @@ def _loss(self): # Cross entropy error if self.categorical: self.loss += metrics.masked_softmax_cross_entropy(self.outputs, self.placeholders['labels'], - self.placeholders['labels_mask']) + self.placeholders['labels_mask']) # L2 else: diff = self.labels - self.outputs - self.loss += tf.reduce_sum(tf.sqrt(tf.reduce_sum(diff * diff, axis=1))) + self.loss += tf.reduce_sum( + tf.sqrt(tf.reduce_sum(diff * diff, axis=1))) def _accuracy(self): if self.categorical: self.accuracy = metrics.masked_accuracy(self.outputs, self.placeholders['labels'], - self.placeholders['labels_mask']) + self.placeholders['labels_mask']) def _build(self): self.layers.append(layers.Dense(input_dim=self.input_dim, - output_dim=self.dims[1], - act=tf.nn.relu, - dropout=self.placeholders['dropout'], - sparse_inputs=False, - logging=self.logging)) + output_dim=self.dims[1], + act=tf.nn.relu, + dropout=self.placeholders['dropout'], + sparse_inputs=False, + logging=self.logging)) self.layers.append(layers.Dense(input_dim=self.dims[1], - output_dim=self.output_dim, - act=lambda x: x, - dropout=self.placeholders['dropout'], - logging=self.logging)) + output_dim=self.output_dim, + act=lambda x: x, + dropout=self.placeholders['dropout'], + logging=self.logging)) def predict(self): return tf.nn.softmax(self.outputs) + class GeneralizedModel(Model): """ Base class for models that aren't constructed from traditional, sequential layers. Subclasses must set self.outputs in _build method (Removes the layers idiom from build method of the Model class) + + GeneralizedModel 这个类相比于Model类,主要是删去了中间的序列模型层,该模型需要其子类自己去定义中间层的计算逻辑以及输出 + """ def __init__(self, **kwargs): super(GeneralizedModel, self).__init__(**kwargs) - def build(self): """ Wrapper for _build() """ @@ -166,7 +175,8 @@ def build(self): self._build() # Store model variables for easy access - variables = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope=self.name) + variables = tf.get_collection( + tf.GraphKeys.GLOBAL_VARIABLES, scope=self.name) self.vars = {var.name: var for var in variables} # Build metrics @@ -175,14 +185,16 @@ def build(self): self.opt_op = self.optimizer.minimize(self.loss) -# SAGEInfo is a namedtuple that specifies the parameters + +# SAGEInfo is a namedtuple that specifies the parameters # of the recursive GraphSAGE layers SAGEInfo = namedtuple("SAGEInfo", - ['layer_name', # name of the layer (to get feature embedding etc.) - 'neigh_sampler', # callable neigh_sampler constructor - 'num_samples', - 'output_dim' # the output (i.e., hidden) dimension - ]) + ['layer_name', # name of the layer (to get feature embedding etc.) + 'neigh_sampler', # callable neigh_sampler constructor + 'num_samples', + 'output_dim' # the output (i.e., hidden) dimension + ]) + class SampleAndAggregate(GeneralizedModel): """ @@ -190,9 +202,9 @@ class SampleAndAggregate(GeneralizedModel): """ def __init__(self, placeholders, features, adj, degrees, - layer_infos, concat=True, aggregator_type="mean", - model_size="small", identity_dim=0, - **kwargs): + layer_infos, concat=True, aggregator_type="mean", + model_size="small", identity_dim=0, + **kwargs): ''' Args: - placeholders: Stanford TensorFlow placeholder object. @@ -206,7 +218,19 @@ def __init__(self, placeholders, features, adj, degrees, - aggregator_type: how to aggregate neighbor information - model_size: one of "small" and "big" - identity_dim: Set to positive int to use identity features (slow and cannot generalize, but better accuracy) + + - features:节点特征 [num_nodes,50] + - adj: 图的邻接表, [num_nodes,maxdegree] + - degrees:列表,表示每个节点的度数[num_nodes] + - layer_infos:描述所有参数的 SAGEInfo 命名元组列表 + 递归层。 参见上面的 SAGEInfo 定义。 + - concat:是否在递归迭代期间拼接,是或者否 + - aggregator_type:聚合方式的定义 + - model_size:模型大小,有small 和big, + - identity_dim:int,若>0则加入额外特征(速度慢且泛化性差,但准确度更高) ''' + + # 选择聚合器类型 super(SampleAndAggregate, self).__init__(**kwargs) if aggregator_type == "mean": self.aggregator_cls = MeanAggregator @@ -222,61 +246,91 @@ def __init__(self, placeholders, features, adj, degrees, raise Exception("Unknown aggregator: ", self.aggregator_cls) # get info from placeholders... + # 两个输入 ,batch1和batch2 是一条边的两个顶点id,即每条边的两个顶点,分别放进batch1和batch2中,用作正样本 self.inputs1 = placeholders["batch1"] self.inputs2 = placeholders["batch2"] self.model_size = model_size self.adj_info = adj + + # 若identity_dim>0,则创建额外的嵌入特征,扩充到feature的列维度上 if identity_dim > 0: - self.embeds = tf.get_variable("node_embeddings", [adj.get_shape().as_list()[0], identity_dim]) + self.embeds = tf.get_variable( + "node_embeddings", [adj.get_shape().as_list()[0], identity_dim]) else: - self.embeds = None - if features is None: + self.embeds = None + if features is None: if identity_dim == 0: - raise Exception("Must have a positive value for identity feature dimension if no input features given.") + raise Exception( + "Must have a positive value for identity feature dimension if no input features given.") self.features = self.embeds else: - self.features = tf.Variable(tf.constant(features, dtype=tf.float32), trainable=False) + self.features = tf.Variable(tf.constant( + features, dtype=tf.float32), trainable=False) # 节点特征, if not self.embeds is None: + # (feature的最终特征维度为 原始特征维度50+identity_dim) self.features = tf.concat([self.embeds, self.features], axis=1) self.degrees = degrees - self.concat = concat + self.concat = concat # 布尔值,是否拼接 - self.dims = [(0 if features is None else features.shape[1]) + identity_dim] - self.dims.extend([layer_infos[i].output_dim for i in range(len(layer_infos))]) + # dim是一个列表,代表每一层的输出维度,第一层是输入层,维度=输入特征的维度,后面的维度是从layer_info得到的 + # 本实验中,dims = [50,128,128] + + self.dims = [ + (0 if features is None else features.shape[1]) + identity_dim] + self.dims.extend( + [layer_infos[i].output_dim for i in range(len(layer_infos))]) self.batch_size = placeholders["batch_size"] self.placeholders = placeholders self.layer_infos = layer_infos - self.optimizer = tf.train.AdamOptimizer(learning_rate=FLAGS.learning_rate) + # 优化器选择 + self.optimizer = tf.train.AdamOptimizer( + learning_rate=FLAGS.learning_rate) + # 构建模型 self.build() def sample(self, inputs, layer_infos, batch_size=None): """ Sample neighbors to be the supportive fields for multi-layer convolutions. + 对节点邻居采样,作为该点的是支持域 + samples[0] 维度是 [batch_size] + samples[1] [layer_infos[1].num_samples * batch_size] + samples[2] [layer_infos[1].num_samples * layer_infos[0].num_samples * batch_size] + Args: inputs: batch inputs batch_size: the number of inputs (different for batch inputs and negative samples). """ - + if batch_size is None: batch_size = self.batch_size - samples = [inputs] + samples = [inputs] # samples[0] 是输入, # size of convolution support at each layer per node + # support_sizes 存的是的各层的采样数目,是一个列表 + # support_sizes[0] = 1, 初始状态就是节点本身 + # support_sizes[1] = layer_infos[-1].num_samples * 1, 本实验中为10 + # support_sizes[2] = layer_infos[-1].num_samples * layer_infos[-2].num_samples * 1, 本实验中为250 + # 以此类推,从最外层的邻居数依次往内乘 + support_size = 1 support_sizes = [support_size] - for k in range(len(layer_infos)): - t = len(layer_infos) - k - 1 + + for k in range(len(layer_infos)): # k为跳数,也是层数, 实验中k = 0 1 + t = len(layer_infos) - k - 1 # t = 1 0 + # 每一跳的邻居数目是前一跳的邻居节点数*该层的采样数 support_size *= layer_infos[t].num_samples - sampler = layer_infos[t].neigh_sampler + sampler = layer_infos[t].neigh_sampler # 采样器选择,实验中采样器是同一种 + + # 采样器的两个输入,第一个是将被采样的节点id,第二个是采样数 node = sampler((samples[k], layer_infos[t].num_samples)) - samples.append(tf.reshape(node, [support_size * batch_size,])) + # reshape并放进数组 + samples.append(tf.reshape(node, [support_size * batch_size, ])) support_sizes.append(support_size) return samples, support_sizes - def aggregate(self, samples, input_features, dims, num_samples, support_sizes, batch_size=None, - aggregators=None, name=None, concat=False, model_size="small"): + aggregators=None, name=None, concat=False, model_size="small"): """ At each layer, aggregate hidden representations of neighbors to compute the hidden representations at next layer. Args: @@ -290,49 +344,100 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b batch_size: the number of inputs (different for batch inputs and negative samples). Returns: The hidden representation at the final layer for all nodes in batch + + samples: 一个列表,里面存放的是邻居节点id, + sample[0]是初始节点,可以理解为第0跳邻居采样 (hop) + sample[1]是对sample[0]中每一个节点进行邻居采样,即第1跳采样 + sample[2]是对sample[1]中每一个节点进行邻居采样,即第2跳采样 + 以此类推 + input_features: 矩阵,存放的是全量的节点的特征 + dims: 列表,代表每一层的中间表达的维度 + + num_samples: 列表,表示模型每一层的邻居采样数目,实验中为[25,10] + support_sizes: the number of nodes to gather information from for each layer. + batch_size: the number of inputs (different for batch inputs and negative samples). + Returns: + + + + """ if batch_size is None: batch_size = self.batch_size # length: number of layers + 1 - hidden = [tf.nn.embedding_lookup(input_features, node_samples) for node_samples in samples] + # 根据节点id,从全量的特征矩阵里获取节点特征 + # hidden[0] [batch, num_features] + # hidden[1] [layer_infos[1].num_samples * batch_size, num_features] + # hidden[2] [layer_infos[1].num_samples * layer_infos[0].num_samples * batch_size, num_features] + # num_features表示的是特征维度,实验中为50 + + hidden = [tf.nn.embedding_lookup( + input_features, node_samples) for node_samples in samples] + + # 输入batch1的时候,该项为aggregators = None, 输入batch2或者neg_samples的时候,aggregators为batch1的aggregators new_agg = aggregators is None + if new_agg: aggregators = [] - for layer in range(len(num_samples)): + for layer in range(len(num_samples)): # 按层数循环 if new_agg: - dim_mult = 2 if concat and (layer != 0) else 1 + dim_mult = 2 if concat and (layer != 0) else 1 # 维度系数,如果有concat=True,从第二层开始,输入维度需要乘2 # aggregator at current layer + # 判断是否是最后一层,如果是的话,会有个参数act=lambda x: x if layer == len(num_samples) - 1: - aggregator = self.aggregator_cls(dim_mult*dims[layer], dims[layer+1], act=lambda x : x, - dropout=self.placeholders['dropout'], - name=name, concat=concat, model_size=model_size) + aggregator = self.aggregator_cls(dim_mult*dims[layer], dims[layer+1], act=lambda x: x, + dropout=self.placeholders['dropout'], + name=name, concat=concat, model_size=model_size) else: + # 初始化一个聚合器类,类别是超参定义的,需要的参数是输入维度、输出维度、dropout系数等等 aggregator = self.aggregator_cls(dim_mult*dims[layer], dims[layer+1], - dropout=self.placeholders['dropout'], - name=name, concat=concat, model_size=model_size) + dropout=self.placeholders['dropout'], + name=name, concat=concat, model_size=model_size) aggregators.append(aggregator) else: + # 在batch2或者neg_samples输入的时候,直接使用已有的聚合器 aggregator = aggregators[layer] + + # 本实验中,aggregator1 的输入输出维度分别为:50,256, 参数矩阵维度为50,128 ,后面有个拼接 + # aggregator2 的输入输出维度为:256,256,参数矩阵维度为256,128 + + + # hidden representation at current layer for all support nodes that are various hops away next_hidden = [] + + # as layer increases, the number of support nodes needed decreases - for hop in range(len(num_samples) - layer): + # 随着层数增加,跳数需要减少 + for hop in range(len(num_samples) - layer): dim_mult = 2 if concat and (layer != 0) else 1 - neigh_dims = [batch_size * support_sizes[hop], - num_samples[len(num_samples) - hop - 1], + + # 每个节点的特征,是由自身的特征和其邻居节点的特征聚合而来的, + # hidden[hop+1]包含了hidden[hop]中节点的所有邻居特征 + # 因为hidden[i]是二维的,而mean_aggregator是需要将邻居节点特征平均, + # 因此需要将它reshape一下,方便在后面的处理中取所有邻居的均值 + # neigh_dims = [batch_size * 当前跳数的支持节点数,当前层的需要采样的邻居节点数,特征数] + + # + neigh_dims = [batch_size * support_sizes[hop], + num_samples[len(num_samples) - hop - 1], dim_mult*dims[layer]] h = aggregator((hidden[hop], - tf.reshape(hidden[hop + 1], neigh_dims))) + tf.reshape(hidden[hop + 1], neigh_dims))) next_hidden.append(h) hidden = next_hidden return hidden[0], aggregators def _build(self): + + # 将第batch2作为标签,即batch1和batch2是一对正样本对 labels = tf.reshape( - tf.cast(self.placeholders['batch2'], dtype=tf.int64), - [self.batch_size, 1]) + tf.cast(self.placeholders['batch2'], dtype=tf.int64), + [self.batch_size, 1]) + + # 获取负样本, 按照给定的概率分布进行采样 self.neg_samples, _, _ = (tf.nn.fixed_unigram_candidate_sampler( true_classes=labels, num_true=1, @@ -342,66 +447,132 @@ def _build(self): distortion=0.75, unigrams=self.degrees.tolist())) - # perform "convolution" + + # 根据节点id去采样其邻居节点id samples1, support_sizes1 = self.sample(self.inputs1, self.layer_infos) samples2, support_sizes2 = self.sample(self.inputs2, self.layer_infos) - num_samples = [layer_info.num_samples for layer_info in self.layer_infos] + + # 每层需要的采样数 实验中是[25,10] + num_samples = [ + layer_info.num_samples for layer_info in self.layer_infos] + + + # 获取batch1的特征表达,该步传入的聚合器参数为None,会构建一个聚合器返回 self.outputs1, self.aggregators = self.aggregate(samples1, [self.features], self.dims, num_samples, - support_sizes1, concat=self.concat, model_size=self.model_size) + support_sizes1, concat=self.concat, model_size=self.model_size) + + # 获取batch2的特征表达,其中聚合器是直接使用上一步生成的 self.outputs2, _ = self.aggregate(samples2, [self.features], self.dims, num_samples, - support_sizes2, aggregators=self.aggregators, concat=self.concat, - model_size=self.model_size) - + support_sizes2, aggregators=self.aggregators, concat=self.concat, + model_size=self.model_size) + + # 对负样本采样 neg_samples, neg_support_sizes = self.sample(self.neg_samples, self.layer_infos, - FLAGS.neg_sample_size) + FLAGS.neg_sample_size) + + # 获取负样本的特征表达,聚合器也是用和之前同一个,注意batch_size参数,这里赋值的是负样本数量,和正样本的batch_size不同 self.neg_outputs, _ = self.aggregate(neg_samples, [self.features], self.dims, num_samples, - neg_support_sizes, batch_size=FLAGS.neg_sample_size, aggregators=self.aggregators, - concat=self.concat, model_size=self.model_size) + neg_support_sizes, batch_size=FLAGS.neg_sample_size, aggregators=self.aggregators, + concat=self.concat, model_size=self.model_size) dim_mult = 2 if self.concat else 1 + + # 这里生成了一个预测层,注意参数bilinear_weights,这个值如果为True,则会生成一个可训练的参数矩阵,在后续的计算loss会用到 + # 在这里设置了否,则无参数矩阵,本质上就是一个计算loss的类 self.link_pred_layer = BipartiteEdgePredLayer(dim_mult*self.dims[-1], - dim_mult*self.dims[-1], self.placeholders, act=tf.nn.sigmoid, - bilinear_weights=False, - name='edge_predict') + dim_mult*self.dims[-1], self.placeholders, act=tf.nn.sigmoid, + bilinear_weights=False, + name='edge_predict') + + # 对输出的样本执行L2规范化,dim=1,表示按行做,即按样本点做 + # x_l2[i] = x[i]/sqrt(sum(x^2)) self.outputs1 = tf.nn.l2_normalize(self.outputs1, 1) self.outputs2 = tf.nn.l2_normalize(self.outputs2, 1) self.neg_outputs = tf.nn.l2_normalize(self.neg_outputs, 1) def build(self): + + # 构建模型的输出 self._build() # TF graph management + # 构建模型的损失函数和准确度指标 self._loss() self._accuracy() + + # 除以batch,得到的平均loss self.loss = self.loss / tf.cast(self.batch_size, tf.float32) + + # 计算梯度 grads_and_vars = self.optimizer.compute_gradients(self.loss) - clipped_grads_and_vars = [(tf.clip_by_value(grad, -5.0, 5.0) if grad is not None else None, var) - for grad, var in grads_and_vars] + + # 梯度裁剪,若梯度大于5则置为5,小于-5则置为-5 + clipped_grads_and_vars = [(tf.clip_by_value(grad, -5.0, 5.0) if grad is not None else None, var) + for grad, var in grads_and_vars] self.grad, _ = clipped_grads_and_vars[0] + + # 更新参数 self.opt_op = self.optimizer.apply_gradients(clipped_grads_and_vars) def _loss(self): + + # L2正则化项 for aggregator in self.aggregators: for var in aggregator.vars.values(): self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var) - - self.loss += self.link_pred_layer.loss(self.outputs1, self.outputs2, self.neg_outputs) + + # 根据之前生成的预测层,计算loss,该loss有三个选项:_xent_loss、_skipgram_loss、_hinge_loss + self.loss += self.link_pred_layer.loss( + self.outputs1, self.outputs2, self.neg_outputs) tf.summary.scalar('loss', self.loss) def _accuracy(self): - # shape: [batch_size] + """ + 计算性能指标 + 模型计算了三组数据:一条边上的两个顶点(batch1和batch2)和采样得到的负样本(neg_samples)的特征值 + 主体思想是希望边两端点之间的相似度>该点和所有neg_samles相似度 + ①计算正样本对的"亲和度" + ②计算顶点和负样本的"亲和度" + ③将两组数据拼接,拼接后的数组维度[batch_size, neg_samples_size + 1],意义是每一个顶点和负样本、正样本之间的"亲和度" + ④计算正样本对之间的亲和度的排名,排名越靠前越好 + mrr值, + """ + + # ①计算正样本对的"亲和度" + # aff值在本实验即是两个输入按元素点乘,再按行求和 + # shape : [batch_size,] 表示了该batch中,每个节点和其邻居节点的“亲和度”,越大代表越相似 + aff = self.link_pred_layer.affinity(self.outputs1, self.outputs2) - # shape : [batch_size x num_neg_samples] - self.neg_aff = self.link_pred_layer.neg_cost(self.outputs1, self.neg_outputs) - self.neg_aff = tf.reshape(self.neg_aff, [self.batch_size, FLAGS.neg_sample_size]) + + + # ②计算顶点和负样本的"亲和度" + # 返回的是一个矩阵,维度:[batch_size,num_neg_samples] + # 含义是一组batch里每一个节点对每个负样本的"亲和度" + self.neg_aff = self.link_pred_layer.neg_cost( + self.outputs1, self.neg_outputs) + + self.neg_aff = tf.reshape( + self.neg_aff, [self.batch_size, FLAGS.neg_sample_size]) + + # ③将两组数据拼接,拼接后的数组维度[batch_size, neg_samples_size + 1],意义是每一个顶点和负样本、正样本之间的"亲和度" + # shape : [batch_size,1] _aff = tf.expand_dims(aff, axis=1) + # shape : [batch_size,num_neg_samples + 1] self.aff_all = tf.concat(axis=1, values=[self.neg_aff, _aff]) size = tf.shape(self.aff_all)[1] + + # ④利用top_k函数,两步计算出正样本对之间的亲和度的排名, + # self.ranks中表示的是每个顶点和负样本、正样本之间的亲和度排名,维度:[batch_size, neg_samples_size + 1] _, indices_of_ranks = tf.nn.top_k(self.aff_all, k=size) _, self.ranks = tf.nn.top_k(-indices_of_ranks, k=size) - self.mrr = tf.reduce_mean(tf.div(1.0, tf.cast(self.ranks[:, -1] + 1, tf.float32))) + + + # 取self.ranks最后一列,即正样本的排名序数,因为是从0算起的,所以要+1 + # mrr = 1.0/rank + self.mrr = tf.reduce_mean( + tf.div(1.0, tf.cast(self.ranks[:, -1] + 1, tf.float32))) tf.summary.scalar('mrr', self.mrr) @@ -429,15 +600,15 @@ def __init__(self, placeholders, dict_size, degrees, name=None, # following the tensorflow word2vec tutorial self.target_embeds = tf.Variable( - tf.random_uniform([dict_size, nodevec_dim], -1, 1), - name="target_embeds") + tf.random_uniform([dict_size, nodevec_dim], -1, 1), + name="target_embeds") self.context_embeds = tf.Variable( - tf.truncated_normal([dict_size, nodevec_dim], - stddev=1.0 / math.sqrt(nodevec_dim)), - name="context_embeds") + tf.truncated_normal([dict_size, nodevec_dim], + stddev=1.0 / math.sqrt(nodevec_dim)), + name="context_embeds") self.context_bias = tf.Variable( - tf.zeros([dict_size]), - name="context_bias") + tf.zeros([dict_size]), + name="context_bias") self.optimizer = tf.train.GradientDescentOptimizer(learning_rate=lr) @@ -445,8 +616,8 @@ def __init__(self, placeholders, dict_size, degrees, name=None, def _build(self): labels = tf.reshape( - tf.cast(self.placeholders['batch2'], dtype=tf.int64), - [self.batch_size, 1]) + tf.cast(self.placeholders['batch2'], dtype=tf.int64), + [self.batch_size, 1]) self.neg_samples, _, _ = (tf.nn.fixed_unigram_candidate_sampler( true_classes=labels, num_true=1, @@ -456,14 +627,19 @@ def _build(self): distortion=0.75, unigrams=self.degrees.tolist())) - self.outputs1 = tf.nn.embedding_lookup(self.target_embeds, self.inputs1) - self.outputs2 = tf.nn.embedding_lookup(self.context_embeds, self.inputs2) - self.outputs2_bias = tf.nn.embedding_lookup(self.context_bias, self.inputs2) - self.neg_outputs = tf.nn.embedding_lookup(self.context_embeds, self.neg_samples) - self.neg_outputs_bias = tf.nn.embedding_lookup(self.context_bias, self.neg_samples) + self.outputs1 = tf.nn.embedding_lookup( + self.target_embeds, self.inputs1) + self.outputs2 = tf.nn.embedding_lookup( + self.context_embeds, self.inputs2) + self.outputs2_bias = tf.nn.embedding_lookup( + self.context_bias, self.inputs2) + self.neg_outputs = tf.nn.embedding_lookup( + self.context_embeds, self.neg_samples) + self.neg_outputs_bias = tf.nn.embedding_lookup( + self.context_bias, self.neg_samples) self.link_pred_layer = BipartiteEdgePredLayer(self.hidden_dim, self.hidden_dim, - self.placeholders, bilinear_weights=False) + self.placeholders, bilinear_weights=False) def build(self): self._build() @@ -476,26 +652,31 @@ def _minimize(self): self.opt_op = self.optimizer.minimize(self.loss) def _loss(self): - aff = tf.reduce_sum(tf.multiply(self.outputs1, self.outputs2), 1) + self.outputs2_bias - neg_aff = tf.matmul(self.outputs1, tf.transpose(self.neg_outputs)) + self.neg_outputs_bias + aff = tf.reduce_sum(tf.multiply( + self.outputs1, self.outputs2), 1) + self.outputs2_bias + neg_aff = tf.matmul(self.outputs1, tf.transpose( + self.neg_outputs)) + self.neg_outputs_bias true_xent = tf.nn.sigmoid_cross_entropy_with_logits( - labels=tf.ones_like(aff), logits=aff) + labels=tf.ones_like(aff), logits=aff) negative_xent = tf.nn.sigmoid_cross_entropy_with_logits( - labels=tf.zeros_like(neg_aff), logits=neg_aff) + labels=tf.zeros_like(neg_aff), logits=neg_aff) loss = tf.reduce_sum(true_xent) + tf.reduce_sum(negative_xent) self.loss = loss / tf.cast(self.batch_size, tf.float32) tf.summary.scalar('loss', self.loss) - + def _accuracy(self): # shape: [batch_size] aff = self.link_pred_layer.affinity(self.outputs1, self.outputs2) # shape : [batch_size x num_neg_samples] - self.neg_aff = self.link_pred_layer.neg_cost(self.outputs1, self.neg_outputs) - self.neg_aff = tf.reshape(self.neg_aff, [self.batch_size, FLAGS.neg_sample_size]) + self.neg_aff = self.link_pred_layer.neg_cost( + self.outputs1, self.neg_outputs) + self.neg_aff = tf.reshape( + self.neg_aff, [self.batch_size, FLAGS.neg_sample_size]) _aff = tf.expand_dims(aff, axis=1) self.aff_all = tf.concat(axis=1, values=[self.neg_aff, _aff]) size = tf.shape(self.aff_all)[1] _, indices_of_ranks = tf.nn.top_k(self.aff_all, k=size) _, self.ranks = tf.nn.top_k(-indices_of_ranks, k=size) - self.mrr = tf.reduce_mean(tf.div(1.0, tf.cast(self.ranks[:, -1] + 1, tf.float32))) + self.mrr = tf.reduce_mean( + tf.div(1.0, tf.cast(self.ranks[:, -1] + 1, tf.float32))) tf.summary.scalar('mrr', self.mrr) diff --git a/graphsage/unsupervised_train.py b/graphsage/unsupervised_train.py index 44ef6091..dd4f0efd 100644 --- a/graphsage/unsupervised_train.py +++ b/graphsage/unsupervised_train.py @@ -118,6 +118,13 @@ def save_val_embeddings(sess, model, minibatch_iter, size, out_dir, mod=""): def construct_placeholders(): # Define placeholders + ''' + batch1和batch2分别代表一条边的两个端点,在后续的处理中用作正样本对 + neg_samples 是负样本的数量 + dropout 是特征传入下一层的时候的丢弃概率,有助于模型的泛化性能 + + ''' + placeholders = { 'batch1' : tf.placeholder(tf.int32, shape=(None), name='batch1'), 'batch2' : tf.placeholder(tf.int32, shape=(None), name='batch2'), @@ -130,30 +137,39 @@ def construct_placeholders(): return placeholders def train(train_data, test_data=None): + + #读取图、节点特征、节点映射表 + G = train_data[0] - features = train_data[1] + features = train_data[1] # shape = [num, 50] id_map = train_data[2] if not features is None: - # pad with dummy zero vector + # pad with dummy zero vector,features的特征加一列全0 features = np.vstack([features, np.zeros((features.shape[1],))]) + + # 根据开关判断是否加入随机游走的路线 context_pairs = train_data[3] if FLAGS.random_context else None + + # 定义一些占位符 placeholders = construct_placeholders() + + # 创建一个边batch迭代器类,每个batch是一条边,边上的两个点的id作为模型的输入 minibatch = EdgeMinibatchIterator(G, id_map, placeholders, batch_size=FLAGS.batch_size, max_degree=FLAGS.max_degree, - num_neg_samples=FLAGS.neg_sample_size, - context_pairs = context_pairs) - adj_info_ph = tf.placeholder(tf.int32, shape=minibatch.adj.shape) + num_neg_samples=FLAGS.neg_sample_size, # num_neg_samples 这个形参在这里没有用到 + context_pairs = context_pairs) + adj_info_ph = tf.placeholder(tf.int32, shape=minibatch.adj.shape) # 邻接表 adj_info = tf.Variable(adj_info_ph, trainable=False, name="adj_info") if FLAGS.model == 'graphsage_mean': # Create model sampler = UniformNeighborSampler(adj_info) layer_infos = [SAGEInfo("node", sampler, FLAGS.samples_1, FLAGS.dim_1), - SAGEInfo("node", sampler, FLAGS.samples_2, FLAGS.dim_2)] + SAGEInfo("node", sampler, FLAGS.samples_2, FLAGS.dim_2)] # samples1 =25, sample2=10 model = SampleAndAggregate(placeholders, features, @@ -265,15 +281,15 @@ def train(train_data, test_data=None): epoch_val_costs.append(0) while not minibatch.end(): # Construct feed dictionary - feed_dict = minibatch.next_minibatch_feed_dict() + feed_dict = minibatch.next_minibatch_feed_dict() # 按batch读取数据 feed_dict.update({placeholders['dropout']: FLAGS.dropout}) t = time.time() # Training step outs = sess.run([merged, model.opt_op, model.loss, model.ranks, model.aff_all, - model.mrr, model.outputs1], feed_dict=feed_dict) + model.mrr, model.outputs1], feed_dict=feed_dict) # 训练 train_cost = outs[2] - train_mrr = outs[5] + train_mrr = outs[5] if train_shadow_mrr is None: train_shadow_mrr = train_mrr# else: From 0bad0a0f7219b68723b72ee48f014796f988557e Mon Sep 17 00:00:00 2001 From: opeceipeno Date: Fri, 25 Feb 2022 13:46:21 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=8Cpy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/unsupervised_train.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/graphsage/unsupervised_train.py b/graphsage/unsupervised_train.py index dd4f0efd..5d0886dc 100644 --- a/graphsage/unsupervised_train.py +++ b/graphsage/unsupervised_train.py @@ -149,7 +149,7 @@ def train(train_data, test_data=None): features = np.vstack([features, np.zeros((features.shape[1],))]) - # 根据开关判断是否加入随机游走的路线 + # 根据开关判断是否加入随机游走的信息 context_pairs = train_data[3] if FLAGS.random_context else None # 定义一些占位符 @@ -162,7 +162,9 @@ def train(train_data, test_data=None): max_degree=FLAGS.max_degree, num_neg_samples=FLAGS.neg_sample_size, # num_neg_samples 这个形参在这里没有用到 context_pairs = context_pairs) - adj_info_ph = tf.placeholder(tf.int32, shape=minibatch.adj.shape) # 邻接表 + + # 根据邻接表的维度创建一个占位符 + adj_info_ph = tf.placeholder(tf.int32, shape=minibatch.adj.shape) adj_info = tf.Variable(adj_info_ph, trainable=False, name="adj_info") if FLAGS.model == 'graphsage_mean': @@ -249,6 +251,8 @@ def train(train_data, test_data=None): else: raise Exception('Error: model name unrecognized.') + + config = tf.ConfigProto(log_device_placement=FLAGS.log_device_placement) config.gpu_options.allow_growth = True #config.gpu_options.per_process_gpu_memory_fraction = GPU_MEM_FRACTION @@ -262,7 +266,7 @@ def train(train_data, test_data=None): # Init variables sess.run(tf.global_variables_initializer(), feed_dict={adj_info_ph: minibatch.adj}) - # Train model + # Train model 训练 train_shadow_mrr = None shadow_mrr = None @@ -271,8 +275,10 @@ def train(train_data, test_data=None): avg_time = 0.0 epoch_val_costs = [] + # 从minibatch获取训练和验证数据的邻接表信息 train_adj_info = tf.assign(adj_info, minibatch.adj) val_adj_info = tf.assign(adj_info, minibatch.test_adj) + for epoch in range(FLAGS.epochs): minibatch.shuffle() @@ -281,11 +287,14 @@ def train(train_data, test_data=None): epoch_val_costs.append(0) while not minibatch.end(): # Construct feed dictionary - feed_dict = minibatch.next_minibatch_feed_dict() # 按batch读取数据 + # 按batch读取数据,每个batch包含边的两个端点,分别放进batch1和batch2中, + feed_dict = minibatch.next_minibatch_feed_dict() feed_dict.update({placeholders['dropout']: FLAGS.dropout}) t = time.time() - # Training step + + # Training step 训练 + # model.opt_op是调参 outs = sess.run([merged, model.opt_op, model.loss, model.ranks, model.aff_all, model.mrr, model.outputs1], feed_dict=feed_dict) # 训练 train_cost = outs[2] @@ -296,9 +305,10 @@ def train(train_data, test_data=None): train_shadow_mrr -= (1-0.99) * (train_shadow_mrr - train_mrr) if iter % FLAGS.validate_iter == 0: - # Validation + # Validation 验证的时候,需要用验证集的邻接表信息,因此这里跑一下tf.assign的操作 sess.run(val_adj_info.op) val_cost, ranks, val_mrr, duration = evaluate(sess, model, minibatch, size=FLAGS.validate_batch_size) + # 验证完毕再切回训练集的邻接表信息 sess.run(train_adj_info.op) epoch_val_costs[-1] += val_cost if shadow_mrr is None: From d8f85b90905695ee182a024c205bac61427b36ca Mon Sep 17 00:00:00 2001 From: 13929520142 Date: Fri, 25 Feb 2022 14:37:31 +0800 Subject: [PATCH 03/28] =?UTF-8?q?=E6=B5=8B=E8=AF=95push?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/aggregators.py | 151 ++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 72 deletions(-) diff --git a/graphsage/aggregators.py b/graphsage/aggregators.py index 7dbd2523..4dcff553 100644 --- a/graphsage/aggregators.py +++ b/graphsage/aggregators.py @@ -3,14 +3,16 @@ from .layers import Layer, Dense from .inits import glorot, zeros + class MeanAggregator(Layer): """ Aggregates via mean followed by matmul and non-linearity. """ + # mean聚合 def __init__(self, input_dim, output_dim, neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, - name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, + name=None, concat=False, **kwargs): super(MeanAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -28,9 +30,9 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([neigh_input_dim, output_dim], - name='neigh_weights') + name='neigh_weights') self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -43,15 +45,15 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, def _call(self, inputs): self_vecs, neigh_vecs = inputs - neigh_vecs = tf.nn.dropout(neigh_vecs, 1-self.dropout) - self_vecs = tf.nn.dropout(self_vecs, 1-self.dropout) + neigh_vecs = tf.nn.dropout(neigh_vecs, 1 - self.dropout) + self_vecs = tf.nn.dropout(self_vecs, 1 - self.dropout) neigh_means = tf.reduce_mean(neigh_vecs, axis=1) - + # [nodes] x [out_dim] from_neighs = tf.matmul(neigh_means, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + if not self.concat: output = tf.add_n([from_self, from_neighs]) else: @@ -60,9 +62,10 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - + return self.act(output) + class GCNAggregator(Layer): """ Aggregates via mean followed by matmul and non-linearity. @@ -70,7 +73,7 @@ class GCNAggregator(Layer): """ def __init__(self, input_dim, output_dim, neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(GCNAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -88,7 +91,7 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, with tf.variable_scope(self.name + name + '_vars'): self.vars['weights'] = glorot([neigh_input_dim, output_dim], - name='neigh_weights') + name='neigh_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -101,26 +104,27 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, def _call(self, inputs): self_vecs, neigh_vecs = inputs - neigh_vecs = tf.nn.dropout(neigh_vecs, 1-self.dropout) - self_vecs = tf.nn.dropout(self_vecs, 1-self.dropout) - means = tf.reduce_mean(tf.concat([neigh_vecs, - tf.expand_dims(self_vecs, axis=1)], axis=1), axis=1) - + neigh_vecs = tf.nn.dropout(neigh_vecs, 1 - self.dropout) + self_vecs = tf.nn.dropout(self_vecs, 1 - self.dropout) + means = tf.reduce_mean(tf.concat([neigh_vecs, + tf.expand_dims(self_vecs, axis=1)], axis=1), axis=1) + # [nodes] x [out_dim] output = tf.matmul(means, self.vars['weights']) # bias if self.bias: output += self.vars['bias'] - + return self.act(output) class MaxPoolingAggregator(Layer): """ Aggregates via max-pooling over MLP functions. """ + def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(MaxPoolingAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -143,18 +147,18 @@ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=No self.mlp_layers = [] self.mlp_layers.append(Dense(input_dim=neigh_input_dim, - output_dim=hidden_dim, - act=tf.nn.relu, - dropout=dropout, - sparse_inputs=False, - logging=self.logging)) + output_dim=hidden_dim, + act=tf.nn.relu, + dropout=dropout, + sparse_inputs=False, + logging=self.logging)) with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([hidden_dim, output_dim], - name='neigh_weights') - + name='neigh_weights') + self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -179,10 +183,10 @@ def _call(self, inputs): h_reshaped = l(h_reshaped) neigh_h = tf.reshape(h_reshaped, (batch_size, num_neighbors, self.hidden_dim)) neigh_h = tf.reduce_max(neigh_h, axis=1) - + from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + if not self.concat: output = tf.add_n([from_self, from_neighs]) else: @@ -191,14 +195,16 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - + return self.act(output) + class MeanPoolingAggregator(Layer): """ Aggregates via mean-pooling over MLP functions. """ + def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(MeanPoolingAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -221,18 +227,18 @@ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=No self.mlp_layers = [] self.mlp_layers.append(Dense(input_dim=neigh_input_dim, - output_dim=hidden_dim, - act=tf.nn.relu, - dropout=dropout, - sparse_inputs=False, - logging=self.logging)) + output_dim=hidden_dim, + act=tf.nn.relu, + dropout=dropout, + sparse_inputs=False, + logging=self.logging)) with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([hidden_dim, output_dim], - name='neigh_weights') - + name='neigh_weights') + self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -257,10 +263,10 @@ def _call(self, inputs): h_reshaped = l(h_reshaped) neigh_h = tf.reshape(h_reshaped, (batch_size, num_neighbors, self.hidden_dim)) neigh_h = tf.reduce_mean(neigh_h, axis=1) - + from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + if not self.concat: output = tf.add_n([from_self, from_neighs]) else: @@ -269,15 +275,16 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - + return self.act(output) class TwoMaxLayerPoolingAggregator(Layer): """ Aggregates via pooling over two MLP functions. """ + def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(TwoMaxLayerPoolingAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -302,25 +309,24 @@ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=No self.mlp_layers = [] self.mlp_layers.append(Dense(input_dim=neigh_input_dim, - output_dim=hidden_dim_1, - act=tf.nn.relu, - dropout=dropout, - sparse_inputs=False, - logging=self.logging)) + output_dim=hidden_dim_1, + act=tf.nn.relu, + dropout=dropout, + sparse_inputs=False, + logging=self.logging)) self.mlp_layers.append(Dense(input_dim=hidden_dim_1, - output_dim=hidden_dim_2, - act=tf.nn.relu, - dropout=dropout, - sparse_inputs=False, - logging=self.logging)) - + output_dim=hidden_dim_2, + act=tf.nn.relu, + dropout=dropout, + sparse_inputs=False, + logging=self.logging)) with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([hidden_dim_2, output_dim], - name='neigh_weights') - + name='neigh_weights') + self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -345,10 +351,10 @@ def _call(self, inputs): h_reshaped = l(h_reshaped) neigh_h = tf.reshape(h_reshaped, (batch_size, num_neighbors, self.hidden_dim_2)) neigh_h = tf.reduce_max(neigh_h, axis=1) - + from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + if not self.concat: output = tf.add_n([from_self, from_neighs]) else: @@ -357,14 +363,16 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - + return self.act(output) + class SeqAggregator(Layer): """ Aggregates via a standard LSTM. """ + def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(SeqAggregator, self).__init__(**kwargs) self.dropout = dropout @@ -387,10 +395,10 @@ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=No with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([hidden_dim, output_dim], - name='neigh_weights') - + name='neigh_weights') + self.vars['self_weights'] = glorot([input_dim, output_dim], - name='self_weights') + name='self_weights') if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -416,15 +424,15 @@ def _call(self, inputs): with tf.variable_scope(self.name) as scope: try: rnn_outputs, rnn_states = tf.nn.dynamic_rnn( - self.cell, neigh_vecs, - initial_state=initial_state, dtype=tf.float32, time_major=False, - sequence_length=length) + self.cell, neigh_vecs, + initial_state=initial_state, dtype=tf.float32, time_major=False, + sequence_length=length) except ValueError: scope.reuse_variables() rnn_outputs, rnn_states = tf.nn.dynamic_rnn( - self.cell, neigh_vecs, - initial_state=initial_state, dtype=tf.float32, time_major=False, - sequence_length=length) + self.cell, neigh_vecs, + initial_state=initial_state, dtype=tf.float32, time_major=False, + sequence_length=length) batch_size = tf.shape(rnn_outputs)[0] max_len = tf.shape(rnn_outputs)[1] out_size = int(rnn_outputs.get_shape()[2]) @@ -434,7 +442,7 @@ def _call(self, inputs): from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) - + output = tf.add_n([from_self, from_neighs]) if not self.concat: @@ -445,6 +453,5 @@ def _call(self, inputs): # bias if self.bias: output += self.vars['bias'] - - return self.act(output) + return self.act(output) From 3bb58c8f4f4ec51924db34a452307b0956a57825 Mon Sep 17 00:00:00 2001 From: opeceipeno Date: Fri, 25 Feb 2022 17:31:31 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=EF=BC=8C=E5=8C=85=E6=8B=AC=E4=BA=86Layers.py=20Prediction.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/layers.py | 26 +++++- graphsage/models.py | 151 ++++++++++++++++++-------------- graphsage/prediction.py | 45 +++++++--- graphsage/unsupervised_train.py | 19 +++- 4 files changed, 159 insertions(+), 82 deletions(-) diff --git a/graphsage/layers.py b/graphsage/layers.py index d1b7b37e..24d2af8f 100644 --- a/graphsage/layers.py +++ b/graphsage/layers.py @@ -39,6 +39,11 @@ class Layer(object): (i.e. takes input, returns output) __call__(inputs): Wrapper for _call() _log_vars(): Log all variables + + 最基础的层类型 + name:定义层的名称,字符型 + logging:布尔型,如果开的话就可以打印训练过程中,当需要查看一个张量在训练过程中值的分布情况时,可通过tf.summary.histogram()将其分布情况以直方图的形式在TensorBoard直方图仪表板上显示. + 这里并没有参数矩阵、激活函数等值,都是在其子类中实现 """ def __init__(self, **kwargs): @@ -55,6 +60,7 @@ def __init__(self, **kwargs): self.logging = logging self.sparse_inputs = False + # 用于定义该层的计算逻辑的函数,该类这里没计算逻辑,需要子类去实, Dense类就有一个实现 def _call(self, inputs): return inputs @@ -73,7 +79,20 @@ def _log_vars(self): class Dense(Layer): - """Dense layer.""" + """Dense layer. + 一个基础的全连接层 + 需要的入参: + dropout :0-1的数字,表示输入被丢弃的概率 + act:激活函数的类型选取,例如sigmoid、relu等 + featureless:意义暂时不明,没有用到 + bias :偏置项 + input_dim:输入维度 + output_dim :输出维度 + + """ + + + def __init__(self, input_dim, output_dim, dropout=0., act=tf.nn.relu, placeholders=None, bias=True, featureless=False, @@ -93,6 +112,8 @@ def __init__(self, input_dim, output_dim, dropout=0., if sparse_inputs: self.num_features_nonzero = placeholders['num_features_nonzero'] + + # 根据输入输出维度生成参数矩阵 with tf.variable_scope(self.name + '_vars'): self.vars['weights'] = tf.get_variable('weights', shape=(input_dim, output_dim), dtype=tf.float32, @@ -104,6 +125,9 @@ def __init__(self, input_dim, output_dim, dropout=0., if self.logging: self._log_vars() + + #调用模型的时候的计算逻辑定义 + # 此全连接中, output = XW + b, (还有dropout和激活函数等操作) def _call(self, inputs): x = inputs diff --git a/graphsage/models.py b/graphsage/models.py index 018fc47c..e3ea8e83 100644 --- a/graphsage/models.py +++ b/graphsage/models.py @@ -56,7 +56,7 @@ def build(self): # Build sequential layer model self.activations.append(self.inputs) # 第一步,将输入数据加进激活层,作为第一层 for layer in self.layers: - # 逐层计算中间层,并将每一层的输出都放进activations中保存 + # 逐层计算,并将每一层的输出都放进activations中保存 hidden = layer(self.activations[-1]) self.activations.append(hidden) self.outputs = self.activations[-1] # 模型的输出即为最后一层 @@ -175,6 +175,8 @@ def build(self): self._build() # Store model variables for easy access + # 和Model类相比,GeneralizedModel在build的时候,并没去生成序列层 + # self.output必须在它的子类build()函数中实现。 variables = tf.get_collection( tf.GraphKeys.GLOBAL_VARIABLES, scope=self.name) self.vars = {var.name: var for var in variables} @@ -219,14 +221,13 @@ def __init__(self, placeholders, features, adj, degrees, - model_size: one of "small" and "big" - identity_dim: Set to positive int to use identity features (slow and cannot generalize, but better accuracy) - - features:节点特征 [num_nodes,50] - - adj: 图的邻接表, [num_nodes,maxdegree] - - degrees:列表,表示每个节点的度数[num_nodes] - - layer_infos:描述所有参数的 SAGEInfo 命名元组列表 - 递归层。 参见上面的 SAGEInfo 定义。 + - features:节点特征 [num_nodes,num_features] + - adj: 图的邻接表, [num_nodes, maxdegree] maxdegree是个超参,表示对于每个节点,最多只记录其maxdegree个邻居信息 + - degrees:列表,表示每个节点的度数长度为[num_nodes] + - layer_infos:一个列表,记录了每一层的信息包括,名称、邻居采样器、 - concat:是否在递归迭代期间拼接,是或者否 - aggregator_type:聚合方式的定义 - - model_size:模型大小,有small 和big, + - model_size:模型大小,有small 和big, 隐藏层的维度有区别 - identity_dim:int,若>0则加入额外特征(速度慢且泛化性差,但准确度更高) ''' @@ -246,9 +247,11 @@ def __init__(self, placeholders, features, adj, degrees, raise Exception("Unknown aggregator: ", self.aggregator_cls) # get info from placeholders... - # 两个输入 ,batch1和batch2 是一条边的两个顶点id,即每条边的两个顶点,分别放进batch1和batch2中,用作正样本 + # batch1和batch2 是一条边的两个顶点id,即每条边的两个顶点,分别放进batch1和batch2中 + # 他们后续会分别作为模型的输入,得到中间表达结果output1和output2,然后在会用表达结果计算性能指标 self.inputs1 = placeholders["batch1"] self.inputs2 = placeholders["batch2"] + self.model_size = model_size self.adj_info = adj @@ -265,14 +268,15 @@ def __init__(self, placeholders, features, adj, degrees, self.features = self.embeds else: self.features = tf.Variable(tf.constant( - features, dtype=tf.float32), trainable=False) # 节点特征, + features, dtype=tf.float32), trainable=False) # 节点特征通过tf.Variable方式获取,不可训练 if not self.embeds is None: # (feature的最终特征维度为 原始特征维度50+identity_dim) self.features = tf.concat([self.embeds, self.features], axis=1) + self.degrees = degrees - self.concat = concat # 布尔值,是否拼接 + self.concat = concat # 布尔值,表示在模型计算完batch1和batch2的特征表达之后,是否拼接 - # dim是一个列表,代表每一层的输出维度,第一层是输入层,维度=输入特征的维度,后面的维度是从layer_info得到的 + # dim是一个列表,代表aggregator每一层的输出维度,第一层是输入层,维度=输入特征的维度,后面的维度是从layer_info得到的 # 本实验中,dims = [50,128,128] self.dims = [ @@ -283,7 +287,8 @@ def __init__(self, placeholders, features, adj, degrees, self.placeholders = placeholders self.layer_infos = layer_infos - # 优化器选择 + # 优化器选择为adam方法,是当前最常用的梯度更新策略 + self.optimizer = tf.train.AdamOptimizer( learning_rate=FLAGS.learning_rate) @@ -293,10 +298,20 @@ def __init__(self, placeholders, features, adj, degrees, def sample(self, inputs, layer_infos, batch_size=None): """ Sample neighbors to be the supportive fields for multi-layer convolutions. - 对节点邻居采样,作为该点的是支持域 - samples[0] 维度是 [batch_size] - samples[1] [layer_infos[1].num_samples * batch_size] - samples[2] [layer_infos[1].num_samples * layer_infos[0].num_samples * batch_size] + 函数功能:对输入的每一个节点,根据采样跳数目,递归地采样邻居,作为该节点的支持域 + + samples是一个列表,列表的每一个元素又是一个列表,长度不一,存放的是该跳数下的所有的邻居节点id + 示例: + samples[0] 维度是 [batch_size,] ,即是自身 + samples[1] [layer_infos[1].num_samples * batch_size,] + samples[2] [layer_infos[1].num_samples * layer_infos[0].num_samples * batch_size,] + 以此类推 + + # support_sizes 存的是的各层的采样数目,是一个列表,每个元素是一个正整数 + # support_sizes[0] = 1, 意义是初始状态,邻居就是节点本身 + # support_sizes[1] = layer_infos[-1].num_samples * 1, 本实验中为10 + # support_sizes[2] = layer_infos[-1].num_samples * layer_infos[-2].num_samples * 1, 本实验中为10*15=250 + # 以此类推,从最外层的邻居数依次往内乘 Args: inputs: batch inputs @@ -307,25 +322,25 @@ def sample(self, inputs, layer_infos, batch_size=None): batch_size = self.batch_size samples = [inputs] # samples[0] 是输入, # size of convolution support at each layer per node - # support_sizes 存的是的各层的采样数目,是一个列表 - # support_sizes[0] = 1, 初始状态就是节点本身 - # support_sizes[1] = layer_infos[-1].num_samples * 1, 本实验中为10 - # support_sizes[2] = layer_infos[-1].num_samples * layer_infos[-2].num_samples * 1, 本实验中为250 - # 以此类推,从最外层的邻居数依次往内乘 support_size = 1 support_sizes = [support_size] for k in range(len(layer_infos)): # k为跳数,也是层数, 实验中k = 0 1 t = len(layer_infos) - k - 1 # t = 1 0 - # 每一跳的邻居数目是前一跳的邻居节点数*该层的采样数 + + # 每一跳的邻居数目是前一跳的邻居节点数*该层的采样数,有个累乘的逻辑 support_size *= layer_infos[t].num_samples - sampler = layer_infos[t].neigh_sampler # 采样器选择,实验中采样器是同一种 - # 采样器的两个输入,第一个是将被采样的节点id,第二个是采样数 + sampler = layer_infos[t].neigh_sampler # 采样器选择 + + # 采样器的两个输入,第一个入参是将要被采样的节点id,第二个入参是对这些节点,要采样多少个邻居 node = sampler((samples[k], layer_infos[t].num_samples)) - # reshape并放进数组 + + # reshape成一维数组,再添加进samples中 samples.append(tf.reshape(node, [support_size * batch_size, ])) + + # 同时记录好每一层的采样数 support_sizes.append(support_size) return samples, support_sizes @@ -351,11 +366,9 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b sample[2]是对sample[1]中每一个节点进行邻居采样,即第2跳采样 以此类推 input_features: 矩阵,存放的是全量的节点的特征 - dims: 列表,代表每一层的中间表达的维度 num_samples: 列表,表示模型每一层的邻居采样数目,实验中为[25,10] - support_sizes: the number of nodes to gather information from for each layer. - batch_size: the number of inputs (different for batch inputs and negative samples). + Returns: @@ -367,31 +380,36 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b batch_size = self.batch_size # length: number of layers + 1 - # 根据节点id,从全量的特征矩阵里获取节点特征 + # 遍历samples列表,根据每一个元素中存放的节点id,从全量的特征矩阵里获取所需的节点特征 + + hidden = [tf.nn.embedding_lookup( + input_features, node_samples) for node_samples in samples] # hidden[0] [batch, num_features] # hidden[1] [layer_infos[1].num_samples * batch_size, num_features] # hidden[2] [layer_infos[1].num_samples * layer_infos[0].num_samples * batch_size, num_features] # num_features表示的是特征维度,实验中为50 - - hidden = [tf.nn.embedding_lookup( - input_features, node_samples) for node_samples in samples] - # 输入batch1的时候,该项为aggregators = None, 输入batch2或者neg_samples的时候,aggregators为batch1的aggregators + + # 输入batch1的时候,该项为aggregators = None, 输入batch2或者neg_samples的时候,aggregators为batch1生成的aggregators + # 即他们用的是同一个聚合器 new_agg = aggregators is None if new_agg: aggregators = [] for layer in range(len(num_samples)): # 按层数循环 if new_agg: - dim_mult = 2 if concat and (layer != 0) else 1 # 维度系数,如果有concat=True,从第二层开始,输入维度需要乘2 + dim_mult = 2 if concat and (layer != 0) else 1 # aggregator at current layer + # 根据给定的参数,初始化一个聚合器类, + # 其中,聚合器有多种选择,是由超参定义的, + # 另外需要的参数是输入维度、输出维度、dropout系数等等 + # 注意输入维度前面有个dim_mult,该值为1或者2,如果concat=True,表示节点自身的结果和邻居的会拼接一下,则从第二层开始,输入维度需要乘2 # 判断是否是最后一层,如果是的话,会有个参数act=lambda x: x if layer == len(num_samples) - 1: aggregator = self.aggregator_cls(dim_mult*dims[layer], dims[layer+1], act=lambda x: x, dropout=self.placeholders['dropout'], name=name, concat=concat, model_size=model_size) else: - # 初始化一个聚合器类,类别是超参定义的,需要的参数是输入维度、输出维度、dropout系数等等 aggregator = self.aggregator_cls(dim_mult*dims[layer], dims[layer+1], dropout=self.placeholders['dropout'], name=name, concat=concat, model_size=model_size) @@ -399,45 +417,44 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b else: # 在batch2或者neg_samples输入的时候,直接使用已有的聚合器 aggregator = aggregators[layer] - + # 本实验中,aggregator1 的输入输出维度分别为:50,256, 参数矩阵维度为50,128 ,后面有个拼接 # aggregator2 的输入输出维度为:256,256,参数矩阵维度为256,128 - - # hidden representation at current layer for all support nodes that are various hops away + # 该变量存放的是当前层,各节点利用邻居节点的信息更新后的中间表达, next_hidden = [] - - + # as layer increases, the number of support nodes needed decreases # 随着层数增加,跳数需要减少 - for hop in range(len(num_samples) - layer): + for hop in range(len(num_samples) - layer): dim_mult = 2 if concat and (layer != 0) else 1 # 每个节点的特征,是由自身的特征和其邻居节点的特征聚合而来的, # hidden[hop+1]包含了hidden[hop]中节点的所有邻居特征 - # 因为hidden[i]是二维的,而mean_aggregator是需要将邻居节点特征平均, + # 因为hidden[i]存放为二维,而mean_aggregator是需要将邻居节点特征平均, # 因此需要将它reshape一下,方便在后面的处理中取所有邻居的均值 # neigh_dims = [batch_size * 当前跳数的支持节点数,当前层的需要采样的邻居节点数,特征数] - - # + # neigh_dims = [batch_size * support_sizes[hop], num_samples[len(num_samples) - hop - 1], dim_mult*dims[layer]] h = aggregator((hidden[hop], - tf.reshape(hidden[hop + 1], neigh_dims))) + tf.reshape(hidden[hop + 1], neigh_dims))) next_hidden.append(h) hidden = next_hidden + + # 输出的hidden[0],本实验中,shape=[batch_size,128*2] return hidden[0], aggregators def _build(self): - # 将第batch2作为标签,即batch1和batch2是一对正样本对 + # 将第batch2视为标签,即batch1和batch2是一对正样本对 labels = tf.reshape( tf.cast(self.placeholders['batch2'], dtype=tf.int64), [self.batch_size, 1]) - # 获取负样本, 按照给定的概率分布进行采样 + # 获取负样本, 按照给定的概率分布unigrams进行采样 self.neg_samples, _, _ = (tf.nn.fixed_unigram_candidate_sampler( true_classes=labels, num_true=1, @@ -450,24 +467,24 @@ def _build(self): # perform "convolution" # 根据节点id去采样其邻居节点id + # 返回的结果:samples,support_sizes samples1, support_sizes1 = self.sample(self.inputs1, self.layer_infos) samples2, support_sizes2 = self.sample(self.inputs2, self.layer_infos) - + # 每层需要的采样数 实验中是[25,10] num_samples = [ layer_info.num_samples for layer_info in self.layer_infos] - # 获取batch1的特征表达,该步传入的聚合器参数为None,会构建一个聚合器返回 self.outputs1, self.aggregators = self.aggregate(samples1, [self.features], self.dims, num_samples, support_sizes1, concat=self.concat, model_size=self.model_size) - - # 获取batch2的特征表达,其中聚合器是直接使用上一步生成的 + + # 获取batch2的特征表达,其中聚合器是直接使用上一步生成的:aggregators=self.aggregators self.outputs2, _ = self.aggregate(samples2, [self.features], self.dims, num_samples, support_sizes2, aggregators=self.aggregators, concat=self.concat, model_size=self.model_size) - - # 对负样本采样 + + # 对负样本做邻居节点采样,和上面的正样本是同样的处理 neg_samples, neg_support_sizes = self.sample(self.neg_samples, self.layer_infos, FLAGS.neg_sample_size) @@ -478,15 +495,15 @@ def _build(self): dim_mult = 2 if self.concat else 1 + # 这里生成了一个预测层,注意参数bilinear_weights,这个值如果为True,则会生成一个可训练的参数矩阵,在后续的计算loss会用到 - # 在这里设置了否,则无参数矩阵,本质上就是一个计算loss的类 + # 但是本实验在这里设置了否,则无参数矩阵,本质上就是一个计算loss的类,完全不影响上述aggregator的输出 self.link_pred_layer = BipartiteEdgePredLayer(dim_mult*self.dims[-1], dim_mult*self.dims[-1], self.placeholders, act=tf.nn.sigmoid, bilinear_weights=False, name='edge_predict') - - # 对输出的样本执行L2规范化,dim=1,表示按行做,即按样本点做 + # 对输出的样本执行L2规范化,dim=0或者1,1是表示按行做 # x_l2[i] = x[i]/sqrt(sum(x^2)) self.outputs1 = tf.nn.l2_normalize(self.outputs1, 1) self.outputs2 = tf.nn.l2_normalize(self.outputs2, 1) @@ -508,21 +525,23 @@ def build(self): # 计算梯度 grads_and_vars = self.optimizer.compute_gradients(self.loss) - # 梯度裁剪,若梯度大于5则置为5,小于-5则置为-5 + # 梯度裁剪,若梯度大于5则置为5,小于-5则置为-5, clipped_grads_and_vars = [(tf.clip_by_value(grad, -5.0, 5.0) if grad is not None else None, var) for grad, var in grads_and_vars] + + # clipped_grads_and_vars 是一个元组,(grad,var),表示梯度值和变量值 self.grad, _ = clipped_grads_and_vars[0] - # 更新参数 + # 利用裁剪后的梯度更新模型参数 self.opt_op = self.optimizer.apply_gradients(clipped_grads_and_vars) def _loss(self): - + # L2正则化项 for aggregator in self.aggregators: for var in aggregator.vars.values(): self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var) - + # 根据之前生成的预测层,计算loss,该loss有三个选项:_xent_loss、_skipgram_loss、_hinge_loss self.loss += self.link_pred_layer.loss( self.outputs1, self.outputs2, self.neg_outputs) @@ -538,14 +557,13 @@ def _accuracy(self): ③将两组数据拼接,拼接后的数组维度[batch_size, neg_samples_size + 1],意义是每一个顶点和负样本、正样本之间的"亲和度" ④计算正样本对之间的亲和度的排名,排名越靠前越好 mrr值, - """ - + """ + # ①计算正样本对的"亲和度" # aff值在本实验即是两个输入按元素点乘,再按行求和 # shape : [batch_size,] 表示了该batch中,每个节点和其邻居节点的“亲和度”,越大代表越相似 - - aff = self.link_pred_layer.affinity(self.outputs1, self.outputs2) + aff = self.link_pred_layer.affinity(self.outputs1, self.outputs2) # ②计算顶点和负样本的"亲和度" # 返回的是一个矩阵,维度:[batch_size,num_neg_samples] @@ -555,7 +573,7 @@ def _accuracy(self): self.neg_aff = tf.reshape( self.neg_aff, [self.batch_size, FLAGS.neg_sample_size]) - + # ③将两组数据拼接,拼接后的数组维度[batch_size, neg_samples_size + 1],意义是每一个顶点和负样本、正样本之间的"亲和度" # shape : [batch_size,1] _aff = tf.expand_dims(aff, axis=1) @@ -568,9 +586,8 @@ def _accuracy(self): _, indices_of_ranks = tf.nn.top_k(self.aff_all, k=size) _, self.ranks = tf.nn.top_k(-indices_of_ranks, k=size) - # 取self.ranks最后一列,即正样本的排名序数,因为是从0算起的,所以要+1 - # mrr = 1.0/rank + # mrr = 1.0/rank self.mrr = tf.reduce_mean( tf.div(1.0, tf.cast(self.ranks[:, -1] + 1, tf.float32))) tf.summary.scalar('mrr', self.mrr) diff --git a/graphsage/prediction.py b/graphsage/prediction.py index 0c00c68e..70f68170 100644 --- a/graphsage/prediction.py +++ b/graphsage/prediction.py @@ -11,8 +11,8 @@ class BipartiteEdgePredLayer(Layer): def __init__(self, input_dim1, input_dim2, placeholders, dropout=False, act=tf.nn.sigmoid, - loss_fn='xent', neg_sample_weights=1.0, - bias=False, bilinear_weights=False, **kwargs): + loss_fn='xent', neg_sample_weights=1.0, + bias=False, bilinear_weights=False, **kwargs): """ Basic class that applies skip-gram-like loss (i.e., dot product of node+target and node and negative samples) @@ -21,6 +21,7 @@ def __init__(self, input_dim1, input_dim2, placeholders, dropout=False, act=tf.n false, it is assumed that input dimensions are the same and the affinity will be based on dot product. """ + super(BipartiteEdgePredLayer, self).__init__(**kwargs) self.input_dim1 = input_dim1 self.input_dim2 = input_dim2 @@ -44,13 +45,13 @@ def __init__(self, input_dim1, input_dim2, placeholders, dropout=False, act=tf.n with tf.variable_scope(self.name + '_vars'): # bilinear form if bilinear_weights: - #self.vars['weights'] = glorot([input_dim1, input_dim2], + # self.vars['weights'] = glorot([input_dim1, input_dim2], # name='pred_weights') self.vars['weights'] = tf.get_variable( - 'pred_weights', - shape=(input_dim1, input_dim2), - dtype=tf.float32, - initializer=tf.contrib.layers.xavier_initializer()) + 'pred_weights', + shape=(input_dim1, input_dim2), + dtype=tf.float32, + initializer=tf.contrib.layers.xavier_initializer()) if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') @@ -89,6 +90,7 @@ def neg_cost(self, inputs1, neg_samples, hard_neg_samples=None): if self.bilinear_weights: inputs1 = tf.matmul(inputs1, self.vars['weights']) neg_aff = tf.matmul(inputs1, tf.transpose(neg_samples)) + return neg_aff def loss(self, inputs1, inputs2, neg_samples): @@ -96,17 +98,37 @@ def loss(self, inputs1, inputs2, neg_samples): Args: neg_samples: tensor of shape [num_neg_samples x input_dim2]. Negative samples for all inputs in batch inputs1. + """ return self.loss_fn(inputs1, inputs2, neg_samples) def _xent_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): + """ + 对应论文的公式(1) + """ + # 计算正样本对的亲和度 aff = self.affinity(inputs1, inputs2) + + # 计算顶点和各个负样本的亲和度 neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) + + + """ + 计算正样本的交叉熵损失,正样本label赋值全1 + 公式 = y * -log(sigmoid(aff)) + (1 - y) * -log(1 - sigmoid(aff)) + 正样本y=1,负样本y=0,分别可以省略一项 + """ true_xent = tf.nn.sigmoid_cross_entropy_with_logits( - labels=tf.ones_like(aff), logits=aff) + labels=tf.ones_like(aff), logits=aff) + + # 计算负样本的交叉熵损失,负样本label赋值全0 negative_xent = tf.nn.sigmoid_cross_entropy_with_logits( - labels=tf.zeros_like(neg_aff), logits=neg_aff) - loss = tf.reduce_sum(true_xent) + self.neg_sample_weights * tf.reduce_sum(negative_xent) + labels=tf.zeros_like(neg_aff), logits=neg_aff) + + + # neg_sample_weights 默认为1.0 + loss = tf.reduce_sum( + true_xent) + self.neg_sample_weights * tf.reduce_sum(negative_xent) return loss def _skipgram_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): @@ -119,7 +141,8 @@ def _skipgram_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): def _hinge_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): aff = self.affinity(inputs1, inputs2) neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) - diff = tf.nn.relu(tf.subtract(neg_aff, tf.expand_dims(aff, 1) - self.margin), name='diff') + diff = tf.nn.relu(tf.subtract( + neg_aff, tf.expand_dims(aff, 1) - self.margin), name='diff') loss = tf.reduce_sum(diff) self.neg_shape = tf.shape(neg_aff) return loss diff --git a/graphsage/unsupervised_train.py b/graphsage/unsupervised_train.py index 5d0886dc..ad870855 100644 --- a/graphsage/unsupervised_train.py +++ b/graphsage/unsupervised_train.py @@ -71,11 +71,16 @@ def log_dir(): # Define model evaluation function def evaluate(sess, model, minibatch_iter, size=None): t_test = time.time() + + # 评估阶段,这里传入的是验证集 feed_dict_val = minibatch_iter.val_feed_dict(size) outs_val = sess.run([model.loss, model.ranks, model.mrr], feed_dict=feed_dict_val) return outs_val[0], outs_val[1], outs_val[2], (time.time() - t_test) + + +# 这个函数在无监督中没有用到,暂时忽略 def incremental_evaluate(sess, model, minibatch_iter, size): t_test = time.time() finished = False @@ -141,7 +146,7 @@ def train(train_data, test_data=None): #读取图、节点特征、节点映射表 G = train_data[0] - features = train_data[1] # shape = [num, 50] + features = train_data[1] # shape = [num_nodes, num_features] id_map = train_data[2] if not features is None: @@ -168,7 +173,8 @@ def train(train_data, test_data=None): adj_info = tf.Variable(adj_info_ph, trainable=False, name="adj_info") if FLAGS.model == 'graphsage_mean': - # Create model + # Create model + # sampler = UniformNeighborSampler(adj_info) layer_infos = [SAGEInfo("node", sampler, FLAGS.samples_1, FLAGS.dim_1), SAGEInfo("node", sampler, FLAGS.samples_2, FLAGS.dim_2)] # samples1 =25, sample2=10 @@ -294,11 +300,18 @@ def train(train_data, test_data=None): t = time.time() # Training step 训练 - # model.opt_op是调参 + # merged是保存了的所有的tf.summary相关的变量,用于tensorboard绘图, + # model.opt_op 是优化调参的操作 + # 其他的变量 + # + # outs = sess.run([merged, model.opt_op, model.loss, model.ranks, model.aff_all, model.mrr, model.outputs1], feed_dict=feed_dict) # 训练 train_cost = outs[2] train_mrr = outs[5] + + + # train_shadow_mrr值是一个滑动平均更新的方式 if train_shadow_mrr is None: train_shadow_mrr = train_mrr# else: From f6664dc131657e6f09f761442b72ba0d61a58c6d Mon Sep 17 00:00:00 2001 From: pengyi Date: Mon, 28 Feb 2022 17:45:10 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0predication.py=E7=9A=84?= =?UTF-8?q?=E4=B8=80=E4=BA=9B=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/prediction.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/graphsage/prediction.py b/graphsage/prediction.py index 70f68170..8223bc4e 100644 --- a/graphsage/prediction.py +++ b/graphsage/prediction.py @@ -20,6 +20,9 @@ def __init__(self, input_dim1, input_dim2, placeholders, dropout=False, act=tf.n bilinear_weights: use a bilinear weight for affinity calculation: u^T A v. If set to false, it is assumed that input dimensions are the same and the affinity will be based on dot product. + + 一个基础类,使用了"skip-gram" 类型的损失函数(节点和目标的点乘以及节点和负样本的点乘) + """ super(BipartiteEdgePredLayer, self).__init__(**kwargs) @@ -70,6 +73,12 @@ def affinity(self, inputs1, inputs2): """ Affinity score between batch of inputs1 and inputs2. Args: inputs1: tensor of shape [batch_size x feature_size]. + + 计算正样本对之间的"亲和度": + ①特征矩阵点乘(没有bilinear_weights的情况下) + ②求均值 + + 返回的是样本和其对应的正样本之间的亲和度,尺寸:[batch_size,1] """ # shape: [batch_size, input_dim1] if self.bilinear_weights: @@ -86,6 +95,11 @@ def neg_cost(self, inputs1, neg_samples, hard_neg_samples=None): Returns: Tensor of shape [batch_size x num_neg_samples]. For each node, a list of affinities to negative samples is computed. + 计算输入样本和每一个负样本之间的"亲和度": + ①inputs_features × neg_features.T + + 返回的是样本和每一个负样本之间的"亲和度",尺寸是[batch_size, num_neg_samples] + """ if self.bilinear_weights: inputs1 = tf.matmul(inputs1, self.vars['weights']) @@ -104,7 +118,18 @@ def loss(self, inputs1, inputs2, neg_samples): def _xent_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): """ - 对应论文的公式(1) + 计算正样本的交叉熵损失,正样本label赋值全1, 负样本label赋值全0 + 公式 : y * -log(sigmoid(x)) + (1 - y) * -log(1 - sigmoid(x)) + 正样本y=1,负样本y=0,分别可以省略一项 + + ①计算正样本对的亲和度 + ②计算样本和负样本的亲和度 + ③将label全部设为1,计算正样本对产生的loss + ④将label全部设为0,计算所有负样本产生的loss + ⑤将两个loss平均一下 + + 对应论文的公式(1) + """ # 计算正样本对的亲和度 aff = self.affinity(inputs1, inputs2) @@ -114,14 +139,12 @@ def _xent_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): """ - 计算正样本的交叉熵损失,正样本label赋值全1 - 公式 = y * -log(sigmoid(aff)) + (1 - y) * -log(1 - sigmoid(aff)) - 正样本y=1,负样本y=0,分别可以省略一项 + """ true_xent = tf.nn.sigmoid_cross_entropy_with_logits( labels=tf.ones_like(aff), logits=aff) - # 计算负样本的交叉熵损失,负样本label赋值全0 + # 计算负样本的交叉熵损失 negative_xent = tf.nn.sigmoid_cross_entropy_with_logits( labels=tf.zeros_like(neg_aff), logits=neg_aff) From 3104d644f258307dbf79737b88b0e23f584c5791 Mon Sep 17 00:00:00 2001 From: pengyi Date: Mon, 28 Feb 2022 18:06:28 +0800 Subject: [PATCH 06/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E7=9A=84=E6=B5=81=E7=A8=8B=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 - ...64\346\226\260\346\265\201\347\250\213.png" | Bin 0 -> 140657 bytes 2 files changed, 1 deletion(-) create mode 100644 "graphsage/doc/\350\212\202\347\202\271\346\233\264\346\226\260\346\265\201\347\250\213.png" diff --git a/.gitignore b/.gitignore index 98f12e98..d3ecac84 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ # Custom *.idea -*.png *.pdf tmp/ *.txt diff --git "a/graphsage/doc/\350\212\202\347\202\271\346\233\264\346\226\260\346\265\201\347\250\213.png" "b/graphsage/doc/\350\212\202\347\202\271\346\233\264\346\226\260\346\265\201\347\250\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..807cc1f40c86d943156cf8baf9c1ca742f4b14b5 GIT binary patch literal 140657 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 ZHUZmV=`?@KGWZK7e_7?yTNz`o{{h%3EExa* literal 0 HcmV?d00001 From df85c51b37fd16de87000374d989b23a5023ec6a Mon Sep 17 00:00:00 2001 From: pengyi Date: Tue, 1 Mar 2022 11:40:57 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0supervised=5Fmodels?= =?UTF-8?q?=E3=80=81unsupervised=5Ftrain=E7=AD=89=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=9A=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/layers.py | 1 + graphsage/minibatch.py | 5 ++--- graphsage/models.py | 5 ++--- graphsage/supervised_models.py | 31 ++++++++++++++++++++++++++++--- graphsage/unsupervised_train.py | 21 ++++++++++++++++++++- 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/graphsage/layers.py b/graphsage/layers.py index 24d2af8f..57ec291c 100644 --- a/graphsage/layers.py +++ b/graphsage/layers.py @@ -81,6 +81,7 @@ def _log_vars(self): class Dense(Layer): """Dense layer. 一个基础的全连接层 + 需要的入参: dropout :0-1的数字,表示输入被丢弃的概率 act:激活函数的类型选取,例如sigmoid、relu等 diff --git a/graphsage/minibatch.py b/graphsage/minibatch.py index 0a1cd963..0af16c82 100644 --- a/graphsage/minibatch.py +++ b/graphsage/minibatch.py @@ -151,14 +151,13 @@ def incremental_val_feed_dict(self, size, iter_num): def incremental_embed_feed_dict(self, size, iter_num): node_list = self.nodes - val_nodes = node_list[iter_num*size:min((iter_num+1)*size, - len(node_list))] + val_nodes = node_list[iter_num*size:min((iter_num+1)*size,len(node_list))] val_edges = [(n,n) for n in val_nodes] return self.batch_feed_dict(val_edges), (iter_num+1)*size >= len(node_list), val_edges def label_val(self): train_edges = [] - val_edges = [] + val_edges = [] for n1, n2 in self.G.edges(): if (self.G.node[n1]['val'] or self.G.node[n1]['test'] or self.G.node[n2]['val'] or self.G.node[n2]['test']): diff --git a/graphsage/models.py b/graphsage/models.py index e3ea8e83..8abd46ed 100644 --- a/graphsage/models.py +++ b/graphsage/models.py @@ -537,7 +537,8 @@ def build(self): def _loss(self): - # L2正则化项 + # 参数的L2正则化项 + # output = sum(t ** 2) / 2 for aggregator in self.aggregators: for var in aggregator.vars.values(): self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var) @@ -556,13 +557,11 @@ def _accuracy(self): ②计算顶点和负样本的"亲和度" ③将两组数据拼接,拼接后的数组维度[batch_size, neg_samples_size + 1],意义是每一个顶点和负样本、正样本之间的"亲和度" ④计算正样本对之间的亲和度的排名,排名越靠前越好 - mrr值, """ # ①计算正样本对的"亲和度" # aff值在本实验即是两个输入按元素点乘,再按行求和 # shape : [batch_size,] 表示了该batch中,每个节点和其邻居节点的“亲和度”,越大代表越相似 - aff = self.link_pred_layer.affinity(self.outputs1, self.outputs2) # ②计算顶点和负样本的"亲和度" diff --git a/graphsage/supervised_models.py b/graphsage/supervised_models.py index 9ea123ce..ab1c7c70 100644 --- a/graphsage/supervised_models.py +++ b/graphsage/supervised_models.py @@ -27,6 +27,9 @@ def __init__(self, num_classes, - aggregator_type: how to aggregate neighbor information - model_size: one of "small" and "big" - sigmoid_loss: Set to true if nodes can belong to multiple classes + + + 该初始化部分和Model.SampleAndAggregate类基本相同 ''' models.GeneralizedModel.__init__(self, **kwargs) @@ -76,22 +79,41 @@ def __init__(self, num_classes, def build(self): - samples1, support_sizes1 = self.sample(self.inputs1, self.layer_infos) + + """ + 输出特征表达outputs1、计算梯度的流程和SampleAndAggregate的基本相同 + + 只是该有监督模型在特征表达的结果后面加了一层全连接(Dense),用来预测 + """ + # 调用的父类SampleAndAggregate的采样方案 + samples1, support_sizes1 = self.sample(self.inputs1, self.layer_infos) num_samples = [layer_info.num_samples for layer_info in self.layer_infos] + + # 构建聚合器并输出节点表达结果 self.outputs1, self.aggregators = self.aggregate(samples1, [self.features], self.dims, num_samples, support_sizes1, concat=self.concat, model_size=self.model_size) - dim_mult = 2 if self.concat else 1 + dim_mult = 2 if self.concat else 1 + # L2规范化 self.outputs1 = tf.nn.l2_normalize(self.outputs1, 1) - dim_mult = 2 if self.concat else 1 + + + """ + 全连接层 + 该层的输入维度为aggregator输出的特征表达的维度 + 输出维度为分类数 + """ self.node_pred = layers.Dense(dim_mult*self.dims[-1], self.num_classes, dropout=self.placeholders['dropout'], act=lambda x : x) # TF graph management self.node_preds = self.node_pred(self.outputs1) + # 定义损失函数 self._loss() + + # 梯度计算过程和SampleAndAggregate一样 grads_and_vars = self.optimizer.compute_gradients(self.loss) clipped_grads_and_vars = [(tf.clip_by_value(grad, -5.0, 5.0) if grad is not None else None, var) for grad, var in grads_and_vars] @@ -101,6 +123,8 @@ def build(self): def _loss(self): # Weight decay loss + + #L2正则化项 for aggregator in self.aggregators: for var in aggregator.vars.values(): self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var) @@ -108,6 +132,7 @@ def _loss(self): self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var) # classification loss + # 交叉熵损失,激活函数可选sigmoid或者softmax,目的是将结果映射到[0,1]之间 if self.sigmoid_loss: self.loss += tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits( logits=self.node_preds, diff --git a/graphsage/unsupervised_train.py b/graphsage/unsupervised_train.py index ad870855..373a3f31 100644 --- a/graphsage/unsupervised_train.py +++ b/graphsage/unsupervised_train.py @@ -97,6 +97,16 @@ def incremental_evaluate(sess, model, minibatch_iter, size): return np.mean(val_losses), np.mean(val_mrrs), (time.time() - t_test) def save_val_embeddings(sess, model, minibatch_iter, size, out_dir, mod=""): + """ + 输入: + model:训练好了的模型 + minibatch_iter:迭代类 + size: batch_size + out_dir: 输出目录 + + 该函数作用是,训练好模型之后,再将所有的节点都输入到模型计算,保存其特征表达到本地 + """ + val_embeddings = [] finished = False seen = set([]) @@ -104,18 +114,27 @@ def save_val_embeddings(sess, model, minibatch_iter, size, out_dir, mod=""): iter_num = 0 name = "val" while not finished: + + # 获取batch dict数据 + # finished是一个信号,如果为真代表节点遍历完毕,则退出 feed_dict_val, finished, edges = minibatch_iter.incremental_embed_feed_dict(size, iter_num) iter_num += 1 + + # 计算特征表达 outs_val = sess.run([model.loss, model.mrr, model.outputs1], feed_dict=feed_dict_val) - #ONLY SAVE FOR embeds1 because of planetoid + + # ONLY SAVE FOR embeds1 because of planetoid for i, edge in enumerate(edges): if not edge[0] in seen: + # outs_val[-1]表示的是 model.outputs1这个变量, + # outs_val[-1][i,:]是第i个节点的特征 val_embeddings.append(outs_val[-1][i,:]) nodes.append(edge[0]) seen.add(edge[0]) if not os.path.exists(out_dir): os.makedirs(out_dir) + val_embeddings = np.vstack(val_embeddings) np.save(out_dir + name + mod + ".npy", val_embeddings) with open(out_dir + name + mod + ".txt", "w") as fp: From 01accbdb844900a786716fc2144c6f559ec73772 Mon Sep 17 00:00:00 2001 From: pengyi Date: Tue, 1 Mar 2022 17:00:57 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=92=8C=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...202\347\202\271\346\265\201\347\250\213.png" | Bin 0 -> 56391 bytes graphsage/models.py | 12 ++++++------ graphsage/neigh_samplers.py | 6 ++++++ 3 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 "graphsage/doc/\351\207\207\346\240\267\351\202\273\345\261\205\350\212\202\347\202\271\346\265\201\347\250\213.png" diff --git "a/graphsage/doc/\351\207\207\346\240\267\351\202\273\345\261\205\350\212\202\347\202\271\346\265\201\347\250\213.png" "b/graphsage/doc/\351\207\207\346\240\267\351\202\273\345\261\205\350\212\202\347\202\271\346\265\201\347\250\213.png" new file mode 100644 index 0000000000000000000000000000000000000000..4a822afd248ed0964d867904ceafcd2f0cd7cfd9 GIT binary patch literal 56391 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 literal 0 HcmV?d00001 diff --git a/graphsage/models.py b/graphsage/models.py index 8abd46ed..62cf30ab 100644 --- a/graphsage/models.py +++ b/graphsage/models.py @@ -326,19 +326,19 @@ def sample(self, inputs, layer_infos, batch_size=None): support_size = 1 support_sizes = [support_size] - for k in range(len(layer_infos)): # k为跳数,也是层数, 实验中k = 0 1 - t = len(layer_infos) - k - 1 # t = 1 0 + for k in range(len(layer_infos)): # k为跳数,实验中k = 0 1 + t = len(layer_infos) - k - 1 # t = 1 0 # 每一跳的邻居数目是前一跳的邻居节点数*该层的采样数,有个累乘的逻辑 support_size *= layer_infos[t].num_samples sampler = layer_infos[t].neigh_sampler # 采样器选择 - # 采样器的两个输入,第一个入参是将要被采样的节点id,第二个入参是对这些节点,要采样多少个邻居 + # 采样器的两个输入,第一个入参是将要被采样的节点id,第二个入参是采样多少个邻居 node = sampler((samples[k], layer_infos[t].num_samples)) # reshape成一维数组,再添加进samples中 - samples.append(tf.reshape(node, [support_size * batch_size, ])) + samples.append(tf.reshape(node, [support_size * batch_size, ])) # 同时记录好每一层的采样数 support_sizes.append(support_size) @@ -422,7 +422,7 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b # aggregator2 的输入输出维度为:256,256,参数矩阵维度为256,128 # hidden representation at current layer for all support nodes that are various hops away - # 该变量存放的是当前层,各节点利用邻居节点的信息更新后的中间表达, + # 该变量存放的是当前层,各节点利用邻居节点的信息更新后的中间表达 next_hidden = [] # as layer increases, the number of support nodes needed decreases @@ -437,7 +437,7 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b # neigh_dims = [batch_size * 当前跳数的支持节点数,当前层的需要采样的邻居节点数,特征数] # neigh_dims = [batch_size * support_sizes[hop], - num_samples[len(num_samples) - hop - 1], + num_samples[len(num_samples) - hop - 1], # 这个维度,对应sample函数里的 t = len(layer_infos) - k - 1 dim_mult*dims[layer]] h = aggregator((hidden[hop], tf.reshape(hidden[hop + 1], neigh_dims))) diff --git a/graphsage/neigh_samplers.py b/graphsage/neigh_samplers.py index 9c83d553..14292636 100644 --- a/graphsage/neigh_samplers.py +++ b/graphsage/neigh_samplers.py @@ -23,7 +23,13 @@ def __init__(self, adj_info, **kwargs): def _call(self, inputs): ids, num_samples = inputs + + # 从全量的邻接表里根据ids获取各个节点的邻居节点 adj_lists = tf.nn.embedding_lookup(self.adj_info, ids) + + # 该步操作是 转置——乱序——转置, 目的是对列做打乱操作,如果直接打乱的话就是行操作 adj_lists = tf.transpose(tf.random_shuffle(tf.transpose(adj_lists))) + + # 对列切片,只需要num_samples列的邻居,多的部分去掉 adj_lists = tf.slice(adj_lists, [0,0], [-1, num_samples]) return adj_lists From 5d9cfdbb92aa7801f81472ee8fbda8fe06420545 Mon Sep 17 00:00:00 2001 From: pengyi Date: Wed, 2 Mar 2022 17:15:53 +0800 Subject: [PATCH 09/28] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/models.py | 22 +++++++++++++++++++--- graphsage/unsupervised_train.py | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/graphsage/models.py b/graphsage/models.py index 62cf30ab..805ae12c 100644 --- a/graphsage/models.py +++ b/graphsage/models.py @@ -97,6 +97,8 @@ def load(self, sess=None): # 从本地文件读取模型 print("Model restored from file: %s" % save_path) + +# 多层感知机,是一个基础的深度模型 class MLP(Model): """ A standard multi-layer perceptron """ @@ -449,12 +451,22 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b def _build(self): - # 将第batch2视为标签,即batch1和batch2是一对正样本对 + # 将batch2 reshape一下,用作下一步采样的输入 labels = tf.reshape( tf.cast(self.placeholders['batch2'], dtype=tf.int64), [self.batch_size, 1]) + """ + tf.nn.fixed_unigram_candidate_sampler函数功能是从[0,range_max)中随机采样num_sampled个类 + 其中,返回的类是一个列表,每一个元素属于[0, range_max), 代表一个类别 + 每个类被采样的概率由参数unigrams决定,可以是表示概率的数组,也可以是表示count的数组(count大表示被采样的概率大) + range_max参数代表从[0,range_max)中采样,这里等于节点数,刚好是对应节点id + + -------- + 在本实验中,就是利用这个函数,利用每个节点的度数形成概率分布,从节点集合中获取一批节点id,在后续视作负样本 + true_classes个参数传入的是labels,但经测试,采样的结果和这个参数是无关的样子 + 返回的结果neg_samples里面是一个列表,每一个元素代表的是节点id + """ - # 获取负样本, 按照给定的概率分布unigrams进行采样 self.neg_samples, _, _ = (tf.nn.fixed_unigram_candidate_sampler( true_classes=labels, num_true=1, @@ -471,7 +483,8 @@ def _build(self): samples1, support_sizes1 = self.sample(self.inputs1, self.layer_infos) samples2, support_sizes2 = self.sample(self.inputs2, self.layer_infos) - # 每层需要的采样数 实验中是[25,10] + # 每层需要的采样数 实验中是[25,10] + num_samples = [ layer_info.num_samples for layer_info in self.layer_infos] @@ -505,6 +518,7 @@ def _build(self): # 对输出的样本执行L2规范化,dim=0或者1,1是表示按行做 # x_l2[i] = x[i]/sqrt(sum(x^2)) + # 对应论文 Algorithm 1的第7行 self.outputs1 = tf.nn.l2_normalize(self.outputs1, 1) self.outputs2 = tf.nn.l2_normalize(self.outputs2, 1) self.neg_outputs = tf.nn.l2_normalize(self.neg_outputs, 1) @@ -592,6 +606,8 @@ def _accuracy(self): tf.summary.scalar('mrr', self.mrr) + +# class Node2VecModel(GeneralizedModel): def __init__(self, placeholders, dict_size, degrees, name=None, nodevec_dim=50, lr=0.001, **kwargs): diff --git a/graphsage/unsupervised_train.py b/graphsage/unsupervised_train.py index 373a3f31..469ad95d 100644 --- a/graphsage/unsupervised_train.py +++ b/graphsage/unsupervised_train.py @@ -173,7 +173,7 @@ def train(train_data, test_data=None): features = np.vstack([features, np.zeros((features.shape[1],))]) - # 根据开关判断是否加入随机游走的信息 + # 根据开关判断是否使用随机游走的边,如果为真则使用随机游走的边代替图G里的边信息 context_pairs = train_data[3] if FLAGS.random_context else None # 定义一些占位符 From 1f633e7bfdd6f8b721515d55102946b868cade38 Mon Sep 17 00:00:00 2001 From: levinxo Date: Thu, 3 Mar 2022 11:49:13 +0800 Subject: [PATCH 10/28] utils.py annotation --- graphsage/utils.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/graphsage/utils.py b/graphsage/utils.py index ff05072d..d80a9a49 100644 --- a/graphsage/utils.py +++ b/graphsage/utils.py @@ -16,7 +16,13 @@ WALK_LEN=5 N_WALKS=50 + +""" +加载数据集,并进行简单的预处理 +如下注释以toy-ppi数据为例 +""" def load_data(prefix, normalize=True, load_walks=False): + # DATA 1, 14755 nodes, 228431 links G_data = json.load(open(prefix + "-G.json")) G = json_graph.node_link_graph(G_data) if isinstance(G.nodes()[0], int): @@ -24,14 +30,23 @@ def load_data(prefix, normalize=True, load_walks=False): else: conversion = lambda n : n + # DATA 2, numpy数组, (14755, 50) dtype(float64) if os.path.exists(prefix + "-feats.npy"): feats = np.load(prefix + "-feats.npy") else: print("No features present.. Only identity features will be used.") feats = None + + # DATA 3, {"0": 0, "1": 1}, len: 14755 + # node ids to int value indexing feature tensor, + # 用来做string id到int id的映射,其实没啥用 id_map = json.load(open(prefix + "-id_map.json")) id_map = {conversion(k):int(v) for k,v in id_map.items()} walks = [] + + # DATA4, dict, len: 14755, column: 121 + # from nodes ids to class value (int or list) + # 分类标签 class_map = json.load(open(prefix + "-class_map.json")) if isinstance(list(class_map.values())[0], list): lab_conversion = lambda n : n @@ -42,6 +57,7 @@ def load_data(prefix, normalize=True, load_walks=False): ## Remove all nodes that do not have val/test annotations ## (necessary because of networkx weirdness with the Reddit data) + # 移除损坏的节点:无val和test字段的,即验证和测试标识字段 broken_count = 0 for node in G.nodes(): if not 'val' in G.node[node] or not 'test' in G.node[node]: @@ -51,6 +67,9 @@ def load_data(prefix, normalize=True, load_walks=False): ## Make sure the graph has edge train_removed annotations ## (some datasets might already have this..) + # edge: (0, 800) 边,是个元组,表示源节点ID和目标节点ID + # G[0]:某节点与所有的关联节点组成的边的集合 + # 下面这段代码的作用:标记需要在训练中移除的关联关系 print("Loaded data.. now preprocessing..") for edge in G.edges(): if (G.node[edge[0]]['val'] or G.node[edge[1]]['val'] or @@ -59,10 +78,14 @@ def load_data(prefix, normalize=True, load_walks=False): else: G[edge[0]][edge[1]]['train_removed'] = False - if normalize and not feats is None: + if normalize and feats is not None: from sklearn.preprocessing import StandardScaler + # 训练集的id集合,only int, len: 9716 train_ids = np.array([id_map[n] for n in G.nodes() if not G.node[n]['val'] and not G.node[n]['test']]) train_feats = feats[train_ids] + # 特征缩放,标准化:z = (x - u) / s + # u is the mean of the training samples + # s is the standard deviation of the training samples scaler = StandardScaler() scaler.fit(train_feats) feats = scaler.transform(feats) From c25041e750f3557a23a1a75d0b8eb03f19190f9c Mon Sep 17 00:00:00 2001 From: levinxo Date: Thu, 3 Mar 2022 16:33:02 +0800 Subject: [PATCH 11/28] update minibatch.py annotation --- graphsage/minibatch.py | 9 +++++++++ graphsage/supervised_train.py | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/graphsage/minibatch.py b/graphsage/minibatch.py index 0af16c82..e28a6dc3 100644 --- a/graphsage/minibatch.py +++ b/graphsage/minibatch.py @@ -224,15 +224,23 @@ def _make_label_vec(self, node): return label_vec def construct_adj(self): + # 一个numpy 2dim的数组,用于存储各个节点的邻接点,最多为max_degree个邻接点 + # adj shape: (14756, 128) adj = len(self.id2idx)*np.ones((len(self.id2idx)+1, self.max_degree)) + # (14755,) 用于存储所有节点的degree值 deg = np.zeros((len(self.id2idx),)) for nodeid in self.G.nodes(): + # 测试集合验证集的节点直接跳过 if self.G.node[nodeid]['test'] or self.G.node[nodeid]['val']: continue + + # 获取所有训练集中节点邻居节点的id neighbors = np.array([self.id2idx[neighbor] for neighbor in self.G.neighbors(nodeid) if (not self.G[nodeid][neighbor]['train_removed'])]) + + # 不足degree的邻接点补足degree,超过的随机选择degree个邻接点 deg[self.id2idx[nodeid]] = len(neighbors) if len(neighbors) == 0: continue @@ -246,6 +254,7 @@ def construct_adj(self): def construct_test_adj(self): adj = len(self.id2idx)*np.ones((len(self.id2idx)+1, self.max_degree)) for nodeid in self.G.nodes(): + # 所有邻接点的id,这里没有限制训练或测试集 neighbors = np.array([self.id2idx[neighbor] for neighbor in self.G.neighbors(nodeid)]) if len(neighbors) == 0: diff --git a/graphsage/supervised_train.py b/graphsage/supervised_train.py index c5bff002..7da9d4e7 100644 --- a/graphsage/supervised_train.py +++ b/graphsage/supervised_train.py @@ -131,7 +131,7 @@ def train(train_data, test_data=None): num_classes = len(set(class_map.values())) if not features is None: - # pad with dummy zero vector + # pad with dummy zero vector, row wise features = np.vstack([features, np.zeros((features.shape[1],))]) context_pairs = train_data[3] if FLAGS.random_context else None From 9a59693d1482be5d76b77c83990f85a4b1f75f2b Mon Sep 17 00:00:00 2001 From: pengyi Date: Fri, 4 Mar 2022 16:11:40 +0800 Subject: [PATCH 12/28] update --- .gitignore | 216 ++++++++++++------------- graphsage/models.py | 2 +- graphsage/prediction.py | 348 ++++++++++++++++++++-------------------- 3 files changed, 283 insertions(+), 283 deletions(-) diff --git a/.gitignore b/.gitignore index d3ecac84..3c79a046 100644 --- a/.gitignore +++ b/.gitignore @@ -1,108 +1,108 @@ -# Custom -*.idea -*.pdf -tmp/ -*.txt -*swp* -*.sw? -gcn_back -.DS_STORE -*.aux -*.log -*.out -*.bbl -*.synctex.gz - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject - -*.pickle -*.pkl - +# Custom +*.idea +*.pdf +tmp/ +*.txt +*swp* +*.sw? +gcn_back +.DS_STORE +*.aux +*.log +*.out +*.bbl +*.synctex.gz + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +*.pickle +*.pkl + diff --git a/graphsage/models.py b/graphsage/models.py index 805ae12c..73b547a3 100644 --- a/graphsage/models.py +++ b/graphsage/models.py @@ -557,7 +557,7 @@ def _loss(self): for var in aggregator.vars.values(): self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var) - # 根据之前生成的预测层,计算loss,该loss有三个选项:_xent_loss、_skipgram_loss、_hinge_loss + # 根据之前生成的预测层,计算loss,该loss有三个选项:_xent_loss、_skipgram_loss、_hinge_loss,论文中使用的是第一个 self.loss += self.link_pred_layer.loss( self.outputs1, self.outputs2, self.neg_outputs) tf.summary.scalar('loss', self.loss) diff --git a/graphsage/prediction.py b/graphsage/prediction.py index 8223bc4e..3e6f4c22 100644 --- a/graphsage/prediction.py +++ b/graphsage/prediction.py @@ -1,174 +1,174 @@ -from __future__ import division -from __future__ import print_function - -from graphsage.inits import zeros -from graphsage.layers import Layer -import tensorflow as tf - -flags = tf.app.flags -FLAGS = flags.FLAGS - - -class BipartiteEdgePredLayer(Layer): - def __init__(self, input_dim1, input_dim2, placeholders, dropout=False, act=tf.nn.sigmoid, - loss_fn='xent', neg_sample_weights=1.0, - bias=False, bilinear_weights=False, **kwargs): - """ - Basic class that applies skip-gram-like loss - (i.e., dot product of node+target and node and negative samples) - Args: - bilinear_weights: use a bilinear weight for affinity calculation: u^T A v. If set to - false, it is assumed that input dimensions are the same and the affinity will be - based on dot product. - - 一个基础类,使用了"skip-gram" 类型的损失函数(节点和目标的点乘以及节点和负样本的点乘) - - """ - - super(BipartiteEdgePredLayer, self).__init__(**kwargs) - self.input_dim1 = input_dim1 - self.input_dim2 = input_dim2 - self.act = act - self.bias = bias - self.eps = 1e-7 - - # Margin for hinge loss - self.margin = 0.1 - self.neg_sample_weights = neg_sample_weights - - self.bilinear_weights = bilinear_weights - - if dropout: - self.dropout = placeholders['dropout'] - else: - self.dropout = 0. - - # output a likelihood term - self.output_dim = 1 - with tf.variable_scope(self.name + '_vars'): - # bilinear form - if bilinear_weights: - # self.vars['weights'] = glorot([input_dim1, input_dim2], - # name='pred_weights') - self.vars['weights'] = tf.get_variable( - 'pred_weights', - shape=(input_dim1, input_dim2), - dtype=tf.float32, - initializer=tf.contrib.layers.xavier_initializer()) - - if self.bias: - self.vars['bias'] = zeros([self.output_dim], name='bias') - - if loss_fn == 'xent': - self.loss_fn = self._xent_loss - elif loss_fn == 'skipgram': - self.loss_fn = self._skipgram_loss - elif loss_fn == 'hinge': - self.loss_fn = self._hinge_loss - - if self.logging: - self._log_vars() - - def affinity(self, inputs1, inputs2): - """ Affinity score between batch of inputs1 and inputs2. - Args: - inputs1: tensor of shape [batch_size x feature_size]. - - 计算正样本对之间的"亲和度": - ①特征矩阵点乘(没有bilinear_weights的情况下) - ②求均值 - - 返回的是样本和其对应的正样本之间的亲和度,尺寸:[batch_size,1] - """ - # shape: [batch_size, input_dim1] - if self.bilinear_weights: - prod = tf.matmul(inputs2, tf.transpose(self.vars['weights'])) - self.prod = prod - result = tf.reduce_sum(inputs1 * prod, axis=1) - else: - result = tf.reduce_sum(inputs1 * inputs2, axis=1) - return result - - def neg_cost(self, inputs1, neg_samples, hard_neg_samples=None): - """ For each input in batch, compute the sum of its affinity to negative samples. - - Returns: - Tensor of shape [batch_size x num_neg_samples]. For each node, a list of affinities to - negative samples is computed. - 计算输入样本和每一个负样本之间的"亲和度": - ①inputs_features × neg_features.T - - 返回的是样本和每一个负样本之间的"亲和度",尺寸是[batch_size, num_neg_samples] - - """ - if self.bilinear_weights: - inputs1 = tf.matmul(inputs1, self.vars['weights']) - neg_aff = tf.matmul(inputs1, tf.transpose(neg_samples)) - - return neg_aff - - def loss(self, inputs1, inputs2, neg_samples): - """ negative sampling loss. - Args: - neg_samples: tensor of shape [num_neg_samples x input_dim2]. Negative samples for all - inputs in batch inputs1. - - """ - return self.loss_fn(inputs1, inputs2, neg_samples) - - def _xent_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): - """ - 计算正样本的交叉熵损失,正样本label赋值全1, 负样本label赋值全0 - 公式 : y * -log(sigmoid(x)) + (1 - y) * -log(1 - sigmoid(x)) - 正样本y=1,负样本y=0,分别可以省略一项 - - ①计算正样本对的亲和度 - ②计算样本和负样本的亲和度 - ③将label全部设为1,计算正样本对产生的loss - ④将label全部设为0,计算所有负样本产生的loss - ⑤将两个loss平均一下 - - 对应论文的公式(1) - - """ - # 计算正样本对的亲和度 - aff = self.affinity(inputs1, inputs2) - - # 计算顶点和各个负样本的亲和度 - neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) - - - """ - - """ - true_xent = tf.nn.sigmoid_cross_entropy_with_logits( - labels=tf.ones_like(aff), logits=aff) - - # 计算负样本的交叉熵损失 - negative_xent = tf.nn.sigmoid_cross_entropy_with_logits( - labels=tf.zeros_like(neg_aff), logits=neg_aff) - - - # neg_sample_weights 默认为1.0 - loss = tf.reduce_sum( - true_xent) + self.neg_sample_weights * tf.reduce_sum(negative_xent) - return loss - - def _skipgram_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): - aff = self.affinity(inputs1, inputs2) - neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) - neg_cost = tf.log(tf.reduce_sum(tf.exp(neg_aff), axis=1)) - loss = tf.reduce_sum(aff - neg_cost) - return loss - - def _hinge_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): - aff = self.affinity(inputs1, inputs2) - neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) - diff = tf.nn.relu(tf.subtract( - neg_aff, tf.expand_dims(aff, 1) - self.margin), name='diff') - loss = tf.reduce_sum(diff) - self.neg_shape = tf.shape(neg_aff) - return loss - - def weights_norm(self): - return tf.nn.l2_norm(self.vars['weights']) +from __future__ import division +from __future__ import print_function + +from graphsage.inits import zeros +from graphsage.layers import Layer +import tensorflow as tf + +flags = tf.app.flags +FLAGS = flags.FLAGS + + +class BipartiteEdgePredLayer(Layer): + def __init__(self, input_dim1, input_dim2, placeholders, dropout=False, act=tf.nn.sigmoid, + loss_fn='xent', neg_sample_weights=1.0, + bias=False, bilinear_weights=False, **kwargs): + """ + Basic class that applies skip-gram-like loss + (i.e., dot product of node+target and node and negative samples) + Args: + bilinear_weights: use a bilinear weight for affinity calculation: u^T A v. If set to + false, it is assumed that input dimensions are the same and the affinity will be + based on dot product. + + 一个基础类,使用了"skip-gram" 类型的损失函数(节点和目标的点乘以及节点和负样本的点乘) + + """ + + super(BipartiteEdgePredLayer, self).__init__(**kwargs) + self.input_dim1 = input_dim1 + self.input_dim2 = input_dim2 + self.act = act + self.bias = bias + self.eps = 1e-7 + + # Margin for hinge loss + self.margin = 0.1 + self.neg_sample_weights = neg_sample_weights + + self.bilinear_weights = bilinear_weights + + if dropout: + self.dropout = placeholders['dropout'] + else: + self.dropout = 0. + + # output a likelihood term + self.output_dim = 1 + with tf.variable_scope(self.name + '_vars'): + # bilinear form + if bilinear_weights: + # self.vars['weights'] = glorot([input_dim1, input_dim2], + # name='pred_weights') + self.vars['weights'] = tf.get_variable( + 'pred_weights', + shape=(input_dim1, input_dim2), + dtype=tf.float32, + initializer=tf.contrib.layers.xavier_initializer()) + + if self.bias: + self.vars['bias'] = zeros([self.output_dim], name='bias') + + if loss_fn == 'xent': + self.loss_fn = self._xent_loss + elif loss_fn == 'skipgram': + self.loss_fn = self._skipgram_loss + elif loss_fn == 'hinge': + self.loss_fn = self._hinge_loss + + if self.logging: + self._log_vars() + + def affinity(self, inputs1, inputs2): + """ Affinity score between batch of inputs1 and inputs2. + Args: + inputs1: tensor of shape [batch_size x feature_size]. + + 计算正样本对之间的"亲和度": + ①特征矩阵点乘(没有bilinear_weights的情况下) + ②求均值 + + 返回的是样本和其对应的正样本之间的亲和度,尺寸:[batch_size,1] + """ + # shape: [batch_size, input_dim1] + if self.bilinear_weights: + prod = tf.matmul(inputs2, tf.transpose(self.vars['weights'])) + self.prod = prod + result = tf.reduce_sum(inputs1 * prod, axis=1) + else: + result = tf.reduce_sum(inputs1 * inputs2, axis=1) + return result + + def neg_cost(self, inputs1, neg_samples, hard_neg_samples=None): + """ For each input in batch, compute the sum of its affinity to negative samples. + + Returns: + Tensor of shape [batch_size x num_neg_samples]. For each node, a list of affinities to + negative samples is computed. + 计算输入样本和每一个负样本之间的"亲和度": + ①inputs_features × neg_features.T + + 返回的是样本和每一个负样本之间的"亲和度",尺寸是[batch_size, num_neg_samples] + + """ + if self.bilinear_weights: + inputs1 = tf.matmul(inputs1, self.vars['weights']) + neg_aff = tf.matmul(inputs1, tf.transpose(neg_samples)) + + return neg_aff + + def loss(self, inputs1, inputs2, neg_samples): + """ negative sampling loss. + Args: + neg_samples: tensor of shape [num_neg_samples x input_dim2]. Negative samples for all + inputs in batch inputs1. + + """ + return self.loss_fn(inputs1, inputs2, neg_samples) + + def _xent_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): + """ + 计算正样本的交叉熵损失,正样本label赋值全1, 负样本label赋值全0 + 公式 : y * -log(sigmoid(x)) + (1 - y) * -log(1 - sigmoid(x)) + 正样本y=1,负样本y=0,分别可以省略一项 + + ①计算正样本对的亲和度 + ②计算样本和负样本的亲和度 + ③将label全部设为1,计算正样本对产生的loss + ④将label全部设为0,计算所有负样本产生的loss + ⑤将两个loss平均一下 + + 对应论文的公式(1) + + """ + # 计算正样本对的亲和度 + aff = self.affinity(inputs1, inputs2) + + # 计算顶点和各个负样本的亲和度 + neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) + + + """ + + """ + true_xent = tf.nn.sigmoid_cross_entropy_with_logits( + labels=tf.ones_like(aff), logits=aff) + + # 计算负样本的交叉熵损失 + negative_xent = tf.nn.sigmoid_cross_entropy_with_logits( + labels=tf.zeros_like(neg_aff), logits=neg_aff) + + + # neg_sample_weights 默认为1.0 + loss = tf.reduce_sum( + true_xent) + self.neg_sample_weights * tf.reduce_sum(negative_xent) + return loss + + def _skipgram_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): + aff = self.affinity(inputs1, inputs2) + neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) + neg_cost = tf.log(tf.reduce_sum(tf.exp(neg_aff), axis=1)) + loss = tf.reduce_sum(aff - neg_cost) + return loss + + def _hinge_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): + aff = self.affinity(inputs1, inputs2) + neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) + diff = tf.nn.relu(tf.subtract( + neg_aff, tf.expand_dims(aff, 1) - self.margin), name='diff') + loss = tf.reduce_sum(diff) + self.neg_shape = tf.shape(neg_aff) + return loss + + def weights_norm(self): + return tf.nn.l2_norm(self.vars['weights']) From 445e6b061ec166a36f47d5ce8942483eef9bbb47 Mon Sep 17 00:00:00 2001 From: pengyi Date: Tue, 8 Mar 2022 14:12:54 +0800 Subject: [PATCH 13/28] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E4=BA=91=E6=9C=BA=E5=99=A8=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\345\231\250\344\277\241\346\201\257.md" | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 "aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" diff --git "a/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" "b/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" new file mode 100644 index 00000000..5d8b80c2 --- /dev/null +++ "b/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" @@ -0,0 +1,55 @@ +## 阿里云机器信息 + +IP:敏感信息不放在网上 + +mag240原数据目录:`/mnt/ogb-dataset/mag240m/data/raw` + +``` +├── RELEASE_v1.txt +├── mapping //空文件夹 +├── meta.pt +├── processed +│ ├── author___affiliated_with___institution +│ │ └── edge_index.npy //作者和机构的边,shape=[2,num_edges] +│ ├── author___writes___paper +│ │ └── edge_index.npy //作者和论文的边,shape=[2,num_edges] +│ ├── paper +│ │ ├── node_feat.npy //论文节点的特征,shape=[num_node,768] +│ │ ├── node_label.npy // 论文的标签 +│ │ └── node_year.npy // 论文年份 +│ └── paper___cites___paper +│ └── edge_index.npy // 论文和城市的边shape=[2,num_edges] +├── raw //空文件夹 +└── split_dict.pt //切分训练集、验证集、测试集方式的文件,用torch读取是一个字典,keys=[‘train’,’valid’,’test’], value是node_index + +``` + + + +### docker镜像 + +#### opeceipeno/dgl:v1.4 + +ogb代码的运行环境,想法是通过虚拟环境去激活各个方案的运行环境,当前做好了Google的mag240m运行环境 + +[GitHub地址](https://github.com/deepmind/deepmind-research/tree/master/ogb_lsc/mag) + +``` +docker run --gpus all -it -v /mnt:/mnt opeceipeno/dgl:v1.4 bash +# 启动容器后,激活Google代码的运行环境 +source /py3_venv/google_ogb_mag240m/bin/activate +# /workspace 目录有代码 +``` + +Google方案预处理后的数据目录:`/mnt/ogb-dataset/mag240m/data/preprocessed`,相当于执行完了`run_preprocessing.sh`脚本,下一步是可以复现实验, + + + +#### opeceipeno/graphsage:gpu + +graphSAGE的环境,[GitHub地址](https://github.com/qksidmx/GraphSAGE) + +``` +docker run --gpus all -it opeceipeno/graphsage:gpu bash +#/notebook目录下面有代码,运行实验参考readme文档 +``` \ No newline at end of file From 9a005b38cb5b551838d4274148aa5cfaf77c7fba Mon Sep 17 00:00:00 2001 From: 13929520142 Date: Tue, 8 Mar 2022 17:11:46 +0800 Subject: [PATCH 14/28] =?UTF-8?q?=E8=81=9A=E5=90=88=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/aggregators.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/graphsage/aggregators.py b/graphsage/aggregators.py index 4dcff553..9a62e61e 100644 --- a/graphsage/aggregators.py +++ b/graphsage/aggregators.py @@ -27,7 +27,7 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, name = '/' + name else: name = '' - + # 权重矩阵设置对应伪代码中的W,这里自身节点和输入节点采用了不同的W,推测是为了防止过拟合 with tf.variable_scope(self.name + name + '_vars'): self.vars['neigh_weights'] = glorot([neigh_input_dim, output_dim], name='neigh_weights') @@ -42,14 +42,16 @@ def __init__(self, input_dim, output_dim, neigh_input_dim=None, self.input_dim = input_dim self.output_dim = output_dim + # 输入维度[batchSize, numNeigh, numNeighDim],依次为batch大小,每一跳节点数量,节点特征数 def _call(self, inputs): self_vecs, neigh_vecs = inputs neigh_vecs = tf.nn.dropout(neigh_vecs, 1 - self.dropout) self_vecs = tf.nn.dropout(self_vecs, 1 - self.dropout) + # 均值聚合后neigh_mean shape变为[batchSize,numNeighDim],原来每个batchSize的向量均值聚合为1个 neigh_means = tf.reduce_mean(neigh_vecs, axis=1) - # [nodes] x [out_dim] + # [nodes] x W,相乘后shape变为[batchSize, outputDim] from_neighs = tf.matmul(neigh_means, self.vars['neigh_weights']) from_self = tf.matmul(self_vecs, self.vars["self_weights"]) @@ -106,10 +108,13 @@ def _call(self, inputs): neigh_vecs = tf.nn.dropout(neigh_vecs, 1 - self.dropout) self_vecs = tf.nn.dropout(self_vecs, 1 - self.dropout) + # 这里做了两个操作,首先取并集,首先在原来shape为[batchSize,numSelfDim]增加一个维度变为[batchSize,1,numSelfDim] + # 然后在2维上连接邻居向量原来的shape变为[batchSize,numNeigh+numSelf,numNeighDim] + # 最后在2维上均值聚合后,shape变为[batchSize,numNeighDim] means = tf.reduce_mean(tf.concat([neigh_vecs, tf.expand_dims(self_vecs, axis=1)], axis=1), axis=1) - # [nodes] x [out_dim] + # 因为二者已经合为并集,故只需要一个权重矩阵即可,[nodes] x W后,shape变为[batchSize, outputDim] output = tf.matmul(means, self.vars['weights']) # bias @@ -177,10 +182,14 @@ def _call(self, inputs): batch_size = dims[0] num_neighbors = dims[1] # [nodes * sampled neighbors] x [hidden_dim] + # 将邻居矩阵由3维降为2维,此时shape为[batch_size * numNeighbors,neighDim] h_reshaped = tf.reshape(neigh_h, (batch_size * num_neighbors, self.neigh_input_dim)) + # 将降为2维后的邻居矩阵与池化层相乘,池化层shape为[neigh_input_dim,hidden_dim] + # 相乘后矩阵shape变为[batch_size * num_neighbors,hidden_dim] for l in self.mlp_layers: h_reshaped = l(h_reshaped) + # 将经过池化层的矩阵还原为[batch_size, num_neighbors, hidden_dim],然后进行最大聚合降维为[batch_size, hidden_dim] neigh_h = tf.reshape(h_reshaped, (batch_size, num_neighbors, self.hidden_dim)) neigh_h = tf.reduce_max(neigh_h, axis=1) @@ -198,7 +207,7 @@ def _call(self, inputs): return self.act(output) - +# 与最大池化基本相同,只是采用取均值的方式聚合向量集合 class MeanPoolingAggregator(Layer): """ Aggregates via mean-pooling over MLP functions. """ @@ -278,7 +287,7 @@ def _call(self, inputs): return self.act(output) - +# 与最大池化基本相同,经过两次mlp layer,最后shape为[batch_size,hidden_dim_2] class TwoMaxLayerPoolingAggregator(Layer): """ Aggregates via pooling over two MLP functions. """ @@ -416,11 +425,17 @@ def _call(self, inputs): dims = tf.shape(neigh_vecs) batch_size = dims[0] initial_state = self.cell.zero_state(batch_size, tf.float32) + # 将neigh_vecs 将向量每个维度值取为正数,然后进行最大值降维,降维后shape为[batch_size, num_neighbors] + # 再通过tf.sign转化为0,1的矩阵 used = tf.sign(tf.reduce_max(tf.abs(neigh_vecs), axis=2)) + # 在2维上将所有数进行求和降维及获得1维向量[batch_size],这样就获得了每个batch_size的序列值 length = tf.reduce_sum(used, axis=1) + # 为了防止某个batch_size序列值为0的情况,将其强行转为1,作为lstm的输入序列步长 length = tf.maximum(length, tf.constant(1.)) length = tf.cast(length, tf.int32) + # 进行lstm聚合rnn_outputs为结果值,rnn_states为最后一个单元的状态,这里用不上 + # 聚合后rnn_outputs shape为[batch_size,max_len,hidden_dim] with tf.variable_scope(self.name) as scope: try: rnn_outputs, rnn_states = tf.nn.dynamic_rnn( @@ -436,8 +451,13 @@ def _call(self, inputs): batch_size = tf.shape(rnn_outputs)[0] max_len = tf.shape(rnn_outputs)[1] out_size = int(rnn_outputs.get_shape()[2]) + # 生成索引,生成规则为如下:1.先生成shape为[batch_size]的1维向量,具体为[1,2,3...batch_size-2,batch_size-1] + # 2.每个元素乘以max_len + # 3.将原来的lstm序列步长减1后再相加(数组从0开始) index = tf.range(0, batch_size) * max_len + (length - 1) + # 将rnn_outputs shape变为[-1,hidden_dim],-1代表自适应降维,应该是batch_size*max_len flat = tf.reshape(rnn_outputs, [-1, out_size]) + # 根据索引将对应元素从flat取出来shape变为[index.length,hidden_dim] neigh_h = tf.gather(flat, index) from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) From 424694e9b8f0b041f1e76ac98c08c16a5f4f0ed8 Mon Sep 17 00:00:00 2001 From: pengyi Date: Tue, 8 Mar 2022 17:50:34 +0800 Subject: [PATCH 15/28] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=B8=80=E5=A4=84?= =?UTF-8?q?=E6=8F=8F=E8=BF=B0=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git "a/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" "b/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" index 5d8b80c2..2a330537 100644 --- "a/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" +++ "b/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" @@ -18,7 +18,7 @@ mag240原数据目录:`/mnt/ogb-dataset/mag240m/data/raw` │ │ ├── node_label.npy // 论文的标签 │ │ └── node_year.npy // 论文年份 │ └── paper___cites___paper -│ └── edge_index.npy // 论文和城市的边shape=[2,num_edges] +│ └── edge_index.npy // 论文引用关系的边shape=[2,num_edges] ├── raw //空文件夹 └── split_dict.pt //切分训练集、验证集、测试集方式的文件,用torch读取是一个字典,keys=[‘train’,’valid’,’test’], value是node_index @@ -52,4 +52,4 @@ graphSAGE的环境,[GitHub地址](https://github.com/qksidmx/GraphSAGE) ``` docker run --gpus all -it opeceipeno/graphsage:gpu bash #/notebook目录下面有代码,运行实验参考readme文档 -``` \ No newline at end of file +``` From 9fe0150ab4a56440c2b44ab9911b0088b55a303f Mon Sep 17 00:00:00 2001 From: levinxo Date: Tue, 8 Mar 2022 17:51:58 +0800 Subject: [PATCH 16/28] add preprocess notebook for google colab --- graphsage/layers.py | 8 +- graphsage/minibatch.py | 5 +- graphsage/supervised_train.py | 2 + preprocess.ipynb | 446 ++++++++++++++++++++++++++++++++++ 4 files changed, 456 insertions(+), 5 deletions(-) create mode 100644 preprocess.ipynb diff --git a/graphsage/layers.py b/graphsage/layers.py index 57ec291c..d4bcb3c9 100644 --- a/graphsage/layers.py +++ b/graphsage/layers.py @@ -40,10 +40,10 @@ class Layer(object): __call__(inputs): Wrapper for _call() _log_vars(): Log all variables - 最基础的层类型 - name:定义层的名称,字符型 - logging:布尔型,如果开的话就可以打印训练过程中,当需要查看一个张量在训练过程中值的分布情况时,可通过tf.summary.histogram()将其分布情况以直方图的形式在TensorBoard直方图仪表板上显示. - 这里并没有参数矩阵、激活函数等值,都是在其子类中实现 + 最基础的层类型 + name:定义层的名称,字符型 + logging:布尔型,如果开的话就可以打印训练过程中,当需要查看一个张量在训练过程中值的分布情况时,可通过tf.summary.histogram()将其分布情况以直方图的形式在TensorBoard直方图仪表板上显示. + 这里并没有参数矩阵、激活函数等值,都是在其子类中实现 """ def __init__(self, **kwargs): diff --git a/graphsage/minibatch.py b/graphsage/minibatch.py index e28a6dc3..0dcb9a2d 100644 --- a/graphsage/minibatch.py +++ b/graphsage/minibatch.py @@ -186,6 +186,9 @@ class NodeMinibatchIterator(object): num_classes -- number of output classes batch_size -- size of the minibatches max_degree -- maximum size of the downsampled adjacency lists + 以toy-ppi数据集举例: + label_map为输出,维度为(14755, 121) + num_class为label_map的第二维,即121 """ def __init__(self, G, id2idx, placeholders, label_map, num_classes, @@ -225,7 +228,7 @@ def _make_label_vec(self, node): def construct_adj(self): # 一个numpy 2dim的数组,用于存储各个节点的邻接点,最多为max_degree个邻接点 - # adj shape: (14756, 128) + # adjacency shape: (14756, 128) adj = len(self.id2idx)*np.ones((len(self.id2idx)+1, self.max_degree)) # (14755,) 用于存储所有节点的degree值 deg = np.zeros((len(self.id2idx),)) diff --git a/graphsage/supervised_train.py b/graphsage/supervised_train.py index 7da9d4e7..98349e07 100644 --- a/graphsage/supervised_train.py +++ b/graphsage/supervised_train.py @@ -136,6 +136,7 @@ def train(train_data, test_data=None): context_pairs = train_data[3] if FLAGS.random_context else None placeholders = construct_placeholders(num_classes) + #实例化 NodeMinibatch 迭代器 minibatch = NodeMinibatchIterator(G, id_map, placeholders, @@ -144,6 +145,7 @@ def train(train_data, test_data=None): batch_size=FLAGS.batch_size, max_degree=FLAGS.max_degree, context_pairs = context_pairs) + # adjacency shape: (14756, 128) 包装为placeholder adj_info_ph = tf.placeholder(tf.int32, shape=minibatch.adj.shape) adj_info = tf.Variable(adj_info_ph, trainable=False, name="adj_info") diff --git a/preprocess.ipynb b/preprocess.ipynb new file mode 100644 index 00000000..164c2615 --- /dev/null +++ b/preprocess.ipynb @@ -0,0 +1,446 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "graph.ipynb", + "provenance": [], + "collapsed_sections": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "lFBIUQovI53M" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 319 + }, + "id": "FXItxCYQ5xE2", + "outputId": "45d18b2d-50ad-4361-fb5c-1401166b8757" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {} + } + ], + "source": [ + "# python -m graphsage.supervised_train --train_prefix ./example_data/toy-ppi --model graphsage_mean --sigmoid\n", + "# test networkx and visualization\n", + "import networkx as nx\n", + "import tensorflow as tf\n", + "tf.compat.v1.disable_eager_execution()\n", + "\n", + "G = nx.complete_graph(6)\n", + "nx.draw(G)" + ] + }, + { + "cell_type": "code", + "source": [ + "# download code and data\n", + "!git clone https://github.com/williamleif/GraphSAGE" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "S6709xbNrBok", + "outputId": "51e908ea-9105-4844-9427-b57a417bf9da" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Cloning into 'GraphSAGE'...\n", + "remote: Enumerating objects: 265, done.\u001b[K\n", + "remote: Counting objects: 100% (7/7), done.\u001b[K\n", + "remote: Compressing objects: 100% (7/7), done.\u001b[K\n", + "remote: Total 265 (delta 3), reused 0 (delta 0), pack-reused 258\u001b[K\n", + "Receiving objects: 100% (265/265), 6.43 MiB | 11.28 MiB/s, done.\n", + "Resolving deltas: 100% (160/160), done.\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "import json\n", + "from networkx.readwrite import json_graph\n", + "import os\n", + "import numpy as np\n", + "import sys\n", + "\n", + "CODE_ROOT = \"GraphSAGE/graphsage\"\n", + "sys.path.append(\"GraphSAGE\")\n", + "\n", + "def load_data():\n", + " data_path = 'GraphSAGE/example_data'\n", + " # DATA 1, 14755 nodes, 228431 links\n", + " G_data = json.load(open(data_path + '/toy-ppi-G.json'))\n", + " #G_data['nodes'] = G_data['nodes'][:100]\n", + " #G_data['links'] = G_data['links'][:100]\n", + " G = json_graph.node_link_graph(G_data)\n", + " \n", + " conversion = lambda n : n\n", + " lab_conversion = lambda n : n\n", + " \n", + " # DATA 2, (14755, 50) dtype('float64')\n", + " feats = np.load(data_path + '/toy-ppi-feats.npy')\n", + " \n", + " # DATA 3, {\"0\": 0, \"1\": 1}, len: 14755\n", + " # node ids to integer values indexing feature tensor\n", + " # 其实没什么用\n", + " id_map = json.load(open(data_path + \"/toy-ppi-id_map.json\"))\n", + " \n", + " # DATA 4, dict, len: 14755, column 121\n", + " # from node ids to class values (integer or list)\n", + " # 分类标签\n", + " class_map = json.load(open(data_path + \"/toy-ppi-class_map.json\"))\n", + " \n", + " broken_count = 0\n", + " for node in G.nodes():\n", + " if not 'val' in G.nodes()[node] or not 'test' in G.nodes()[node]:\n", + " G.remove_node(node)\n", + " broken_count += 1\n", + " print(\"Removed {:d} nodes that lacked proper annotations due to networkx versioning issues\".format(broken_count))\n", + " \n", + " # edge: (0, 800) 边\n", + " # G[0]: 某结点与所有的关联结点组成的边的集合\n", + " # 标记需要在训练中移除的关联关系,即边\n", + " for edge in G.edges():\n", + " if (G.nodes()[edge[0]]['val'] or G.nodes()[edge[1]]['val'] or\n", + " G.nodes()[edge[0]]['test'] or G.nodes()[edge[1]]['test']):\n", + " G[edge[0]][edge[1]]['train_removed'] = True\n", + " else:\n", + " G[edge[0]][edge[1]]['train_removed'] = False\n", + " \n", + " from sklearn.preprocessing import StandardScaler\n", + " \n", + " # 训练集的id集合,result only int, len: 9716\n", + " train_ids = np.array([id_map[str(n)] for n in G.nodes() \\\n", + " if not G.nodes()[n]['val'] and not G.nodes()[n]['test']])\n", + " \n", + " train_feats = feats[train_ids]\n", + " \n", + " # 特征缩放,标准化:z = (x - u) / s\n", + " # u is the mean of the training samples\n", + " # s is the standard deviation of the training samples\n", + " scaler = StandardScaler()\n", + " scaler.fit(train_feats)\n", + " feats = scaler.transform(feats)\n", + "\n", + " walks = []\n", + "\n", + " return G, feats, id_map, walks, class_map" + ], + "metadata": { + "id": "jg81uw5O6Gaz" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def construct_placeholders(num_classes):\n", + " # Define placeholders\n", + " placeholders = {\n", + " 'labels' : tf.compat.v1.placeholder(tf.float32, shape=(None, num_classes), name='labels'),\n", + " 'dropout': tf.compat.v1.placeholder_with_default(0., shape=(), name='dropout'),\n", + " 'batch' : tf.compat.v1.placeholder(tf.int32, shape=(None), name='batch1'),\n", + " 'batch_size' : tf.compat.v1.placeholder(tf.int32, name='batch_size'),\n", + " }\n", + " return placeholders" + ], + "metadata": { + "id": "mReVSrV9UzYO" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "train_data = load_data()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Rv1F4lYF_FfW", + "outputId": "7b9e88c6-8ba5-4128-9564-55a7d4cc835c" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Removed 0 nodes that lacked proper annotations due to networkx versioning issues\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "G = train_data[0]\n", + "features = train_data[1]\n", + "id_map = train_data[2]\n", + "context_pairs = train_data[3]\n", + "class_map = train_data[4]\n", + "\n", + "# num_classes = 121\n", + "num_classes = len(list(class_map.values())[0])\n", + "# pad with dummy zero vector, row wise\n", + "features = np.vstack([features, np.zeros((features.shape[1],))])\n", + "placeholders = construct_placeholders(num_classes)" + ], + "metadata": { + "id": "CjSUlil1kNZP" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "class NodeMinibatchIterator(object):\n", + "\n", + " \"\"\"\n", + " This minibatch iterator iterates over nodes for supervised learning.\n", + "\n", + " G -- networkx graph\n", + " id2idx -- dict mapping node ids to integer values indexing feature tensor\n", + " placeholders -- standard tensorflow placeholders object for feeding\n", + " label_map -- map from node ids to class values (integer or list)\n", + " num_classes -- number of output classes\n", + " batch_size -- size of the minibatches\n", + " max_degree -- maximum size of the downsampled adjacency lists\n", + " 以toy-ppi数据集举例:\n", + " label_map为输出,维度为(14755, 121)\n", + " num_class为label_map的第二维,即121\n", + " \"\"\"\n", + " def __init__(self, G, id2idx,\n", + " placeholders, label_map, num_classes,\n", + " batch_size=100, max_degree=25,\n", + " **kwargs):\n", + "\n", + " self.G = G\n", + " self.nodes = G.nodes()\n", + " self.id2idx = id2idx\n", + " self.placeholders = placeholders\n", + " self.batch_size = batch_size\n", + " self.max_degree = max_degree\n", + " self.batch_num = 0\n", + " self.label_map = label_map\n", + " self.num_classes = num_classes\n", + "\n", + " self.adj, self.deg = self.construct_adj()\n", + " self.test_adj = self.construct_test_adj()\n", + "\n", + " self.val_nodes = [n for n in self.G.nodes() if self.G.nodes()[n]['val']]\n", + " self.test_nodes = [n for n in self.G.nodes() if self.G.nodes()[n]['test']]\n", + "\n", + " # 不参与训练的结点id\n", + " self.no_train_nodes_set = set(self.val_nodes + self.test_nodes)\n", + " # 可训练的结点id\n", + " self.train_nodes = set(G.nodes()).difference(self.no_train_nodes_set)\n", + " # don't train on nodes that only have edges to test set\n", + " # 只保留有邻居的结点\n", + " self.train_nodes = [n for n in self.train_nodes if self.deg[id2idx[str(n)]] > 0]\n", + "\n", + " def _make_label_vec(self, node):\n", + " label = self.label_map[node]\n", + " if isinstance(label, list):\n", + " label_vec = np.array(label)\n", + " else:\n", + " label_vec = np.zeros((self.num_classes))\n", + " class_ind = self.label_map[node]\n", + " label_vec[class_ind] = 1\n", + " return label_vec\n", + "\n", + " def construct_adj(self):\n", + " # adjacency shape: (14756, 128) ,用于存储所有节点的邻居节点id\n", + " adj = len(self.id2idx) * np.ones((len(self.id2idx)+1, self.max_degree))\n", + " # (14755,) ,用于存储所有结点的degree值\n", + " deg = np.zeros((len(self.id2idx),))\n", + "\n", + " for nodeid in self.G.nodes():\n", + " if self.G.nodes()[nodeid]['test'] or self.G.nodes()[nodeid]['val']:\n", + " continue\n", + "\n", + " # 获取所有训练集的邻居节点的id\n", + " neighbors = np.array([self.id2idx[str(neighbor)]\n", + " for neighbor in self.G.neighbors(nodeid)\n", + " if (not self.G[nodeid][neighbor]['train_removed'])])\n", + " \n", + " deg[self.id2idx[str(nodeid)]] = len(neighbors)\n", + " if len(neighbors) == 0:\n", + " continue\n", + " if len(neighbors) > self.max_degree:\n", + " neighbors = np.random.choice(neighbors, self.max_degree, replace=False)\n", + " elif len(neighbors) < self.max_degree:\n", + " neighbors = np.random.choice(neighbors, self.max_degree, replace=True)\n", + " adj[self.id2idx[str(nodeid)], :] = neighbors\n", + " return adj, deg\n", + "\n", + " def construct_test_adj(self):\n", + " adj = len(self.id2idx) * np.ones((len(self.id2idx)+1, self.max_degree))\n", + " for nodeid in self.G.nodes():\n", + " # 所有邻居节点的id,这里没有限制训练集或测试集\n", + " neighbors = np.array([self.id2idx[str(neighbor)]\n", + " for neighbor in self.G.neighbors(nodeid)])\n", + " if len(neighbors) == 0:\n", + " continue\n", + " if len(neighbors) > self.max_degree:\n", + " neighbors = np.random.choice(neighbors, self.max_degree, replace=False)\n", + " elif len(neighbors) < self.max_degree:\n", + " neighbors = np.random.choice(neighbors, self.max_degree, replace=True)\n", + " adj[self.id2idx[str(nodeid)], :] = neighbors\n", + " return adj\n", + "\n", + " def end(self):\n", + " return self.batch_num * self.batch_size >= len(self.train_nodes)\n", + "\n", + " def batch_feed_dict(self, batch_nodes, val=False):\n", + " batch1id = batch_nodes\n", + " batch1 = [self.id2idx[n] for n in batch1id]\n", + "\n", + " labels = np.vstack([self._make_label_vec(node) for node in batch1id])\n", + " feed_dict = dict()\n", + " feed_dict.update({self.placeholders['batch_size'] : len(batch1)})\n", + " feed_dict.update({self.placeholders['batch']: batch1})\n", + " feed_dict.update({self.placeholders['labels']: labels})\n", + "\n", + " return feed_dict, labels\n", + "\n", + " def node_val_feed_dict(self, size=None, test=False):\n", + " if test:\n", + " val_nodes = self.test_nodes\n", + " else:\n", + " val_nodes = self.val_nodes\n", + " if not size is None:\n", + " val_nodes = np.random.choice(val_nodes, size, replace=True)\n", + " # add a dummy neighbor\n", + " ret_val = self.batch_feed_dict(val_nodes)\n", + " return ret_val[0], ret_val[1]\n", + "\n", + " def incremental_node_val_feed_dict(self, size, iter_num, test=False):\n", + " if test:\n", + " val_nodes = self.test_nodes\n", + " else:\n", + " val_nodes = self.val_nodes\n", + " val_node_subset = val_nodes[iter_num*size:min((iter_num+1)*size,\n", + " len(val_nodes))]\n", + "\n", + " # add a dummy neighbor\n", + " ret_val = self.batch_feed_dict(val_node_subset)\n", + " return ret_val[0], ret_val[1], (iter_num+1)*size >= len(val_nodes), val_node_subset\n", + "\n", + " def num_training_batches(self):\n", + " return len(self.train_nodes) // self.batch_size + 1\n", + "\n", + " def next_minibatch_feed_dict(self):\n", + " start_idx = self.batch_num * self.batch_size\n", + " self.batch_num += 1\n", + " end_idx = min(start_idx + self.batch_size, len(self.train_nodes))\n", + " batch_nodes = self.train_nodes[start_idx : end_idx]\n", + " return self.batch_feed_dict(batch_nodes)\n", + "\n", + " def incremental_embed_feed_dict(self, size, iter_num):\n", + " node_list = self.nodes\n", + " val_nodes = node_list[iter_num*size:min((iter_num+1)*size,\n", + " len(node_list))]\n", + " return self.batch_feed_dict(val_nodes), (iter_num+1)*size >= len(node_list), val_nodes\n", + "\n", + " def shuffle(self):\n", + " \"\"\" Re-shuffle the training set.\n", + " Also reset the batch number.\n", + " \"\"\"\n", + " self.train_nodes = np.random.permutation(self.train_nodes)\n", + " self.batch_num = 0\n" + ], + "metadata": { + "id": "ZYCKM4i5PmPf" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "\"\"\"\n", + " This minibatch iterator iterates over nodes for supervised learning.\n", + "\n", + " G -- networkx graph\n", + " id2idx -- dict mapping node ids to integer values indexing feature tensor\n", + " placeholders -- standard tensorflow placeholders object for feeding\n", + " label_map -- map from node ids to class values (integer or list)\n", + " num_classes -- number of output classes\n", + " batch_size -- size of the minibatches\n", + " max_degree -- maximum size of the downsampled adjacency lists\n", + "\"\"\"\n", + "# 实例化 NodeMinibatch 迭代器\n", + "minibatch = NodeMinibatchIterator(G,\n", + " id_map,\n", + " placeholders,\n", + " class_map,\n", + " num_classes,\n", + " batch_size=512,\n", + " max_degree=128,\n", + " context_pairs = context_pairs)\n", + "\n", + "# adjacency shape: (14756, 128) 包装为placeholder\n", + "adj_info_ph = tf.compat.v1.placeholder(tf.int32, shape=minibatch.adj.shape)\n", + "adj_info = tf.Variable(adj_info_ph, trainable=False, name=\"adj_info\")\n", + "\n", + "# 接着就是构建模型了,需要改动的兼容代码过多,暂不继续了" + ], + "metadata": { + "id": "mUW98eVhQ5H7" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "" + ], + "metadata": { + "id": "Wp3DZreLrdtF" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file From 7a5972fca5850c4de54ea1046f069c4149fe2956 Mon Sep 17 00:00:00 2001 From: 13929520142 Date: Wed, 9 Mar 2022 10:46:41 +0800 Subject: [PATCH 17/28] =?UTF-8?q?lstm=E6=B3=A8=E9=87=8A=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/aggregators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graphsage/aggregators.py b/graphsage/aggregators.py index 6f117a1b..134f6474 100644 --- a/graphsage/aggregators.py +++ b/graphsage/aggregators.py @@ -459,6 +459,8 @@ def _call(self, inputs): # 生成索引,生成规则为如下:1.先生成shape为[batch_size]的1维向量,具体为[1,2,3...batch_size-2,batch_size-1] # 2.每个元素乘以max_len # 3.将原来的lstm序列步长减1后再相加(数组从0开始) + # 该index的意义就是为了取每个batch的最后一个聚合结果,因为lstm为序列化的聚合,训练的结果是逐步从第一个 + # 传递至最后一个,获得最后一个batch的结果就相当于获得了这个batch全部的lstm聚合结果 index = tf.range(0, batch_size) * max_len + (length - 1) # 将rnn_outputs shape变为[-1,hidden_dim],-1代表自适应降维,应该是batch_size*max_len flat = tf.reshape(rnn_outputs, [-1, out_size]) From c4eb6bedfa0cb615ab1428ac3a0328c22429dc70 Mon Sep 17 00:00:00 2001 From: 13929520142 Date: Fri, 25 Feb 2022 14:37:31 +0800 Subject: [PATCH 18/28] =?UTF-8?q?=E6=B5=8B=E8=AF=95push?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/aggregators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphsage/aggregators.py b/graphsage/aggregators.py index 134f6474..184393a5 100644 --- a/graphsage/aggregators.py +++ b/graphsage/aggregators.py @@ -386,7 +386,7 @@ class SeqAggregator(Layer): """ def __init__(self, input_dim, output_dim, model_size="small", neigh_input_dim=None, - dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): + dropout=0., bias=False, act=tf.nn.relu, name=None, concat=False, **kwargs): super(SeqAggregator, self).__init__(**kwargs) self.dropout = dropout From 6f19648d15a9c29482e24cc9bde87235d08128ce Mon Sep 17 00:00:00 2001 From: 13929520142 Date: Wed, 9 Mar 2022 11:35:45 +0800 Subject: [PATCH 19/28] =?UTF-8?q?lstm=E5=9B=BE=E7=89=87=E6=B7=BB=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/doc/lstm.png | Bin 0 -> 136469 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 graphsage/doc/lstm.png diff --git a/graphsage/doc/lstm.png b/graphsage/doc/lstm.png new file mode 100644 index 0000000000000000000000000000000000000000..6a019413ca8e5bfcc7e3c7a08c038b36fa230991 GIT binary patch literal 136469 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�s}UhawRnBO5?|z}YZdypkx> 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%0AXBt`^AGQKi= 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 literal 0 HcmV?d00001 From 81bc968c14322e12e6ba57ee61264c6e319f40d9 Mon Sep 17 00:00:00 2001 From: pengyi Date: Wed, 9 Mar 2022 14:59:01 +0800 Subject: [PATCH 20/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=AE=9E=E9=AA=8C?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...36\351\252\214\347\273\223\346\236\234.md" | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 "graphsage/doc/\345\256\236\351\252\214\347\273\223\346\236\234.md" diff --git "a/graphsage/doc/\345\256\236\351\252\214\347\273\223\346\236\234.md" "b/graphsage/doc/\345\256\236\351\252\214\347\273\223\346\236\234.md" new file mode 100644 index 00000000..6d12a507 --- /dev/null +++ "b/graphsage/doc/\345\256\236\351\252\214\347\273\223\346\236\234.md" @@ -0,0 +1,98 @@ +### 有监督 + +```bash +python -m graphsage.supervised_train \ +--train_prefix ./ppi/ppi \ +--model graphsage_maxpool \ +--learning_rate 0.001 \ +--epochs 200 \ +--batch_size 1024 \ +--validate_iter 10 \ +--random_context False \ +--sigmoid \ +--gpu 0 + +Full validation stats: loss= 0.35714 f1_micro= 0.71096 f1_macro= 0.63525 time= 0.30733 +``` + + + +```bash +python -m graphsage.supervised_train \ +--train_prefix ./ppi/ppi \ +--model gcn \ +--print_every 50 \ +--epochs 200 \ +--batch_size 1024 \ +--learning_rate 0.001 \ +--validate_iter 10 \ +--random_context False \ +--sigmoid \ +--gpu 0 + + +Full validation stats: loss= 0.49029 f1_micro= 0.52777 f1_macro= 0.34071 time= 0.25776 +``` + + + +```bash +python -m graphsage.supervised_train \ +--train_prefix ./ppi/ppi \ +--model graphsage_seq \ +--learning_rate 0.001 \ +--epochs 200 \ +--batch_size 1024 \ +--validate_iter 10 \ +--random_context False \ +--sigmoid \ +--gpu 0 + +Full validation stats: loss= 0.34045 f1_micro= 0.72752 f1_macro= 0.65601 time= 0.41571 +``` + + + +```bash +python -m graphsage.supervised_train \ +--train_prefix ./ppi/ppi \ +--model graphsage_mean \ +--learning_rate 0.001 \ +--epochs 200 \ +--batch_size 1024 \ +--validate_iter 10 \ +--random_context False \ +--sigmoid \ +--gpu 0 + +Full validation stats: loss= 0.42566 f1_micro= 0.60157 f1_macro= 0.47732 time= 0.30706 +``` + + + +### 无监督 + + + +```bash +python -m graphsage.unsupervised_train \ +--train_prefix ./ppi/ppi \ +--model graphsage_mean \ +--model_size big \ +--print_every 50 \ +--epoch 50 \ +--batch_size 1024 \ +--dropout 0.1 \ +--learning_rate 0.0001 \ +--validate_iter 10 \ +--random_context False +--gpu 0 + + +python eval_scripts/ppi_eval.py ./ppi ./unsup-ppi/graphsage_mean_big_0.000100 test + +F1-micro 0.761944872 +``` + + + From 3bc371ccba0cab1a18b5b3927f57a9d9a5173c21 Mon Sep 17 00:00:00 2001 From: yxy <1016545097> Date: Thu, 10 Mar 2022 15:02:24 +0800 Subject: [PATCH 21/28] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/inits.py | 9 +++++++++ graphsage/prediction.py | 20 +++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/graphsage/inits.py b/graphsage/inits.py index c3351494..a38880d5 100644 --- a/graphsage/inits.py +++ b/graphsage/inits.py @@ -8,23 +8,32 @@ def uniform(shape, scale=0.05, name=None): """Uniform init.""" + #根据shpae参数生成一个均匀分布值为minval和maxval之间的矩阵 initial = tf.random_uniform(shape, minval=-scale, maxval=scale, dtype=tf.float32) + #创建一个tf的节点变量 return tf.Variable(initial, name=name) def glorot(shape, name=None): """Glorot & Bengio (AISTATS 2010) init.""" + #获取矩阵元素的平方根 init_range = np.sqrt(6.0/(shape[0]+shape[1])) + #根据shpae参数生成一个均匀分布值为minval和maxval之间的矩阵,值为上面算出来的平方根 initial = tf.random_uniform(shape, minval=-init_range, maxval=init_range, dtype=tf.float32) + #创建一个tf的节点变量 return tf.Variable(initial, name=name) def zeros(shape, name=None): """All zeros.""" + #生成一个全是0的矩阵 initial = tf.zeros(shape, dtype=tf.float32) + #创建一个tf的节点变量 return tf.Variable(initial, name=name) def ones(shape, name=None): """All ones.""" + #生成一个全是1的矩阵 initial = tf.ones(shape, dtype=tf.float32) + #创建一个tf的节点变量 return tf.Variable(initial, name=name) diff --git a/graphsage/prediction.py b/graphsage/prediction.py index 8223bc4e..a64be482 100644 --- a/graphsage/prediction.py +++ b/graphsage/prediction.py @@ -45,20 +45,25 @@ def __init__(self, input_dim1, input_dim2, placeholders, dropout=False, act=tf.n # output a likelihood term self.output_dim = 1 + #生成命名空间 with tf.variable_scope(self.name + '_vars'): # bilinear form if bilinear_weights: # self.vars['weights'] = glorot([input_dim1, input_dim2], # name='pred_weights') + #获取已存在的变量,如果不存在就创建一个,pred_weights为变量名,shape为维度,initializer为初始化的方式 self.vars['weights'] = tf.get_variable( 'pred_weights', shape=(input_dim1, input_dim2), dtype=tf.float32, + #该函数返回一个用于初始化权重的初始化程序“Xavier”。这个初始化器是用来保持每一层的阶梯大小都差不多相同。 initializer=tf.contrib.layers.xavier_initializer()) + #如果bias=False就不生成矩阵,初始化时定义的默认值为False,如果为true就生成为0的矩阵 if self.bias: self.vars['bias'] = zeros([self.output_dim], name='bias') + #根据loss_fn传入的参数,把参数同名的方法赋值给loss_fn if loss_fn == 'xent': self.loss_fn = self._xent_loss elif loss_fn == 'skipgram': @@ -82,10 +87,13 @@ def affinity(self, inputs1, inputs2): """ # shape: [batch_size, input_dim1] if self.bilinear_weights: + #inputs2矩阵和权重矩阵相乘,权重矩阵做了参数转置处理 prod = tf.matmul(inputs2, tf.transpose(self.vars['weights'])) self.prod = prod + #向量prod * inputs1形状张量得到一个一维矩阵 result = tf.reduce_sum(inputs1 * prod, axis=1) else: + #向量inputs1 * inputs2形状张量得到一个一维矩阵 result = tf.reduce_sum(inputs1 * inputs2, axis=1) return result @@ -102,11 +110,14 @@ def neg_cost(self, inputs1, neg_samples, hard_neg_samples=None): """ if self.bilinear_weights: + #inputs1矩阵和权重相乘 inputs1 = tf.matmul(inputs1, self.vars['weights']) + #inputs1矩阵和neg_samples矩阵相乘,neg_samples参数做了转置处理 neg_aff = tf.matmul(inputs1, tf.transpose(neg_samples)) return neg_aff + #调用loss_fn函数 def loss(self, inputs1, inputs2, neg_samples): """ negative sampling loss. Args: @@ -138,9 +149,7 @@ def _xent_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) - """ - - """ + #计算损失,logits和labels必须有相同的类型和大小 true_xent = tf.nn.sigmoid_cross_entropy_with_logits( labels=tf.ones_like(aff), logits=aff) @@ -158,15 +167,20 @@ def _skipgram_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): aff = self.affinity(inputs1, inputs2) neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) neg_cost = tf.log(tf.reduce_sum(tf.exp(neg_aff), axis=1)) + #off矩阵 减去 neg_cost矩阵,再求和得出损失 loss = tf.reduce_sum(aff - neg_cost) return loss def _hinge_loss(self, inputs1, inputs2, neg_samples, hard_neg_samples=None): aff = self.affinity(inputs1, inputs2) neg_aff = self.neg_cost(inputs1, neg_samples, hard_neg_samples) + #tf.nn.relu:将输入小于0的值赋值为0,输入大于0的值不变 + #tf.subtract:返回的数据类型与neg_aff相同,且第一个参数减去第二个参数的操作是元素级别的 + #tf.expand_dims:用于给函数增加维度 diff = tf.nn.relu(tf.subtract( neg_aff, tf.expand_dims(aff, 1) - self.margin), name='diff') loss = tf.reduce_sum(diff) + #得到neg_aff矩阵的shape self.neg_shape = tf.shape(neg_aff) return loss From d2ad9f0459e424abf0539acc6b90f2c3b3c35c52 Mon Sep 17 00:00:00 2001 From: kashmir Date: Thu, 10 Mar 2022 19:02:29 +0800 Subject: [PATCH 22/28] =?UTF-8?q?lstm=E5=87=BD=E6=95=B0=E6=B3=A8=E9=87=8A?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/aggregators.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graphsage/aggregators.py b/graphsage/aggregators.py index 184393a5..fdf62f63 100644 --- a/graphsage/aggregators.py +++ b/graphsage/aggregators.py @@ -441,6 +441,11 @@ def _call(self, inputs): # 进行lstm聚合rnn_outputs为结果值,rnn_states为最后一个单元的状态,这里用不上 # 聚合后rnn_outputs shape为[batch_size,max_len,hidden_dim] + # lstm每个cell的输入为上个cell的状态c和上个cell的结果h与这一层的输入x相连的向量v + # lstm通过3个门控制每个cell的输出,3个门实际上就是3个sigmoid函数,每个门通过各自的权重矩阵控制输出的结果. + # 第一个门是遗忘门g1用来控制传入v中那些信息会被保留,第二个门g2输入门用来控制v哪些信息会被输入到下一个状态,第三个门g3用来控制v哪些信息会被输出 + # 每一层的c计算方式为 (c * g1) contact g2 ,每一层的输出h计算方式为 tanh((c * g1) contact g2) * g3 + # 传递至最后一个cell的输出即为整个输出的结果 with tf.variable_scope(self.name) as scope: try: rnn_outputs, rnn_states = tf.nn.dynamic_rnn( @@ -464,7 +469,7 @@ def _call(self, inputs): index = tf.range(0, batch_size) * max_len + (length - 1) # 将rnn_outputs shape变为[-1,hidden_dim],-1代表自适应降维,应该是batch_size*max_len flat = tf.reshape(rnn_outputs, [-1, out_size]) - # 根据索引将对应元素从flat取出来shape变为[index.length,hidden_dim] + # 根据索引将对应元素从flat取出来shape变为[index.length,hidden_dim], index.length = batch_size neigh_h = tf.gather(flat, index) from_neighs = tf.matmul(neigh_h, self.vars['neigh_weights']) From 197eb3df9391cd9a0843a8f5843a3b588451c810 Mon Sep 17 00:00:00 2001 From: pengyi Date: Fri, 11 Mar 2022 15:15:57 +0800 Subject: [PATCH 23/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=B4=9F=E6=A0=B7?= =?UTF-8?q?=E6=9C=AC=E9=87=87=E6=A0=B7=E7=9A=84=E4=B8=80=E4=B8=AA=E7=96=91?= =?UTF-8?q?=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- graphsage/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/graphsage/models.py b/graphsage/models.py index 73b547a3..746b6c0a 100644 --- a/graphsage/models.py +++ b/graphsage/models.py @@ -463,8 +463,11 @@ def _build(self): -------- 在本实验中,就是利用这个函数,利用每个节点的度数形成概率分布,从节点集合中获取一批节点id,在后续视作负样本 - true_classes个参数传入的是labels,但经测试,采样的结果和这个参数是无关的样子 + true_classes个参数传入的是labels,但经测试,采样的结果和这个参数是无关的样子,而且实际是有可能会采样到负样本的 返回的结果neg_samples里面是一个列表,每一个元素代表的是节点id + + 参考 https://github.com/williamleif/GraphSAGE/issues/76, 这个里面作者说了是可能会采样到正样本 + 只是他们假设,当整个图数据集远大于邻域计算图时,采样到正样本的概率很小。 """ self.neg_samples, _, _ = (tf.nn.fixed_unigram_candidate_sampler( From 29cffe796b6d1ef043ca19b7d40dfe10f808bffb Mon Sep 17 00:00:00 2001 From: pengyi Date: Mon, 14 Mar 2022 16:44:00 +0800 Subject: [PATCH 24/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20dgl=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E6=B5=81=E7=A8=8B`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...26\350\257\221\346\265\201\347\250\213.md" | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 "graphsage/doc/dgl-\345\274\200\345\217\221\347\216\257\345\242\203-\347\274\226\350\257\221\346\265\201\347\250\213.md" diff --git "a/graphsage/doc/dgl-\345\274\200\345\217\221\347\216\257\345\242\203-\347\274\226\350\257\221\346\265\201\347\250\213.md" "b/graphsage/doc/dgl-\345\274\200\345\217\221\347\216\257\345\242\203-\347\274\226\350\257\221\346\265\201\347\250\213.md" new file mode 100644 index 00000000..214dffe9 --- /dev/null +++ "b/graphsage/doc/dgl-\345\274\200\345\217\221\347\216\257\345\242\203-\347\274\226\350\257\221\346\265\201\347\250\213.md" @@ -0,0 +1,38 @@ + + + + +### 镜像名:opeceipeno/dgl:devel-gpu + + + +#### 启动镜像 + +```bash +docker run --gpus all -ti opeceipeno/dgl:devel-gpu bash +``` + +#### 编译安装dgl + +```bash +# 查看环境列表 +conda env list +# 激活pytorch的编译环境 +conda activate pytorch-ci + +# 源码在/workspace/dgl 编译 +cd /workspace/dgl/build +cmake -DUSE_CUDA=ON -DBUILD_TORCH=ON .. +make -j4 + +# 安装pip包 +cd ../python +python setup.py install + +# 测试 +python -c "import dgl; print(dgl.__version__);import torch; print(torch.cuda.is_available())" + +# 一个官方的示例脚本 +cd /workspace && python dgl_introduction-gpu.py +``` + From 570e0979a8368350130cc57aff755950f63fee6e Mon Sep 17 00:00:00 2001 From: pengyi Date: Tue, 15 Mar 2022 16:41:06 +0800 Subject: [PATCH 25/28] =?UTF-8?q?=E6=95=B4=E7=90=86=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\345\231\250\344\277\241\346\201\257.md" | 55 +++ graphsage/doc/preprocess.ipynb | 446 ++++++++++++++++++ 2 files changed, 501 insertions(+) create mode 100644 "graphsage/doc/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" create mode 100644 graphsage/doc/preprocess.ipynb diff --git "a/graphsage/doc/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" "b/graphsage/doc/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" new file mode 100644 index 00000000..2a330537 --- /dev/null +++ "b/graphsage/doc/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" @@ -0,0 +1,55 @@ +## 阿里云机器信息 + +IP:敏感信息不放在网上 + +mag240原数据目录:`/mnt/ogb-dataset/mag240m/data/raw` + +``` +├── RELEASE_v1.txt +├── mapping //空文件夹 +├── meta.pt +├── processed +│ ├── author___affiliated_with___institution +│ │ └── edge_index.npy //作者和机构的边,shape=[2,num_edges] +│ ├── author___writes___paper +│ │ └── edge_index.npy //作者和论文的边,shape=[2,num_edges] +│ ├── paper +│ │ ├── node_feat.npy //论文节点的特征,shape=[num_node,768] +│ │ ├── node_label.npy // 论文的标签 +│ │ └── node_year.npy // 论文年份 +│ └── paper___cites___paper +│ └── edge_index.npy // 论文引用关系的边shape=[2,num_edges] +├── raw //空文件夹 +└── split_dict.pt //切分训练集、验证集、测试集方式的文件,用torch读取是一个字典,keys=[‘train’,’valid’,’test’], value是node_index + +``` + + + +### docker镜像 + +#### opeceipeno/dgl:v1.4 + +ogb代码的运行环境,想法是通过虚拟环境去激活各个方案的运行环境,当前做好了Google的mag240m运行环境 + +[GitHub地址](https://github.com/deepmind/deepmind-research/tree/master/ogb_lsc/mag) + +``` +docker run --gpus all -it -v /mnt:/mnt opeceipeno/dgl:v1.4 bash +# 启动容器后,激活Google代码的运行环境 +source /py3_venv/google_ogb_mag240m/bin/activate +# /workspace 目录有代码 +``` + +Google方案预处理后的数据目录:`/mnt/ogb-dataset/mag240m/data/preprocessed`,相当于执行完了`run_preprocessing.sh`脚本,下一步是可以复现实验, + + + +#### opeceipeno/graphsage:gpu + +graphSAGE的环境,[GitHub地址](https://github.com/qksidmx/GraphSAGE) + +``` +docker run --gpus all -it opeceipeno/graphsage:gpu bash +#/notebook目录下面有代码,运行实验参考readme文档 +``` diff --git a/graphsage/doc/preprocess.ipynb b/graphsage/doc/preprocess.ipynb new file mode 100644 index 00000000..164c2615 --- /dev/null +++ b/graphsage/doc/preprocess.ipynb @@ -0,0 +1,446 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "graph.ipynb", + "provenance": [], + "collapsed_sections": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "" + ], + "metadata": { + "id": "lFBIUQovI53M" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 319 + }, + "id": "FXItxCYQ5xE2", + "outputId": "45d18b2d-50ad-4361-fb5c-1401166b8757" + }, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "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\n", + "text/plain": [ + "

" + ] + }, + "metadata": {} + } + ], + "source": [ + "# python -m graphsage.supervised_train --train_prefix ./example_data/toy-ppi --model graphsage_mean --sigmoid\n", + "# test networkx and visualization\n", + "import networkx as nx\n", + "import tensorflow as tf\n", + "tf.compat.v1.disable_eager_execution()\n", + "\n", + "G = nx.complete_graph(6)\n", + "nx.draw(G)" + ] + }, + { + "cell_type": "code", + "source": [ + "# download code and data\n", + "!git clone https://github.com/williamleif/GraphSAGE" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "S6709xbNrBok", + "outputId": "51e908ea-9105-4844-9427-b57a417bf9da" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Cloning into 'GraphSAGE'...\n", + "remote: Enumerating objects: 265, done.\u001b[K\n", + "remote: Counting objects: 100% (7/7), done.\u001b[K\n", + "remote: Compressing objects: 100% (7/7), done.\u001b[K\n", + "remote: Total 265 (delta 3), reused 0 (delta 0), pack-reused 258\u001b[K\n", + "Receiving objects: 100% (265/265), 6.43 MiB | 11.28 MiB/s, done.\n", + "Resolving deltas: 100% (160/160), done.\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "import json\n", + "from networkx.readwrite import json_graph\n", + "import os\n", + "import numpy as np\n", + "import sys\n", + "\n", + "CODE_ROOT = \"GraphSAGE/graphsage\"\n", + "sys.path.append(\"GraphSAGE\")\n", + "\n", + "def load_data():\n", + " data_path = 'GraphSAGE/example_data'\n", + " # DATA 1, 14755 nodes, 228431 links\n", + " G_data = json.load(open(data_path + '/toy-ppi-G.json'))\n", + " #G_data['nodes'] = G_data['nodes'][:100]\n", + " #G_data['links'] = G_data['links'][:100]\n", + " G = json_graph.node_link_graph(G_data)\n", + " \n", + " conversion = lambda n : n\n", + " lab_conversion = lambda n : n\n", + " \n", + " # DATA 2, (14755, 50) dtype('float64')\n", + " feats = np.load(data_path + '/toy-ppi-feats.npy')\n", + " \n", + " # DATA 3, {\"0\": 0, \"1\": 1}, len: 14755\n", + " # node ids to integer values indexing feature tensor\n", + " # 其实没什么用\n", + " id_map = json.load(open(data_path + \"/toy-ppi-id_map.json\"))\n", + " \n", + " # DATA 4, dict, len: 14755, column 121\n", + " # from node ids to class values (integer or list)\n", + " # 分类标签\n", + " class_map = json.load(open(data_path + \"/toy-ppi-class_map.json\"))\n", + " \n", + " broken_count = 0\n", + " for node in G.nodes():\n", + " if not 'val' in G.nodes()[node] or not 'test' in G.nodes()[node]:\n", + " G.remove_node(node)\n", + " broken_count += 1\n", + " print(\"Removed {:d} nodes that lacked proper annotations due to networkx versioning issues\".format(broken_count))\n", + " \n", + " # edge: (0, 800) 边\n", + " # G[0]: 某结点与所有的关联结点组成的边的集合\n", + " # 标记需要在训练中移除的关联关系,即边\n", + " for edge in G.edges():\n", + " if (G.nodes()[edge[0]]['val'] or G.nodes()[edge[1]]['val'] or\n", + " G.nodes()[edge[0]]['test'] or G.nodes()[edge[1]]['test']):\n", + " G[edge[0]][edge[1]]['train_removed'] = True\n", + " else:\n", + " G[edge[0]][edge[1]]['train_removed'] = False\n", + " \n", + " from sklearn.preprocessing import StandardScaler\n", + " \n", + " # 训练集的id集合,result only int, len: 9716\n", + " train_ids = np.array([id_map[str(n)] for n in G.nodes() \\\n", + " if not G.nodes()[n]['val'] and not G.nodes()[n]['test']])\n", + " \n", + " train_feats = feats[train_ids]\n", + " \n", + " # 特征缩放,标准化:z = (x - u) / s\n", + " # u is the mean of the training samples\n", + " # s is the standard deviation of the training samples\n", + " scaler = StandardScaler()\n", + " scaler.fit(train_feats)\n", + " feats = scaler.transform(feats)\n", + "\n", + " walks = []\n", + "\n", + " return G, feats, id_map, walks, class_map" + ], + "metadata": { + "id": "jg81uw5O6Gaz" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def construct_placeholders(num_classes):\n", + " # Define placeholders\n", + " placeholders = {\n", + " 'labels' : tf.compat.v1.placeholder(tf.float32, shape=(None, num_classes), name='labels'),\n", + " 'dropout': tf.compat.v1.placeholder_with_default(0., shape=(), name='dropout'),\n", + " 'batch' : tf.compat.v1.placeholder(tf.int32, shape=(None), name='batch1'),\n", + " 'batch_size' : tf.compat.v1.placeholder(tf.int32, name='batch_size'),\n", + " }\n", + " return placeholders" + ], + "metadata": { + "id": "mReVSrV9UzYO" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "train_data = load_data()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Rv1F4lYF_FfW", + "outputId": "7b9e88c6-8ba5-4128-9564-55a7d4cc835c" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Removed 0 nodes that lacked proper annotations due to networkx versioning issues\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "G = train_data[0]\n", + "features = train_data[1]\n", + "id_map = train_data[2]\n", + "context_pairs = train_data[3]\n", + "class_map = train_data[4]\n", + "\n", + "# num_classes = 121\n", + "num_classes = len(list(class_map.values())[0])\n", + "# pad with dummy zero vector, row wise\n", + "features = np.vstack([features, np.zeros((features.shape[1],))])\n", + "placeholders = construct_placeholders(num_classes)" + ], + "metadata": { + "id": "CjSUlil1kNZP" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "class NodeMinibatchIterator(object):\n", + "\n", + " \"\"\"\n", + " This minibatch iterator iterates over nodes for supervised learning.\n", + "\n", + " G -- networkx graph\n", + " id2idx -- dict mapping node ids to integer values indexing feature tensor\n", + " placeholders -- standard tensorflow placeholders object for feeding\n", + " label_map -- map from node ids to class values (integer or list)\n", + " num_classes -- number of output classes\n", + " batch_size -- size of the minibatches\n", + " max_degree -- maximum size of the downsampled adjacency lists\n", + " 以toy-ppi数据集举例:\n", + " label_map为输出,维度为(14755, 121)\n", + " num_class为label_map的第二维,即121\n", + " \"\"\"\n", + " def __init__(self, G, id2idx,\n", + " placeholders, label_map, num_classes,\n", + " batch_size=100, max_degree=25,\n", + " **kwargs):\n", + "\n", + " self.G = G\n", + " self.nodes = G.nodes()\n", + " self.id2idx = id2idx\n", + " self.placeholders = placeholders\n", + " self.batch_size = batch_size\n", + " self.max_degree = max_degree\n", + " self.batch_num = 0\n", + " self.label_map = label_map\n", + " self.num_classes = num_classes\n", + "\n", + " self.adj, self.deg = self.construct_adj()\n", + " self.test_adj = self.construct_test_adj()\n", + "\n", + " self.val_nodes = [n for n in self.G.nodes() if self.G.nodes()[n]['val']]\n", + " self.test_nodes = [n for n in self.G.nodes() if self.G.nodes()[n]['test']]\n", + "\n", + " # 不参与训练的结点id\n", + " self.no_train_nodes_set = set(self.val_nodes + self.test_nodes)\n", + " # 可训练的结点id\n", + " self.train_nodes = set(G.nodes()).difference(self.no_train_nodes_set)\n", + " # don't train on nodes that only have edges to test set\n", + " # 只保留有邻居的结点\n", + " self.train_nodes = [n for n in self.train_nodes if self.deg[id2idx[str(n)]] > 0]\n", + "\n", + " def _make_label_vec(self, node):\n", + " label = self.label_map[node]\n", + " if isinstance(label, list):\n", + " label_vec = np.array(label)\n", + " else:\n", + " label_vec = np.zeros((self.num_classes))\n", + " class_ind = self.label_map[node]\n", + " label_vec[class_ind] = 1\n", + " return label_vec\n", + "\n", + " def construct_adj(self):\n", + " # adjacency shape: (14756, 128) ,用于存储所有节点的邻居节点id\n", + " adj = len(self.id2idx) * np.ones((len(self.id2idx)+1, self.max_degree))\n", + " # (14755,) ,用于存储所有结点的degree值\n", + " deg = np.zeros((len(self.id2idx),))\n", + "\n", + " for nodeid in self.G.nodes():\n", + " if self.G.nodes()[nodeid]['test'] or self.G.nodes()[nodeid]['val']:\n", + " continue\n", + "\n", + " # 获取所有训练集的邻居节点的id\n", + " neighbors = np.array([self.id2idx[str(neighbor)]\n", + " for neighbor in self.G.neighbors(nodeid)\n", + " if (not self.G[nodeid][neighbor]['train_removed'])])\n", + " \n", + " deg[self.id2idx[str(nodeid)]] = len(neighbors)\n", + " if len(neighbors) == 0:\n", + " continue\n", + " if len(neighbors) > self.max_degree:\n", + " neighbors = np.random.choice(neighbors, self.max_degree, replace=False)\n", + " elif len(neighbors) < self.max_degree:\n", + " neighbors = np.random.choice(neighbors, self.max_degree, replace=True)\n", + " adj[self.id2idx[str(nodeid)], :] = neighbors\n", + " return adj, deg\n", + "\n", + " def construct_test_adj(self):\n", + " adj = len(self.id2idx) * np.ones((len(self.id2idx)+1, self.max_degree))\n", + " for nodeid in self.G.nodes():\n", + " # 所有邻居节点的id,这里没有限制训练集或测试集\n", + " neighbors = np.array([self.id2idx[str(neighbor)]\n", + " for neighbor in self.G.neighbors(nodeid)])\n", + " if len(neighbors) == 0:\n", + " continue\n", + " if len(neighbors) > self.max_degree:\n", + " neighbors = np.random.choice(neighbors, self.max_degree, replace=False)\n", + " elif len(neighbors) < self.max_degree:\n", + " neighbors = np.random.choice(neighbors, self.max_degree, replace=True)\n", + " adj[self.id2idx[str(nodeid)], :] = neighbors\n", + " return adj\n", + "\n", + " def end(self):\n", + " return self.batch_num * self.batch_size >= len(self.train_nodes)\n", + "\n", + " def batch_feed_dict(self, batch_nodes, val=False):\n", + " batch1id = batch_nodes\n", + " batch1 = [self.id2idx[n] for n in batch1id]\n", + "\n", + " labels = np.vstack([self._make_label_vec(node) for node in batch1id])\n", + " feed_dict = dict()\n", + " feed_dict.update({self.placeholders['batch_size'] : len(batch1)})\n", + " feed_dict.update({self.placeholders['batch']: batch1})\n", + " feed_dict.update({self.placeholders['labels']: labels})\n", + "\n", + " return feed_dict, labels\n", + "\n", + " def node_val_feed_dict(self, size=None, test=False):\n", + " if test:\n", + " val_nodes = self.test_nodes\n", + " else:\n", + " val_nodes = self.val_nodes\n", + " if not size is None:\n", + " val_nodes = np.random.choice(val_nodes, size, replace=True)\n", + " # add a dummy neighbor\n", + " ret_val = self.batch_feed_dict(val_nodes)\n", + " return ret_val[0], ret_val[1]\n", + "\n", + " def incremental_node_val_feed_dict(self, size, iter_num, test=False):\n", + " if test:\n", + " val_nodes = self.test_nodes\n", + " else:\n", + " val_nodes = self.val_nodes\n", + " val_node_subset = val_nodes[iter_num*size:min((iter_num+1)*size,\n", + " len(val_nodes))]\n", + "\n", + " # add a dummy neighbor\n", + " ret_val = self.batch_feed_dict(val_node_subset)\n", + " return ret_val[0], ret_val[1], (iter_num+1)*size >= len(val_nodes), val_node_subset\n", + "\n", + " def num_training_batches(self):\n", + " return len(self.train_nodes) // self.batch_size + 1\n", + "\n", + " def next_minibatch_feed_dict(self):\n", + " start_idx = self.batch_num * self.batch_size\n", + " self.batch_num += 1\n", + " end_idx = min(start_idx + self.batch_size, len(self.train_nodes))\n", + " batch_nodes = self.train_nodes[start_idx : end_idx]\n", + " return self.batch_feed_dict(batch_nodes)\n", + "\n", + " def incremental_embed_feed_dict(self, size, iter_num):\n", + " node_list = self.nodes\n", + " val_nodes = node_list[iter_num*size:min((iter_num+1)*size,\n", + " len(node_list))]\n", + " return self.batch_feed_dict(val_nodes), (iter_num+1)*size >= len(node_list), val_nodes\n", + "\n", + " def shuffle(self):\n", + " \"\"\" Re-shuffle the training set.\n", + " Also reset the batch number.\n", + " \"\"\"\n", + " self.train_nodes = np.random.permutation(self.train_nodes)\n", + " self.batch_num = 0\n" + ], + "metadata": { + "id": "ZYCKM4i5PmPf" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "\"\"\"\n", + " This minibatch iterator iterates over nodes for supervised learning.\n", + "\n", + " G -- networkx graph\n", + " id2idx -- dict mapping node ids to integer values indexing feature tensor\n", + " placeholders -- standard tensorflow placeholders object for feeding\n", + " label_map -- map from node ids to class values (integer or list)\n", + " num_classes -- number of output classes\n", + " batch_size -- size of the minibatches\n", + " max_degree -- maximum size of the downsampled adjacency lists\n", + "\"\"\"\n", + "# 实例化 NodeMinibatch 迭代器\n", + "minibatch = NodeMinibatchIterator(G,\n", + " id_map,\n", + " placeholders,\n", + " class_map,\n", + " num_classes,\n", + " batch_size=512,\n", + " max_degree=128,\n", + " context_pairs = context_pairs)\n", + "\n", + "# adjacency shape: (14756, 128) 包装为placeholder\n", + "adj_info_ph = tf.compat.v1.placeholder(tf.int32, shape=minibatch.adj.shape)\n", + "adj_info = tf.Variable(adj_info_ph, trainable=False, name=\"adj_info\")\n", + "\n", + "# 接着就是构建模型了,需要改动的兼容代码过多,暂不继续了" + ], + "metadata": { + "id": "mUW98eVhQ5H7" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "" + ], + "metadata": { + "id": "Wp3DZreLrdtF" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file From 3fab4e3b4d7ff21bcfd2b5386c8255d93ae46438 Mon Sep 17 00:00:00 2001 From: java002 Date: Wed, 16 Mar 2022 15:38:50 +0800 Subject: [PATCH 26/28] minibatch cimmit --- graphsage/minibatch.py | 92 ++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/graphsage/minibatch.py b/graphsage/minibatch.py index 0dcb9a2d..e83418f5 100644 --- a/graphsage/minibatch.py +++ b/graphsage/minibatch.py @@ -5,19 +5,20 @@ np.random.seed(123) +#边迭代器,在无监督训练中使用 class EdgeMinibatchIterator(object): """ This minibatch iterator iterates over batches of sampled edges or random pairs of co-occuring edges. - G -- networkx graph - id2idx -- dict mapping node ids to index in feature tensor - placeholders -- tensorflow placeholders object - context_pairs -- if not none, then a list of co-occuring node pairs (from random walks) - batch_size -- size of the minibatches - max_degree -- maximum size of the downsampled adjacency lists - n2v_retrain -- signals that the iterator is being used to add new embeddings to a n2v model - fixed_n2v -- signals that the iterator is being used to retrain n2v with only existing nodes as context + G -- networkx graph 拓扑图 + id2idx -- dict mapping node ids to index in feature tensor 节点索引,映射toy-ppi-id_map.json文件 + placeholders -- tensorflow placeholders object tf的占位符 + context_pairs -- if not none, then a list of co-occuring node pairs (from random walks) 随机步数采用结果,采样流程见utils + batch_size -- size of the minibatches 批次大小 + max_degree -- maximum size of the downsampled adjacency lists 最大的度(邻居节点的数量) + n2v_retrain -- signals that the iterator is being used to add new embeddings to a n2v model 重新训练n2v模型的标识 + fixed_n2v -- signals that the iterator is being used to retrain n2v with only existing nodes as context 只用外部节点训练n2v模型标识 """ def __init__(self, G, id2idx, placeholders, context_pairs=None, batch_size=100, max_degree=25, @@ -30,18 +31,20 @@ def __init__(self, G, id2idx, self.placeholders = placeholders self.batch_size = batch_size self.max_degree = max_degree - self.batch_num = 0 + self.batch_num = 0 #批次数,初始化为0,训练过程中递增 - self.nodes = np.random.permutation(G.nodes()) - self.adj, self.deg = self.construct_adj() - self.test_adj = self.construct_test_adj() - if context_pairs is None: + self.nodes = np.random.permutation(G.nodes()) #节点乱序 + self.adj, self.deg = self.construct_adj() #adj矩阵:所有训练节点的取max_degree个邻居节点,deg矩阵:所有训练节点的有效邻居数 + self.test_adj = self.construct_test_adj() #所有节点(包含测试和验证节点)的adj矩阵 + if context_pairs is None: #若入参context_pairs不为空,取context_pairs作为训练边,否则取G图中所有边 edges = G.edges() else: edges = context_pairs self.train_edges = self.edges = np.random.permutation(edges) if not n2v_retrain: + #训练边剔除顶点不存在拓扑图,顶点有效邻居个数,顶点为test或val的边 self.train_edges = self._remove_isolated(self.train_edges) + #验证边取顶点为test或val的边 self.val_edges = [e for e in G.edges() if G[e[0]][e[1]]['train_removed']] else: if fixed_n2v: @@ -49,19 +52,22 @@ def __init__(self, G, id2idx, else: self.train_edges = self.val_edges = self.edges + #打印训练节点和测试节点的数量 print(len([n for n in G.nodes() if not G.node[n]['test'] and not G.node[n]['val']]), 'train nodes') print(len([n for n in G.nodes() if G.node[n]['test'] or G.node[n]['val']]), 'test nodes') self.val_set_size = len(self.val_edges) + #剔除顶点1为测试或训练节点的边 def _n2v_prune(self, edges): is_val = lambda n : self.G.node[n]["val"] or self.G.node[n]["test"] return [e for e in edges if not is_val(e[1])] + #剔除顶点不在G图中,顶点的有效邻居数为0且顶点不为test def _remove_isolated(self, edge_list): new_edge_list = [] missing = 0 for n1, n2 in edge_list: - if not n1 in self.G.node or not n2 in self.G.node: + if not n1 in self.G.node or not n2 in self.G.node: #顶点1或顶点2不在G图中 missing += 1 continue if (self.deg[self.id2idx[n1]] == 0 or self.deg[self.id2idx[n2]] == 0) \ @@ -73,26 +79,32 @@ def _remove_isolated(self, edge_list): print("Unexpected missing:", missing) return new_edge_list + #获取adj和deg两个矩阵,adj矩阵每行为当年节点的的指定数量邻居节点id,按ididx索引排列 + #deg为每个节点的训练邻居节点的个数 def construct_adj(self): + #adj初始化:一个节点总数+1行,max_degree列,初始化值全部为节点总数的二维矩阵 + #deg初始化:一个节点总数行的一维矩阵 adj = len(self.id2idx)*np.ones((len(self.id2idx)+1, self.max_degree)) deg = np.zeros((len(self.id2idx),)) - for nodeid in self.G.nodes(): - if self.G.node[nodeid]['test'] or self.G.node[nodeid]['val']: + for nodeid in self.G.nodes():#对全部节点循环 + if self.G.node[nodeid]['test'] or self.G.node[nodeid]['val']: #测试或验证节点直接跳过 continue neighbors = np.array([self.id2idx[neighbor] for neighbor in self.G.neighbors(nodeid) - if (not self.G[nodeid][neighbor]['train_removed'])]) - deg[self.id2idx[nodeid]] = len(neighbors) + if (not self.G[nodeid][neighbor]['train_removed'])])#取所有不为test和val的邻居节点 + deg[self.id2idx[nodeid]] = len(neighbors)#deg赋值为邻居个数 if len(neighbors) == 0: continue - if len(neighbors) > self.max_degree: + #取max_degree个邻居节点 + if len(neighbors) > self.max_degree: #邻居节点大于max_degree,无重复采样 neighbors = np.random.choice(neighbors, self.max_degree, replace=False) - elif len(neighbors) < self.max_degree: + elif len(neighbors) < self.max_degree:#邻居节点小于max_degree,有重复采样 neighbors = np.random.choice(neighbors, self.max_degree, replace=True) adj[self.id2idx[nodeid], :] = neighbors return adj, deg + #取所有节点的adj矩阵,方式与construct_adj相同 def construct_test_adj(self): adj = len(self.id2idx)*np.ones((len(self.id2idx)+1, self.max_degree)) for nodeid in self.G.nodes(): @@ -106,10 +118,11 @@ def construct_test_adj(self): neighbors = np.random.choice(neighbors, self.max_degree, replace=True) adj[self.id2idx[nodeid], :] = neighbors return adj - + #判断当前epoch是否结束 def end(self): return self.batch_num * self.batch_size >= len(self.train_edges) + #将边集合转为feed_dict,batch_size:集合总数,batch1:节点1集合,batch2:节点2集合 def batch_feed_dict(self, batch_edges): batch1 = [] batch2 = [] @@ -124,6 +137,7 @@ def batch_feed_dict(self, batch_edges): return feed_dict + #下一批次feed_dict def next_minibatch_feed_dict(self): start_idx = self.batch_num * self.batch_size self.batch_num += 1 @@ -131,9 +145,11 @@ def next_minibatch_feed_dict(self): batch_edges = self.train_edges[start_idx : end_idx] return self.batch_feed_dict(batch_edges) + #取当前是第几批次 def num_training_batches(self): return len(self.train_edges) // self.batch_size + 1 + #取指定大小的训练边的feed_dict,首次取数 def val_feed_dict(self, size=None): edge_list = self.val_edges if size is None: @@ -143,18 +159,19 @@ def val_feed_dict(self, size=None): val_edges = [edge_list[i] for i in ind[:min(size, len(ind))]] return self.batch_feed_dict(val_edges) + #下一批次的训练边的feed_dict def incremental_val_feed_dict(self, size, iter_num): edge_list = self.val_edges val_edges = edge_list[iter_num*size:min((iter_num+1)*size, len(edge_list))] return self.batch_feed_dict(val_edges), (iter_num+1)*size >= len(self.val_edges), val_edges - + #去下一批次的节点自己到自己组成的边,并转为feed_dict def incremental_embed_feed_dict(self, size, iter_num): node_list = self.nodes val_nodes = node_list[iter_num*size:min((iter_num+1)*size,len(node_list))] val_edges = [(n,n) for n in val_nodes] return self.batch_feed_dict(val_edges), (iter_num+1)*size >= len(node_list), val_edges - + #将全量边分为训练边和验证边 def label_val(self): train_edges = [] val_edges = [] @@ -166,6 +183,7 @@ def label_val(self): train_edges.append((n1,n2)) return train_edges, val_edges + #洗牌 def shuffle(self): """ Re-shuffle the training set. Also reset the batch number. @@ -174,6 +192,7 @@ def shuffle(self): self.nodes = np.random.permutation(self.nodes) self.batch_num = 0 +#节点迭代器,在有监督训练中使用 class NodeMinibatchIterator(object): """ @@ -182,8 +201,8 @@ class NodeMinibatchIterator(object): G -- networkx graph id2idx -- dict mapping node ids to integer values indexing feature tensor placeholders -- standard tensorflow placeholders object for feeding - label_map -- map from node ids to class values (integer or list) - num_classes -- number of output classes + label_map -- map from node ids to class values (integer or list) 所有节点的类标数据,映射文件toy-ppi-map + num_classes -- number of output classes 每个类标数据的维度 batch_size -- size of the minibatches max_degree -- maximum size of the downsampled adjacency lists 以toy-ppi数据集举例: @@ -205,17 +224,20 @@ def __init__(self, G, id2idx, self.label_map = label_map self.num_classes = num_classes + #adj:采样邻居矩阵,deg:训练邻居节点个数矩阵 self.adj, self.deg = self.construct_adj() self.test_adj = self.construct_test_adj() - + #验证节点集合,测试节点集合 self.val_nodes = [n for n in self.G.nodes() if self.G.node[n]['val']] self.test_nodes = [n for n in self.G.nodes() if self.G.node[n]['test']] - + #非训练节点集合,训练节点集合 self.no_train_nodes_set = set(self.val_nodes + self.test_nodes) self.train_nodes = set(G.nodes()).difference(self.no_train_nodes_set) # don't train on nodes that only have edges to test set + #剔除有效边为0的节点 self.train_nodes = [n for n in self.train_nodes if self.deg[id2idx[n]] > 0] + #若类标数据为list,转为一维矩阵;若为单数值,则创建一个全零矩阵,并将该数据位置位1 def _make_label_vec(self, node): label = self.label_map[node] if isinstance(label, list): @@ -254,6 +276,7 @@ def construct_adj(self): adj[self.id2idx[nodeid], :] = neighbors return adj, deg + #所有节点的adj矩阵,包含test和val节点 def construct_test_adj(self): adj = len(self.id2idx)*np.ones((len(self.id2idx)+1, self.max_degree)) for nodeid in self.G.nodes(): @@ -269,13 +292,16 @@ def construct_test_adj(self): adj[self.id2idx[nodeid], :] = neighbors return adj + #判断是否结束 def end(self): return self.batch_num * self.batch_size >= len(self.train_nodes) + #节点集合的feed_dict,batch_size:集合大小,batch:节点的index信息,labels:集合的类标数据 def batch_feed_dict(self, batch_nodes, val=False): batch1id = batch_nodes batch1 = [self.id2idx[n] for n in batch1id] - + + #按照batch_nodes的顺序,将batch_nodes的类标数据堆叠成2维数组 labels = np.vstack([self._make_label_vec(node) for node in batch1id]) feed_dict = dict() feed_dict.update({self.placeholders['batch_size'] : len(batch1)}) @@ -284,6 +310,7 @@ def batch_feed_dict(self, batch_nodes, val=False): return feed_dict, labels + #从测试节点或验证节点中取size个节点,获取节点list的feed_dict def node_val_feed_dict(self, size=None, test=False): if test: val_nodes = self.test_nodes @@ -295,21 +322,24 @@ def node_val_feed_dict(self, size=None, test=False): ret_val = self.batch_feed_dict(val_nodes) return ret_val[0], ret_val[1] + #取训练或验证节点的下一批次节点的feed_dict def incremental_node_val_feed_dict(self, size, iter_num, test=False): if test: val_nodes = self.test_nodes else: val_nodes = self.val_nodes - val_node_subset = val_nodes[iter_num*size:min((iter_num+1)*size, + val_node_subset = val_nodes[iter_num*size:min((iter_num+1)*size, len(val_nodes))] # add a dummy neighbor ret_val = self.batch_feed_dict(val_node_subset) return ret_val[0], ret_val[1], (iter_num+1)*size >= len(val_nodes), val_node_subset + #当前是第几批次 def num_training_batches(self): return len(self.train_nodes) // self.batch_size + 1 + #训练节点的下一批次的feed_dict def next_minibatch_feed_dict(self): start_idx = self.batch_num * self.batch_size self.batch_num += 1 @@ -317,12 +347,14 @@ def next_minibatch_feed_dict(self): batch_nodes = self.train_nodes[start_idx : end_idx] return self.batch_feed_dict(batch_nodes) + #全量节点的下一批次节点feed_dict def incremental_embed_feed_dict(self, size, iter_num): node_list = self.nodes - val_nodes = node_list[iter_num*size:min((iter_num+1)*size, + val_nodes = node_list[iter_num*size:min((iter_num+1)*size, len(node_list))] return self.batch_feed_dict(val_nodes), (iter_num+1)*size >= len(node_list), val_nodes + #洗牌 def shuffle(self): """ Re-shuffle the training set. Also reset the batch number. From 3515aa79c7b09a9d5eea69fbb6f5c0a641a12c86 Mon Sep 17 00:00:00 2001 From: pengyi Date: Wed, 16 Mar 2022 15:42:10 +0800 Subject: [PATCH 27/28] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...72\345\231\250\344\277\241\346\201\257.md" | 55 --- graphsage/models.py | 62 ++- graphsage/supervised_models.py | 4 +- graphsage/unsupervised_train.py | 24 +- preprocess.ipynb | 446 ------------------ 5 files changed, 59 insertions(+), 532 deletions(-) delete mode 100644 "aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" delete mode 100644 preprocess.ipynb diff --git "a/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" "b/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" deleted file mode 100644 index 2a330537..00000000 --- "a/aliyun\346\234\272\345\231\250\344\277\241\346\201\257.md" +++ /dev/null @@ -1,55 +0,0 @@ -## 阿里云机器信息 - -IP:敏感信息不放在网上 - -mag240原数据目录:`/mnt/ogb-dataset/mag240m/data/raw` - -``` -├── RELEASE_v1.txt -├── mapping //空文件夹 -├── meta.pt -├── processed -│ ├── author___affiliated_with___institution -│ │ └── edge_index.npy //作者和机构的边,shape=[2,num_edges] -│ ├── author___writes___paper -│ │ └── edge_index.npy //作者和论文的边,shape=[2,num_edges] -│ ├── paper -│ │ ├── node_feat.npy //论文节点的特征,shape=[num_node,768] -│ │ ├── node_label.npy // 论文的标签 -│ │ └── node_year.npy // 论文年份 -│ └── paper___cites___paper -│ └── edge_index.npy // 论文引用关系的边shape=[2,num_edges] -├── raw //空文件夹 -└── split_dict.pt //切分训练集、验证集、测试集方式的文件,用torch读取是一个字典,keys=[‘train’,’valid’,’test’], value是node_index - -``` - - - -### docker镜像 - -#### opeceipeno/dgl:v1.4 - -ogb代码的运行环境,想法是通过虚拟环境去激活各个方案的运行环境,当前做好了Google的mag240m运行环境 - -[GitHub地址](https://github.com/deepmind/deepmind-research/tree/master/ogb_lsc/mag) - -``` -docker run --gpus all -it -v /mnt:/mnt opeceipeno/dgl:v1.4 bash -# 启动容器后,激活Google代码的运行环境 -source /py3_venv/google_ogb_mag240m/bin/activate -# /workspace 目录有代码 -``` - -Google方案预处理后的数据目录:`/mnt/ogb-dataset/mag240m/data/preprocessed`,相当于执行完了`run_preprocessing.sh`脚本,下一步是可以复现实验, - - - -#### opeceipeno/graphsage:gpu - -graphSAGE的环境,[GitHub地址](https://github.com/qksidmx/GraphSAGE) - -``` -docker run --gpus all -it opeceipeno/graphsage:gpu bash -#/notebook目录下面有代码,运行实验参考readme文档 -``` diff --git a/graphsage/models.py b/graphsage/models.py index 746b6c0a..7d5d84c0 100644 --- a/graphsage/models.py +++ b/graphsage/models.py @@ -97,7 +97,6 @@ def load(self, sess=None): # 从本地文件读取模型 print("Model restored from file: %s" % save_path) - # 多层感知机,是一个基础的深度模型 class MLP(Model): """ A standard multi-layer perceptron """ @@ -301,7 +300,10 @@ def sample(self, inputs, layer_infos, batch_size=None): """ Sample neighbors to be the supportive fields for multi-layer convolutions. 函数功能:对输入的每一个节点,根据采样跳数目,递归地采样邻居,作为该节点的支持域 + 输入: + inputs:一批次的节点id + 输出: samples是一个列表,列表的每一个元素又是一个列表,长度不一,存放的是该跳数下的所有的邻居节点id 示例: samples[0] 维度是 [batch_size,] ,即是自身 @@ -309,11 +311,11 @@ def sample(self, inputs, layer_infos, batch_size=None): samples[2] [layer_infos[1].num_samples * layer_infos[0].num_samples * batch_size,] 以此类推 - # support_sizes 存的是的各层的采样数目,是一个列表,每个元素是一个正整数 - # support_sizes[0] = 1, 意义是初始状态,邻居就是节点本身 - # support_sizes[1] = layer_infos[-1].num_samples * 1, 本实验中为10 - # support_sizes[2] = layer_infos[-1].num_samples * layer_infos[-2].num_samples * 1, 本实验中为10*15=250 - # 以此类推,从最外层的邻居数依次往内乘 + support_sizes 存的是的各层的采样数目,是一个列表,每个元素是一个正整数 + support_sizes[0] = 1, 意义是初始状态,邻居就是节点本身 + support_sizes[1] = layer_infos[-1].num_samples * 1, 本实验中为10 + support_sizes[2] = layer_infos[-1].num_samples * layer_infos[-2].num_samples * 1, 本实验中为10*15=250 + 以此类推,从最外层的邻居数依次往内乘 Args: inputs: batch inputs @@ -329,7 +331,7 @@ def sample(self, inputs, layer_infos, batch_size=None): support_sizes = [support_size] for k in range(len(layer_infos)): # k为跳数,实验中k = 0 1 - t = len(layer_infos) - k - 1 # t = 1 0 + t = len(layer_infos) - k - 1 # t = 1 0 # 每一跳的邻居数目是前一跳的邻居节点数*该层的采样数,有个累乘的逻辑 support_size *= layer_infos[t].num_samples @@ -340,7 +342,7 @@ def sample(self, inputs, layer_infos, batch_size=None): node = sampler((samples[k], layer_infos[t].num_samples)) # reshape成一维数组,再添加进samples中 - samples.append(tf.reshape(node, [support_size * batch_size, ])) + samples.append(tf.reshape(node, [support_size * batch_size, ])) # 同时记录好每一层的采样数 support_sizes.append(support_size) @@ -383,15 +385,14 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b # length: number of layers + 1 # 遍历samples列表,根据每一个元素中存放的节点id,从全量的特征矩阵里获取所需的节点特征 - + hidden = [tf.nn.embedding_lookup( input_features, node_samples) for node_samples in samples] # hidden[0] [batch, num_features] # hidden[1] [layer_infos[1].num_samples * batch_size, num_features] # hidden[2] [layer_infos[1].num_samples * layer_infos[0].num_samples * batch_size, num_features] # num_features表示的是特征维度,实验中为50 - - + # 输入batch1的时候,该项为aggregators = None, 输入batch2或者neg_samples的时候,aggregators为batch1生成的aggregators # 即他们用的是同一个聚合器 new_agg = aggregators is None @@ -437,9 +438,10 @@ def aggregate(self, samples, input_features, dims, num_samples, support_sizes, b # 因为hidden[i]存放为二维,而mean_aggregator是需要将邻居节点特征平均, # 因此需要将它reshape一下,方便在后面的处理中取所有邻居的均值 # neigh_dims = [batch_size * 当前跳数的支持节点数,当前层的需要采样的邻居节点数,特征数] - # + # neigh_dims = [batch_size * support_sizes[hop], - num_samples[len(num_samples) - hop - 1], # 这个维度,对应sample函数里的 t = len(layer_infos) - k - 1 + # 这个维度,对应sample函数里的 t = len(layer_infos) - k - 1 + num_samples[len(num_samples) - hop - 1], dim_mult*dims[layer]] h = aggregator((hidden[hop], tf.reshape(hidden[hop + 1], neigh_dims))) @@ -463,11 +465,10 @@ def _build(self): -------- 在本实验中,就是利用这个函数,利用每个节点的度数形成概率分布,从节点集合中获取一批节点id,在后续视作负样本 - true_classes个参数传入的是labels,但经测试,采样的结果和这个参数是无关的样子,而且实际是有可能会采样到负样本的 + true_classes个参数传入的是labels,但经测试,采样的结果和这个参数是无关的样子,而且实际是有可能会采样到正样本的 返回的结果neg_samples里面是一个列表,每一个元素代表的是节点id - 参考 https://github.com/williamleif/GraphSAGE/issues/76, 这个里面作者说了是可能会采样到正样本 - 只是他们假设,当整个图数据集远大于邻域计算图时,采样到正样本的概率很小。 + 参考 https://github.com/williamleif/GraphSAGE/issues/76, 这个里面作者说了是可能会采样到正样本,只是他们假设,当整个图数据集远大于邻域计算图时,采样到正样本的概率很小。 """ self.neg_samples, _, _ = (tf.nn.fixed_unigram_candidate_sampler( @@ -486,8 +487,8 @@ def _build(self): samples1, support_sizes1 = self.sample(self.inputs1, self.layer_infos) samples2, support_sizes2 = self.sample(self.inputs2, self.layer_infos) - # 每层需要的采样数 实验中是[25,10] - + # 每层需要的采样数 实验中是[25,10] + num_samples = [ layer_info.num_samples for layer_info in self.layer_infos] @@ -511,7 +512,6 @@ def _build(self): dim_mult = 2 if self.concat else 1 - # 这里生成了一个预测层,注意参数bilinear_weights,这个值如果为True,则会生成一个可训练的参数矩阵,在后续的计算loss会用到 # 但是本实验在这里设置了否,则无参数矩阵,本质上就是一个计算loss的类,完全不影响上述aggregator的输出 self.link_pred_layer = BipartiteEdgePredLayer(dim_mult*self.dims[-1], @@ -545,8 +545,8 @@ def build(self): # 梯度裁剪,若梯度大于5则置为5,小于-5则置为-5, clipped_grads_and_vars = [(tf.clip_by_value(grad, -5.0, 5.0) if grad is not None else None, var) for grad, var in grads_and_vars] - - # clipped_grads_and_vars 是一个元组,(grad,var),表示梯度值和变量值 + + # clipped_grads_and_vars 是一个元组,(grad,var),表示梯度值和变量值,这里只取了grad self.grad, _ = clipped_grads_and_vars[0] # 利用裁剪后的梯度更新模型参数 @@ -597,20 +597,30 @@ def _accuracy(self): self.aff_all = tf.concat(axis=1, values=[self.neg_aff, _aff]) size = tf.shape(self.aff_all)[1] - # ④利用top_k函数,两步计算出正样本对之间的亲和度的排名, + # ④利用top_k函数,两步计算出正样本对之间的亲和度的排名 + # tf.nn.top_k函数是根据输入的数组,返回最大的k个数和他们的序号 + # 这里两次利用tok_k函数,得到rank值 # self.ranks中表示的是每个顶点和负样本、正样本之间的亲和度排名,维度:[batch_size, neg_samples_size + 1] + # 示例: + # result = tf.constant([0.5, 0.9, 0.3, 0.4, 0.6, 0.8, 0.7, 0.1]) + # _, indices_of_ranks = tf.nn.top_k(result, k=len(result)) + # indices_of_ranks.numpy() : [1, 5, 6, 4, 0, 3, 2, 7] 按顺序看,表示最大数序号为1,第二大的数序号为5 ... + # _, ranks = tf.nn.top_k(-indices_of_ranks, k=len(result)) + # ranks.numpy() : [4, 0, 6, 5, 3, 1, 2, 7] 这里得到的就是result中每个元素的排名序号,0表示最大,以此类推 + # _, indices_of_ranks = tf.nn.top_k(self.aff_all, k=size) _, self.ranks = tf.nn.top_k(-indices_of_ranks, k=size) - # 取self.ranks最后一列,即正样本的排名序数,因为是从0算起的,所以要+1 - # mrr = 1.0/rank + # 取self.ranks最后一列,即正样本的排名序数,因为是从0算起的,所以要+1 + # Mean Reciprocal Rank(MRR) = 1.0/rank + # 通过正确的检索结果值在所有检索结果中的排名来评估排序性能, rank越大,mrr值越小 + self.mrr = tf.reduce_mean( tf.div(1.0, tf.cast(self.ranks[:, -1] + 1, tf.float32))) tf.summary.scalar('mrr', self.mrr) - -# +# class Node2VecModel(GeneralizedModel): def __init__(self, placeholders, dict_size, degrees, name=None, nodevec_dim=50, lr=0.001, **kwargs): diff --git a/graphsage/supervised_models.py b/graphsage/supervised_models.py index ab1c7c70..84a6c1ad 100644 --- a/graphsage/supervised_models.py +++ b/graphsage/supervised_models.py @@ -118,13 +118,15 @@ def build(self): clipped_grads_and_vars = [(tf.clip_by_value(grad, -5.0, 5.0) if grad is not None else None, var) for grad, var in grads_and_vars] self.grad, _ = clipped_grads_and_vars[0] + self.opt_op = self.optimizer.apply_gradients(clipped_grads_and_vars) + self.preds = self.predict() def _loss(self): # Weight decay loss - #L2正则化项 + # L2正则化项 for aggregator in self.aggregators: for var in aggregator.vars.values(): self.loss += FLAGS.weight_decay * tf.nn.l2_loss(var) diff --git a/graphsage/unsupervised_train.py b/graphsage/unsupervised_train.py index 469ad95d..c7bd8df2 100644 --- a/graphsage/unsupervised_train.py +++ b/graphsage/unsupervised_train.py @@ -70,10 +70,20 @@ def log_dir(): # Define model evaluation function def evaluate(sess, model, minibatch_iter, size=None): + """ + 评估函数 + 输入: + model:训练好了的模型 + minibatch_iter:迭代类 + size: batch_size + """ + t_test = time.time() # 评估阶段,这里传入的是验证集 feed_dict_val = minibatch_iter.val_feed_dict(size) + + # 这里只跑模型的loss等指标 outs_val = sess.run([model.loss, model.ranks, model.mrr], feed_dict=feed_dict_val) return outs_val[0], outs_val[1], outs_val[2], (time.time() - t_test) @@ -288,7 +298,7 @@ def train(train_data, test_data=None): merged = tf.summary.merge_all() summary_writer = tf.summary.FileWriter(log_dir(), sess.graph) - # Init variables + # Init variables 初始化参数 sess.run(tf.global_variables_initializer(), feed_dict={adj_info_ph: minibatch.adj}) # Train model 训练 @@ -300,7 +310,9 @@ def train(train_data, test_data=None): avg_time = 0.0 epoch_val_costs = [] - # 从minibatch获取训练和验证数据的邻接表信息 + # 利用tf.assgin函数,将邻接表数据赋值给adj_info + # 训练的时候,用训练的邻接表,验证的时候用验证的邻接表 + # 此时操作并不会执行,在之后sess.run的时候才会真正执行 train_adj_info = tf.assign(adj_info, minibatch.adj) val_adj_info = tf.assign(adj_info, minibatch.test_adj) @@ -321,7 +333,6 @@ def train(train_data, test_data=None): # Training step 训练 # merged是保存了的所有的tf.summary相关的变量,用于tensorboard绘图, # model.opt_op 是优化调参的操作 - # 其他的变量 # # outs = sess.run([merged, model.opt_op, model.loss, model.ranks, model.aff_all, @@ -331,16 +342,21 @@ def train(train_data, test_data=None): # train_shadow_mrr值是一个滑动平均更新的方式 + # 在得到该批次数据的mrr的时候,不直接更新,而是将历史的mrr值也引入进来计算,避免评估结果抖动较大 + # 参考 https://www.pianshen.com/article/11191400472/ if train_shadow_mrr is None: train_shadow_mrr = train_mrr# else: + # 1-0.99中,0.99为衰减率 train_shadow_mrr -= (1-0.99) * (train_shadow_mrr - train_mrr) if iter % FLAGS.validate_iter == 0: - # Validation 验证的时候,需要用验证集的邻接表信息,因此这里跑一下tf.assign的操作 + # Validation 验证的时候,需要用验证集的邻接表信息,因此这里跑一下tf.assign的操作, 将验证的邻接表赋给adj_info + sess.run(val_adj_info.op) val_cost, ranks, val_mrr, duration = evaluate(sess, model, minibatch, size=FLAGS.validate_batch_size) # 验证完毕再切回训练集的邻接表信息 + sess.run(train_adj_info.op) epoch_val_costs[-1] += val_cost if shadow_mrr is None: diff --git a/preprocess.ipynb b/preprocess.ipynb deleted file mode 100644 index 164c2615..00000000 --- a/preprocess.ipynb +++ /dev/null @@ -1,446 +0,0 @@ -{ - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "name": "graph.ipynb", - "provenance": [], - "collapsed_sections": [] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "markdown", - "source": [ - "" - ], - "metadata": { - "id": "lFBIUQovI53M" - } - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 319 - }, - "id": "FXItxCYQ5xE2", - "outputId": "45d18b2d-50ad-4361-fb5c-1401166b8757" - }, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {} - } - ], - "source": [ - "# python -m graphsage.supervised_train --train_prefix ./example_data/toy-ppi --model graphsage_mean --sigmoid\n", - "# test networkx and visualization\n", - "import networkx as nx\n", - "import tensorflow as tf\n", - "tf.compat.v1.disable_eager_execution()\n", - "\n", - "G = nx.complete_graph(6)\n", - "nx.draw(G)" - ] - }, - { - "cell_type": "code", - "source": [ - "# download code and data\n", - "!git clone https://github.com/williamleif/GraphSAGE" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "S6709xbNrBok", - "outputId": "51e908ea-9105-4844-9427-b57a417bf9da" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Cloning into 'GraphSAGE'...\n", - "remote: Enumerating objects: 265, done.\u001b[K\n", - "remote: Counting objects: 100% (7/7), done.\u001b[K\n", - "remote: Compressing objects: 100% (7/7), done.\u001b[K\n", - "remote: Total 265 (delta 3), reused 0 (delta 0), pack-reused 258\u001b[K\n", - "Receiving objects: 100% (265/265), 6.43 MiB | 11.28 MiB/s, done.\n", - "Resolving deltas: 100% (160/160), done.\n" - ] - } - ] - }, - { - "cell_type": "code", - "source": [ - "import json\n", - "from networkx.readwrite import json_graph\n", - "import os\n", - "import numpy as np\n", - "import sys\n", - "\n", - "CODE_ROOT = \"GraphSAGE/graphsage\"\n", - "sys.path.append(\"GraphSAGE\")\n", - "\n", - "def load_data():\n", - " data_path = 'GraphSAGE/example_data'\n", - " # DATA 1, 14755 nodes, 228431 links\n", - " G_data = json.load(open(data_path + '/toy-ppi-G.json'))\n", - " #G_data['nodes'] = G_data['nodes'][:100]\n", - " #G_data['links'] = G_data['links'][:100]\n", - " G = json_graph.node_link_graph(G_data)\n", - " \n", - " conversion = lambda n : n\n", - " lab_conversion = lambda n : n\n", - " \n", - " # DATA 2, (14755, 50) dtype('float64')\n", - " feats = np.load(data_path + '/toy-ppi-feats.npy')\n", - " \n", - " # DATA 3, {\"0\": 0, \"1\": 1}, len: 14755\n", - " # node ids to integer values indexing feature tensor\n", - " # 其实没什么用\n", - " id_map = json.load(open(data_path + \"/toy-ppi-id_map.json\"))\n", - " \n", - " # DATA 4, dict, len: 14755, column 121\n", - " # from node ids to class values (integer or list)\n", - " # 分类标签\n", - " class_map = json.load(open(data_path + \"/toy-ppi-class_map.json\"))\n", - " \n", - " broken_count = 0\n", - " for node in G.nodes():\n", - " if not 'val' in G.nodes()[node] or not 'test' in G.nodes()[node]:\n", - " G.remove_node(node)\n", - " broken_count += 1\n", - " print(\"Removed {:d} nodes that lacked proper annotations due to networkx versioning issues\".format(broken_count))\n", - " \n", - " # edge: (0, 800) 边\n", - " # G[0]: 某结点与所有的关联结点组成的边的集合\n", - " # 标记需要在训练中移除的关联关系,即边\n", - " for edge in G.edges():\n", - " if (G.nodes()[edge[0]]['val'] or G.nodes()[edge[1]]['val'] or\n", - " G.nodes()[edge[0]]['test'] or G.nodes()[edge[1]]['test']):\n", - " G[edge[0]][edge[1]]['train_removed'] = True\n", - " else:\n", - " G[edge[0]][edge[1]]['train_removed'] = False\n", - " \n", - " from sklearn.preprocessing import StandardScaler\n", - " \n", - " # 训练集的id集合,result only int, len: 9716\n", - " train_ids = np.array([id_map[str(n)] for n in G.nodes() \\\n", - " if not G.nodes()[n]['val'] and not G.nodes()[n]['test']])\n", - " \n", - " train_feats = feats[train_ids]\n", - " \n", - " # 特征缩放,标准化:z = (x - u) / s\n", - " # u is the mean of the training samples\n", - " # s is the standard deviation of the training samples\n", - " scaler = StandardScaler()\n", - " scaler.fit(train_feats)\n", - " feats = scaler.transform(feats)\n", - "\n", - " walks = []\n", - "\n", - " return G, feats, id_map, walks, class_map" - ], - "metadata": { - "id": "jg81uw5O6Gaz" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "def construct_placeholders(num_classes):\n", - " # Define placeholders\n", - " placeholders = {\n", - " 'labels' : tf.compat.v1.placeholder(tf.float32, shape=(None, num_classes), name='labels'),\n", - " 'dropout': tf.compat.v1.placeholder_with_default(0., shape=(), name='dropout'),\n", - " 'batch' : tf.compat.v1.placeholder(tf.int32, shape=(None), name='batch1'),\n", - " 'batch_size' : tf.compat.v1.placeholder(tf.int32, name='batch_size'),\n", - " }\n", - " return placeholders" - ], - "metadata": { - "id": "mReVSrV9UzYO" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "train_data = load_data()" - ], - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "Rv1F4lYF_FfW", - "outputId": "7b9e88c6-8ba5-4128-9564-55a7d4cc835c" - }, - "execution_count": null, - "outputs": [ - { - "output_type": "stream", - "name": "stdout", - "text": [ - "Removed 0 nodes that lacked proper annotations due to networkx versioning issues\n" - ] - } - ] - }, - { - "cell_type": "code", - "source": [ - "G = train_data[0]\n", - "features = train_data[1]\n", - "id_map = train_data[2]\n", - "context_pairs = train_data[3]\n", - "class_map = train_data[4]\n", - "\n", - "# num_classes = 121\n", - "num_classes = len(list(class_map.values())[0])\n", - "# pad with dummy zero vector, row wise\n", - "features = np.vstack([features, np.zeros((features.shape[1],))])\n", - "placeholders = construct_placeholders(num_classes)" - ], - "metadata": { - "id": "CjSUlil1kNZP" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "class NodeMinibatchIterator(object):\n", - "\n", - " \"\"\"\n", - " This minibatch iterator iterates over nodes for supervised learning.\n", - "\n", - " G -- networkx graph\n", - " id2idx -- dict mapping node ids to integer values indexing feature tensor\n", - " placeholders -- standard tensorflow placeholders object for feeding\n", - " label_map -- map from node ids to class values (integer or list)\n", - " num_classes -- number of output classes\n", - " batch_size -- size of the minibatches\n", - " max_degree -- maximum size of the downsampled adjacency lists\n", - " 以toy-ppi数据集举例:\n", - " label_map为输出,维度为(14755, 121)\n", - " num_class为label_map的第二维,即121\n", - " \"\"\"\n", - " def __init__(self, G, id2idx,\n", - " placeholders, label_map, num_classes,\n", - " batch_size=100, max_degree=25,\n", - " **kwargs):\n", - "\n", - " self.G = G\n", - " self.nodes = G.nodes()\n", - " self.id2idx = id2idx\n", - " self.placeholders = placeholders\n", - " self.batch_size = batch_size\n", - " self.max_degree = max_degree\n", - " self.batch_num = 0\n", - " self.label_map = label_map\n", - " self.num_classes = num_classes\n", - "\n", - " self.adj, self.deg = self.construct_adj()\n", - " self.test_adj = self.construct_test_adj()\n", - "\n", - " self.val_nodes = [n for n in self.G.nodes() if self.G.nodes()[n]['val']]\n", - " self.test_nodes = [n for n in self.G.nodes() if self.G.nodes()[n]['test']]\n", - "\n", - " # 不参与训练的结点id\n", - " self.no_train_nodes_set = set(self.val_nodes + self.test_nodes)\n", - " # 可训练的结点id\n", - " self.train_nodes = set(G.nodes()).difference(self.no_train_nodes_set)\n", - " # don't train on nodes that only have edges to test set\n", - " # 只保留有邻居的结点\n", - " self.train_nodes = [n for n in self.train_nodes if self.deg[id2idx[str(n)]] > 0]\n", - "\n", - " def _make_label_vec(self, node):\n", - " label = self.label_map[node]\n", - " if isinstance(label, list):\n", - " label_vec = np.array(label)\n", - " else:\n", - " label_vec = np.zeros((self.num_classes))\n", - " class_ind = self.label_map[node]\n", - " label_vec[class_ind] = 1\n", - " return label_vec\n", - "\n", - " def construct_adj(self):\n", - " # adjacency shape: (14756, 128) ,用于存储所有节点的邻居节点id\n", - " adj = len(self.id2idx) * np.ones((len(self.id2idx)+1, self.max_degree))\n", - " # (14755,) ,用于存储所有结点的degree值\n", - " deg = np.zeros((len(self.id2idx),))\n", - "\n", - " for nodeid in self.G.nodes():\n", - " if self.G.nodes()[nodeid]['test'] or self.G.nodes()[nodeid]['val']:\n", - " continue\n", - "\n", - " # 获取所有训练集的邻居节点的id\n", - " neighbors = np.array([self.id2idx[str(neighbor)]\n", - " for neighbor in self.G.neighbors(nodeid)\n", - " if (not self.G[nodeid][neighbor]['train_removed'])])\n", - " \n", - " deg[self.id2idx[str(nodeid)]] = len(neighbors)\n", - " if len(neighbors) == 0:\n", - " continue\n", - " if len(neighbors) > self.max_degree:\n", - " neighbors = np.random.choice(neighbors, self.max_degree, replace=False)\n", - " elif len(neighbors) < self.max_degree:\n", - " neighbors = np.random.choice(neighbors, self.max_degree, replace=True)\n", - " adj[self.id2idx[str(nodeid)], :] = neighbors\n", - " return adj, deg\n", - "\n", - " def construct_test_adj(self):\n", - " adj = len(self.id2idx) * np.ones((len(self.id2idx)+1, self.max_degree))\n", - " for nodeid in self.G.nodes():\n", - " # 所有邻居节点的id,这里没有限制训练集或测试集\n", - " neighbors = np.array([self.id2idx[str(neighbor)]\n", - " for neighbor in self.G.neighbors(nodeid)])\n", - " if len(neighbors) == 0:\n", - " continue\n", - " if len(neighbors) > self.max_degree:\n", - " neighbors = np.random.choice(neighbors, self.max_degree, replace=False)\n", - " elif len(neighbors) < self.max_degree:\n", - " neighbors = np.random.choice(neighbors, self.max_degree, replace=True)\n", - " adj[self.id2idx[str(nodeid)], :] = neighbors\n", - " return adj\n", - "\n", - " def end(self):\n", - " return self.batch_num * self.batch_size >= len(self.train_nodes)\n", - "\n", - " def batch_feed_dict(self, batch_nodes, val=False):\n", - " batch1id = batch_nodes\n", - " batch1 = [self.id2idx[n] for n in batch1id]\n", - "\n", - " labels = np.vstack([self._make_label_vec(node) for node in batch1id])\n", - " feed_dict = dict()\n", - " feed_dict.update({self.placeholders['batch_size'] : len(batch1)})\n", - " feed_dict.update({self.placeholders['batch']: batch1})\n", - " feed_dict.update({self.placeholders['labels']: labels})\n", - "\n", - " return feed_dict, labels\n", - "\n", - " def node_val_feed_dict(self, size=None, test=False):\n", - " if test:\n", - " val_nodes = self.test_nodes\n", - " else:\n", - " val_nodes = self.val_nodes\n", - " if not size is None:\n", - " val_nodes = np.random.choice(val_nodes, size, replace=True)\n", - " # add a dummy neighbor\n", - " ret_val = self.batch_feed_dict(val_nodes)\n", - " return ret_val[0], ret_val[1]\n", - "\n", - " def incremental_node_val_feed_dict(self, size, iter_num, test=False):\n", - " if test:\n", - " val_nodes = self.test_nodes\n", - " else:\n", - " val_nodes = self.val_nodes\n", - " val_node_subset = val_nodes[iter_num*size:min((iter_num+1)*size,\n", - " len(val_nodes))]\n", - "\n", - " # add a dummy neighbor\n", - " ret_val = self.batch_feed_dict(val_node_subset)\n", - " return ret_val[0], ret_val[1], (iter_num+1)*size >= len(val_nodes), val_node_subset\n", - "\n", - " def num_training_batches(self):\n", - " return len(self.train_nodes) // self.batch_size + 1\n", - "\n", - " def next_minibatch_feed_dict(self):\n", - " start_idx = self.batch_num * self.batch_size\n", - " self.batch_num += 1\n", - " end_idx = min(start_idx + self.batch_size, len(self.train_nodes))\n", - " batch_nodes = self.train_nodes[start_idx : end_idx]\n", - " return self.batch_feed_dict(batch_nodes)\n", - "\n", - " def incremental_embed_feed_dict(self, size, iter_num):\n", - " node_list = self.nodes\n", - " val_nodes = node_list[iter_num*size:min((iter_num+1)*size,\n", - " len(node_list))]\n", - " return self.batch_feed_dict(val_nodes), (iter_num+1)*size >= len(node_list), val_nodes\n", - "\n", - " def shuffle(self):\n", - " \"\"\" Re-shuffle the training set.\n", - " Also reset the batch number.\n", - " \"\"\"\n", - " self.train_nodes = np.random.permutation(self.train_nodes)\n", - " self.batch_num = 0\n" - ], - "metadata": { - "id": "ZYCKM4i5PmPf" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "\"\"\"\n", - " This minibatch iterator iterates over nodes for supervised learning.\n", - "\n", - " G -- networkx graph\n", - " id2idx -- dict mapping node ids to integer values indexing feature tensor\n", - " placeholders -- standard tensorflow placeholders object for feeding\n", - " label_map -- map from node ids to class values (integer or list)\n", - " num_classes -- number of output classes\n", - " batch_size -- size of the minibatches\n", - " max_degree -- maximum size of the downsampled adjacency lists\n", - "\"\"\"\n", - "# 实例化 NodeMinibatch 迭代器\n", - "minibatch = NodeMinibatchIterator(G,\n", - " id_map,\n", - " placeholders,\n", - " class_map,\n", - " num_classes,\n", - " batch_size=512,\n", - " max_degree=128,\n", - " context_pairs = context_pairs)\n", - "\n", - "# adjacency shape: (14756, 128) 包装为placeholder\n", - "adj_info_ph = tf.compat.v1.placeholder(tf.int32, shape=minibatch.adj.shape)\n", - "adj_info = tf.Variable(adj_info_ph, trainable=False, name=\"adj_info\")\n", - "\n", - "# 接着就是构建模型了,需要改动的兼容代码过多,暂不继续了" - ], - "metadata": { - "id": "mUW98eVhQ5H7" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "" - ], - "metadata": { - "id": "Wp3DZreLrdtF" - }, - "execution_count": null, - "outputs": [] - } - ] -} \ No newline at end of file From 091912aa7066a720a9d8ab2604bd6db14b8917aa Mon Sep 17 00:00:00 2001 From: java002 Date: Wed, 16 Mar 2022 17:03:55 +0800 Subject: [PATCH 28/28] minibach annotation commit --- graphsage/minibatch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graphsage/minibatch.py b/graphsage/minibatch.py index e83418f5..e6fb8b86 100644 --- a/graphsage/minibatch.py +++ b/graphsage/minibatch.py @@ -1,4 +1,4 @@ -from __future__ import division +from __future__ import division from __future__ import print_function import numpy as np @@ -335,7 +335,7 @@ def incremental_node_val_feed_dict(self, size, iter_num, test=False): ret_val = self.batch_feed_dict(val_node_subset) return ret_val[0], ret_val[1], (iter_num+1)*size >= len(val_nodes), val_node_subset - #当前是第几批次 + #返回当前是第几批次 def num_training_batches(self): return len(self.train_nodes) // self.batch_size + 1