【导读】 在GCN如日中天的时候,我们不忽略它的巨大缺陷,即无法快速表征新的节点,这限制了它的应用场景。GraphSAGE属于归纳式学习方法,通过学习聚合函数来学习图的拓扑结构,进而方便的得到新节点的特征,所得节点特征会根据邻居关系变化而变化。本文将以MaxPooling聚合方法为例手把手教大家构建GraphSAGE模型。

系列教程《GNN-algorithms》

本文为系列教程《GNN-algorithms》中的内容,该系列教程不仅会深入介绍GNN的理论基础,还结合了TensorFlow GNN框架tf_geometric对各种GNN模型(GCN、GAT、GIN、SAGPool等)的实现进行了详细地介绍。本系列教程作者王有泽(https://github.com/wangyouze)也是tf_geometric框架的贡献者之一。

系列教程《GNN-algorithms》Github链接: https://github.com/wangyouze/GNN-algorithms

TensorFlow GNN框架tf_geometric的Github链接: https://github.com/CrawlScript/tf_geometric

前序讲解: 系列教程GNN-algorithms之一:《图卷积网络(GCN)的前世今生》

系列教程GNN-algorithms之二:《切比雪夫显神威—ChebyNet》 系列教程GNN-algorithms之三:《将图卷积简化进行到底—SGC》

前言

本教程将带你一起在PPI(蛋白质网络)数据集上用Tensorflow搭建GraphSAGE框架中的MaxPooling聚合模型实现有监督下的图节点标签预测任务。完整代码可以在Github中进行下载: https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_graph_sage.py

GraphSAGE简介

GraphSAGE是一种在超大规模图上利用节点的属性信息高效产生未知节点特征表示的归纳式学习框架。GraphSAGE可以被用来生成节点的低维向量表示,尤其对于是那些具有丰富节点属性的Graph效果显著。目前大多数的框架都是直推式学习模型,也就是说只能够在一张固定的Graph上进行表示学习,这些模型既不能够对那些在训练中未见的节点进行有效的向量表示,也不能够跨图进行节点表示学习。GraphSAGE作为一种归纳式的表示学习框架能够利用节点丰富的属性信息有效地生成未知节点的特征表示。

GraphSAGE的核心思想是通过学习一个对邻居节点进行聚合表示的函数来产生中心节点的特征表示而不是学习节点本身的embedding。它既可以进行有监督学习也可以进行无监督学习,GraphSAGE中的聚合函数有以下几种:

Mean Aggregator Mean 聚合近乎等价于GCN中的卷积传播操作,具体来说就是对中心节点的邻居节点的特征向量进行求均值操作,然后和中心节点特征向量进行拼接,中间要经过两次非线性变换。

GCN Aggregator GCN的归纳式学习版本

Pooling Aggregator

先对中心节点的邻居节点表示向量进行一次非线性变换,然后对变换后的邻居表示向量进行池化操作(mean pooling或者max pooling),最后将pooling所得结果与中心节点的特征表示分别进行非线性变换,并将所得结果进行拼接或者相加从而得到中心节点在该层的向量表示。 * LSTM Aggregator LSTM Aggregator 将中心节点的邻居节点随机打乱作为输入序列,将所得向量表示与中心节点的向量表示分别经过非线性变换后拼接得到中心节点在该层的向量表示。LSTM本身是用于序列数据,而邻居节点没有明显的序列关系,因此输入到LSTM中的邻居节点需要随机打乱顺序。

下面我们将以MaxPooling聚合方法为例构建GraphSAGE模型进行有监督学习下的分类任务。 * 教程中完整的代码链接: https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_graph_sage.py * 论文地址:https://arxiv.org/pdf/1706.02216.pdf

教程目录

数据集PPI

开发环境

max_pooling_graph_sage的具体实现 GraphSAGE的训练

GraphSAGE的评估

PPI数据集

PPI(Protein-protein interaction networks)数据集由24个对应人体不同组织的图组成。其中20个图用于训练,2个图用于验证,2个图用于测试。平均每张图有2372个节点,每个节点有50个特征。测试集中的图与训练集中的图没有交叉,即在训练阶段测试集中的图是不可见的。每个节点拥有多种标签,标签的种类总共有121种。

开发环境

操作系统: Windows / Linux / Mac OS

Python 版本: >= 3.5 * 依赖包:

tf_geometric(一个基于Tensorflow的GNN库)

根据你的环境(是否已安装TensorFlow、是否需要GPU)从下面选择一条安装命令即可一键安装所有Python依赖:``` pip install -U tf_geometric # 这会使用你自带的TensorFlow,注意你需要tensorflow/tensorflow-gpu >= 1.14.0 or >= 2.0.0b1

pip install -U tf_geometric[tf1-cpu] # 这会自动安装TensorFlow 1.x CPU版

pip install -U tf_geometric[tf1-gpu] # 这会自动安装TensorFlow 1.x GPU版

pip install -U tf_geometric[tf2-cpu] # 这会自动安装TensorFlow 2.x CPU版

pip install -U tf_geometric[tf2-gpu] # 这会自动安装TensorFlow 2.x GPU版


教程使用的核心库是tf_geometric,一个基于TensorFlow的GNN库。tf_geometric的详细教程可以在其Github主页上查询:

https://github.com/CrawlScript/tf_geometric



**max_pooling_graph_sage的具体实现**




MaxPooling 聚合函数是一个带有max-pooling操作的单层神经网络。我们首先传递每个中心节点的邻居节点向量到一个非线性层中。由于我们的tf_geometric是基于边表结构进行相关Graph操作,所以我们先通过tf.gather转换得到所有节点的邻居节点的特征向量组成的特征矩阵。

     row, col = edge_index` repeated_x = tf.gather(x, row)`` neighbor_x = tf.gather(x, col)`

row是Graph中的源节点序列,low是Graph中的目标节点序列,x是Graph中的节点特征矩阵。tf.gather是根据节点序列从节点特征矩阵中选取对应的节点特征堆叠形成所有邻居节点组成的特征矩阵。tf.gather的具体操作如下:
![](https://cdn.zhuanzhi.ai/vfiles/004896f9f92645f6ac3434a1a843986c)

tf.gather:https://www.tensorflow.org/api_docs/python/tf/gather
得到加权后的邻居节点特征向量

    neighbor_x = gcn_mapper(repeated_x, neighbor_x, edge_weight=edge_weight)

在进行max-pooling操作之前将所有邻居节点的特征向量输入全连接网络计算邻居节点的特征表示。(将MLP看做是一组函数)

     neighbor_x = dropout(neighbor_x)` h = neighbor_x @ mlp_kernel`` if mlp_bias is not None:`` h += mlp_bias``

if activation is not None: h = activation(h)`


对邻居节点特征向量进行max-pooling操作,然后将所得向量与经过变换的中心节点特征向量拼接输出。一个理想的聚合方法就应该是简单,可学习且对称的。换句话说,一个理想的aggregator应该学会如何聚合邻居节点的特征表示,并对邻居节点的顺序不敏感,同时不会造成巨大的训练开销。

     reduced_h = max_reducer(h, row, num_nodes=len(x))` reduced_h = dropout(reduced_h)`` x = dropout(x)``

from_neighs = reduced_h @ neighs_kernel from_x = x @ self_kerneloutput = tf.concat([from_neighs, from_x], axis=1) if bias is not None:output += bias if activation is not None: output = activation(output) if normalize:output = tf.nn.l2_normalize(output, axis=-1) `` return output`




**构建模型**




导入相关库
本教程使用的核心库是**tf_geometric**,借助这个GNN库我们可以方便的对数据集进行导入,预处理图数据以及搭建图神经网络。另外我们还引用了tf.keras.layers中的Dropout用来缓解过拟合以及sklearn中的micro f1_score函数作为评价指标。



    # coding=utf-8`import os``import tensorflow as tf``from tensorflow import keras``import numpy as np``from tf_geometric.layers.conv.graph_sage import MaxPoolingGraphSage``from tf_geometric.datasets.ppi import PPIDataset``from sklearn.metrics import f1_score``from tqdm import tqdm``from tf_geometric.utils.graph_utils import RandomNeighborSampler`


加载数据集,我们使用**tf_geometric**自带的PPI数据集。tf_geometric提供了非常简单的图数据构建接口,只需要传入简单的Python数组或Numpy数组作为节点特征和邻接表就可以构建自己的数据集。示例请看GIN:
 
    train_graphs, valid_graphs, test_graphs = PPIDataset().load_data()

我们使用tf_geometric自带的PPI数据集,返回划分好的训练集(20),验证集(2),测试集(2)。

对Graph中的每个节点的邻居节点进行采样
由于每个节点的邻居节点的数目不一,出于计算效率的考虑,我们对每个节点采样一定数量的邻居节点作为之后聚合领域信息时的邻居节点。设定采样数量为num_sample,如果邻居节点的数量大于num_sample,那我们采用无放回采样。如果邻居节点的数量小于num_sample,我们采用有放回采样,直到所采样的邻居节点数量达到num_sample。RandomNeighborSampler提前对每张图进行预处理,将相关的图信息与各自的图绑定。
  
    # traverse all graphs`for graph in train_graphs + valid_graphs + test_graphs:`` neighbor_sampler = RandomNeighborSampler(graph.edge_index)`` graph.cache["sampler"] = neighbor_sampler`

需要注意的是,由于模型可能会同时作用在多个图上,为了保证每张图的邻居节点在抽样结束之后不发生混淆,我们将抽样结果与每个Graph对象绑定在一起,即将抽样信息保存在“cache"这个缓存字典之中。

采用两层MaxPooling聚合函数来聚合Graph中邻居节点蕴含的信息。

    graph_sages = [` MaxPoolingGraphSage(units=128, activation=tf.nn.relu),`` MaxPoolingGraphSage(units=128, activation=tf.nn.relu)``]``

fc = tf.keras.Sequential([ keras.layers.Dropout(0.3),tf.keras.layers.Dense(num_classes)])num_sampled_neighbors_list = [25, 10]def forward(graph, training=False):neighbor_sampler = graph.cache["sampler"] h = graph.xfor i, (graph_sage, num_sampled_neighbors) in enumerate(zip(graph_sages, num_sampled_neighbors_list)): sampled_edge_index, sampled_edge_weight = neighbor_sampler.sample(k=num_sampled_neighbors)h = graph_sage([h, sampled_edge_index, sampled_edge_weight], training=training) h = fc(h, training=training) `` return h`


两层MaxPooling聚合函数的邻居节点采样数目分别为25和10。之前我们已经通过RandomNeighborSampler为每张图处理好了相关的图结构信息,现在只需要根据每层的抽样数目num_sampled_neighbors分别进行抽样(neighbor_sample.sample())。将抽样所得的边sampled_edge_indext,边的权重sampled_edge_weights以及节点的特征向量x输入到GrapSAGE模型中。由于Dropout层在训练和预测阶段的状态不同,为此,我们通过参数training来决定是否需要Dropout发挥作用。

**GraphSAGE训练**




模型的训练与其他基于Tensorflow框架的模型训练基本一致,主要步骤有定义优化器,计算误差与梯度,反向传播等。需要注意的是,训练阶段forward函数的参数training=True,即此时模型执行Dropout操作。当预测阶段,输入为valid_graphs或者test_graphs时,forward的参数training=False,此时不执行Dropout操作。GraphSAGE论文用模型在第10轮训练后的表现来评估模型,因此这里我们将epoches设置为10。

    optimizer = tf.keras.optimizers.Adam(learning_rate=1e-2)`

for epoch in tqdm(range(10)): for graph in train_graphs:with tf.GradientTape() as tape: logits = forward(graph, training=True)loss = compute_loss(logits, tape.watched_variables()) vars = tape.watched_variables() grads = tape.gradient(loss, vars)optimizer.apply_gradients(zip(grads, vars)) if epoch % 1 == 0: valid_f1_mic = evaluate(valid_graphs)test_f1_mic = evaluate(test_graphs) print("epoch = {}\tloss = {}\tvalid_f1_micro = {}".format(epoch, loss, valid_f1_mic))`` print("epoch = {}\ttest_f1_micro = {}".format(epoch, test_f1_mic))`


* 
计算模型Loss
由于PPI数据集中的每个节点具有多个标签,属于多标签,多分类任务,因此我们选用sigmoid交叉熵函数。这里的logits是模型对节点标签的预测结果,graph.y是图节点的真实标签。为了防止模型出现过拟合现象,我们对模型的参数使用L2正则化。


    def compute_loss(logits, vars):` losses = tf.nn.sigmoid_cross_entropy_with_logits(`` logits=logits,`` labels=tf.convert_to_tensor(graph.y, dtype=tf.float32)`` )``

kernel_vals = [var for var in vars if "kernel" in var.name] l2_losses = [tf.nn.l2_loss(kernel_var) for kernel_var in kernel_vals] return tf.reduce_mean(losses) + tf.add_n(l2_losses) * 1e-5`





**GraphSAGE评估**




我们使用F1 Score来评估MaxPoolingGraphSAGE聚合邻居节点信息进行分类任务的性能。将测试集中的图(训练阶段unseen)输入到经过训练的MaxPoolingGraphSAGE得到预测结果,最后预测结果与其对应的labels转换为一维数组,输入到sklearn中的f1_score方法,得到F1_Score。

    def evaluate(graphs):` y_preds = []`` y_true = []``

for graph in graphs: y_true.append(graph.y)logits = forward(graph) y_preds.append(logits.numpy()) y_pred = np.concatenate(y_preds, axis=0)y = np.concatenate(y_true, axis=0) mic = calc_f1(y, y_pred) `` return mic`




**运行结果**


    epoch = 1 loss = 0.5231980085372925 valid_f1_micro = 0.45228990047917433`epoch = 1 test_f1_micro = 0.45506719065662915`` 27%|██▋ | 3/11 [01:11<03:12, 24.11s/it]epoch = 2 loss = 0.5082718729972839 valid_f1_micro = 0.4825462475136504``epoch = 2 test_f1_micro = 0.4882603340749235``epoch = 3 loss = 0.49998781085014343 valid_f1_micro = 0.4906942451215627``epoch = 3 test_f1_micro = 0.502555249743498`` 45%|████▌ | 5/11 [01:55<02:16, 22.79s/it]epoch = 4 loss = 0.4901132583618164 valid_f1_micro = 0.5383310665693446``epoch = 4 test_f1_micro = 0.5478608072643453``epoch = 5 loss = 0.484283983707428 valid_f1_micro = 0.5455753374297568``epoch = 5 test_f1_micro = 0.5516753473281046`` 64%|██████▎ | 7/11 [02:41<01:31, 22.95s/it]epoch = 6 loss = 0.4761819541454315 valid_f1_micro = 0.5417373280572828``epoch = 6 test_f1_micro = 0.5504290907273931`` 73%|███████▎ | 8/11 [03:03<01:08, 22.71s/it]epoch = 7 loss = 0.46836230158805847 valid_f1_micro = 0.5720065995217665``epoch = 7 test_f1_micro = 0.5843164717276317`` 82%|████████▏ | 9/11 [03:24<00:44, 22.34s/it]epoch = 8 loss = 0.4760943651199341 valid_f1_micro = 0.5752257074185534``epoch = 8 test_f1_micro = 0.5855495700393325`` 91%|█████████ | 10/11 [03:47<00:22, 22.34s/it]epoch = 9 loss = 0.461212694644928 valid_f1_micro = 0.5812645586399496``epoch = 9 test_f1_micro = 0.5930584548044271``100%|██████████| 11/11 [04:08<00:00, 22.61s/it]``epoch = 10 loss = 0.4568028450012207 valid_f1_micro = 0.5833869662874881``epoch = 10 test_f1_micro = 0.5964539684054789`



**完整代码**




教程中的完整代码链接:
 
demo_graph_sage.py:https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_graph_sage.py



本教程(属于系列教程**《GNN-algorithms》**)Github链接:

https://github.com/wangyouze/GNN-algorithms



成为VIP会员查看完整内容
0
26

相关内容

**【导读】**自GCN异军突起后,图神经网络这个领域也逐渐壮大。但是疑惑也随之而来,为什么GNN会这么有效?论文How Powerful Are Graph Neural Networks?给出了答案。本文将简要介绍图同构网络GIN的来龙去脉,并手把手教你搭建基于Tensorflow框架的GIN模型。

前言


How Powerful are Graph Neural Networks?相信有很多人会和我一样在边掉头发边跑模型的时候会产生这样的疑惑。本教程将手把手教大家搭建基于Tensorflow的GIN模型,同时把该论文(ICLR 2019 best student paper)中关于GNNs为什么有效果的观点进行简要介绍。

GIN简介


GIN的起源 如GCN和GraphSAGE,都是通过迭代聚合一阶邻居信息来更新节点的特征表示,可以拆分为三个步骤: Aggregate:聚合一阶邻居节点的特征。 Combine:将邻域特征与中心节点的特征融合,更新中心节点的特征。 Readout:如果是图分类任务,需要把Graph中所有节点特征转换为Graph的特征表示。

上述方法都是基于经验主义,缺乏从理论的角度来分析GNN。GIN则是借助Weisfeiler-Lehman(WL) test 来分析GNN到底做了什么而变得如何powerful,在何种条件下GNN可以在图分类任务上和WL test一样强大。 * WL test WL test是判断两个Graph结构是否相同的有效方法,主要通过迭代以下步骤来判断Graph的同构性: (初始化:将节点的id作为自身的标签。)

  1. 聚合:将邻居节点和自身的标签进行聚合。
  2. 更新节点标签:使用Hash表将节点聚合标签映射作为节点的的新标签。

WL test迭代过程如下图:

    (此图引用自知乎 https://zhuanlan.zhihu.com/p/62006729,如有侵权,请联系删除)
    上图a中的G图中节点1的邻居有节点4;节点2的邻居有节点3和节点5;节点3的邻居有节点2,节点4,节点5;节点4的邻居有节点1,节点3,节点5;节点5的邻居有节点2,节点3,节点4。(步骤1)聚合邻居节点和自身标签后的结果就是b图中的G。然后用Hash将聚合后的结果映射为一个新的标签,进行标签压缩,如图c。用压缩后的标签来替代之前的聚合结果,进行标签更新(步骤二),如图d,G‘同理。
     对于Graph的特征表示,WL test方法用迭代前后图中节点标签的个数作为Graph的表示特征,如图e所示。从上图我们可以看出WL_test的迭代过程和GNN的聚合过程非常相似,并且作者也证明了WL_test是图神经网络聚合邻域信息能力的上限。
  •   GIN节点更新过程
      作者提出如果GNN中的Aggregate,Combine和Readout函数是单射(即原像与像的映射关系为一对一),则GNN可以达到上限,和WL_test一样。
      作者证明了当节点特征X可数时,将节点特征的聚合方式(Aggregate)设置为sum,邻域特征与中心节点特征的融合系数设置为1+![](https://cdn.zhuanzhi.ai/vfiles/45c0aa6dda7aacd8d595f4fc4ea93e2e)
    

,会存在一个函数 使得聚合函数(Combine)为单射函数,即:

为单射函数。 同时作者进一步证明对于任意的聚合函数

在满足单射性的条件下可以分解为:

然后用借助多层感知机(MLP)强大的拟合能力来学习上面公式中的 和f,最后得到基于MLP+SUM的GIN模型:

对于每轮迭代产生的节点特征求和,然后拼接作为Graph的特征表示:

完整代码下载地址: https://github.com/wangyouze/tf_geometric/blob/sage/demo/demo_gin.py * 论文下载地址:https://arxiv.org/pdf/1810.00826.pdf * 文献参考地址:https://zhuanlan.zhihu.com/p/66589334

教程目录


开发环境

GIN的实现 * 模型构建 * GIN训练 * GIN评估

开发环境


操作系统: Windows / Linux / Mac OS

Python 版本: >= 3.5 * 依赖包: * tf_geometric(一个基于Tensorflow的GNN库) 根据你的环境(是否已安装TensorFlow、是否需要GPU)从下面选择一条安装命令即可一键安装所有Python依赖:

        pip install -U tf_geometric # 这会使用你自带的TensorFlow,注意你需要tensorflow/tensorflow-gpu >= 1.14.0 or >= 2.0.0b1

 pip install -U tf_geometric[tf1-cpu] # 这会自动安装TensorFlow 1.x CPU版

 pip install -U tf_geometric[tf1-gpu] # 这会自动安装TensorFlow 1.x GPU版

 pip install -U tf_geometric[tf2-cpu] # 这会自动安装TensorFlow 2.x CPU版

 pip install -U tf_geometric[tf2-gpu] # 这会自动安装TensorFlow 2.x GPU版

教程使用的核心库是tf_geometric,一个基于TensorFlow的GNN库。tf_geometric的详细教程可以在其Github主页上查询: * https://github.com/CrawlScript/tf_geometric

GIN的实现


GIN聚合节点信息公式为:

GIN的实现很简单。首先我们聚合中心节点的一阶邻域信息

tf_geometric提供了便捷的邻域信息聚合机制API。 * * * * * *

        h = aggregate_neighbors(` x, edge_index, edge_weight,`` identity_mapper,`` sum_reducer,`` identity_updater`` )`

然后计算

更新中心节点的特征表示。 可以设置为学习参数也可以设置为固定值。 *

         h = x * (1 + eps) + h

MLP拟合特征变换函数 和f * * * * *

        h = mlp(h)` if activation is not None:`` h = activation(h)``
`` return h`

模型构建


导入相关库 本教程使用的核心库是tf_geometric,我们用它来进行图数据导入、图数据预处理及图神经网络构建。GIN的具体实现已经在上面详细介绍,另外我们后面会使用keras.metrics.Accuracy评估模型性能。

        # coding=utf-8`import os``import tensorflow as tf``import numpy as np``from tensorflow import keras``from sklearn.model_selection import train_test_split``os.environ["CUDA_VISIBLE_DEVICES"] = "0"`

我们选用论文中的生物数据集NCI1训练和评估模型性能。第一次加载NCI1数据集,预计需要花费几分钟时间。数据集第一次被预处理之后,tf_geometric会自动保存预处理的结果,以便下一次调用。对于一个TU dataset会包含节点标签,节点属性等,每个graph的处理结果会被以字典形式保存,多个图的预处理结果以list的形式返回。``` graph_dicts = tfg.datasets.TUDataset("NCI1").load_data()


* 
自己用数据构建Graph Object,即图模型输入的三要素:节点特征,边连接信息以及标签。GIN的目标是当模型不依赖于输入的节点特征时,学习网络的拓扑结构。因此对于生物数据集NCI1,我们把节点的类别标签用one_hot表示后作为输入特征(convert_node_labels_to_one_hot将节点标签转换为节点特征,十分简单,可在源码中查看该函数的实现)。```
def construct_graph(graph_dict):
 return tfg.Graph(
 x=convert_node_labels_to_one_hot(graph_dict["node_labels"]),
 edge_index=graph_dict["edge_index"],
 y=graph_dict["graph_label"] # graph_dict["graph_label"] is a list with one int element
 )

graphs = [construct_graph(graph_dict) for graph_dict in graph_dicts]

定义模型。根据论文描述,我们的模型有五层GIN作为隐藏层,MLP设置为2层来学习特征变换 和f,每个隐藏层后用Batch_normalization对数据进行归一化(抑制梯度消失和梯度爆炸)。

        class GINPoolNetwork(keras.Model):` def __init__(self, num_gins, units, num_classes, *args, **kwargs):`` super().__init__(*args, **kwargs)``
`` self.gins = [`` tfg.layers.GIN(`` keras.Sequential([`` keras.layers.Dense(units, activation=tf.nn.relu),`` keras.layers.Dense(units),`` keras.layers.BatchNormalization()`` ])`` )`` for _ in range(num_gins) # num_gins blocks`` ]``
`` self.mlp = keras.Sequential([`` keras.layers.Dense(128, activation=tf.nn.relu),`` keras.layers.Dropout(0.3),`` keras.layers.Dense(num_classes)`` ])``
`` def call(self, inputs, training=False, mask=None):``
`` if len(inputs) == 4:`` x, edge_index, edge_weight, node_graph_index = inputs`` else:`` x, edge_index, node_graph_index = inputs`` edge_weight = None``
`` hidden_outputs = []`` h = x``
`` for gin in self.gins:`` h = gin([h, edge_index, edge_weight], training=training)`` hidden_outputs.append(h)`

对每一隐藏层的输出进行sum pooling,将5层的pooling结果拼接后进行非线性变换输出。 公式如下:

         h = tf.concat(hidden_outputs, axis=-1)` h = tfg.nn.sum_pool(h, node_graph_index)`` logits = self.mlp(h, training=training)`` return logit`

GIN训练


数据集划分

        train_graphs, test_graphs = train_test_split(graphs, test_size=0.1)

计算标签种类 *

        num_classes = np.max([graph.y[0] for graph in graphs]) + 1

初始化模型 *

        model = GIN(32)

模型的训练与其他基于Tensorflow框架的模型训练基本一致,主要步骤有定义优化器,计算误差与梯度,反向传播等。我们将训练集中的graphs以batch的形式输入模型进行训练,对于graphs划分为batch可以调用我们tf_geometric中的函数create_graph_generator。


        optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)`train_batch_generator = create_graph_generator(train_graphs, batch_size, shuffle=True, infinite=True)``
``best_test_acc = 0``for step in range(0, 1000):`` batch_graph = next(train_batch_generator)`` with tf.GradientTape() as tape:`` inputs = [batch_graph.x, batch_graph.edge_index, batch_graph.edge_weight,`` batch_graph.node_graph_index]`` logits = model(inputs, training=True)`` losses = tf.nn.softmax_cross_entropy_with_logits(`` logits=logits,`` labels=tf.one_hot(batch_graph.y, depth=num_classes)`` )``
`` loss = tf.reduce_mean(losses)`` vars = tape.watched_variables()`` grads = tape.gradient(loss, vars)`` optimizer.apply_gradients(zip(grads, vars))``
`` if step % 10 == 0:`` train_acc = evaluate(train_graphs, batch_size)`` test_acc = evaluate(test_graphs, batch_size)``
`` if best_test_acc < test_acc:`` best_test_acc = test_acc``
`` print("step = {}\tloss = {}\ttrain_acc = {}\ttest_acc={}".format(step, loss, train_acc, best_test_acc))`

GIN评估


在评估模型性能的时候我们将测试集中的graph以batch的形式输入到我们的模型之中,用keras自带的keras.metrics.Accuracy计算准确率。

        def evaluate():
 accuracy_m = keras.metrics.Accuracy()

 for test_batch_graph in create_graph_generator(test_graphs, batch_size, shuffle=False, infinite=False):
 logits = forward(test_batch_graph)
 preds = tf.argmax(logits, axis=-1)
 accuracy_m.update_state(test_batch_graph.y, preds)

 return accuracy_m.result().numpy()

运行结果``` step = 0 loss = 12.347851753234863 train_acc = 0.49905380606651306 test_acc=0.5036496520042419 step = 10 loss = 0.8783968091011047 train_acc = 0.5509597063064575 test_acc=0.525547444820404 step = 20 loss = 0.6645355820655823 train_acc = 0.5404163002967834 test_acc=0.525547444820404 step = 30 loss = 0.6511620283126831 train_acc = 0.5904298424720764 test_acc=0.5790753960609436 ... step = 820 loss = 0.36381691694259644 train_acc = 0.8553662896156311 test_acc=0.8175182342529297 step = 830 loss = 0.33948060870170593 train_acc = 0.8645579814910889 test_acc=0.8248175382614136 step = 840 loss = 0.3843861520290375 train_acc = 0.8599621653556824 test_acc=0.8248175382614136 step = 850 loss = 0.3698282241821289 train_acc = 0.850229799747467 test_acc=0.8248175382614136




**完整代码链接**


***


demo_gin.py:
 https://github.com/wangyouze/tf_geometric/blob/sage/demo/demo_gin.py


本教程(属于系列教程**《GNN-algorithms》**)Github链接:
* 
https://github.com/wangyouze/GNN-algorithms




成为VIP会员查看完整内容
0
31

【导读】 基于谱域的图卷积网络用多项式近似卷积核的方式来避免计算的高复杂度,但这会导致模型的性能损失。TAGCN是基于空域方法的图卷积模型,通过多个固定尺寸的卷积核来学习图的拓扑结构特征。TAGCN本质上和CNN中的卷积是一致的。本教程将结合TAGCN原理,教你手把手构建基于Tensorflow的TAGCN模型,在Cora数据集上进行节点分类任务。

系列教程《GNN-algorithms》


本文为系列教程《GNN-algorithms》中的内容,该系列教程不仅会深入介绍GNN的理论基础,还结合了TensorFlow GNN框架tf_geometric对各种GNN模型(GCN、GAT、GIN、SAGPool等)的实现进行了详细地介绍。本系列教程作者王有泽(https://github.com/wangyouze)也是tf_geometric框架的贡献者之一。

系列教程《GNN-algorithms》Github链接: * https://github.com/wangyouze/GNN-algorithms

TensorFlow GNN框架tf_geometric的Github链接: * https://github.com/CrawlScript/tf_geometric

前序讲解: 系列教程GNN-algorithms之一:《图卷积网络(GCN)的前世今生》

系列教程GNN-algorithms之二:《切比雪夫显神威—ChebyNet》 系列教程GNN-algorithms之三:《将图卷积简化进行到底—SGC》 系列教程GNN-algorithms之四:《Inductive Learning 大神—GraphSAGE》

系列教程GNN-algorithms之五:《注意力机制在图上的应用—GAT》

前言


在教程第二节介绍了GCN的变体SGC,这一届我们继续介绍GCN的另外一个变体TAGCN模型。本教程将教你如何使用Tensorflow构建GCN的变体TAGCN模型进行节点分类任务。完整代码可在Github中下载: https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_tagcn.py

TAGCN模型简介


TAGCN是GCN的变体之一,全称TOPOLOGY ADAPTIVE GRAPH CONVOLUTIONAL NETWORKS(TAGCN)。相比于GCN对卷积核进行Chebyshev多项式近似后取k=1,TAGCN用k个图卷积核来提取不同尺寸的局部特征,并且将k保留下来作为超参数。其中的K个卷积核的感受野分别为1到K,类似于GoogleNet中每一个卷积层都有大小不同的卷积核提取特征。 TAGCN的卷积过程如下:

添加自环,对邻接矩阵进行归一化处理:

是K个图卷积核, 是每个图卷积核的系数,相比GCN,TAGCN保留了超参数K:

  1. k个卷积核在图结构数据上提取特征,进行线性组合:

  2. 仿照CNN结构,添加非线性操作:

下图展示了TAGCN在k=3时的卷积过程,类似于CNN中的每一个卷积层由多个卷积核提取feature map后形成多个channel:

将3个卷积核提取的特征进行线性组合:

总结:

TAGCN仿照CNN在每一层使用K个图卷积核分别提取不同尺寸的局部特征,避免了之前对卷积核进行近似而不能完整,充分的提取图信息的缺陷,提高了模型的表达能力。 1. TAGCN可以用于无向图和有向图,由于只需计算邻接矩阵的系数,降低了计算复杂度。

教程完整代码链接:https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_tagcn.py * 论文地址:https://arxiv.org/pdf/1710.10370.pdf

教程目录


开发环境 * TAGCN的实现 * 模型构建 * TAGCN训练 * TAGCN评估

开发环境


操作系统: Windows / Linux / Mac OS

Python 版本: >= 3.5 * 依赖包: * tf_geometric(一个基于Tensorflow的GNN库) 根据你的环境(是否已安装TensorFlow、是否需要GPU)从下面选择一条安装命令即可一键安装所有Python依赖:

        pip install -U tf_geometric # 这会使用你自带的TensorFlow,注意你需要tensorflow/tensorflow-gpu >= 1.14.0 or >= 2.0.0b1

 pip install -U tf_geometric[tf1-cpu] # 这会自动安装TensorFlow 1.x CPU版

 pip install -U tf_geometric[tf1-gpu] # 这会自动安装TensorFlow 1.x GPU版

 pip install -U tf_geometric[tf2-cpu] # 这会自动安装TensorFlow 2.x CPU版

 pip install -U tf_geometric[tf2-gpu] # 这会自动安装TensorFlow 2.x GPU版

教程使用的核心库是tf_geometric,一个基于TensorFlow的GNN库。tf_geometric的详细教程可以在其Github主页上查询: * https://github.com/CrawlScript/tf_geometric

TAGCN的实现


首先我们对图的邻接矩阵添加自环,进行归一化处理:

其中xs用来存储k个多项式卷积核提取的feature map。``` xs = [x] updated_edge_index, normed_edge_weight = gcn_norm_edge(edge_index, x.shape[0], edge_weight, renorm, improved, cache)


分别计算每个图卷积核提取图中节点的邻域信息,即计算k阶多项式,并以此将结果存储到xs中:

for k in range(K): h = aggregate_neighbors( xs[-1], updated_edge_index, normed_edge_weight, gcn_mapper, sum_reducer, identity_updater )

xs.append(h)



将K个图卷积核提取的feature_map拼接,然后线性变换输出结果:```
h = tf.concat(xs, axis=-1)

 out = h @ kernel
 if bias is not None:
 out += bias

 if activation is not None:
 out = activation(out)

 return out

模型构建


导入相关库 本教程使用的核心库是tf_geometric,我们用它来进行图数据导入、图数据预处理及图神经网络构建。SGC的具体实现已经在上面详细介绍,另外我们后面会使用keras.metrics.Accuracy评估模型性能。```

coding=utf-8

import os

os.environ["CUDA_VISIBLE_DEVICES"] = "0" import tensorflow as tf import numpy as np from tensorflow import keras from tf_geometric.layers.conv.tagcn import TAGCN from tf_geometric.datasets.cora import CoraDataset


* 
使用**tf_geometric**自带的图结构数据接口加载Cora数据集:```
graph, (train_index, valid_index, test_index) = CoraDataset().load_data()

定义模型,引入keras.layers中的Dropout层随机关闭神经元缓解过拟合。由于Dropout层在训练和预测阶段的状态不同,为此,我们通过参数training来决定是否需要Dropout发挥作用。``` tagcn0 = TAGCN(16) tagcn1 = TAGCN(num_classes) dropout = keras.layers.Dropout(0.3)

def forward(graph, training=False): h = tagcn0([graph.x, graph.edge_index, graph.edge_weight], cache=graph.cache) h = dropout(h, training=training) h = tagcn1([h, graph.edge_index, graph.edge_weight], cache=graph.cache)

return h





**TAGCN训练**

***


模型的训练与其他基于Tensorflow框架的模型训练基本一致,主要步骤有定义优化器,计算误差与梯度,反向传播等。TAGCN论文用模型在第100轮训练后的表现来评估模型,因此这里我们设置epoches=100。```
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)

best_test_acc = tmp_valid_acc = 0
for step in range(1, 101):
 with tf.GradientTape() as tape:
 logits = forward(graph, training=True)
 loss = compute_loss(logits, train_index, tape.watched_variables())

 vars = tape.watched_variables()
 grads = tape.gradient(loss, vars)
 optimizer.apply_gradients(zip(grads, vars))

 valid_acc = evaluate(valid_index)
 test_acc = evaluate(test_index)
 if test_acc > best_test_acc:
 best_test_acc = test_acc
 tmp_valid_acc = valid_acc
 print("step = {}\tloss = {}\tvalid_acc = {}\tbest_test_acc = {}".format(step, loss, tmp_valid_acc, best_test_acc))

用交叉熵损失函数计算模型损失。注意在加载Cora数据集的时候,返回值是整个图数据以及相应的train_mask,valid_mask,test_mask。TAGCN在训练的时候的输入时整个Graph,在计算损失的时候通过train_mask来计算模型在训练集上的迭代损失。因此,此时传入的mask_index是train_index。由于是多分类任务,需要将节点的标签转换为one-hot向量以便于模型输出的结果维度对应。由于图神经模型在小数据集上很容易就会疯狂拟合数据,所以这里用L2正则化缓解过拟合。``` def compute_loss(logits, mask_index, vars): masked_logits = tf.gather(logits, mask_index) masked_labels = tf.gather(graph.y, mask_index) losses = tf.nn.softmax_cross_entropy_with_logits( logits=masked_logits, labels=tf.one_hot(masked_labels, depth=num_classes) )

kernel_vals = [var for var in vars if "kernel" in var.name] l2_losses = [tf.nn.l2_loss(kernel_var) for kernel_var in kernel_vals]

return tf.reduce_mean(losses) + tf.add_n(l2_losses) * 5e-4




**TAGCN评估**

***


在评估模型性能的时候我们只需传入valid_mask或者test_mask,通过tf.gather函数就可以拿出验证集或测试集在模型上的预测结果与真实标签,用keras自带的keras.metrics.Accuracy计算准确率。

def evaluate(mask): logits = forward(graph) logits = tf.nn.log_softmax(logits, axis=-1) masked_logits = tf.gather(logits, mask) masked_labels = tf.gather(graph.y, mask)

y_pred = tf.argmax(masked_logits, axis=-1, output_type=tf.int32)

accuracy_m = keras.metrics.Accuracy() accuracy_m.update_state(masked_labels, y_pred) return accuracy_m.result().numpy()




**运行结果**

***


****

    step = 1 loss = 1.9557496309280396 valid_acc = 0.3240000009536743 best_test_acc = 0.3700000047683716

step = 2 loss = 1.6913329362869263 valid_acc = 0.4519999921321869 best_test_acc = 0.5139999985694885 step = 3 loss = 1.3922057151794434 valid_acc = 0.5220000147819519 best_test_acc = 0.5849999785423279 step = 4 loss = 1.106893539428711 valid_acc = 0.6539999842643738 best_test_acc = 0.7110000252723694 ... step = 96 loss = 0.03752553462982178 valid_acc = 0.7960000038146973 best_test_acc = 0.8209999799728394 step = 97 loss = 0.03963441401720047 valid_acc = 0.7960000038146973 best_test_acc = 0.8209999799728394 step = 98 loss = 0.04120985418558121 valid_acc = 0.7960000038146973 best_test_acc = 0.8209999799728394 step = 99 loss = 0.03467567265033722 valid_acc = 0.7960000038146973 best_test_acc = 0.8209999799728394 step = 100 loss = 0.03561935946345329 valid_acc = 0.7960000038146973 best_test_acc = 0.8209999799728394


**完整代码**


教程中的完整代码链接:
* 
demo_tagcn.py:https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_tagcn.py

**

**

本教程(属于系列教程**《GNN-algorithms》**)Github链接:
* 
https://github.com/wangyouze/GNN-algorithms




成为VIP会员查看完整内容
0
35

【导读】 GAT并非是基于谱域方法的图神经模型,GAT利用节点的空间邻域信息来学习节点的特征表示,这与基于谱域方法的GCN恰恰相反。在本教程中,我们将手把手教你如何构建基于Tensorflow的GAT模型在Cora数据集上实现节点分类任务。

系列教程《GNN-algorithms》


本文为系列教程《GNN-algorithms》中的内容,该系列教程不仅会深入介绍GNN的理论基础,还结合了TensorFlow GNN框架tf_geometric对各种GNN模型(GCN、GAT、GIN、SAGPool等)的实现进行了详细地介绍。本系列教程作者王有泽(https://github.com/wangyouze)也是tf_geometric框架的贡献者之一。

系列教程《GNN-algorithms》Github链接: * https://github.com/wangyouze/GNN-algorithms

TensorFlow GNN框架tf_geometric的Github链接: * https://github.com/CrawlScript/tf_geometric

前序讲解: 系列教程GNN-algorithms之一:《图卷积网络(GCN)的前世今生》

系列教程GNN-algorithms之二:《切比雪夫显神威—ChebyNet》

系列教程GNN-algorithms之三:《将图卷积简化进行到底—SGC》

系列教程GNN-algorithms之四:《Inductive Learning 大神—GraphSAGE》

前言

Graph Attention Networks(GAT)发表在ICLR2018上,利用masked self-attention 来学习中心节点与邻居节点之间的注意力权重,根据权重大小聚合邻居节点的空间信息来更新中心节点的特征表示,从而解决了基于卷积或者多项式近似卷积核等方法的固有缺陷。本教程将从具体实现的角度带你一起深入了解GAT。

GAT简介

GCN通过图的拉普拉斯矩阵来聚合邻居节点的特征信息,这种方式和图本身的结构紧密相关,这限制了GCN在训练时未见的图结构上的泛化能力。GAT利用注意力机制来对邻居节点特征加权求和,从而聚合邻域信息,完全摆脱了图结构的束缚,是一种归纳式学习方式。 GAT中的attention是self-attention,即Q(Query),K(Key),V(value)三个矩阵均来自统一输入。和所有的Attention机制一样,GAT的计算也分两步走: 1. 计算注意力系数。

对于中心节点,我们需要逐个计算它与邻居节点之间的注意力系数:

首先对节点特征进行变换或者说是增加维度(常见的特征增强手段),然后将变换后的中心节点特征逐个与其邻居节点特征拼接后输入一个单层的神经网络a,所得结果就是中心节点与该邻居节点之间的注意力系数,当然,注意力系数还需要经过softmax归一化,将其转换为概率分布。 具体来说我们在实现过程中首先计算了中心节点Q向量与其邻居节点K向量之间的点乘,然后为了防止其结果过大,会除以一个尺度 ,其中 为Query或者Key向量的维度。公式表示为:

通过加权求和的方式聚合节点信息。

利用上面计算所得的注意力系数对邻居节点的特征V进行线性组合作为中心节点的特征表示:

俗话说“一个篱笆三个桩,一个好汉三个帮”,GAT也秉承了这种思想,采用多头注意力机制(multi-head attention)来捕获邻居节点在不同的方面对中心节点影响力的强弱。我们将K 个head分别提取的节点特征表示进行拼接作为最终的节点表示:

我们可以看出GAT中的节点信息更新过程中是逐点运算的,每一个中心节点只与它的邻居节点有关,参数a和W也只与节点特征相关,与图结构无关。改变图的结构只需要改变节点的邻居关系 重新计算,因此GAT与GraphSAGE一样都适用于inductive任务。而大名鼎鼎的GCN在节点特征的每一次更新上都需要全图参与,学习到的参数也很大程度上与图结构有关,因此GCN在inductive任务上不给力了。 GNN引入Attention机制有三大好处:

  1. 参数少。Attention模型的复杂度与GCN等相比,复杂度更小,参数也更少,对算力的要求也就更小。
  2. 速度快。Attention更加有利于并行计算。Attention机制中的每一步计算不依赖于前面一部的计算结果,并行性良好。
  3. 效果好。Attention机制不会弱化对于长距离的信息记忆。

教程代码下载链接:

https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_gat.py GAT论文地址:https://arxiv.org/pdf/1710.10903.pdf 文献参考:https://zhuanlan.zhihu.com/p/81350196

教程目录

开发环境 * GAT的实现 * 模型构建 * GAT训练 * GAT评估

开发环境

操作系统: Windows / Linux / Mac OS

Python 版本: >= 3.5 * 依赖包: * tf_geometric(一个基于Tensorflow的GNN库) 根据你的环境(是否已安装TensorFlow、是否需要GPU)从下面选择一条安装命令即可一键安装所有Python依赖:

         pip install -U tf_geometric # 这会使用你自带的TensorFlow,注意你需要tensorflow/tensorflow-gpu >= 1.14.0 or >= 2.0.0b1

 pip install -U tf_geometric[tf1-cpu] # 这会自动安装TensorFlow 1.x CPU版

 pip install -U tf_geometric[tf1-gpu] # 这会自动安装TensorFlow 1.x GPU版

 pip install -U tf_geometric[tf2-cpu] # 这会自动安装TensorFlow 2.x CPU版

 pip install -U tf_geometric[tf2-gpu] # 这会自动安装TensorFlow 2.x GPU版

教程使用的核心库是tf_geometric,一个基于TensorFlow的GNN库。tf_geometric的详细教程可以在其Github主页上查询:

https://github.com/CrawlScript/tf_geometric

GAT的实现

首先添加自环,即添加节点自身之间的连接边,这样中心节点在稍后的特征更新中也会计算自己原先的特征。

         num_nodes = x.shape[0]`
`` # self-attention`` edge_index, edge_weight = add_self_loop_edge(edge_index, num_nodes)`

row为中心节点序列,col为一阶邻居节点序列

         row, col = edge_index

将节点特征向量X通过不同的变换得到Q(Query),K(Key)和V(value)向量。通过tf.gather得到中心节点的特征向量Q和相应的邻居节点的特征向量K。

         Q = query_activation(x @ query_kernel + query_bias)` Q = tf.gather(Q, row)``
`` K = key_activation(x @ key_kernel + key_bias)`` K = tf.gather(K, col)``
`` V = x @ kernel`

由于是multi-head attention,所以Q,K,V也需要划分为num_heads份,即每一个head都有自己相应的Q,K,V。相应的,为了计算方便,将图节点连接关系矩阵也进行扩展,每一个head都要对应整个graph。最后将Q,K矩阵相乘(每一个中心节点的特征向量与其邻居节点的特征向量相乘)得到的attention_score,通过segmen_softmax进行归一化操作。

        # xxxxx_ denotes the multi-head style stuff` Q_ = tf.concat(tf.split(Q, num_heads, axis=-1), axis=0)`` K_ = tf.concat(tf.split(K, num_heads, axis=-1), axis=0)`` V_ = tf.concat(tf.split(V, num_heads, axis=-1), axis=0)`` edge_index_ = tf.concat([edge_index + i * num_nodes for i in range(num_heads)], axis=1)``
`` att_score_ = tf.reduce_sum(Q_ * K_, axis=-1)`` normed_att_score_ = segment_softmax(att_score_, edge_index_[0], num_nodes * num_heads)`

将归一化后的attention系数当做边的权重来对邻居节点进行加权求和操作,从而更新节点特征。由于是multi-head attention,所以将同一个节点在每一个attention下的节点特征拼接输出。

         h_ = aggregate_neighbors(` V_, edge_index_, normed_att_score_,`` gcn_mapper,`` sum_reducer,`` identity_updater`` )``
`` h = tf.concat(tf.split(h_, num_heads, axis=0), axis=-1)``
`` if bias is not None:`` h += bias``
`` if activation is not None:`` h = activation(h)``
`` return h`

模型构建


导入相关库 本教程使用的核心库是tf_geometric,我们用它来进行图数据导入、图数据预处理及图神经网络构建。GAT的具体实现已经在上面详细介绍,另外我们后面会使用keras.metrics.Accuracy评估模型性能。 * * * * * *

        # coding=utf-8`import os``os.environ["CUDA_VISIBLE_DEVICES"] = "0"``import tf_geometric as tfg``import tensorflow as tf``from tensorflow import keras`

使用tf_geometric自带的图结构数据接口加载Cora数据集:``` graph, (train_index, valid_index, test_index) = CoraDataset().load_data()


* 
定义图模型。我们构建两层GAT,即GAT只聚合2-hop的邻居特征,Dropout层用来缓解模型过拟合(小数据集上尤其)。```
gat0 = tfg.layers.GAT(64, activation=tf.nn.relu, num_heads=8, drop_rate=drop_rate, attention_units=8)
gat1 = tfg.layers.GAT(num_classes, drop_rate=0.6, attention_units=1)
dropout = keras.layers.Dropout(drop_rate)

def forward(graph, training=False):
 h = graph.x
 h = dropout(h, training=training)
 h = gat0([h, graph.edge_index], training=training)
 h = dropout(h, training=training)
 h = gat1([h, graph.edge_index], training=training)
 return h

GAT训练


模型的训练与其他基于Tensorflow框架的模型训练基本一致,主要步骤有定义优化器,计算误差与梯度,反向传播等。

        optimizer = tf.keras.optimizers.Adam(learning_rate=5e-3)`for step in range(2000):`` with tf.GradientTape() as tape:`` logits = forward(graph, training=True)`` loss = compute_loss(logits, train_index, tape.watched_variables())``
`` vars = tape.watched_variables()`` grads = tape.gradient(loss, vars)`` optimizer.apply_gradients(zip(grads, vars))``
`` if step % 20 == 0:`` accuracy = evaluate()`` print("step = {}\tloss = {}\taccuracy = {}".format(step, loss, accuracy))`

用交叉熵损失函数计算模型损失。注意在加载Cora数据集的时候,返回值是整个图数据以及相应的train_mask,valid_mask,test_mask。GAT在训练的时候的输入是整个Graph,在计算损失的时候通过train_mask来计算模型在训练集上的迭代损失。因此,此时传入的mask_index是train_index。由于是多分类任务,需要将节点的标签转换为one-hot向量以便于模型输出的结果维度对应。由于图神经模型在小数据集上很容易就会疯狂拟合数据,所以这里用L2正则化缓解过拟合。``` def compute_loss(logits, mask_index, vars): masked_logits = tf.gather(logits, mask_index) masked_labels = tf.gather(graph.y, mask_index) losses = tf.nn.softmax_cross_entropy_with_logits( logits=masked_logits, labels=tf.one_hot(masked_labels, depth=num_classes) )

kernel_vals = [var for var in vars if "kernel" in var.name] l2_losses = [tf.nn.l2_loss(kernel_var) for kernel_var in kernel_vals]

return tf.reduce_mean(losses) + tf.add_n(l2_losses) * 5e-4





**GAT评估**

***


在评估模型性能的时候我们只需传入valid_mask或者test_mask,通过tf.gather函数就可以拿出验证集或测试集在模型上的预测结果与真实标签,用keras自带的keras.metrics.Accuracy计算准确率。```
def evaluate(mask):
 logits = forward(graph)
 logits = tf.nn.log_softmax(logits, axis=-1)
 masked_logits = tf.gather(logits, mask)
 masked_labels = tf.gather(graph.y, mask)

 y_pred = tf.argmax(masked_logits, axis=-1, output_type=tf.int32)

 accuracy_m = keras.metrics.Accuracy()
 accuracy_m.update_state(masked_labels, y_pred)
 return accuracy_m.result().numpy()

运行结果


运行结果与论文中结果一致



        step = 20 loss = 1.784507393836975 accuracy = 0.7839999794960022
step = 40 loss = 1.5089114904403687 accuracy = 0.800000011920929
step = 60 loss = 1.243167757987976 accuracy = 0.8140000104904175
...
step = 1120 loss = 0.8608425855636597 accuracy = 0.8130000233650208
step = 1140 loss = 0.8169388771057129 accuracy = 0.8019999861717224
step = 1160 loss = 0.7581816911697388 accuracy = 0.8019999861717224
step = 1180 loss = 0.8362383842468262 accuracy = 0.8009999990463257

完整代码


教程中完整代码链接:demo_gat.py:教程代码下载链接: https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_gat.py

本教程(属于系列教程**《GNN-algorithms》**)Github链接: * https://github.com/wangyouze/GNN-algorithms

成为VIP会员查看完整内容
0
39

【导读】 SGC对GCN进行了简化,通过反复消除GCN层之间的非线性变换并将得到的函数折叠成一个线性变换来减少GCN的额外复杂度。实验结果表明,这些简化操作并不会对许多下游应用的准确性产生负面影响。本文将结合公式推导手把手教大家构建基于Tensorflow的SGC模型。

系列教程《GNN-algorithms》


本文为系列教程《GNN-algorithms》中的内容,该系列教程不仅会深入介绍GNN的理论基础,还结合了TensorFlow GNN框架tf_geometric对各种GNN模型(GCN、GAT、GIN、SAGPool等)的实现进行了详细地介绍。本系列教程作者王有泽(https://github.com/wangyouze)也是tf_geometric框架的贡献者之一。

系列教程《GNN-algorithms》Github链接: * https://github.com/wangyouze/GNN-algorithms

TensorFlow GNN框架tf_geometric的Github链接: * https://github.com/CrawlScript/tf_geometric

前言


GCN作为一种经典的图神经网络模型,已经成为了诸多新手入门图神经网络的必学模型,而近些年对于GCN的各种魔改也层出不穷。本着爱屋及乌的目的,本教程将教你如何用Tensorflow构建GCN的变体SGC模型进行节点分类任务。完整的代码可在Github中下载: https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_sgc.py

SGC模型简介


SGC是GCN的变体之一,全称Simplifying Graph Convolutional Networks,论文发表在ICML2019上。相比于GCN,SGC通过消除GCN层之间的非线性,将非线性的GCN转变为一个简单的线性模型,减小了模型复杂度,在很多任务上比GCN以及其他GNN模型更加高效。

下面我们对GCN与SGC在节点分类任务上的异同点进行对比:

GCN做节点分类任务时:

  1. 对邻接矩阵进行归一化并且添加自环:

  1. 对输入的节点特征进行平滑处理:

  2. 对接点特征进行非线性转换

所以对于节点分类任务,一个K层的GCN可以表示为:

SGC移除了GCN每层之间的激活函数,将原先的非线性变换简化为线性变换,因此SGC在做节点分类任务时:

  1. 对邻接矩阵进行归一化并且添加自环:

  2. 对输入的节点特征进行平滑处理:

  3. 对节点特征进行线性转换

所以对于节点分类任务,一个K层的SGC可以表示为:

简写为:

SGC中的 可以提前计算,大大减少了计算量。 通过以上的对比,我们可以清晰的认识到二者之间的异同点,下面我们将基于tf_geometric实现SGC模型(SGC已经集成到GNN库tf_geometric中)。 教程中完整的代码链接: * demo_sgc.py:https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_sgc.py * 论文地址:https://arxiv.org/pdf/1902.07153.pdf

教程目录


开发环境 * SGC 的实现 * 模型构建 * SGC训练 * SGC评估

开发环境


操作系统: Windows / Linux / Mac OS

Python 版本: >= 3.5 * 依赖包:

tf_geometric(一个基于Tensorflow的GNN库)

根据你的环境(是否已安装TensorFlow、是否需要GPU)从下面选择一条安装命令即可一键安装所有Python依赖:``` pip install -U tf_geometric # 这会使用你自带的TensorFlow,注意你需要tensorflow/tensorflow-gpu >= 1.14.0 or >= 2.0.0b1

pip install -U tf_geometric[tf1-cpu] # 这会自动安装TensorFlow 1.x CPU版

pip install -U tf_geometric[tf1-gpu] # 这会自动安装TensorFlow 1.x GPU版

pip install -U tf_geometric[tf2-cpu] # 这会自动安装TensorFlow 2.x CPU版

pip install -U tf_geometric[tf2-gpu] # 这会自动安装TensorFlow 2.x GPU版


教程使用的核心库是tf_geometric,一个基于TensorFlow的GNN库。tf_geometric的详细教程可以在其Github主页上查询:
* 
https://github.com/CrawlScript/tf_geometric



**SGC的实现**

***


对图的邻接矩阵添加自环,进行对称归一化处理:
![](https://cdn.zhuanzhi.ai/vfiles/16ac36198a83fead03e861140736a39e)

updated_edge_index, normed_edge_weight = gcn_norm_edge(edge_index, x.shape[0], edge_weight,renorm, improved, cache)


计算,扩大模型的感受野。aggregator_neighbor聚合一阶邻居节点信息,迭代聚合K次,相当于聚合了距离中心节点k-hop的邻域信息。```
 h = x
 for _ in range(K):
 h = aggregate_neighbors(
 h,
 updated_edge_index,
 normed_edge_weight,
 gcn_mapper,
 sum_reducer,
 identity_updater
 )

对上面的聚合结果进行线性变换,即计算:

然后返回计算结果:

h = h @ kernel

 if bias is not None:
 h += bias
 return h

以上我们实现了SGC中的

的部分,现在我们只需要在模型的最后一层的输出上添加softmax激活函数(为了获得概率输出)就可以进行节点分类了。 模型构建


导入相关库 本教程使用的核心库是tf_geometric,我们用它来进行图数据导入、图数据预处理及图神经网络构建。SGC的具体实现已经在上面详细介绍,另外我们后面会使用keras.metrics.Accuracy评估模型性能。 * * * * * * *

        # coding=utf-8`import os``os.environ["CUDA_VISIBLE_DEVICES"] = "0"``import tensorflow as tf``from tensorflow import keras``from tf_geometric.layers.conv.sgc import SGC``from tf_geometric.datasets.cora import CoraDataset`

使用tf_geometric自带的图结构数据接口加载Cora数据集:``` graph, (train_index, valid_index, test_index) = CoraDataset().load_data()


* 
定义模型,这里我们只聚合2阶邻域内的信息。```
model = SGC(num_classes, k=2)

SGC训练


模型的训练与其他基于Tensorflow框架的模型训练基本一致,主要步骤有定义优化器,计算误差与梯度,反向传播等。SGC模型K阶的计算结果由softmax映射到(0,1)直接进行多分类任务。在每一个step结束的时候,我们分别计算模型在验证集和测试集上的准确率。``` optimizer = tf.keras.optimizers.Adam(learning_rate=0.2) for step in range(1,101): with tf.GradientTape() as tape: logits = model([graph.x, graph.edge_index, graph.edge_weight], cache=graph.cache) logits = tf.nn.log_softmax(logits,axis=1) loss = compute_loss(logits, train_index, tape.watched_variables())

vars = tape.watched_variables() grads = tape.gradient(loss, vars) optimizer.apply_gradients(zip(grads, vars))

valid_acc = evaluate(valid_index) test_acc = evaluate(test_index)

print("step = {}\tloss = {}\tvalid_acc = {}\ttest_acc = {}".format(step, loss, valid_acc, test_acc))


* 
用交叉熵损失函数计算模型损失。注意在加载Cora数据集的时候,返回值是整个图数据以及相应的train_mask,valid_mask,test_mask。SGC在训练的时候的输入是整个Graph,在计算损失的时候通过train_mask来计算模型在训练集上的迭代损失。因此,此时传入的mask_index是train_index。由于是多分类任务,需要将节点的标签转换为one-hot向量以便于模型输出的结果维度对应。由于图神经模型在小数据集上很容易就会疯狂拟合数据,所以这里用L2正则化缓解过拟合。```
def compute_loss(logits, mask_index, vars):
 masked_logits = tf.gather(logits, mask_index)
 masked_labels = tf.gather(graph.y, mask_index)

 losses = tf.nn.softmax_cross_entropy_with_logits(
 logits=masked_logits,
 labels=tf.one_hot(masked_labels, depth=num_classes)
 )

 kernel_vals = [var for var in vars if "kernel" in var.name]
 l2_losses = [tf.nn.l2_loss(kernel_var) for kernel_var in kernel_vals]

 return tf.reduce_mean(losses) + tf.add_n(l2_losses) * 5e-5

SGC评估


在评估模型性能的时候我们只需传入valid_mask或者test_mask,通过tf.gather函数就可以拿出验证集或测试集在模型上的预测结果与真实标签,用keras自带的keras.metrics.Accuracy计算准确率。``` def evaluate(mask): logits = forward(graph) logits = tf.nn.log_softmax(logits, axis=1) masked_logits = tf.gather(logits, mask) masked_labels = tf.gather(graph.y, mask)

y_pred = tf.argmax(masked_logits, axis=-1, output_type=tf.int32) accuracy_m = keras.metrics.Accuracy() accuracy_m.update_state(masked_labels, y_pred) return accuracy_m.result().numpy()




**运行结果**

***


sgc在100轮训练后在测试集上的准确率最高为0.81
    step = 1 loss = 1.9458770751953125 valid_acc = 0.5120000243186951 test_acc = 0.5389999747276306

step = 2 loss = 1.8324840068817139 valid_acc = 0.722000002861023 test_acc = 0.7350000143051147 step = 3 loss = 1.7052000761032104 valid_acc = 0.4740000069141388 test_acc = 0.4729999899864197 step = 4 loss = 1.6184687614440918 valid_acc = 0.5580000281333923 test_acc = 0.5360000133514404 ... step = 97 loss = 0.9681359529495239 valid_acc = 0.7919999957084656 test_acc = 0.8130000233650208 step = 98 loss = 0.9678354263305664 valid_acc = 0.7919999957084656 test_acc = 0.8100000023841858 step = 99 loss = 0.9675441384315491 valid_acc = 0.7919999957084656 test_acc = 0.8100000023841858 step = 100 loss = 0.967261791229248 valid_acc = 0.7919999957084656 test_acc = 0.8100000023841858




**完整代码**

***


教程中的完整代码链接:
* 
demo_sgc.py:https://github.com/CrawlScript/tf_geometric/blob/master/demo/demo_sgc.py



本教程(属于系列教程**《GNN-algorithms》**)Github链接:
* 
https://github.com/wangyouze/GNN-algorithms



成为VIP会员查看完整内容
0
29

【导读】利用Chebyshev多项式拟合图卷积核应该是GCN中比较普遍的应用方法。Chebyshev多项式核主要解决了两个问题:1.经过公式推导变换不再需要特征向量的分解。2.通过Chebyshev的迭代定义降低了计算复杂度。本文将结合公式推导详细介绍基于tensorflow的ChebyNet实现。

系列教程《GNN-algorithms》

本文为系列教程《GNN-algorithms》中的内容,该系列教程不仅会深入介绍GNN的理论基础,还结合了TensorFlow GNN框架tf_geometric对各种GNN模型(GCN、GAT、GIN、SAGPool等)的实现进行了详细地介绍。本系列教程作者王有泽(https://github.com/wangyouze)也是tf_geometric框架的贡献者之一。

系列教程《GNN-algorithms》Github链接: https://github.com/wangyouze/GNN-algorithms
TensorFlow GNN框架tf_geometric的Github链接: https://github.com/CrawlScript/tf_geometric

成为VIP会员查看完整内容
0
40

【导读】图卷积网络(Graph Convolutional Networks)作为最近几年兴起的一种基于图结构的广义神经网络,因为其独特的计算能力,受到了学术界和工业界的关注与研究。传统深度学习模型如 LSTM 和 CNN在欧式空间中表现不俗,却无法直接应用在非欧式数据上。为此,研究者们通过引入图论中抽象意义上的“图”来表示非欧式空间中的结构化数据,并通过图卷积网络来提取(graph)的拓扑结构,以挖掘蕴藏在图结构数据中的深层次信息。本文结合公式推导详细介绍了图卷积网络(GCN)的前世今生,有助于大家深入了解GCN。

系列教程《GNN-algorithms》

本文为系列教程《GNN-algorithms》的内容,该系列教程不仅会深入介绍GNN的理论基础,还结合了TensorFlow GNN框架tf_geometric对各种GNN模型(GCN、GAT、GIN、SAGPool等)的实现进行了详细地介绍。本系列教程作者王有泽(https://github.com/wangyouze)也是tf_geometric框架的贡献者之一。

系列教程《GNN-algorithms》Github链接: https://github.com/wangyouze/GNN-algorithms
TensorFlow GNN框架tf_geometric的Github链接: https://github.com/CrawlScript/tf_geometric

参考文献:

成为VIP会员查看完整内容
0
102
小贴士
相关主题
相关资讯
GraphSAGE: GCN落地必读论文
AI100
26+阅读 · 2019年8月15日
GraphSAGE:我寻思GCN也没我牛逼
极市平台
10+阅读 · 2019年8月12日
PyTorch & PyTorch Geometric图神经网络(GNN)实战
专知
59+阅读 · 2019年6月1日
图注意力网络
科技创新与创业
29+阅读 · 2017年11月22日
图上的归纳表示学习
科技创新与创业
15+阅读 · 2017年11月9日
Representation Learning on Network 网络表示学习笔记
全球人工智能
3+阅读 · 2017年9月30日
相关论文
A Collective Learning Framework to Boost GNN Expressiveness
Mengyue Hang,Jennifer Neville,Bruno Ribeiro
19+阅读 · 2020年3月26日
AdarGCN: Adaptive Aggregation GCN for Few-Shot Learning
Jianhong Zhang,Manli Zhang,Zhiwu Lu,Tao Xiang,Jirong Wen
56+阅读 · 2020年2月28日
Wenwu Zhu,Xin Wang,Peng Cui
19+阅读 · 2020年1月2日
Optimization for deep learning: theory and algorithms
Ruoyu Sun
80+阅读 · 2019年12月19日
Yue Yu,Jie Chen,Tian Gao,Mo Yu
6+阅读 · 2019年4月22日
One-Shot Federated Learning
Neel Guha,Ameet Talwalkar,Virginia Smith
7+阅读 · 2019年3月5日
Yong Wang,Xiao-Ming Wu,Qimai Li,Jiatao Gu,Wangmeng Xiang,Lei Zhang,Victor O. K. Li
9+阅读 · 2018年7月8日
William L. Hamilton,Rex Ying,Jure Leskovec
3+阅读 · 2018年4月10日
Bryan Perozzi,Rami Al-Rfou,Steven Skiena
7+阅读 · 2014年6月27日
Top