深度之眼Paper带读笔记GNN.08.GCN(下)

news2024/9/24 13:15:02

文章目录

  • 前言
    • 细节四:卷积核介绍
      • 图卷积核初代目
      • 图卷积核二代目
      • 契比雪夫多项式例子
      • 小结
    • GCN公式推导
  • 实验设置和结果分析
    • 数据集
    • 节点分类任务
    • 消息传递方式比较
    • 运行效率
  • 总结
    • 关键点
    • 创新点
    • 启发点
  • 代码复现
    • train.py
    • util.py
    • model.py
    • layer.py
  • 作业

前言

本课程来自深度之眼,部分截图来自课程视频。
文章标题:Semi-Supervised Classification with Graph Convolutional Networks
图卷积神经网络的半监督分类(GCN)
作者:Thomas N.Kipf,Max 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)_G=U((U^Th)\odot (U^Tf))\tag{14} (fh)G=U((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_{j=0}^{K-1}\alpha_j\lambda_1^j&&\\ &\ddots&\\ &&\sum_{j=0}^{K-1}\alpha_j\lambda_n^j\\ \end{pmatrix}=\sum_{j=0}^{K-1}\alpha_j\Lambda^j\tag{20} gθ(Λ)= j=0K1αjλ1jj=0K1αjλnj =j=0K1α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^T=U\sum_{j=0}^{K-1}\alpha_j\Lambda^j(\Lambda)U^T=\sum_{j=0}^{K-1}\alpha_jU\Lambda^jU^T=\sum_{j=0}^{K-1}\alpha_jL^j\tag{21} Ugθ(Λ)UT=Uj=0K1αjΛj(Λ)UT=j=0K1αjUΛjUT=j=0K1αjLj(21)
可以看到公式21的最后形态直接是拉普拉斯矩阵,没有特征向量U,也就意味不需要矩阵的特征分解。下面看下公式21的简单证明过程:


因为 U T U = E U^TU=E UTU=E
L 2 = L L = U Λ U T U Λ U T = U Λ 2 U T L^2=LL=U\Lambda U^TU\Lambda U^T=U\Lambda^2U^T L2=LL=UΛUTUΛUT=UΛ2UT
同理:
L 3 = U Λ 3 U T L^3=U\Lambda^3U^T L3=UΛ3UT
⋮ \vdots
L n = U Λ n U T L^n=U\Lambda^nU^T Ln=UΛnUT
因此公式21中的
U Λ j U T = L j U\Lambda^jU^T=L^j UΛjUT=Lj


因此公式18变成了:
y o u t p u t = σ ( ∑ j = 0 K − 1 α j L j x ) y_{output}=\sigma(\sum_{j=0}^{K-1}\alpha_jL^jx) youtput=σ(j=0K1αjLjx)
从推导过程我们可以知道,这个卷积核不用特征分解,因此计算复杂度低;参数量从n变成了K个,K<n;而且比较重要的一点,就是 L j L^j Lj,相当于邻居矩阵: A j A^j Aj,当j=1时,表示节点的直接邻居信息,j=2时,表示1跳邻居,当j=n时,表示的是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_{j=0}^{K-1}\beta_kT_k(\tilde\Lambda) gθ(Λ)=j=0K1β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(karccos(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 \Lambda=2\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 L=2\cfrac{L}{\lambda_{max}}-I L~=2λmaxLI


说明:
这里虽然还是涉及到了 λ 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~Tk1(L~)Tk2(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_{j=0}^{K-1}\beta_kT_k(\tilde\Lambda)U^Tx)\end{aligned} youtput=σ(Ugθ(Λ)UTx)=σ(Uj=0K1βkTk(Λ~)UTx)
由于契比雪夫多项式是用特征值对角矩阵进行的输入,因此可以把两边的矩阵放到里面一起操作:
y o u t p u t = σ ( ∑ j = 0 K − 1 β k T k ( U Λ ~ U T ) x ) y_{output}=\sigma(\sum_{j=0}^{K-1}\beta_kT_k(U\tilde\Lambda U^T)x) youtput=σ(j=0K1β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_{j=0}^{K-1}\beta_kT_k(\tilde L)x) youtput=σ(j=0K1βkTk(L~)x)

契比雪夫多项式例子

假设有如下无向图(这个图上面也有):
在这里插入图片描述
当k=0时,根据公式23:
∑ j = 0 K − 1 β k T k ( L ~ ) = β 0 T 0 ( L ~ ) = β 0 I = β 0 \sum_{j=0}^{K-1}\beta_kT_k(\tilde L)=\beta_0T_0(\tilde L)=\beta_0I=\beta_0 j=0K1β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_0&0 &0 \\ 0 & 0&0 &0 & \beta_0&0 \\ 0 &0 &0 &0 &0 & \beta_0 \end{pmatrix} β0000000β00000000β0000000β0000000β0000000β0
当k=1时,根据公式23:
∑ j = 0 K − 1 β k T k ( L ~ ) = β 0 T 0 ( L ~ ) + β 1 T 1 ( L ~ ) = β 0 + β 1 L ~ \sum_{j=0}^{K-1}\beta_kT_k(\tilde L)=\beta_0T_0(\tilde L)+\beta_1T_1(\tilde L)=\beta_0+\beta_1\tilde L j=0K1βkTk(L~)=β0T0(L~)+β1T1(L~)=β0+β1L~
L = I − D − 0.5 A D − 0.5 L=I-D^{-0.5}AD^{-0.5} L=ID0.5AD0.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& 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 \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& 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 \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& 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 \end{pmatrix} D0.5AD0.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 ) L=I-D^{-0.5}AD^{-0.5}=\begin{pmatrix} 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 \end{pmatrix} L=ID0.5AD0.5= 11/3001/301/211/201/3001/311/300001/211/311/21/301/3100001/301
然后按照公式将 L L L变成 L ~ \tilde L L~
L ~ = 2 L λ m a x − I \tilde L=2\cfrac{L}{\lambda_{max}}-I L~=2λmaxLI
L的最大特征值 λ m a x \lambda_{max} λmax约为:1.87667(https://zs.symbolab.com/)
可以看到当k=1的时候,实际上就是加入了图邻接一跳的邻居信息。

小结

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_{n+1}(x)=2xT_n(x)-T_{n-1}(x) \end{cases} T0(x)=1T1(x)=xTn+1(x)=2xTn(x)Tn1(x)
2.切比雪夫卷积核:
{ g θ ′ ( Λ ) ≈ ∑ k = 0 K − 1 θ k ′ T k ( Λ ~ ) Λ ~ = 2 λ m a x Λ − I N \begin{cases} & g_{\theta'}(\Lambda)\approx\sum_{k=0}^{K-1}\theta_k'T_k(\tilde\Lambda)\\ & \tilde\Lambda=\cfrac{2}{\lambda_{max}}\Lambda-I_N \end{cases} gθ(Λ)k=0K1θkTk(Λ~)Λ~=λ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_{k=0}^{K-1}\theta_k'T_k(\tilde L)x\\ & \tilde L=\cfrac{2}{\lambda_{max}}L-I_N \end{cases} gθxk=0K1θkTk(L~)xL~=λmax2LIN

GCN公式推导

铺垫了这么多,终于到了正题,原文对应第二节,前面几个公式都用的切比雪夫的,不用多说,从公式6看:
g θ ′ ⋆ x ≈ ∑ k = 0 K − 1 θ k ′ T k ( L ~ ) x g_{\theta'}\star x\approx\sum_{k=0}^{K-1}\theta_k'T_k(\tilde L)x gθxk=0K1θkTk(L~)x
如果只考虑k=0和k=1:
g θ ′ ⋆ x ≈ θ 0 ′ x + θ 1 ′ L ~ x = θ 0 ′ x + θ 1 ′ ( 2 λ m a x L − I N ) x g_{\theta'}\star x\approx\theta_0'x+\theta_1'\tilde Lx=\theta_0'x+\theta_1'(\cfrac{2}{\lambda_{max}}L-I_N)x gθxθ0x+θ1L~x=θ0x+θ1(λmax2LIN)x
论文中提到GCN的 λ m a x ≈ 2 \lambda_{max}\approx2 λmax2,代入上面可以得(为什么是2看这里,老师提供了链接:https://zhuanlan.zhihu.com/p/65447367):
g θ ′ ⋆ x ≈ θ 0 ′ x + θ 1 ′ ( L − I N ) x g_{\theta'}\star x\approx\theta_0'x+\theta_1'(L-I_N)x gθxθ0x+θ1(LIN)x
论文里面设定的拉普拉斯矩阵: L = I N − D − 1 2 A D − 1 2 L=I_N-D^{-\frac{1}{2}}AD^{-\frac{1}{2}} L=IND21AD21,代入上面得原文公式6
g θ ′ ⋆ x ≈ θ 0 ′ x − θ 1 ′ D − 1 2 A D − 1 2 x g_{\theta'}\star x\approx\theta_0'x-\theta_1'D^{-\frac{1}{2}}AD^{-\frac{1}{2}}x gθxθ0xθ1D21AD21x
这个时候两个参数不一样(而且二者没有约束),一个是参数量大,二是容易过拟合,因此都设置成一样的。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_N+D^{-\frac{1}{2}}AD^{-\frac{1}{2}})x gθxθ(IN+D21AD21)x
由于 I N + D − 1 2 A D − 1 2 I_N+D^{-\frac{1}{2}}AD^{-\frac{1}{2}} IN+D21AD21的特征值取值范围是[0,2],这个范围不好,因为这样会造成梯度消失或者爆炸(这也是为什么很多NN网络要加normalization操作),因此原文加了一个:renormalization trick:
I N + D − 1 2 A D − 1 2 → D ~ − 1 2 A ~ D ~ − 1 2 I_N+D^{-\frac{1}{2}}AD^{-\frac{1}{2}}\rightarrow \tilde D^{-\frac{1}{2}}\tilde A\tilde D^{-\frac{1}{2}} IN+D21AD21D~21A~D~21
其中:
A ~ = A + I N , D ~ i i = ∑ j A ~ i j \tilde A=A+I_N,\tilde D_{ii}=\sum_j\tilde A_{ij} A~=A+IN,D~ii=jA~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 Z=D~21A~D~21XΘ

实验设置和结果分析

在这里插入图片描述

数据集

在这里插入图片描述
由于是半监督学习,最后一列是带有label的节点的比例。
Cora这个应该比较熟悉了
最后一个是知识图谱的数据集,是bipartite graph(二分图)

节点分类任务

在这里插入图片描述

消息传递方式比较

在这里插入图片描述

运行效率

在这里插入图片描述

总结

关键点

• 模型结构
• 图的拉普拉斯矩阵
• Chebyshev多项式卷积核
• 频域分析

创新点

• 空域和频域联系
• 1st-Chebyshev卷积核实用(就是k=0和k=1的情况)
• 半监督框架+ layer-wise GCN

启发点

• GCN实用化的开始,1st-ChebyshevGCN
• 将chebyshev多项式卷积核截断近似为K=1
• 演化出很多GCN为基础的模型,如RGCN等
• 半监督框架的消息传递策略融合了点的特征和图结构
• 与GNN常用框架之间的联系
• GCN、GAT、GraphSAGE都是非常重要的模型,也是经典baseline

代码复现

在这里插入图片描述
https://github.com/tkipf/pygcn
数据集就用cora。

train.py

from __future__ import division
from __future__ import print_function

import time
import argparse
import numpy as np

import torch
import torch.nn.functional as F
import torch.optim as optim

from pygcn.utils import load_data, accuracy
from pygcn.models import GCN

# Training settings
parser = argparse.ArgumentParser()
# 禁用CUDA训练
parser.add_argument('--no-cuda', action='store_true', default=False,
                    help='Disables CUDA training.')
#是否在训练过程中进行验证
parser.add_argument('--fastmode', action='store_true', default=False,
                    help='Validate during training pass.')
#随机种子
parser.add_argument('--seed', type=int, default=42, help='Random seed.')
#epoch数量
parser.add_argument('--epochs', type=int, default=200,
                    help='Number of epochs to train.')
#学习率
parser.add_argument('--lr', type=float, default=0.01,
                    help='Initial learning rate.')
#权重衰减
parser.add_argument('--weight_decay', type=float, default=5e-4,
                    help='Weight decay (L2 loss on parameters).')
#隐藏层大小
parser.add_argument('--hidden', type=int, default=16,
                    help='Number of hidden units.')
#dropout参数
parser.add_argument('--dropout', type=float, default=0.5,
                    help='Dropout rate (1 - keep probability).')

args = parser.parse_args()
#jupyter要用下面这句
#args=parser.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输出单元数,即样本标签数=样本标签最大值+1,cora里面是7
# dropout参数
model = GCN(nfeat=features.shape[1],
            nhid=args.hidden,
            nclass=labels.max().item() + 1,
            dropout=args.dropout)

# 构造一个优化器对象0ptimizer,用来保存当前的状态,并能够根据计算得到的梯度来更新参数
# Adam优化器
# 1r学习率
# weight_decay权重衰减(L2惩罚)
optimizer = optim.Adam(model.parameters(),
                       lr=args.lr, weight_decay=args.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()固定整个网络参数,没有dropout
    model.train()
    #把梯度置零,也就是把loss关于weight的导数变成0
    optimizer.zero_grad()
    #执行GCN中的forward前向传播
    output = model(features, adj)
    #最大似然/log似然损失函数,idx_train是140(0~139)
    #nll loss:negative log likelihood loss 
    #https://www.cnblogs.com/marsggbo/p/10401215.html
    loss_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和dropout
        output = model(features, adj)

    #EVAL的最大似然/log似然损失函数,idxval是300(200-499)
    loss_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(epoch+1),
          '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 torch


def encode_onehot(labels):
    classes = set(labels)
    #identity创建方矩阵
    #字典key为labe1的值,value为矩阵的每一行
    classes_dict = {c: np.identity(len(classes))[i, :] for i, c in
                    enumerate(classes)}
    #get函数得到字典key对应的value
    #map()会根据提供的函数对指定序列做映射
    #第一个参数 function 以参数序列中的每一个元素调用function函数,返回包含每次 function 函数返回值的新列表
    #map(lambdax:x**2,[1,2,3,4,5])
    #output:[1,4,9,16,25]
    labels_onehot = np.array(list(map(classes_dict.get, labels)),
                             dtype=np.int32)
    return labels_onehot


def load_data(path="./data/cora/", dataset="cora"):
    """Load citation network dataset (cora only for now)"""
    print('Loading {} dataset...'.format(dataset))

    #content file的每一行的格式为:<paper_id> <word attributes><class_label>
    #三个字段的index分别对应0,1:-1,-1
    #feature为第二列到倒数第二列,labe1s为最后一列
    idx_features_labels = np.genfromtxt("{}{}.content".format(path, dataset),
                                        dtype=np.dtype(str))
    #储存为csr型稀疏矩阵
    features = sp.csr_matrix(idx_features_labels[:, 1:-1], dtype=np.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], dtype=np.int32)
    
    # 由于文件中节点并非是按顺序排列的,因此建立一个编号为0-(node_size-1)的哈希表idx_map
    # 哈希表中每一项为o1d id:number,即节点id对应的编号为number
    idx_map = {j: i for i, j in enumerate(idx)}
    
    #edges_unordered为直接从边表文件中直接读取的结果,是一个(edge_num,2)的数组,每一行表示一条边两个端点的idx
    edges_unordered = np.genfromtxt("{}{}.cites".format(path, dataset),
                                    dtype=np.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())),
                     dtype=np.int32).reshape(edges_unordered.shape)
    
    #根据coo矩阵性质,这一段的作用是:网络有多少条边,邻接矩阵就有多少个1,
    #所以先创建一个长度为edge_num的全1数组,每个1的填充位置就是一条边中两个端点的编号,
    #即edges[:,0],edges[:,1],矩阵的形状为(node_size,node_size)
    adj = sp.coo_matrix((np.ones(edges.shape[0]), (edges[:, 0], edges[:, 1])),
                        shape=(labels.shape[0], labels.shape[0]),
                        dtype=np.float32)

    # build symmetric adjacency matrix
    # 对于无向图,邻接矩阵是对称的。上一步得到的adj是按有向图构建的,转换成无向图的邻接矩阵需要扩充成对称矩阵
    # 将i->j与j->i中权重最大的那个,作为无向图的节点节点j的边权。
    # 原文NELL数据集用了这个操作
    # https://blog.csdn.net/Eric_1993/article/details/102907104
    adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)

    features = normalize(features)
    
    # eye创建单位矩阵,第一个参数为行数,第二个为列数
    # normalize对应论文里A^=(D~)-1A~这个公式
    # +eye,对应公式A-=A+I_N
    adj = 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_test


def 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置为0
    r_inv[np.isinf(r_inv)] = 0.
    #用度的一维数组构建对角元素为r_inv的对角矩阵
    r_mat_inv = sp.diags(r_inv)
    #再和邻接矩阵mx做点乘
    mx = r_mat_inv.dot(mx)
    return mx


def accuracy(output, labels):
    #使用type as(tesnor)将张量转换为给定类型的张量
    preds = output.max(1)[1].type_as(labels)
    #记录等于preds的label eq:equal
    correct = 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)
    #只用记录有值的索引,值,以及shape
    return torch.sparse.FloatTensor(indices, values, shape)

model.py

import torch.nn as nn
import torch.nn.functional as F
from pygcn.layers import GraphConvolution


class GCN(nn.Module):
    # nfeat输入单元数,shape[1]表示特征矩阵的维度数(列数)
    # nhid中间层单元数量
    # nclass输出单元数,即样本标签数=样本标签最大值+1,cora里面是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):
        #第一层加非线性函数和dropout
        x = F.relu(self.gc1(x, adj))
        x = F.dropout(x, self.dropout, training=self.training)
        ##gc2层
        x = self.gc2(x, adj)
        #输出为输出层做1og_softmax变换的结果,dim表示1og_softmax将计算的维度
        return F.log_softmax(x, dim=1)

layer.py

import math

import torch

from torch.nn.parameter import Parameter
from torch.nn.modules.module import Module


class GraphConvolution(Module):
    """
    Simple GCN layer, similar to https://arxiv.org/abs/1609.02907
    """

    def __init__(self, in_features, out_features, bias=True):
        super(GraphConvolution, self).__init__()
        #输入特征
        self.in_features = in_features
        #输出特征
        self.out_features = out_features
        #模型要学习的权重W,in_featuifes * out_features
        self.weight = Parameter(torch.FloatTensor(in_features, out_features))
        
        #是否设置bias
        if 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 features,stdv=1/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矩阵相乘
        #先算support=X*W
        support = torch.mm(input, self.weight)
        
        #spmm()是稀疏矩阵乘法,减小运算复杂度
        #计算A*X*W=A*先算support
        output = torch.spmm(adj, support)
        #判断是否需要返回偏置
        if self.bias is not None:
            return output + self.bias
        else:
            return output

    def __repr__(self):
        return self.__class__.__name__ + ' (' \
               + str(self.in_features) + ' -> ' \
               + str(self.out_features) + ')'

作业

【思考题】将实践的代码与论文中的公式对应起来,回顾模型是如何实现的。
【代码实践】为算法设置不同的参数,GCN的层数、hidden_size的大小对模型效果的影响。
【总结】总结GCN的关键技术以及如何代码实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1237008.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图形数据库的实战应用:如何在 Neo4j 中有效管理复杂关系

关系数据库管理系统( RDBMS ) 代表了最先进的技术&#xff0c;这在一定程度上要归功于其由周边技术、工具和广泛的专业技能组成的完善的生态系统。 在这个涵盖信息技术(IT) 和运营技术(OT) 的技术革命时代&#xff0c;人们普遍认识到性能方面出现了重大挑战&#xff0c;特别是…

springboot+bootstrap+java农业电商服务商城系统_30249

本农业电商服务系统是为了提高用户查阅信息的效率和管理人员管理信息的工作效率&#xff0c;可以快速存储大量数据&#xff0c;还有信息检索功能&#xff0c;这大大的满足了管理员、会员和商家这三者的需求。操作简单易懂&#xff0c;合理分析各个模块的功能&#xff0c;尽可能…

鸿蒙原生应用/元服务开发-AGC分发如何编译打包应用

软件包规范 在正式打包应用前&#xff0c;请确保已了解HarmonyOS应用软件包规范。 操作步骤 1.打开DevEco Studio&#xff0c;菜单选择“Build > Build Hap(s)/APP(s) > Build APP(s)”。 2.等待编译构建。编译完成后&#xff0c;将在工程目录“build > outputs >…

企业再不做数字化就OUT了

做数字化是为了让企业活得更好&#xff0c;也是企业活下去的关键&#xff01; 今年是十四五规划的第二年&#xff0c;科技创新与数字生态建设&#xff0c;俨然成为今年政府工作报告中重点强调的方向之一。 “加快数字化发展&#xff0c;打造数字经济新优势&#xff0c;协同推进…

喜报 | 热烈祝贺思腾合力成功挂牌天津OTC专精特新板

近日&#xff0c;天津区域性股权市场企业挂牌上市成果发布会于2023中国民营企业投融资洽谈会上成功举行。在会上公布&#xff0c;思腾合力成功挂牌天津OTC“专精特新”板。 本次活动由北交所&#xff08;新三板&#xff09;天津基地、天津证监局、市发改委&#xff0c;天津滨海…

Linux ps -ef|grep去除 grep --color=auto信息

linux 监控 进程判断是否启动可通过该指令实现 ps -ef|grep java指令结果为 # -v 参数有过滤作用 ps -ef|grep java |grep -v grep

Vue2 基本语法

Vue2 基本语法 前言Vue2 基本语法脚手架文件结构关于不同版本的Vuevue.config.js配置文件ref属性props配置项mixin(混入)插件scoped样式总结TodoList案例webStorage组件的自定义事件全局事件总线&#xff08;GlobalEventBus&#xff09;消息订阅与发布&#xff08;pubsub&#…

Django 创建项目时找不到数据库sqlite3

原因:PyCharm创建Django项目,找不到数据库sqlite3 解决&#xff1a;如果没有默认的db文件&#xff0c;则应在PyCharm终端中执行以下命令&#xff1a; python manage.py makemigrations python manage.py migrate

C题目11:数组a[m]排序

每日小语 双手&#xff0c;且放下一切劳作&#xff0c;前额&#xff0c;也忘掉忧思&#xff0c;此时此刻我所有的感觉就想沉入安睡。 自己敲写 这个问题老师上课讲了一种方法&#xff0c;叫做冒泡排序。基本思想是 1.找最小值&#xff0c;放到a[0] 2.从a[1]~a[3]找最小值&a…

TVS瞬态抑制二极管的工作原理和特点?|深圳比创达电子EMC

TVS二极管一般是用来防止端口瞬间的电压冲击造成后级电路的损坏。防止端口瞬间的电压冲击造成后级电路的损坏。有单向与双向之分&#xff0c;单向TVS一般应用于直流供电电路&#xff0c;双向TVS应用于交流供电电路。 TVS产品的额定瞬态功率应大于电路中可能出现的最大瞬态浪涌…

分享一些简单的英语问候语

昨天和一个朋友聊天&#xff0c;他问我最近有没有某个国家的客户&#xff1f;我说只有一两个&#xff0c;都已经好久没有联系了&#xff0c;上一次问候还是在九月份。他说从十月底开始就收到很多来自当地的询盘&#xff0c;你不妨问下客户最近是否有新的需求&#xff1f; 于是…

如何看待Unity新收费模式?

Unity新收费模式的变化主要在于将收费重心从功能分级收费转变为资源使用量收费&#xff0c;这个改变已经引起了一定的争议和反响。以下是我个人的看法&#xff1a; 优点&#xff1a; 更公平的收费方式&#xff1a;新的收费模式将更加公平&#xff0c;用户只需按照实际使用的数…

【开源】基于Vue.js的婚恋交友网站

项目编号&#xff1a; S 057 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S057&#xff0c;文末获取源码。} 项目编号&#xff1a;S057&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 会员管理模块2.3 新…

SpringBoot : ch04 整合数据源

前言 Spring Boot 是当今最流行的 Java 开发框架之一&#xff0c;它以简洁、高效的特点帮助开发者快速构建稳健的应用程序。在实际项目中&#xff0c;涉及到数据库操作的需求时&#xff0c;我们需要对数据源进行整合。本文将重点介绍如何在 Spring Boot 中整合数据源&#xff…

实时云渲染 助力破解智慧园区痛点困局

智慧园区是运用先进的信息技术&#xff0c;如物联网&#xff08;IoT&#xff09;、大数据、云计算、人工智能、三维可视化等&#xff0c;对园区内的各类设施、资源以及管理进行智能化和数字化升级。其目标是通过科技手段提升园区的运营效率、资源利用率&#xff0c;提供更便捷、…

循环神经网络(RNN)实现股票预测

文章目录 一、前言二、前期工作1. 设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09;2. 导入数据 四、数据预处理1.归一化2.设置测试集训练集 五、构建模型六、激活模型七、训练模型八、结果可视化1.绘制loss图2.预测3.评估 一、前言 我的环境&#xff1a; 语言环…

防雷接地+防雷工程施工综合方案

一、地凯科技防雷工程接地概述 防雷接地工程是指在建筑物或其他设施上安装防雷装置&#xff0c;以防止雷电对人员、设备和建筑物造成危害的工程。防雷装置主要包括避雷针&#xff08;网&#xff09;、引下线、接地体&#xff08;网&#xff09;等部分&#xff0c;其中接地体&a…

Zookeeper初识及安装配置

文章目录 写在前面一、Zookeeper概念二、下载安装2.1 环境准备2.2 下载上传2.3 解压 三、配置启动3.1 配置zoo.cfg3.2 启动Zookeeper 写在前面 最近接受了一个比较老的分布式项目&#xff0c;用的Zookeeper协调服务&#xff0c;所以虽然相关的服务注册等功能有很多可能更好的代…

技术分享|电商数据接口|淘宝天猫京东商品API接口之数据同步

常见的数据同步/集成场景多发生于不同的存储系统、不同的存储格式&#xff0c;如从 mysql 同步数据至数仓、excel 或 csv 导入数据库中&#xff0c;但是众多数据同步解决方案很少涉及从 http 接口同步数据。 如淘宝、拼多多等电商平台&#xff0c;平台内部不同团队之间的数据打…

4.3、Linux进程(2)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 通过系统调用创建进程--fork函数 结果是什么呢&#xff1f; 为什么会出来三个打印呢? 就是因为父进程调用了fork函数创建出了子进程的task_struct,但是一个进程不止task_struct&#xff0c;还有代码和数据&#xff0c;他们…