深度学习入门站 · 理解深度学习
首页 › 第 13 章

图神经网络

数据不总是表格:在关系/图结构上做学习的神经网络。
图 13-1:图神经网络的消息传递
图 13-1:图神经网络的消息传递

前面几章的数据要么是规整的图像网格,要么是顺序排列的文本。但现实里还有一大类"蜘蛛网"形状的数据:社交网络的好友关系、分子里的化学键、论文之间的引用。这类数据叫图(Graph)。这一章我们学怎么让神经网络"看图",核心只有一句话:让每个节点打听邻居的消息——消息传递。学完你会看懂图的表示、图卷积的原理,以及图上的三类常见任务。

什么是图?节点、边和邻接矩阵

把一堆节点(node)用边(edge)连起来就是一张图:节点代表人、原子、论文,边代表它们的关系——好友、化学键、引用。图最大的特点是没有固定顺序:重新编号节点,图还是那张图;而像素一打乱图像就毁了,词序一变句子就变味,图却不会。

生活里图无处不在:道路网、分子、社交网络、论文引用网……甚至一张图片也可以看成图——每个像素是一个节点,和周围像素相连,只不过它非常"规整":每个节点的邻居数量和位置都一样。

怎么把图喂给计算机?最直观的是邻接矩阵(Adjacency Matrix):一张 $N \times N$ 的表格,$N$ 是节点数,第 $m$ 行第 $n$ 列写 1 表示节点 $m$、$n$ 之间有边,否则写 0:

$$A_{mn} = \begin{cases} 1, & \text{节点 } m \text{ 与 } n \text{ 之间有边} \\ 0, & \text{否则} \end{cases}$$

无向图的邻接矩阵是对称的:你是我的朋友,我也是你的朋友。除了结构,节点还常带自己的信息(兴趣、元素种类),叫节点嵌入,拼成矩阵 $X$。于是图 = 结构 $A$ + 信息 $X$,这就是 GNN 的全部输入。

消息传递:GNN 的核心思想

一句话概括图神经网络(GNN):每个节点都去问一圈邻居"你们是什么情况",汇总答案,再结合自己,更新自己的表示,这叫消息传递(message passing)。经过一层又一层,节点就慢慢掺进了越来越远的信息。

📣
生活类比
消息传递就像打听朋友圈的八卦:想知道大家近况,最直接的办法是问一圈直接好友——这是"一步"。如果你的好友把从别人那听来的消息转告你,你就间接知道了朋友的朋友——这是"两步"。传得越远,知道的圈子越大;但每传一步,信息都被"加工"过、也越发模糊。

数学上,节点 $n$ 先收集所有邻居的表示并求和,这叫聚合(aggregation);其中 $\mathcal{N}(n)$ 是节点 $n$ 的邻居集合,$\mathbf{h}^{(m)}_k$ 是第 $k$ 层邻居 $m$ 的表示:

$$\text{agg}[n,k] = \sum_{m \in \mathcal{N}(n)} \mathbf{h}^{(m)}_k$$

然后把"聚合结果 + 自己的表示"一起过线性变换和激活函数,这叫更新(update):

$$\mathbf{h}^{(n)}_{k+1} = a\left[\beta_k + \Omega_k \mathbf{h}^{(n)}_k + \Omega_k\,\text{agg}[n,k]\right]$$

所有节点合在一起写,公式浓缩成一句:邻居特征求和,等价于把嵌入矩阵 $\mathbf{H}_k$ 右乘邻接矩阵 $\mathbf{A}$;想把自己也算进去,就把 $\mathbf{A}$ 换成 $\mathbf{A} + \mathbf{I}$($\mathbf{I}$ 是单位矩阵):

$$\mathbf{H}_{k+1} = a\left[\beta_k \mathbf{1}^{\top} + \Omega_k \mathbf{H}_k (\mathbf{A} + \mathbf{I})\right]$$

这个式子意思就三个字:邻居求和,加上自己,过一层网络。下面用 NumPy 亲手实现这一步——对一个 4 节点的小图,算一次"邻居 + 自己"的聚合:

import numpy as np

# 一个小图:4 个节点,5 条无向边
edges = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)]
N = 4
A = np.zeros((N, N))                 # 邻接矩阵
for i, j in edges:
    A[i, j] = A[j, i] = 1            # 无向图,对称

# 每个节点一个 2 维特征
X = np.array([[1.0, 0.0],
              [0.0, 1.0],
              [1.0, 1.0],
              [0.5, 0.5]])

# 一步消息传递:A + I = "邻居 + 自己"
H1 = X @ (A + np.eye(N))

print("聚合后的新特征(每行一个节点):")
print(H1.T)

跑完你会发现,$H_1$ 的每一列已经是"自己 + 所有直接邻居"的混合特征了。

图卷积:对邻居加权求和

你可能会说:这不就是卷积吗?没错。CNN 里,每个像素的新特征 = 周围固定窗口内像素特征的加权求和;GNN 里,每个节点的新特征 = 邻居特征的聚合再变换。唯一的区别:图像是"规则网格图",每个像素的邻居都在固定的上下左右位置,卷积核可以按位置设计;而普通图里邻居数量不固定、没有"上""下"之分,只能对邻居一视同仁地聚合。所以反过来说:CNN 是 GNN 在"每个节点邻居个数固定且有固定顺序"这个特例下的版本,GNN 是卷积思想在任意图上的推广——这就是图卷积(GCN)名字的由来。

GNN 还有两个关键性质。第一,每一层在所有节点上共用同一套参数 $\Omega$,就像 CNN 用同一个卷积核扫遍整张图,否则每个节点都要各学一套参数,既浪费又无法泛化。第二,正因为参数共享,节点无论怎么重新编号,结果都"跟着一起重排"(等变性)——GNN 天然尊重"图没有顺序"这个事实。

最后记住一个数层数的小技巧:K 层 GNN,节点表示里恰好包含 K 跳以内的邻居信息。1 层只能看见直接好友,2 层能看到"朋友的朋友"……这和 CNN 的感受野是同一个概念。

💡
**核心思想**:GNN 的每一层 = 聚合邻居的消息 + 更新自己;参数在所有节点间共享;**层数 K 决定一个节点能"看"到 K 跳以内的邻居**。和 CNN 一样,它用共享参数利用结构规律,只是把"规则网格"换成了任意形状的图。

图上的三大任务

学会了怎么算节点表示,剩下的问题就是"拿它干什么"。图上的监督学习任务大致分三类:

① 节点分类:给每个节点贴标签,比如根据论文内容与引用关系判断它属于哪个领域。每个节点的最终表示 $\mathbf{h}^{(n)}_K$ 过一层线性变换加 sigmoid,就得到属于类别 1 的概率:

$$\Pr(y^{(n)} = 1 \mid X, A) = \text{sig}\left[\beta_K + \omega_K^{\top} \mathbf{h}^{(n)}_K\right]$$

② 边预测:预测两个节点之间"该不该有边",比如猜两个人会不会成为朋友。把两个节点的表示做点积再过 sigmoid——表示越相似,越可能连边:

$$\Pr(y_{mn} = 1 \mid X, A) = \text{sig}\left[\mathbf{h}^{(m)\top}_K \mathbf{h}^{(n)}_K\right]$$

③ 图分类:给整张图贴标签,比如判断一个分子有没有毒。先把所有节点的表示求平均(平均池化),再把平均值映射成一个数:

$$\Pr(y = 1 \mid X, A) = \text{sig}\left[\beta_K + \omega_K^{\top} \left(\frac{1}{N}\sum_{n} \mathbf{h}^{(n)}_K\right)\right]$$

三类任务训练时都用前面章节的老熟人:分类用交叉熵,回归用最小二乘。GNN 只是换了一个"提取特征"的模型,损失和优化器照旧。

🎯 小测验

1. 一步消息传递中,节点的新表示主要来自哪里?
2. 一个 2 层的 GCN,节点的最终表示最多包含几跳邻居的信息?
3. 判断一个分子是否有毒,属于哪一类图任务?