网站侧栏软件排行榜怎么做的,手机网站建设 jz.woonl,仿美空网 wordpress,wordpress广告代码是什么意思文章目录 前言细节四#xff1a;卷积核介绍图卷积核初代目图卷积核二代目契比雪夫多项式例子小结 GCN公式推导 实验设置和结果分析数据集节点分类任务消息传递方式比较运行效率 总结关键点创新点启发点 代码复现train.pyutil.pymodel.pylayer.py 作业 前言
本课程来自深度之眼… 文章目录 前言细节四卷积核介绍图卷积核初代目图卷积核二代目契比雪夫多项式例子小结 GCN公式推导 实验设置和结果分析数据集节点分类任务消息传递方式比较运行效率 总结关键点创新点启发点 代码复现train.pyutil.pymodel.pylayer.py 作业 前言
本课程来自深度之眼部分截图来自课程视频。 文章标题Semi-Supervised Classification with Graph Convolutional Networks 图卷积神经网络的半监督分类GCN 作者Thomas N.KipfMax Welling 单位University of Amsterdam 发表会议及时间ICLR 2017 公式输入请参考在线Latex公式 之前写的有点问题重新编辑发现CSDN对文章长度做了限制只能切开变成上下两篇了
细节四卷积核介绍
图卷积核初代目
这节通过几篇图卷积相关的论文来讲解图卷积核的演变过程。 Spectral Networks and Deep Locally Connected Networks on Graphs 早期的图卷积的论文其思想是利用公式14其中f是图的输入h是卷积核对这两货分别进行频域的变化后做elementwise点乘然后再逆变换回图信号。 ( f ∗ h ) G U ( ( U T h ) ⊙ ( U T f ) ) (14) (f*h)_GU((U^Th)\odot (U^Tf))\tag{14} (f∗h)GU((UTh)⊙(UTf))(14) 写出计算图某个节点表征的公式 y o u t p u t σ ( U g θ ( Λ ) U T x ) (18) y_{output}\sigma(Ug_{\theta}(\Lambda)U^Tx)\tag{18} youtputσ(Ugθ(Λ)UTx)(18) 其中 g θ ( Λ ) g_{\theta}(\Lambda) gθ(Λ)就是公式15中的对角线矩阵这里写成 g θ ( Λ ) ( θ 1 ⋱ θ n ) (19) g_{\theta}(\Lambda)\begin{pmatrix} \theta_1\\ \ddots\\ \theta_n\\ \end{pmatrix}\tag{19} gθ(Λ) θ1⋱θn (19) 由于公式18要计算特征向量 U U U要对拉普拉斯矩阵进行分解另外加上矩阵的乘法计算复杂度比较高公式18中x是节点的特征整个公式中 θ \theta θ是要学习的参数共有n个整个过程针对所有节点没有利用邻居信息没有localization。 Convolutional Neural Networks on Graphs with Fast Localized Spectral Filtering 这篇论文中用另外一种形式来表示公式18中的 g θ ( Λ ) g_{\theta}(\Lambda) gθ(Λ) g θ ( Λ ) ( ∑ j 0 K − 1 α j λ 1 j ⋱ ∑ j 0 K − 1 α j λ n j ) ∑ j 0 K − 1 α j Λ j (20) g_{\theta}(\Lambda)\begin{pmatrix} \sum_{j0}^{K-1}\alpha_j\lambda_1^j\\ \ddots\\ \sum_{j0}^{K-1}\alpha_j\lambda_n^j\\ \end{pmatrix}\sum_{j0}^{K-1}\alpha_j\Lambda^j\tag{20} gθ(Λ) ∑j0K−1αjλ1j⋱∑j0K−1αjλnj j0∑K−1αjΛj(20) 将公式20的卷积核代入18只看 U g θ ( Λ ) U T Ug_{\theta}(\Lambda)U^T Ugθ(Λ)UT这个部分 U g θ ( Λ ) U T U ∑ j 0 K − 1 α j Λ j ( Λ ) U T ∑ j 0 K − 1 α j U Λ j U T ∑ j 0 K − 1 α j L j (21) Ug_{\theta}(\Lambda)U^TU\sum_{j0}^{K-1}\alpha_j\Lambda^j(\Lambda)U^T\sum_{j0}^{K-1}\alpha_jU\Lambda^jU^T\sum_{j0}^{K-1}\alpha_jL^j\tag{21} Ugθ(Λ)UTUj0∑K−1αjΛj(Λ)UTj0∑K−1αjUΛjUTj0∑K−1αjLj(21) 可以看到公式21的最后形态直接是拉普拉斯矩阵没有特征向量U也就意味不需要矩阵的特征分解。下面看下公式21的简单证明过程 因为 U T U E U^TUE UTUE L 2 L L U Λ U T U Λ U T U Λ 2 U T L^2LLU\Lambda U^TU\Lambda U^TU\Lambda^2U^T L2LLUΛUTUΛUTUΛ2UT 同理 L 3 U Λ 3 U T L^3U\Lambda^3U^T L3UΛ3UT ⋮ \vdots ⋮ L n U Λ n U T L^nU\Lambda^nU^T LnUΛnUT 因此公式21中的 U Λ j U T L j U\Lambda^jU^TL^j UΛjUTLj 因此公式18变成了 y o u t p u t σ ( ∑ j 0 K − 1 α j L j x ) y_{output}\sigma(\sum_{j0}^{K-1}\alpha_jL^jx) youtputσ(j0∑K−1αjLjx) 从推导过程我们可以知道这个卷积核不用特征分解因此计算复杂度低参数量从n变成了K个Kn而且比较重要的一点就是 L j L^j Lj相当于邻居矩阵 A j A^j Aj当j1时表示节点的直接邻居信息j2时表示1跳邻居当jn时表示的是n-1跳邻居不再针对所有节点而是只对j-1跳邻居进行计算实现了localization。
图卷积核二代目
还是同样的文章里面 Convolutional Neural Networks on Graphs with Fast Localized Spectral Filtering 介绍了契比雪夫Chebyshev多项式卷积核也是本文GCN用的卷积核。 同样的是将中的 g θ ( Λ ) g_{\theta}(\Lambda) gθ(Λ)换成另外一种形式即替换为契比雪夫多项式 g θ ( Λ ) ∑ j 0 K − 1 β k T k ( Λ ~ ) g_{\theta}(\Lambda)\sum_{j0}^{K-1}\beta_kT_k(\tilde\Lambda) gθ(Λ)j0∑K−1βkTk(Λ~) 其中 β k \beta_k βk是我们要学习的参数后面则是契比雪夫多项式特征值矩阵做为输入 T k ( x ) cos ( k ⋅ arccos ( x ) ) (22) T_k(x)\cos(k\cdot\arccos(x))\tag{22} Tk(x)cos(k⋅arccos(x))(22) 公式22中 a r c c o s ( x ) arccos(x) arccos(x)的定义域为[-1,1]而拉普拉斯的特征值取值范围是大于0的实数因此要将拉普拉斯的特征值映射到[-1,1]上 先将 Λ λ m a x \cfrac{\Lambda}{\lambda_{max}} λmaxΛ这样特征值取值范围就变成了[0,1]然后使得 Λ ~ 2 Λ λ m a x − I \tilde \Lambda2\cfrac{\Lambda}{\lambda_{max}}-I Λ~2λmaxΛ−I 特征值取值范围就变成了 2 × [ 0 , 1 ] − 1 2\times [0,1]-1 2×[0,1]−1变成了[-1,1]。 这里是对特征向量做操作我们是要避免矩阵分解的因此如果我们直接对拉普拉斯矩阵做上面的缩放操作也会使得缩放后的矩阵的特征向量的取值范围变成了[-1,1] L ~ 2 L λ m a x − I \tilde L2\cfrac{L}{\lambda_{max}}-I L~2λmaxL−I 说明 这里虽然还是涉及到了 λ m a x \lambda_{max} λmax但是在线代里面求最大那个特征向量 λ m a x \lambda_{max} λmax可以不涉及特征分解。 契比雪夫多项式具有如下性质 T k ( L ~ ) 2 L ~ T k − 1 ( L ~ ) − T k − 2 ( L ~ ) T_k(\tilde L)2\tilde LT_{k-1}(\tilde L)-T_{k-2}(\tilde L) Tk(L~)2L~Tk−1(L~)−Tk−2(L~) T 0 ( L ~ ) I , T 1 ( L ~ ) L ~ (23) T_0(\tilde L)I,T_1(\tilde L)\tilde L\tag{23} T0(L~)I,T1(L~)L~(23) GCN其实就是用了公式23契比雪夫多项式的第0项和第1项 接下来继续看 y o u t p u t σ ( U g θ ( Λ ) U T x ) σ ( U ∑ j 0 K − 1 β k T k ( Λ ~ ) U T x ) \begin{aligned}y_{output}\sigma(Ug_{\theta}(\Lambda)U^Tx)\\ \sigma(U\sum_{j0}^{K-1}\beta_kT_k(\tilde\Lambda)U^Tx)\end{aligned} youtputσ(Ugθ(Λ)UTx)σ(Uj0∑K−1βkTk(Λ~)UTx) 由于契比雪夫多项式是用特征值对角矩阵进行的输入因此可以把两边的矩阵放到里面一起操作 y o u t p u t σ ( ∑ j 0 K − 1 β k T k ( U Λ ~ U T ) x ) y_{output}\sigma(\sum_{j0}^{K-1}\beta_kT_k(U\tilde\Lambda U^T)x) youtputσ(j0∑K−1βkTk(UΛ~UT)x) U Λ ~ U T U\tilde\Lambda U^T UΛ~UT这项在上面证明过就是等于 L ~ \tilde L L~的 因此最后的推导结果为 y o u t p u t σ ( ∑ j 0 K − 1 β k T k ( L ~ ) x ) y_{output}\sigma(\sum_{j0}^{K-1}\beta_kT_k(\tilde L)x) youtputσ(j0∑K−1βkTk(L~)x)
契比雪夫多项式例子
假设有如下无向图这个图上面也有 当k0时根据公式23 ∑ j 0 K − 1 β k T k ( L ~ ) β 0 T 0 ( L ~ ) β 0 I β 0 \sum_{j0}^{K-1}\beta_kT_k(\tilde L)\beta_0T_0(\tilde L)\beta_0I\beta_0 j0∑K−1βkTk(L~)β0T0(L~)β0Iβ0 那么这个时候的卷积核为 ( β 0 0 0 0 0 0 0 β 0 0 0 0 0 0 0 0 β 0 0 0 0 0 0 0 β 0 0 0 0 0 0 0 β 0 0 0 0 0 0 0 β 0 ) \begin{pmatrix} \beta_0 0 0 0 0 0 \\ 0 \beta_0 0 0 0 0 \\ 0 0 0 \beta_0 0 0 0 \\ 0 0 0 \beta_00 0 \\ 0 00 0 \beta_00 \\ 0 0 0 0 0 \beta_0 \end{pmatrix} β0000000β00000000β0000000β0000000β0000000β0 当k1时根据公式23 ∑ j 0 K − 1 β k T k ( L ~ ) β 0 T 0 ( L ~ ) β 1 T 1 ( L ~ ) β 0 β 1 L ~ \sum_{j0}^{K-1}\beta_kT_k(\tilde L)\beta_0T_0(\tilde L)\beta_1T_1(\tilde L)\beta_0\beta_1\tilde L j0∑K−1βkTk(L~)β0T0(L~)β1T1(L~)β0β1L~ L I − D − 0.5 A D − 0.5 LI-D^{-0.5}AD^{-0.5} LI−D−0.5AD−0.5 度矩阵 D ( 2 0 0 0 0 0 0 3 0 0 0 0 0 0 2 0 0 0 0 0 0 3 0 0 0 0 0 0 3 0 0 0 0 0 0 1 ) D\begin{pmatrix} 2 0 00 0 0\\ 0 3 00 0 0 \\ 0 0 20 0 0 \\ 0 0 03 0 0 \\ 0 0 00 3 0\\ 0 0 00 0 1 \end{pmatrix} D 200000030000002000000300000030000001 邻接矩阵 A ( 0 1 0 0 1 0 1 0 1 0 1 0 0 1 0 1 0 0 0 0 1 0 1 1 1 1 0 1 0 0 0 0 0 1 0 0 ) A\begin{pmatrix} 0 1 00 1 0\\ 1 0 10 1 0 \\ 0 1 01 0 0 \\ 0 0 10 1 1 \\ 1 1 01 0 0\\ 0 0 01 0 0 \end{pmatrix} A 010010101010010100001011110100000100 D − 0.5 A D − 0.5 ( 0 1 / 2 0 0 1 / 2 0 1 / 3 0 1 / 3 0 1 / 3 0 0 1 / 2 0 1 / 2 0 0 0 0 1 / 3 0 1 / 3 1 / 3 1 / 3 1 / 3 0 1 / 3 0 0 0 0 0 1 0 0 ) D^{-0.5}AD^{-0.5}\begin{pmatrix} 0 1/2 00 1/2 0\\ 1/3 0 1/30 1/3 0 \\ 0 1/2 01/2 0 0 \\ 0 0 1/30 1/3 1/3 \\ 1/3 1/3 01/3 0 0\\ 0 0 01 0 0 \end{pmatrix} D−0.5AD−0.5 01/3001/301/201/201/3001/301/300001/201/311/21/301/3000001/300 L I − D − 0.5 A D − 0.5 ( 1 − 1 / 2 0 0 − 1 / 2 0 − 1 / 3 1 − 1 / 3 0 − 1 / 3 0 0 − 1 / 2 1 − 1 / 2 0 0 0 0 − 1 / 3 1 − 1 / 3 − 1 / 3 − 1 / 3 − 1 / 3 0 − 1 / 3 1 0 0 0 0 − 1 0 1 ) LI-D^{-0.5}AD^{-0.5}\begin{pmatrix} 1 -1/2 00 -1/2 0\\ -1/3 1 -1/30 -1/3 0 \\ 0 -1/2 1-1/2 0 0 \\ 0 0 -1/31 -1/3 -1/3 \\ -1/3 -1/3 0-1/3 1 0\\ 0 0 0-1 0 1 \end{pmatrix} LI−D−0.5AD−0.5 1−1/300−1/30−1/21−1/20−1/300−1/31−1/30000−1/21−1/3−1−1/2−1/30−1/310000−1/301 然后按照公式将 L L L变成 L ~ \tilde L L~ L ~ 2 L λ m a x − I \tilde L2\cfrac{L}{\lambda_{max}}-I L~2λmaxL−I L的最大特征值 λ m a x \lambda_{max} λmax约为1.87667https://zs.symbolab.com/ 可以看到当k1的时候实际上就是加入了图邻接一跳的邻居信息。
小结
1.切比雪夫多项式的递归定义 { T 0 ( x ) 1 T 1 ( x ) x T n 1 ( x ) 2 x T n ( x ) − T n − 1 ( x ) \begin{cases} T_0(x)1\\ T_1(x)x \\ T_{n1}(x)2xT_n(x)-T_{n-1}(x) \end{cases} ⎩ ⎨ ⎧T0(x)1T1(x)xTn1(x)2xTn(x)−Tn−1(x) 2.切比雪夫卷积核 { g θ ′ ( Λ ) ≈ ∑ k 0 K − 1 θ k ′ T k ( Λ ~ ) Λ ~ 2 λ m a x Λ − I N \begin{cases} g_{\theta}(\Lambda)\approx\sum_{k0}^{K-1}\theta_kT_k(\tilde\Lambda)\\ \tilde\Lambda\cfrac{2}{\lambda_{max}}\Lambda-I_N \end{cases} ⎩ ⎨ ⎧gθ′(Λ)≈∑k0K−1θk′Tk(Λ~)Λ~λmax2Λ−IN 3.切比雪夫图卷积 { g θ ′ ⋆ x ≈ ∑ k 0 K − 1 θ k ′ T k ( L ~ ) x L ~ 2 λ m a x L − I N \begin{cases} g_{\theta}\star x\approx\sum_{k0}^{K-1}\theta_kT_k(\tilde L)x\\ \tilde L\cfrac{2}{\lambda_{max}}L-I_N \end{cases} ⎩ ⎨ ⎧gθ′⋆x≈∑k0K−1θk′Tk(L~)xL~λmax2L−IN
GCN公式推导
铺垫了这么多终于到了正题原文对应第二节前面几个公式都用的切比雪夫的不用多说从公式6看 g θ ′ ⋆ x ≈ ∑ k 0 K − 1 θ k ′ T k ( L ~ ) x g_{\theta}\star x\approx\sum_{k0}^{K-1}\theta_kT_k(\tilde L)x gθ′⋆x≈k0∑K−1θk′Tk(L~)x 如果只考虑k0和k1 g θ ′ ⋆ x ≈ θ 0 ′ x θ 1 ′ L ~ x θ 0 ′ x θ 1 ′ ( 2 λ m a x L − I N ) x g_{\theta}\star x\approx\theta_0x\theta_1\tilde Lx\theta_0x\theta_1(\cfrac{2}{\lambda_{max}}L-I_N)x gθ′⋆x≈θ0′xθ1′L~xθ0′xθ1′(λmax2L−IN)x 论文中提到GCN的 λ m a x ≈ 2 \lambda_{max}\approx2 λmax≈2代入上面可以得为什么是2看这里老师提供了链接https://zhuanlan.zhihu.com/p/65447367 g θ ′ ⋆ x ≈ θ 0 ′ x θ 1 ′ ( L − I N ) x g_{\theta}\star x\approx\theta_0x\theta_1(L-I_N)x gθ′⋆x≈θ0′xθ1′(L−IN)x 论文里面设定的拉普拉斯矩阵 L I N − D − 1 2 A D − 1 2 LI_N-D^{-\frac{1}{2}}AD^{-\frac{1}{2}} LIN−D−21AD−21代入上面得原文公式6 g θ ′ ⋆ x ≈ θ 0 ′ x − θ 1 ′ D − 1 2 A D − 1 2 x g_{\theta}\star x\approx\theta_0x-\theta_1D^{-\frac{1}{2}}AD^{-\frac{1}{2}}x gθ′⋆x≈θ0′x−θ1′D−21AD−21x 这个时候两个参数不一样而且二者没有约束一个是参数量大二是容易过拟合因此都设置成一样的。In practice, it can be beneficial to constrain the number of parameters further to address overfitting and to minimize the number of operations (such as matrix multiplications) per layer. 设置 θ 0 ′ − θ 1 ′ θ \theta_0-\theta_1\theta θ0′−θ1′θ得到公式7 g θ ′ ⋆ x ≈ θ ( I N D − 1 2 A D − 1 2 ) x g_{\theta}\star x\approx\theta(I_ND^{-\frac{1}{2}}AD^{-\frac{1}{2}})x gθ′⋆x≈θ(IND−21AD−21)x 由于 I N D − 1 2 A D − 1 2 I_ND^{-\frac{1}{2}}AD^{-\frac{1}{2}} IND−21AD−21的特征值取值范围是[0,2]这个范围不好因为这样会造成梯度消失或者爆炸这也是为什么很多NN网络要加normalization操作因此原文加了一个renormalization trick I N D − 1 2 A D − 1 2 → D ~ − 1 2 A ~ D ~ − 1 2 I_ND^{-\frac{1}{2}}AD^{-\frac{1}{2}}\rightarrow \tilde D^{-\frac{1}{2}}\tilde A\tilde D^{-\frac{1}{2}} IND−21AD−21→D~−21A~D~−21 其中 A ~ A I N , D ~ i i ∑ j A ~ i j \tilde AAI_N,\tilde D_{ii}\sum_j\tilde A_{ij} A~AIN,D~iij∑A~ij 因此得到了最后的公式8 Z D ~ − 1 2 A ~ D ~ − 1 2 X Θ Z\tilde D^{-\frac{1}{2}}\tilde A\tilde D^{-\frac{1}{2}}X\Theta ZD~−21A~D~−21XΘ
实验设置和结果分析 数据集 由于是半监督学习最后一列是带有label的节点的比例。 Cora这个应该比较熟悉了 最后一个是知识图谱的数据集是bipartite graph二分图
节点分类任务 消息传递方式比较 运行效率 总结
关键点
• 模型结构 • 图的拉普拉斯矩阵 • Chebyshev多项式卷积核 • 频域分析
创新点
• 空域和频域联系 • 1st-Chebyshev卷积核实用就是k0和k1的情况 • 半监督框架 layer-wise GCN
启发点
• GCN实用化的开始1st-ChebyshevGCN • 将chebyshev多项式卷积核截断近似为K1 • 演化出很多GCN为基础的模型如RGCN等 • 半监督框架的消息传递策略融合了点的特征和图结构 • 与GNN常用框架之间的联系 • GCN、GAT、GraphSAGE都是非常重要的模型也是经典baseline
代码复现 https://github.com/tkipf/pygcn 数据集就用cora。
train.py
from __future__ import division
from __future__ import print_functionimport time
import argparse
import numpy as npimport torch
import torch.nn.functional as F
import torch.optim as optimfrom pygcn.utils import load_data, accuracy
from pygcn.models import GCN# Training settings
parser argparse.ArgumentParser()
# 禁用CUDA训练
parser.add_argument(--no-cuda, actionstore_true, defaultFalse,helpDisables CUDA training.)
#是否在训练过程中进行验证
parser.add_argument(--fastmode, actionstore_true, defaultFalse,helpValidate during training pass.)
#随机种子
parser.add_argument(--seed, typeint, default42, helpRandom seed.)
#epoch数量
parser.add_argument(--epochs, typeint, default200,helpNumber of epochs to train.)
#学习率
parser.add_argument(--lr, typefloat, default0.01,helpInitial learning rate.)
#权重衰减
parser.add_argument(--weight_decay, typefloat, default5e-4,helpWeight decay (L2 loss on parameters).)
#隐藏层大小
parser.add_argument(--hidden, typeint, default16,helpNumber of hidden units.)
#dropout参数
parser.add_argument(--dropout, typefloat, default0.5,helpDropout rate (1 - keep probability).)args parser.parse_args()
#jupyter要用下面这句
#argsparser.parse_args(args[])
args.cuda not args.no_cuda and torch.cuda.is_available()#产生随机数种子
np.random.seed(args.seed)
torch.manual_seed(args.seed)
if args.cuda:torch.cuda.manual_seed(args.seed)# Load data
#与GAT差不多
#加载数据
#adj:adj样本关系的对称邻接矩阵的稀疏张量
#features样本特征张量
#labels样本标签
#idx train训练集索引列表
#idx val验证集索引列表
#idx_test测/试集索引列表
adj, features, labels, idx_train, idx_val, idx_test load_data()# Model and optimizer
#模型和优化器# GCN模型
# nfeat输入单元数shape[1]表示特征矩阵的维度数列数
# nhid中间层单元数量
# nclass输出单元数即样本标签数样本标签最大值1cora里面是7
# dropout参数
model GCN(nfeatfeatures.shape[1],nhidargs.hidden,nclasslabels.max().item() 1,dropoutargs.dropout)# 构造一个优化器对象0ptimizer用来保存当前的状态并能够根据计算得到的梯度来更新参数
# Adam优化器
# 1r学习率
# weight_decay权重衰减L2惩罚
optimizer optim.Adam(model.parameters(),lrargs.lr, weight_decayargs.weight_decay)#gpu执行下面代码
if args.cuda:model.cuda()features features.cuda()adj adj.cuda()labels labels.cuda()idx_train idx_train.cuda()idx_val idx_val.cuda()idx_test idx_test.cuda()def train(epoch):#取当前时间t time.time()#train的时候使用dropout测试的时候不使用dropout#pytorch里面eval()固定整个网络参数没有dropoutmodel.train()#把梯度置零也就是把loss关于weight的导数变成0optimizer.zero_grad()#执行GCN中的forward前向传播output model(features, adj)#最大似然/log似然损失函数idx_train是1400~139#nll loss:negative log likelihood loss #https://www.cnblogs.com/marsggbo/p/10401215.htmlloss_train F.nll_loss(output[idx_train], labels[idx_train])#计算准确率acc_train accuracy(output[idx_train], labels[idx_train])#反向传播loss_train.backward()#梯度下降optimizer.step()if not args.fastmode:# Evaluate validation set performance separately,# deactivates dropout during validation run.model.eval()#EVAL不启用bn和dropoutoutput model(features, adj)#EVAL的最大似然/log似然损失函数idxval是300200-499loss_val F.nll_loss(output[idx_val], labels[idx_val])#EVAL的准确率acc_val accuracy(output[idx_val], labels[idx_val])#正在送代的epoch数#训练集损失函数值#训练集准确率#验证集损失函数值#验证集准确率#运行时间print(Epoch: {:04d}.format(epoch1),loss_train: {:.4f}.format(loss_train.item()),acc_train: {:.4f}.format(acc_train.item()),loss_val: {:.4f}.format(loss_val.item()),acc_val: {:.4f}.format(acc_val.item()),time: {:.4f}s.format(time.time() - t))#测试参考EVAL注释即可
def test():model.eval()output model(features, adj)loss_test F.nll_loss(output[idx_test], labels[idx_test])acc_test accuracy(output[idx_test], labels[idx_test])print(Test set results:,loss {:.4f}.format(loss_test.item()),accuracy {:.4f}.format(acc_test.item()))# Train model
t_total time.time()
#根据设置epoch数量进行训练
for epoch in range(args.epochs):train(epoch)
print(Optimization Finished!)
print(Total time elapsed: {:.4f}s.format(time.time() - t_total))# Testing
test()
util.py
import numpy as np
import scipy.sparse as sp
import torchdef encode_onehot(labels):classes set(labels)#identity创建方矩阵#字典key为labe1的值value为矩阵的每一行classes_dict {c: np.identity(len(classes))[i, :] for i, c inenumerate(classes)}#get函数得到字典key对应的value#map()会根据提供的函数对指定序列做映射#第一个参数 function 以参数序列中的每一个元素调用function函数返回包含每次 function 函数返回值的新列表#map(lambdax:x**2[12345])#output:[1491625]labels_onehot np.array(list(map(classes_dict.get, labels)),dtypenp.int32)return labels_onehotdef load_data(path./data/cora/, datasetcora):Load citation network dataset (cora only for now)print(Loading {} dataset....format(dataset))#content file的每一行的格式为paper_id word attributesclass_label#三个字段的index分别对应01-1-1#feature为第二列到倒数第二列labe1s为最后一列idx_features_labels np.genfromtxt({}{}.content.format(path, dataset),dtypenp.dtype(str))#储存为csr型稀疏矩阵features sp.csr_matrix(idx_features_labels[:, 1:-1], dtypenp.float32)labels encode_onehot(idx_features_labels[:, -1])# build graph# cites file的每一行格式为cited paper ID被引用文章编号citing paper ID引用文章的编号# 根据前面的contents与这里的cites创建图算出edges矩阵与adj矩阵idx np.array(idx_features_labels[:, 0], dtypenp.int32)# 由于文件中节点并非是按顺序排列的因此建立一个编号为0-(node_size-1)的哈希表idx_map# 哈希表中每一项为o1d id:number即节点id对应的编号为numberidx_map {j: i for i, j in enumerate(idx)}#edges_unordered为直接从边表文件中直接读取的结果是一个(edge_num2)的数组每一行表示一条边两个端点的idxedges_unordered np.genfromtxt({}{}.cites.format(path, dataset),dtypenp.int32)#flatten降维返回一维数组这里把是1*2的数组展开为1维数组#边的edges unordered中存储的是端点id要将每一项的o1d id换成编号number新编号#在idx_map中以idx作为键查找得到对应节点的编号reshape成与edges_unordered形状一样的数组edges np.array(list(map(idx_map.get, edges_unordered.flatten())),dtypenp.int32).reshape(edges_unordered.shape)#根据coo矩阵性质这一段的作用是网络有多少条边邻接矩阵就有多少个1#所以先创建一个长度为edge_num的全1数组每个1的填充位置就是一条边中两个端点的编号#即edges[:,0]edges[,1]矩阵的形状为(node_sizenode_size)adj sp.coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),shape(labels.shape[0], labels.shape[0]),dtypenp.float32)# build symmetric adjacency matrix# 对于无向图邻接矩阵是对称的。上一步得到的adj是按有向图构建的转换成无向图的邻接矩阵需要扩充成对称矩阵# 将i-j与j-i中权重最大的那个作为无向图的节点节点j的边权。# 原文NELL数据集用了这个操作# https://blog.csdn.net/Eric_1993/article/details/102907104adj adj adj.T.multiply(adj.T adj) - adj.multiply(adj.T adj)features normalize(features)# eye创建单位矩阵第一个参数为行数第二个为列数# normalize对应论文里A^D~-1A~这个公式# eye,对应公式A-AI_Nadj normalize(adj sp.eye(adj.shape[0]))#分别构建训练集、验证集、测试集并创建特征矩阵、标签向量和邻接矩阵的tensor用来做模型的输入idx_train range(140)idx_val range(200, 500)idx_test range(500, 1500)features torch.FloatTensor(np.array(features.todense()))labels torch.LongTensor(np.where(labels)[1])#邻接矩阵coo矩阵转为torch的稀疏tensor处理adj sparse_mx_to_torch_sparse_tensor(adj)idx_train torch.LongTensor(idx_train)idx_val torch.LongTensor(idx_val)idx_test torch.LongTensor(idx_test)return adj, features, labels, idx_train, idx_val, idx_testdef normalize(mx):Row-normalize sparse matrix#mx是邻接矩阵传进来之后先做行和得到每一个节点的度就是得到D这个矩阵rowsum np.array(mx.sum(1))#然后对度矩阵求倒数得到D^-1这里不是矩阵是数组r_inv np.power(rowsum, -1).flatten()#如果某一行全为0则r inv算出来会等于无穷大将这些行的rinv置为0r_inv[np.isinf(r_inv)] 0.#用度的一维数组构建对角元素为r_inv的对角矩阵r_mat_inv sp.diags(r_inv)#再和邻接矩阵mx做点乘mx r_mat_inv.dot(mx)return mxdef accuracy(output, labels):#使用type astesnor将张量转换为给定类型的张量preds output.max(1)[1].type_as(labels)#记录等于preds的label eq:equalcorrect preds.eq(labels).double()correct correct.sum()#预测正确数量/总数return correct / len(labels)def sparse_mx_to_torch_sparse_tensor(sparse_mx):Convert a scipy sparse matrix to a torch sparse tensor.sparse_mx sparse_mx.tocoo().astype(np.float32)indices torch.from_numpy(np.vstack((sparse_mx.row, sparse_mx.col)).astype(np.int64))values torch.from_numpy(sparse_mx.data)shape torch.Size(sparse_mx.shape)#只用记录有值的索引值以及shapereturn torch.sparse.FloatTensor(indices, values, shape)
model.py
import torch.nn as nn
import torch.nn.functional as F
from pygcn.layers import GraphConvolutionclass GCN(nn.Module):# nfeat输入单元数shape[1]表示特征矩阵的维度数列数# nhid中间层单元数量# nclass输出单元数即样本标签数样本标签最大值1cora里面是7# dropout参数def __init__(self, nfeat, nhid, nclass, dropout):super(GCN, self).__init__()# nfeat输入nhid第一层输出第二层的输入nclass输出self.gc1 GraphConvolution(nfeat, nhid)self.gc2 GraphConvolution(nhid, nclass)self.dropout dropout#x是输入特征adj是邻接矩阵def forward(self, x, adj):#第一层加非线性函数和dropoutx F.relu(self.gc1(x, adj))x F.dropout(x, self.dropout, trainingself.training)##gc2层x self.gc2(x, adj)#输出为输出层做1og_softmax变换的结果dim表示1og_softmax将计算的维度return F.log_softmax(x, dim1)
layer.py
import mathimport torchfrom torch.nn.parameter import Parameter
from torch.nn.modules.module import Moduleclass GraphConvolution(Module):Simple GCN layer, similar to https://arxiv.org/abs/1609.02907def __init__(self, in_features, out_features, biasTrue):super(GraphConvolution, self).__init__()#输入特征self.in_features in_features#输出特征self.out_features out_features#模型要学习的权重Win_featuifes * out_featuresself.weight Parameter(torch.FloatTensor(in_features, out_features))#是否设置biasif bias:self.bias Parameter(torch.FloatTensor(out_features))else:self.register_parameter(bias, None)self.reset_parameters()def reset_parameters(self):#weight[in features,out features]#size(1)是指out featuresstdv1/sqrt(out features)计算标准差stdv 1. / math.sqrt(self.weight.size(1))self.weight.data.uniform_(-stdv, stdv)if self.bias is not None:self.bias.data.uniform_(-stdv, stdv)def forward(self, input, adj):#计算A*X*W非线性函数ReLU在model里面加#input和self.weight矩阵相乘#先算supportX*Wsupport torch.mm(input, self.weight)#spmm()是稀疏矩阵乘法减小运算复杂度#计算A*X*WA*先算supportoutput torch.spmm(adj, support)#判断是否需要返回偏置if self.bias is not None:return output self.biaselse:return outputdef __repr__(self):return self.__class__.__name__ ( \ str(self.in_features) - \ str(self.out_features) )作业
【思考题】将实践的代码与论文中的公式对应起来回顾模型是如何实现的。 【代码实践】为算法设置不同的参数GCN的层数、hidden_size的大小对模型效果的影响。 【总结】总结GCN的关键技术以及如何代码实现。