GCN 基础:图神经网络概述、核心公式与消息传递

大家好,我是老张。做图神经网络在药物数据上的应用,踩了不少坑。今天咱们先打地基,把 GCN 最核心的东西理清楚。你想想看,药物数据天然就是个图——原子是节点,化学键是边。传统 CNN 处理不了这种不规则结构,所以才有了图神经网络。下面我把图神经网络到底在干什么、GCN 公式怎么来的、消息传递机制如何运作,一口气讲明白。

1. 图神经网络到底在干什么?

说白了,图神经网络的任务就是:给每个节点学一个向量表示。

  • 邻居节点共享信息
  • 节点特征不断融合
  • 最后的向量能用于分类、回归、聚类

拿药物分子举个例子。每个原子有 C、N、O 等元素类型,还有电荷、杂化状态。GCN 会让碳原子看看它周围的氢、氧、氮,然后把邻居的信息“聚合”到自己的特征里。经过几层,碳原子就知道自己处在苯环上还是羰基里。我在项目中遇到过好几次:如果不做消息聚合,模型根本学不出化学环境的差异,预测溶解度总是差一大截。

核心观点:图网络不要求数据排列在网格上,它是一种“按边流动”的学习方式。

2. GCN 核心公式——拆开揉碎

GCN 是图神经网络里最经典的一种。它的公式其实不复杂,我来一步步拆解。

标准的一层 GCN 更新过程:

H^{(l+1)} = σ( Ã · H^{(l)} · W^{(l)} )
其中:
  Ã = D^{-1/2} · A · D^{-1/2}
  A 是邻接矩阵,D 是度矩阵
  H^{(l)} 是第 l 层的节点特征
  W^{(l)} 是可学习的权重矩阵
  σ 是非线性激活函数(比如 ReLU)

第一步:对称归一化邻接矩阵 Ã

为什么要做这一步?原始邻接矩阵 A 里,每个节点的度不同。度数高的节点,如果不归一化,聚合时特征值会暴涨。我在做全图分类时遇到过爆炸问题,后来才意识到是没做对称归一化。用度矩阵 D 的 -1/2 次方左右乘,相当于让每个邻居的贡献除以 sqrt(度)。这样高连接度节点不会吞掉其他信息。

第二步:特征聚合

用 Ã 乘上 H^{(l)},相当于把邻居特征按权重加起来。你想想,如果 Ã 里的第 i 行第 j 列非零,就表示节点 j 的特征会贡献到节点 i。

第三步:线性变换 + 激活

聚合后的特征乘上权重矩阵 W,再过一个 ReLU 函数。这个 W 就是模型要学习的参数,不同任务它会学到不同的特征抽取模式。

我的习惯:第一次写 GCN 时,先写一个单层版本,打印出聚合前后的特征分布,看是否出现 NaN。这是最简单的 debug 手段。
组件 符号 作用
邻接矩阵 A 表示节点间是否有边
度矩阵 D 每个节点的邻居数
归一化邻接 Ã 避免度数高的节点特征爆炸
特征矩阵 H 每行是一个节点的向量
权重矩阵 W 可学习的线性映射

3. 消息传递机制 —— GCN 的灵魂

GCN 的本质是消息传递。每个节点接收邻居发来的“消息”,然后把它们合并,更新自己的特征。

一个通用的消息传递框架包含三步:

  1. 消息生成:对每条边 (u,v),用源节点特征生成一个消息向量。在 GCN 中,消息就是邻居特征本身。
  2. 聚合:把目标节点收到的所有消息加起来(或取平均/最大值)。GCN 用的是加权平均。
  3. 更新:把聚合后的消息与自身特征结合,通过非线性函数得到新特征。

你可以把它想象成一场小组讨论:每个人先发言(消息生成),然后汇总大家的观点(聚合),最后结合自己原来的想法更新认识(更新)。

我曾经踩过的坑:在聚合时,把自身特征也当作一个“虚拟邻居”。有些实现没加自环,导致节点根本看不到自己的初始信息,这种模型性能会大幅下降。解决方法是给邻接矩阵 A 加上单位矩阵 I:A_self = A + I。GCN 原论文就是这么做的。

消息传递与传统 CNN 的区别:

  • CNN 的邻居是固定位置(上下左右),GCN 的邻居是动态的(取决于边结构)
  • CNN 的权重是空间共享的,GCN 的 W 对所有节点共享(但聚合方式由图结构决定)
  • GCN 输出对图结构变化敏感,换了图结构就要重新计算

这张 SVG 展示了消息传递的完整流程,我画了 5 个节点和两层聚合关系。

GCN 消息传递示意图 C1 C2 N3 O4 C5 消息 1 消息 2 消息 3 消息 4 聚合:求和 / 平均 更新:σ( 聚合 × W ) 输出新特征 H^{(l+1)} 用于下一层或分类

图中 C1 收到来自 C2 和 O4 的消息,N3 收到来自 C2 和 C5 的消息。每一条消息都携带邻居的特征,经过聚合和线性变换后,所有节点的特征都被更新了。这个过程在每一层重复,信息就能传播到多跳之外的节点。

4. 实践提示:从公式到代码

写 GCN 时我建议你这样组织代码:

import torch
import torch.nn.functional as F

def gcn_layer(A, H, W):
    # 1. 加自环
    A_hat = A + torch.eye(A.size(0))
    # 2. 对称归一化
    D = torch.diag(torch.pow(A_hat.sum(dim=1), -0.5))
    norm_A = D @ A_hat @ D
    # 3. 聚合 + 变换
    out = norm_A @ H @ W
    return F.relu(out)

注意这一步:torch.eye(A.size(0))。如果不加自环,节点永远无法把自身特征传给下一层。我在做毒理预测时,就是少加了自环,导致模型只关注邻居而忽略了原子自身特性,准确率怎么都上不去。

关键点回顾:
  • 图神经网络的核心是让节点通过边交换信息
  • GCN 用对称归一化的邻接矩阵做聚合
  • 消息传递分为生成、聚合、更新三步
  • 自环和度归一化是两个最容易忽略但影响巨大的细节

好了,这一章到这里。GCN 基础框架我们已经搭好,后面几章会在这个基础上加入注意力机制、跳跃连接,以及专门为药物数据设计的图池化策略。到时候再深入聊。记住:先把这个消息传递的思想吃透,后面的花样都是锦上添花。