第2章 图神经网络基础:图的概念、节点、边、邻接矩阵
好,咱们正式开始啃图神经网络。这一章我得先把图的基础概念给你掰开揉碎讲清楚。你可能会想,不就是图吗?计算机里到处都是图。但图神经网络里的图,和我们平时说的社交网络图,还真有点不一样。
我个人习惯先把“图”这个东西抽象成数学结构来看。你想想看,一个分子不就是一张天然图吗?原子是节点,化学键是边。QSAR任务本质上就是在这样的图上做预测。所以,搞明白图的底层逻辑,后面学GNN才不费劲。
从一碗面说起:图到底是什么?
我看过不少教程,一上来就扔公式。这其实不太友好。让我先从一个特别直观的例子切入:一碗拉面。
- 节点:面条里每一根面条的端点,或者面碗里的配菜(牛肉、香菜、辣椒)。
- 边:面条本身的长度,连接两个端点的就是一根完整的面条。它们之间的关系是“相连”。
- 图:整碗面的结构——哪根面条连了哪根,哪块牛肉挨着哪个辣椒。
在化学里,分子就是一碗结构清晰的“面”。
正式点说,一个图用数学语言可以这么写:
G = (V, E)
V 是节点集合:v₁, v₂, v₃, ..., vₙ
E 是边集合:e₁, e₂, e₃, ..., eₘ
每个节点都有它的属性。边的属性也很关键,比如键的强弱。
节点(Vertex / Node)——分子里的每个原子
节点是图的最小单元。在我接触的QSAR项目里,节点通常对应分子中的原子。我见过把整个官能团当做一个节点的情况,嗯,那属于进阶玩法,初期咱们还是按原子来。
节点的核心信息都在它的特征向量里。我给你看看我曾经处理过的分子节点特征表:
| 特征类别 | 编码方式 | 举例(C原子) |
|---|---|---|
| 原子类型 | One-hot编码 | C: [0,0,1,0,...] |
| 价电子数 | 标量 | 4 |
| 杂化方式 | One-hot编码 | sp³: [1,0,0] |
| 是否在环中 | 二值 | 1 或 0 |
| 电荷 | 标量 | 0 |
这里有个坑我得提醒你:不同特征的尺度差异很大。价电子数是4,电荷可能是-1到1。如果不做归一化,模型会直接把大数特征的节点当成“更重要”的节点。我曾经犯过这种错,训练出来的模型只认原子类型不看电荷,后来重训了一轮。所以,特征预处理千万别省。
边(Edge)——原子间的纽带
边定义了两个节点之间的关系。在分子图里,边就是化学键。但边的类型比你想的要多一些。
我习惯把边分成三种:
- 有向边:箭头从A指向B,关系是单向的。比如反应物到产物。
- 无向边:两个节点互相连接,不分方向。分子里的共价键就是无向的。
- 带权边:边上有权重值。在分子图里,权重可以表示键能或键级。
咱们GNN里处理的分子图,绝大多数是无向带权图。
边的特征也需要编码。举个键特征的例子:
键类型 = {单键:0, 双键:1, 三键:2, 芳香键:3}
键是否共轭 = {是:1, 否:0}
键是否在环内 = {是:1, 否:0}
这些特征组合起来,就构成了每条边的描述。
邻接矩阵——藏在数据里的“地图”
这是我最想让你搞懂的部分。邻接矩阵是图的数学化身。
它是一个N×N的方阵(N是节点数)。如果节点i和节点j之间有边,那么A[i][j] = 1(或权重值);没边就是0。
我画个简单的图帮你理解。假设有这么个小分子:
- 4个原子(节点编号0, 1, 2, 3)
- 3条键:0-1, 0-2, 1-3
对应的邻接矩阵如下:
0 1 2 3
0: [0, 1, 1, 0]
1: [1, 0, 0, 1]
2: [1, 0, 0, 0]
3: [0, 1, 0, 0]
这个矩阵有几个重要性质:
- 对称性:无向图的邻接矩阵沿对角线对称。A[0][1]和A[1][0]都是1。
- 对角线是0:自己不和自已相连(除非有自环)。
- 稀疏性:分子图的邻接矩阵极其稀疏。一个分子几十个原子,矩阵里大部分是0。
关键点:GNN的核心操作——消息传递,本质上就是在邻接矩阵指导下,让每个节点去“看”它的邻居节点。没有邻接矩阵,图就没有结构信息,模型就没法知道谁和谁有关系。
图的存储与读取——从SMILES到矩阵
在QSAR里,我们拿到的数据通常是SMILES字符串。比如:
SMILES: CCO(乙醇)
要把SMILES变成邻接矩阵和节点特征矩阵,我们需要做几步处理。我常用的工具是RDKit,它可以把分子转成图对象:
from rdkit import Chem
from rdkit.Chem import rdmolops
mol = Chem.MolFromSmiles("CCO")
# 获取邻接矩阵(N x N,无向)
adj_matrix = rdmolops.GetAdjacencyMatrix(mol)
# 输出结果(3个原子)
# [[0, 1, 0],
# [1, 0, 1],
# [0, 1, 0]]
注意看,这个结果和分子结构完全吻合:C-C相连,C-O相连,C和O之间隔着C。
我建议你本地跑一下这段代码。自己动手把SMILES转成矩阵,那种感觉和看懂理论完全不一样。
个人建议:初学阶段不要用复杂度太高的分子练手,像苯环或长碳链这种。先用简单的小分子(比如乙醇、甲烷、水),它们的邻接矩阵小,你还能手动验证一遍,不容易出错。这样做一次,之后面对大分子心里就有底了。
本章核心逻辑框架
让我把本章的核心概念串成一张图,这样你看完就能把知识点挂起来:
这张图把整章的逻辑串起来了:图由节点和边构成,各自携带特征,邻接矩阵负责记录连接关系,最后所有的结构信息都汇入消息传递机制——这正是GNN做QSAR预测的根基。
避坑与实战提醒
讲完概念,我分享几个真实项目中踩过的坑,希望能帮你省点时间。
避坑1:对称矩阵别忘保持
我曾经在图构建时把无向图的(A[i][j])设了但(A[j][i])没设,导致模型学到的是有向信息。分子结构是无向的,矩阵必须对称。每次构建后做个简单验证:assert (adj == adj.T).all()。
避坑2:节点特征要对齐矩阵索引
节点0的特征必须对应邻接矩阵第0行。我在做官能团映射时,有一次给节点重新排序后忘了调整矩阵,特征和连接关系错位了。结果模型跑了30轮才收敛,后来发现是数据对齐出了问题。正确做法:给每个节点分配一个永久ID,矩阵和特征表都用这个ID索引。
建议步骤:处理分子数据时,我习惯按这个顺序来:
- 读SMILES → 生成RDKit分子对象
- 获取原子数N → 初始化N×N邻接矩阵
- 遍历所有键,填充矩阵(记得填两个方向)
- 提取每个原子的特征,放入N×d的节点特征矩阵
- 验证矩阵对称性和索引一致性
这个流程每一步都可以用代码检查,不会等到训练时才发现数据有问题。
好了,基础概念就讲到这里。图、节点、边、邻接矩阵——这四个东西弄明白了,你就算摸到了GNN的门。下个知识点我带你看看这些结构怎么传递给模型去做学习。