分子图神经网络基础:GCN、GAT与消息传递机制
做ADMET预测,我们面对的分子本质上是图结构。原子是节点,化学键是边。
你想想看,如果直接把分子当成一个序列或者指纹扔进模型,其实丢掉了很重要的空间信息。我个人习惯把分子图比作一个社交网络——每个原子有它自己的"朋友圈",化学键决定了它们的亲疏关系。
这一章我们就来拆解如何用图神经网络把分子信息编码成向量。说白了,就是教会计算机"看懂"分子长什么样。
分子图的数学表示
先解决一个基础问题:怎么把分子告诉计算机?
一个分子图通常用两部分表示:
- 节点特征矩阵 X:每一行代表一个原子,列是原子属性(原子类型、杂化状态、电荷等)
- 邻接矩阵 A:记录哪些原子之间有化学键连接
分子图定义:G = (V, E, X),其中 V 是节点集合,E 是边集合,X 是节点特征矩阵。对一个有 N 个原子的分子,X 的形状是 (N, d),d 是每个原子的特征维度。
我在项目中遇到过一个问题:苯环的六元环如果只用邻接矩阵,模型其实分不清它和环己烷的区别。所以后来我们会在节点特征里加上芳香性标记,效果立竿见影。
常见的原子特征包括:
| 特征类别 | 具体特征 | 维数 |
|---|---|---|
| 原子类型 | C, N, O, S, F, Cl, Br, I, P, 其他 | 10(one-hot) |
| 度数 | 连接的键数(1-6) | 6 |
| 形式电荷 | -2, -1, 0, +1, +2 | 5 |
| 杂化状态 | sp, sp², sp³ | 3 |
| 芳香性 | 是否在芳香环中 | 1 |
| 氢原子数 | 连接的氢原子数 | 5 |
经验之谈:我建议至少用 40 维以上的特征初始化原子节点。特征太少,GCN 很难学到有区分度的原子嵌入。我之前试过只用原子类型和度数,模型对同分异构体完全无法区分。
GCN:图卷积网络
GCN 是图神经网络的"入门款",但也是基石。
它的核心思想很简单:每个节点从邻居那里"收集"信息,然后更新自己的状态。
单层 GCN 的更新公式:
H⁽ˡ⁺¹⁾ = σ( D̂⁻¹/² · Â · D̂⁻¹/² · H⁽ˡ⁾ · W⁽ˡ⁾ )
其中:
 = A + I (邻接矩阵加自环)
D̂ᵢᵢ = Σⱼ Âᵢⱼ (度矩阵)
H⁽ˡ⁾ 是第 l 层的节点特征
W⁽ˡ⁾ 是可学习的权重矩阵
σ 是激活函数(常用 ReLU)
为什么要加 D̂⁻¹/²?因为不归一化的话,度数高的原子会对周围产生过大的影响,模型会不稳定。归一化相当于给每个邻居的信息加了一个"音量调节"。
两层 GCN 在分子性质预测上效果已经很能打了。我有个习惯:不管后面用什么花哨的模块,先用一层 GCN 把节点特征投影到目标维度,再上注意力或者 Transformer。
避坑:我曾经把 GCN 层数堆到 6 层,结果过平滑到所有原子嵌入几乎变成同一个向量。分子图通常只有 10-50 个原子,2-3 层就足够了。层数再多反而会模糊局部结构信息。
GAT:图注意力网络
GCN 有个潜在问题:它对所有邻居一视同仁。在实际分子里,不是所有邻居都同等重要。
比如一个羰基碳连着 C=O 双键和两个单键,氧原子对这个碳的电子密度影响显然更大。GAT 就是为了解决这个问题——给每个邻居分配不同的注意力权重。
注意力机制的计算过程:
eᵢⱼ = LeakyReLU(aᵀ · [W·hᵢ || W·hⱼ])
αᵢⱼ = softmaxⱼ(eᵢⱼ) = exp(eᵢⱼ) / Σₖ∈𝒩ᵢ exp(eᵢₖ)
hᵢ' = σ( Σⱼ∈𝒩ᵢ αᵢⱼ · W · hⱼ )
其中 a 是注意力向量,|| 表示向量拼接,𝒩ᵢ 是节点 i 的邻居集合。
为什么 GAT 适合 ADMET?
ADMET 任务中,某些官能团对毒性或代谢稳定性起决定性作用。GAT 可以自动学习到这些关键原子,不用我们手工指定药效团。我在 CYP450 抑制预测任务中,把 GCN 换成 GAT 后 F1 值提升了约 4%。
实用建议:多头注意力是 GAT 的标准配置。用 8 个头并行计算注意力,最后拼接或求平均。这样可以捕捉不同类型的"关注模式"。
# 多头注意力输出的两种处理方式
# 拼接(中间层推荐)
hᵢ' = ||ₖ₌₁ᴷ σ( Σⱼ∈𝒩ᵢ αᵢⱼᵏ · Wᵏ · hⱼ )
# 求平均(最后一层推荐)
hᵢ' = σ( 1/K · Σₖ₌₁ᴷ Σⱼ∈𝒩ᵢ αᵢⱼᵏ · Wᵏ · hⱼ )
消息传递机制:统一框架
GCN 和 GAT 其实是消息传递框架下的两个具体实例。把问题抽象成"消息传递"能让我们更容易设计出新的变体。
消息传递框架包含三个步骤:
- 消息构建:每个节点根据自己和邻居的信息生成消息
- 消息聚合:把邻居发来的消息集合起来
- 节点更新:用聚合后的消息更新节点状态
用公式表达:
mᵢ⁽ᵗ⁾ = AGGREGATE( { M(hᵢ⁽ᵗ⁻¹⁾, hⱼ⁽ᵗ⁻¹⁾, eᵢⱼ) | j ∈ 𝒩ᵢ } )
hᵢ⁽ᵗ⁾ = UPDATE( hᵢ⁽ᵗ⁻¹⁾, mᵢ⁽ᵗ⁾ )
其中:
t 是传播步数(对应网络层数)
M 是消息函数(通常是个 MLP)
AGGREGATE 可以是 sum、mean、max 或 attention
UPDATE 可以是 GRU、MLP 或简单的残差连接
我的选择偏好:对分子图来说,AGGREGATE 用 sum 比 mean 效果好。为什么?因为 sum 对分子大小更敏感。比如一个苯环上的氢被替代成硝基,sum 聚合能捕捉到这种整体信号变化,mean 则会被平均掉。
下面这个图展示了我平时搭建分子编码器的完整流程。从原子和化学键出发,经过多轮消息传递,最终汇聚成分子级别表征,然后送到下游 ADMET 预测头。
消息传递的三种聚合方式对比
选择哪种聚合方式?这不是理论问题,是工程问题。我试过各种组合,下面是我总结的规律。
| 聚合方式 | 特点 | 适用场景 | 我的评分 |
|---|---|---|---|
| Sum | 保留分子大小信息,对尺度敏感 | 分子性质预测(LogP、溶解度) | ⭐⭐⭐⭐ |
| Mean | 对大小不敏感,关注局部密度 | 结合位点识别、官能团分类 | ⭐⭐⭐ |
| Attention(GAT) | 加权聚合,能聚焦关键原子 | 毒性位点预测、CYP抑制 | ⭐⭐⭐⭐⭐ |
曾经踩过的坑:我在预测 hERG(人类醚-à-go-相关基因)心脏毒性时,直接用 GCN+Sum 聚合,效果很差。后来换成 GAT 才意识到——hERG 抑制主要由几个关键疏水残基和碱性氮原子决定,GCN 把噪音也加权进去了,而 GAT 自动把注意力集中到那几个关键原子上。
读出头:从原子嵌入到分子嵌入
消息传递结束后,我们得到的是每个原子的嵌入向量。但 ADMET 预测需要的是整个分子的表征。这就需要一个"读出头"——把一堆节点向量压缩成一个全局向量。
三种常见策略:
- 全局池化:对所有节点嵌入做 sum、mean 或 max 池化。简单快速,但会丢失拓扑信息
- Set2Set:参考 Vinyals 的工作,用循环神经网络做有序化输出,能保留一定结构信息
- 虚拟节点:在分子图上额外加一个与所有原子相连的虚拟节点。我跟你说,这个方法在预训练-微调场景下特别实用,虚拟节点可以作为"读出代理"
实战技巧:我建议把 Sum 池化和 Mean 池化拼接起来作为分子嵌入。这样做的好处是同时编码了"总量"和"平均密度"两种信息。在我的预训练实验中,这种方式比单独用任何一种都稳定。
代码实现就是一句:h_mol = torch.cat([h_node.sum(dim=0), h_node.mean(dim=0)])
好了,以上是分子图编码器的核心构造。有了这些基础,下一阶段我们就可以把这些编码器放到预训练框架里,去大规模无标记分子数据上做自监督学习。嗯,这才是把 GNN 用对的关键。