💻图神经网络
图神经网络(Graph Neural Network,GNN)是一种专门用于处理图结构数据的深度学习方
法。与传统的神经网络主要处理规则结构的数据(如图像和文本)不同,GNN能够处理各种不规
则的数据结构,如社交网络、分子结构等。GNN通过在图上定义节点之间的连接关系,利用节点
的邻居信息来更新节点的表示,实现对整个图的信息传递和学习。
📘分子的图结构
图神经网络(GNN)处理的输入是图(Graph),而不是传统的像素矩阵或序列。因此,第一步
是将我们的目标分子——乙醇,抽象地转化为一个数学图。
节点与邻接关系
为了简化手算过程,我们只关注重原子:两个碳原子和一个氧原子。我们将氢原子的影响体现在
节点的特征中。
邻接矩阵 : 描述节点之间的连接关系(化学键)。
节点特征矩阵:是 GCN 的输入。为了演示目的,我们为每个原子分配一个简单的特
征向量(例如一个独热编码和它的连接度):
🔬 真实项目中分子图的表示方式
在真实的分子图神经网络项目中,虽然图的基本原理(节点和边)是一样的,但节点特征和图的复
杂度会有巨大的差异。在 PyTorch Geometric (PyG) 或 Deep Graph Library (DGL) 等专业 GNN 库
中处理分子(例如基于 RDKit 的分子表示)时,图的定义会丰富得多,最后我们会详细的介绍一
下这部分的内容。
📘图卷积
图卷积的输入数据是节点特征矩阵H和邻接矩阵A,下图我们展示了3个节点的图,每个节点特
征数为3的特征,图卷积在计算的时候有两个关键的步骤,分别是节点特征的线性变换和邻接特征
的聚合。
GCN 单层的核心公式是:
节点特征的线性变换
就是使用线性层,对节点特征矩阵进行线性变换,提取特征,节点的特征数目发生变化。
X' 现在代表了每个原子经过权重变换后的新特征。计算过程如下:
邻接特征的聚合
这里为了方便理解,邻接矩阵没有使用稀疏表示,就是使用邻接矩阵adj进行特征聚合,将相邻
节点中的特征信息,传导到该节点上。
新的特征就等于聚合邻居信息和变换特征的乘积
聚合邻居信息
这个矩阵中的每一行和每一列的元素,定义了消息如何从邻居节点传递并加权求和。
总结一下:一次的图卷积,本质上是特征的变换(或者说是特征数的变化),在这个过程中,节点
特征矩阵数据,包含每个节点的属性信息,每层都会通过 GCN 运算更新。归一化邻接矩阵结构,
定义图的拓扑结构和消息传递路径,它是固定的。
📘分子图的表达方式
🔬 真实项目中分子图的表示方式
在真实项目中,分子表示的流程是:
SMILES 字符串 RDKit 解析 PyG/DGL 图对象
安装相关的包:
pip install rdkit
pip install torch_geometric
# 假设 PyTorch, RDKit, PyTorch Geometric (PyG) 库已安装 import torch from rdkit import Chem from torch_geometric.utils.smiles import from_smiles # PyG中用于SMILES转换的实用函数 (或使用更早版本的'from_rdkit') # --- 1. 定义和转换 --- smiles_ethanol = "CCO" # 使用 PyG 的封装函数,一步完成解析、特征提取和图结构构建 # 这个函数内部自动完成了原子特征编码、键索引构建、以及 PyTorch 张量转换。 ethanol_data = from_smiles(smiles_ethanol) # --- 2. 展示结果 --- print("=" * 40) print(f"乙醇分子 SMILES: {smiles_ethanol}") print("PyG Data 对象结构 (封装结果)") print("=" * 40) # PyG Data 对象概览 print(ethanol_data) print("\n--- 关键张量尺寸分析 ---") print(f"节点特征矩阵 (x): {ethanol_data.x.shape}") print(f"邻接信息 (edge_index): {ethanol_data.edge_index.shape}") print("-" * 40)
========================================
乙醇分子 SMILES: CCO
PyG Data 对象结构 (封装结果)
========================================
Data(x=[3, 9], edge_index=[2, 4], edge_attr=[4, 3], smiles='CCO')
--- 关键张量尺寸分析 ---
节点特征矩阵 (x): torch.Size([3, 9])
邻接信息 (edge_index): torch.Size([2, 4])
----------------------------------------
📈 PyG Data 对象结构解读
3 (行): 代表图中的节点数量, 模型忽略了 6 个氢原子。9 (列): 代表每个节点的特征维度特征提取
器为每个重原子编码了 9 种不同的化学属性(例如:原子类型、价态、电荷、隐式氢数等)。2
(行): PyG 的固定格式,第一行是源节点索引,第二行是目标节点索引。4 (列): 代表有向边的总
数。
图卷积神经网络
以乙醇分子为例,模拟前向传播
原始3×9大小的张量
经过隐藏层通道数为16的图卷积,得到3×16大小的张量
再经过隐藏层通道数为16的图卷积,得到3×16大小的张量
再经过一个全局平均池化,得到1×16大小的特征矩阵,全局平均将节点特征聚合为图特征(每个图一个数来表示)
然后再通过一个线性层变成1×16大小的张量
🔍 代码实现
import torch import torch.nn.functional as F from torch_geometric.nn import GCNConv, global_mean_pool from torch_geometric.data import Data import sys # 尝试导入 RDKit 和 PyG 转换工具 from rdkit import Chem from torch_geometric.utils.smiles import from_smiles # --- 1. 真实数据准备 (使用 PyG 封装函数) --- smiles_ethanol = "CCO" # 一步转换:生成包含所有 9 个原子(C, C, O, 6H)的图结构 ethanol_data = from_smiles(smiles_ethanol) # 修复:确保节点特征是浮点类型 (解决 RuntimeError) ethanol_data.x = ethanol_data.x.float() # 生成 Batch Tensor:9 个节点都属于同一个图 (batch size=1) N_NODES = ethanol_data.x.shape[0] ethanol_data.batch = torch.zeros(N_NODES, dtype=torch.long) # --- 2. 展示输入数据结构 --- print("=" * 60) print(f"乙醇分子 SMILES: {smiles_ethanol}") print("PyG Data 对象结构 (GCN 模型输入 - 真实配置)") print("=" * 60) print(f"节点数 N: {ethanol_data.x.shape[0]}") print(f"节点特征矩阵 (x): {ethanol_data.x.shape}") print(f"邻接信息 (edge_index): {ethanol_data.edge_index.shape}") print("-" * 60) # --- 3. 定义 GCN 模型类 (SimpleGCN) --- class SimpleGCN(torch.nn.Module): def __init__(self, num_node_features, hidden_channels, num_classes): super().__init__() self.conv1 = GCNConv(num_node_features, hidden_channels) self.conv2 = GCNConv(hidden_channels, hidden_channels) self.lin = torch.nn.Linear(hidden_channels, num_classes) def forward(self, x, edge_index, batch): print(f"\n[A] 初始输入 x (H(0)): {x.shape}") # GCN 层 1 x = self.conv1(x, edge_index) print(f"[B] GCNConv 1 输出 (H(1)): {x.shape}") x = F.relu(x) # GCN 层 2 x = self.conv2(x, edge_index) print(f"[C] GCNConv 2 输出 (H(2)): {x.shape}") x = F.relu(x) # 全局读出/池化层 x = global_mean_pool(x, batch) print(f"[D] Global Mean Pool 输出: {x.shape} <--- **图级特征**") # 线性分类层 x = self.lin(x) print(f"[E] 最终分类层输出: {x.shape} <--- **预测结果**") return x # --- 4. 模型实例化与运行 --- # 定义模型参数 INPUT_DIM = ethanol_data.x.shape[1] # 自动获取真实/模拟的特征维度 (例如:11) HIDDEN_DIM = 16 OUTPUT_DIM = 1 # 实例化模型 model = SimpleGCN( num_node_features=INPUT_DIM, hidden_channels=HIDDEN_DIM, num_classes=OUTPUT_DIM ) # 执行前向传播 print("=" * 60) print(f"【Simple GCN 前向传播过程(隐藏层维度 D_hidden={HIDDEN_DIM})】") print("=" * 60) output = model( ethanol_data.x, ethanol_data.edge_index, ethanol_data.batch )
============================================================
乙醇分子 SMILES: CCO
PyG Data 对象结构 (GCN 模型输入 - 真实配置)
============================================================
节点数 N: 3
节点特征矩阵 (x): torch.Size([3, 9])
邻接信息 (edge_index): torch.Size([2, 4])
------------------------------------------------------------
============================================================
【Simple GCN 前向传播过程(隐藏层维度 D_hidden=16)】
============================================================
[A] 初始输入 x (H(0)): torch.Size([3, 9])
[B] GCNConv 1 输出 (H(1)): torch.Size([3, 16])
[C] GCNConv 2 输出 (H(2)): torch.Size([3, 16])
[D] Global Mean Pool 输出: torch.Size([1, 16]) <--- **图级特征**
[E] 最终分类层输出: torch.Size([1, 1]) <--- **预测结果**