【GNN】图神经网络:从零开始学习图神经网络
图神经网络(GNN)完全指南
📚 从零开始学习图神经网络
🎯 目标:用最通俗的语言,讲清楚最前沿的图学习技术
🌐 图神经网络是处理关系数据的利器,应用于社交网络、推荐系统、药物发现等领域
📅 最后更新:2025年11月
📋 目录
- 1. 什么是图(Graph)?
- 2. 为什么需要图神经网络?
- 3. 图神经网络基础
- 4. 消息传递机制
- 5. 常见的 GNN 架构
- 6. 图的表示学习
- 7. 实际应用场景
- 8. 代码实现示例
- 9. 进阶话题
- 10. 常见问题解答
1. 什么是图(Graph)?
1.1 生活中的图
在讲图神经网络之前,我们先理解什么是"图"。
图不是照片,而是一种数据结构!
图 = 节点(Node/Vertex)+ 边(Edge)
节点:表示实体(人、物品、地点等)
边:表示关系(朋友、购买、连接等)
1.2 图的例子
例子1:社交网络
张三 ────────── 李四
│ 朋友关系 │
│ │
朋友 朋友
│ │
│ │
王五 ────────── 赵六
节点:人(张三、李四、王五、赵六)
边:朋友关系
可视化:
●───────●
张三 李四
│ │
│ │
●───────●
王五 赵六
例子2:分子结构
H
│
H─C─C─O─H (乙醇分子)
│
H
节点:原子(C, H, O)
边:化学键
例子3:交通网络
北京 ──飞机─→ 上海
│ │
高铁 高铁
↓ ↓
广州 ──飞机─→ 深圳
节点:城市
边:交通方式(有向、带权重)
例子4:知识图谱
苹果公司
│
创始人
│
↓
乔布斯 ─── 出生于 ──→ 旧金山
│
发明了
│
↓
iPhone
节点:实体(公司、人、产品、地点)
边:关系(创始人、出生于、发明了)
1.3 图的数学定义
图 G = (V, E)
V = {v1, v2, ..., vn} 节点集合
E = {(vi, vj), ...} 边集合
例如:
V = {张三, 李四, 王五}
E = {(张三, 李四), (李四, 王五)}
1.4 图的类型
无向图 vs 有向图
无向图:
A ─── B A和B互为朋友(双向)
有向图:
A ──→ B A关注B(单向)
可视化对比:
【无向图】社交好友
●───●───●
A B C
边没有箭头,关系是对称的
【有向图】Twitter关注
●──→●──→●
A B C
边有方向,A关注B,B关注C
加权图 vs 无权图
无权图:
A ─── B 只表示有连接
加权图:
A ──5km─→ B 边有权重(距离、强度等)
可视化:
【加权图】城市距离
北京
│ 1000km
│
上海 ────── 300km ────── 杭州
│
500km
│
广州
同质图 vs 异质图
同质图:
所有节点类型相同,所有边类型相同
例如:只有"人"节点,只有"朋友"关系
异质图:
多种类型的节点和边
例如:有"人"、"电影"、"导演",有"喜欢"、"导演"关系
可视化:
【异质图】电影推荐
用户 ──喜欢──→ 电影 ←──导演─── 导演
● ● ●
张三 《盗梦空间》 诺兰
│ │
朋友 导演
↓ ↓
● ● ●
李四 《星际穿越》 (同一个诺兰)
节点类型:用户、电影、导演
边类型:喜欢、导演、朋友
1.5 图的表示方法
邻接矩阵(Adjacency Matrix)
节点:A, B, C, D
图:
A ─── B
│ │
C ─── D
邻接矩阵 A:
A B C D
A [ 0 1 1 0 ]
B [ 1 0 0 1 ]
C [ 1 0 0 1 ]
D [ 0 1 1 0 ]
A[i][j] = 1 表示节点i和节点j之间有边
优点:
- ✅ 表示简单
- ✅ 查询两个节点是否相连:O(1)
缺点:
- ❌ 空间复杂度:O(n²),大图很浪费
- ❌ 大部分图是稀疏的(边很少),矩阵大部分是0
邻接表(Adjacency List)
节点:A, B, C, D
图(同上)
邻接表:
A: [B, C]
B: [A, D]
C: [A, D]
D: [B, C]
每个节点存储其邻居列表
优点:
- ✅ 空间复杂度:O(V + E),节省空间
- ✅ 遍历邻居快
缺点:
- ❌ 查询两节点是否相连:需要遍历,O(degree)
边列表(Edge List)
边列表:
[(A, B), (A, C), (B, D), (C, D)]
只存储所有边
可视化总结:
┌────────────────────────────────────────────┐
│ 图的三种表示方法 │
├────────────────────────────────────────────┤
│ │
│ 原始图: │
│ A ─── B │
│ │ │ │
│ C ─── D │
│ │
│ 邻接矩阵(稠密存储): │
│ ┌──────────┐ │
│ │ 0 1 1 0 │ │
│ │ 1 0 0 1 │ │
│ │ 1 0 0 1 │ │
│ │ 0 1 1 0 │ │
│ └──────────┘ │
│ │
│ 邻接表(稀疏存储): │
│ A → [B, C] │
│ B → [A, D] │
│ C → [A, D] │
│ D → [B, C] │
│ │
│ 边列表: │
│ (A,B), (A,C), (B,D), (C,D) │
│ │
└────────────────────────────────────────────┘
2. 为什么需要图神经网络?
2.1 传统神经网络的局限
CNN(卷积神经网络)
适用于:网格数据(图像、视频)
图像特点:
- 规则的网格结构(像素排列)
- 每个像素有固定数量的邻居(8个)
- 平移不变性
图的特点:
- 不规则结构
- 每个节点的邻居数量不同
- 无法用固定大小的卷积核
结论:CNN 无法直接用于图!
可视化:
【CNN 适用】图像(规则网格)
●───●───●───●
│ │ │ │
●───●───●───● 每个像素有固定的邻居
│ │ │ │
●───●───●───●
│ │ │ │
●───●───●───●
【不适用】图(不规则)
●
╱│╲
●─●─● 节点A有3个邻居
│╲ 节点B有2个邻居
●─● 节点C有1个邻居
RNN(循环神经网络)
适用于:序列数据(文本、时间序列)
序列特点:
- 有明确的顺序(从左到右)
- 一维结构
图的特点:
- 没有天然的顺序
- 多维关系网络
结论:RNN 无法直接处理图!
可视化:
【RNN 适用】文本序列
我 → 爱 → 机器 → 学习
│ │ │ │
h1 h2 h3 h4
明确的时间顺序
【不适用】社交网络
A
╱│╲
B─C─D
╲│╱
E
没有明确的顺序,应该先处理谁?
2.2 图数据的挑战
挑战1:不规则结构
- 节点数量可变
- 每个节点的邻居数量不同
挑战2:置换不变性
- 节点的编号是任意的
- 改变节点顺序,图的本质不变
挑战3:大规模图
- 社交网络:数亿节点
- 知识图谱:数千万实体
挑战4:动态图
- 关系随时间变化
- 新节点和边不断加入
2.3 图神经网络的解决方案
GNN 的核心思想:
通过"消息传递",让每个节点
1. 聚合邻居的信息
2. 更新自己的表示
3. 多轮迭代,捕捉更大范围的信息
类比:
就像朋友圈,你的观点会受到朋友的影响
朋友的观点又受到他们朋友的影响
经过多轮传播,你能间接了解更多人的想法
可视化示例:
初始状态:
A(1)
╱ ╲
B(2) C(3)
╲ ╱
D(4)
第1轮消息传递:
A 收到 B, C 的信息
B 收到 A, D 的信息
C 收到 A, D 的信息
D 收到 B, C 的信息
更新后:
A(1.5) ← 聚合了 B, C 的信息
╱ ╲
B(2.2) C(2.8)
╲ ╱
D(3.5)
经过多轮传递,节点能"看到"更远的邻居!
3. 图神经网络基础
3.1 GNN 的核心任务
GNN 可以解决三类任务:
┌─────────────────────────────────────────┐
│ GNN 的三大任务类型 │
├─────────────────────────────────────────┤
│ │
│ 1️⃣ 节点级任务(Node-level) │
│ 预测每个节点的属性 │
│ 例如:社交网络中预测用户兴趣 │
│ │
│ 2️⃣ 边级任务(Edge-level) │
│ 预测节点间是否有边,或边的类型 │
│ 例如:推荐系统(用户-物品连接) │
│ │
│ 3️⃣ 图级任务(Graph-level) │
│ 预测整个图的属性 │
│ 例如:分子毒性预测 │
│ │
└─────────────────────────────────────────┘
任务1:节点分类(Node Classification)
任务:预测每个节点的类别
例子:论文引用网络
节点:论文
边:引用关系
目标:预测论文的研究领域
可视化:
● AI
╱│╲
●─●─●
CV│ML NLP 预测中间论文属于哪个领域?
│
●
任务2:链接预测(Link Prediction)
任务:预测两个节点之间是否应该有边
例子:推荐系统
节点:用户、商品
边:购买关系
目标:预测用户可能购买什么商品
可视化:
用户A ────→ 商品1
│ ?
│ ╱
? 商品2
╲ ╱
╲ ╱
商品3
预测:用户A会买商品2吗?
任务3:图分类(Graph Classification)
任务:预测整个图的类别
例子:分子性质预测
节点:原子
边:化学键
目标:预测分子是否有毒
可视化:
H
│
H─C─C─O─H → 预测:无毒 ✓
│
H
(乙醇)
Cl
│
Cl─C─Cl → 预测:有毒 ✗
│
Cl
(四氯化碳)
3.2 GNN 的基本组件
┌────────────────────────────────────────┐
│ GNN 的四大组件 │
├────────────────────────────────────────┤
│ │
│ 1. 节点特征(Node Features) │
│ 每个节点的初始表示 │
│ X ∈ ℝ^(n×d) │
│ │
│ 2. 邻接矩阵(Adjacency Matrix) │
│ 图的结构信息 │
│ A ∈ ℝ^(n×n) │
│ │
│ 3. 消息传递函数(Message Function) │
│ 如何聚合邻居信息 │
│ m_i = Σ φ(h_i, h_j, e_ij) │
│ │
│ 4. 更新函数(Update Function) │
│ 如何更新节点表示 │
│ h_i' = ψ(h_i, m_i) │
│ │
└────────────────────────────────────────┘
3.3 简单的 GNN 层
数学公式:
第 k 层的节点表示:
h_i^(k) = σ( W^(k) · Σ (h_j^(k-1) / √(d_i · d_j)) )
j∈N(i)
其中:
- h_i^(k):节点 i 在第 k 层的表示
- N(i):节点 i 的邻居集合
- W^(k):第 k 层的权重矩阵
- d_i:节点 i 的度(邻居数量)
- σ:激活函数(如 ReLU)
直观理解:
步骤1:聚合邻居
收集所有邻居的特征
步骤2:归一化
按邻居数量归一化(度归一化)
步骤3:线性变换
乘以权重矩阵 W
步骤4:激活
通过非线性激活函数
可视化流程:
┌──────────────────────────────────────────┐
│ 单层 GNN 的计算过程 │
├──────────────────────────────────────────┤
│ │
│ 初始状态: │
│ A(h_A) │
│ ╱ ╲ │
│ B(h_B) C(h_C) │
│ │
│ 对节点 A: │
│ │
│ Step 1: 聚合邻居特征 │
│ m_A = h_B + h_C │
│ │
│ Step 2: 归一化 │
│ m_A = m_A / √(2 × 2) [A和B都有2个邻居] │
│ │
│ Step 3: 线性变换 │
│ z_A = W · m_A │
│ │
│ Step 4: 结合自身 + 激活 │
│ h_A' = σ(W_self · h_A + z_A) │
│ │
│ 更新后: │
│ A(h_A') ← 包含了 B, C 的信息 │
│ ╱ ╲ │
│ B(h_B') C(h_C') │
│ │
└──────────────────────────────────────────┘
4. 消息传递机制
4.1 消息传递的三个阶段
┌────────────────────────────────────────┐
│ 消息传递神经网络 (MPNN) │
├────────────────────────────────────────┤
│ │
│ 阶段1:消息生成 (Message) │
│ m_ij = φ(h_i, h_j, e_ij) │
│ 每条边生成消息 │
│ │
│ 阶段2:消息聚合 (Aggregate) │
│ m_i = ⊕(m_ij) │
│ j∈N(i) │
│ 聚合所有邻居的消息 │
│ ⊕ 可以是:SUM, MEAN, MAX │
│ │
│ 阶段3:节点更新 (Update) │
│ h_i' = ψ(h_i, m_i) │
│ 更新节点表示 │
│ │
└────────────────────────────────────────┘
4.2 详细示例
场景:社交网络,预测用户兴趣
初始图:
A(爱运动:0.8)
╱ ╲
B(?) C(爱读书:0.6)
╲ ╱
D(?)
已知:A喜欢运动,C喜欢读书
未知:B, D 的兴趣?
目标:通过 GNN 预测 B, D 的兴趣
第一层消息传递:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
对节点 B:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
初始特征:
h_B = [?, ?] # 未知,可以初始化为随机向量
邻居:A, D
h_A = [0.8, 0.2] # [运动, 读书]
h_D = [0.3, 0.7] # 初始化
Step 1: 消息生成
从 A 到 B 的消息:
m_AB = W_msg · h_A
= [[0.5, 0.3], · [0.8]
[0.2, 0.4]] [0.2]
= [0.46, 0.24]
从 D 到 B 的消息:
m_DB = W_msg · h_D
= [0.27, 0.34]
Step 2: 消息聚合
m_B = MEAN(m_AB, m_DB)
= ([0.46, 0.24] + [0.27, 0.34]) / 2
= [0.365, 0.29]
Step 3: 节点更新
h_B' = ReLU(W_update · [h_B; m_B])
= ReLU([[...]] · [?, ?, 0.365, 0.29])
= [0.55, 0.45] # 新的表示
解释:
B 的新表示结合了邻居 A, D 的信息
偏向运动多一些(因为 A 影响更大)
多层传递的效果:
【1层 GNN】
A
╱│╲
B─C─D B 只能"看到"直接邻居 A, D
【2层 GNN】
A
╱│╲
B─C─D B 能"看到" 2-hop 邻居:A, C, D
(通过 A 和 D 间接感知 C)
【3层 GNN】
整个图的信息都能传播到 B
类比:
1层 = 你只知道朋友的信息
2层 = 你知道朋友的朋友的信息
3层 = 你知道更远的人的信息
4.3 不同的聚合函数
┌────────────────────────────────────────┐
│ 常见聚合函数对比 │
├────────────────────────────────────────┤
│ │
│ 1. SUM 聚合 │
│ m_i = Σ m_ij │
│ 优点:简单,保留总信息量 │
│ 缺点:受邻居数量影响大 │
│ │
│ 2. MEAN 聚合 │
│ m_i = (1/|N(i)|) Σ m_ij │
│ 优点:归一化,不受邻居数量影响 │
│ 缺点:丢失了节点度的信息 │
│ │
│ 3. MAX 聚合 │
│ m_i = MAX(m_ij) │
│ 优点:捕捉最显著特征 │
│ 缺点:忽略大部分信息 │
│ │
│ 4. ATTENTION 聚合 │
│ m_i = Σ α_ij · m_ij │
│ 优点:自适应权重,重要邻居影响大 │
│ 缺点:计算复杂度高 │
│ │
└────────────────────────────────────────┘
可视化对比:
场景:节点 A 有3个邻居
B(2)
╲
A(?) ← 预测 A 的值
╱ ╲
C(5) D(8)
【SUM 聚合】
m_A = 2 + 5 + 8 = 15
【MEAN 聚合】
m_A = (2 + 5 + 8) / 3 = 5
【MAX 聚合】
m_A = MAX(2, 5, 8) = 8
【ATTENTION 聚合】
假设权重:α_AB=0.2, α_AC=0.3, α_AD=0.5
m_A = 0.2×2 + 0.3×5 + 0.5×8 = 5.9
5. 常见的 GNN 架构
5.1 GCN(图卷积网络)
Graph Convolutional Network (Kipf & Welling, 2017)
公式:
H^(l+1) = σ(D̃^(-1/2) Ã D̃^(-1/2) H^(l) W^(l))
其中:
- Ã = A + I (邻接矩阵 + 自环)
- D̃ 是 Ã 的度矩阵
- H^(l) 是第 l 层的节点特征
- W^(l) 是权重矩阵
- σ 是激活函数
直观理解:
GCN 做了什么?
1. 每个节点聚合邻居的特征
2. 按度归一化(避免度大的节点主导)
3. 线性变换 + 激活
类比:
就像图像卷积,但是:
- 图像卷积:固定 3×3 窗口
- 图卷积:动态大小,取决于邻居数量
可视化:
┌────────────────────────────────────────┐
│ GCN 的计算流程 │
├────────────────────────────────────────┤
│ │
│ 输入图: │
│ A(x_A) │
│ ╱ ╲ │
│ B(x_B) C(x_C) │
│ ╲ ╱ │
│ D(x_D) │
│ │
│ GCN 层 1: │
│ h_A^(1) = σ(W^(0)·[x_A + x_B + x_C]) │
│ 聚合邻居 B, C 的特征 │
│ │
│ GCN 层 2: │
│ h_A^(2) = σ(W^(1)·[h_A^(1) + ...]) │
│ 再聚合一次,感知范围扩大 │
│ │
│ 输出: │
│ 每个节点的embedding,用于下游任务 │
│ │
└────────────────────────────────────────┘
代码示例:
import torch
import torch.nn as nn
class GCNLayer(nn.Module):
def __init__(self, in_features, out_features):
super().__init__()
self.linear = nn.Linear(in_features, out_features)
def forward(self, X, A):
"""
X: 节点特征矩阵 [N, in_features]
A: 归一化的邻接矩阵 [N, N]
"""
# 聚合邻居特征
support = torch.mm(A, X) # [N, in_features]
# 线性变换
output = self.linear(support) # [N, out_features]
return output
5.2 GraphSAGE(图采样聚合)
SAmple and aggreGatE (Hamilton et al., 2017)
核心思想:
不聚合所有邻居,而是采样固定数量的邻居
优点:
- 可扩展到大图
- 计算复杂度可控
- 支持 inductive learning(泛化到新节点)
公式:
h_i^(k) = σ(W^(k) · [h_i^(k-1) || AGG({h_j^(k-1), ∀j∈S(i)})])
S(i):从 N(i) 中采样的邻居子集
||:拼接操作
可视化:
┌────────────────────────────────────────┐
│ GraphSAGE vs GCN │
├────────────────────────────────────────┤
│ │
│ GCN:聚合所有邻居 │
│ │
│ N₁ N₂ N₃ N₄ N₅ │
│ ╲ │ │ │ ╱ │
│ ╲ │ │ │╱ │
│ ╲│ │ ╱ │
│ ╲ │╱ │
│ ╲│╱ │
│ A ← 聚合全部5个邻居 │
│ │
│ GraphSAGE:采样 K 个邻居 │
│ │
│ N₁ N₂ N₃ N₄ N₅ │
│ ✓ ✗ ✓ ✗ │
│ ╲ ╱ │
│ ╲ ╱ │
│ ╲ ╱ │
│ ╲ ╱ │
│ A ← 只采样2个(N₁, N₃) │
│ │
│ 好处: │
│ - 计算量可控(固定K个邻居) │
│ - 大图也能处理 │
│ - 每次采样不同,类似 dropout │
│ │
└────────────────────────────────────────┘
聚合函数的变体:
GraphSAGE 支持多种聚合器:
1. Mean Aggregator
AGG = MEAN({h_j, ∀j∈S(i)})
2. LSTM Aggregator
将邻居看作序列,用 LSTM 处理
3. Pooling Aggregator
AGG = MAX({σ(W·h_j), ∀j∈S(i)})
4. GCN Aggregator
类似标准 GCN
5.3 GAT(图注意力网络)
Graph Attention Network (Veličković et al., 2018)
核心思想:
不同邻居的重要性不同,用注意力机制加权
公式:
α_ij = softmax(e_ij)
e_ij = LeakyReLU(a^T · [W·h_i || W·h_j])
h_i' = σ(Σ α_ij · W·h_j)
j∈N(i)
α_ij:节点 j 对节点 i 的注意力权重
可视化:
┌────────────────────────────────────────┐
│ GAT 注意力机制 │
├────────────────────────────────────────┤
│ │
│ 节点 A 的邻居:B, C, D │
│ │
│ Step 1: 计算注意力分数 │
│ │
│ B(重要) ──── e_AB = 0.9 │
│ ╱ │
│ ╱ │
│ A ────────── e_AC = 0.3 ──── C(不重要)
│ ╲ │
│ ╲ │
│ D(中等) ──── e_AD = 0.6 │
│ │
│ Step 2: Softmax 归一化 │
│ α_AB = 0.45 (45%) │
│ α_AC = 0.15 (15%) │
│ α_AD = 0.40 (40%) │
│ │
│ Step 3: 加权聚合 │
│ h_A' = 0.45×h_B + 0.15×h_C + 0.40×h_D│
│ │
│ 结果:B 的影响最大! │
│ │
└────────────────────────────────────────┘
多头注意力:
类似 Transformer,GAT 也用多头注意力
单头:
h_i' = σ(Σ α_ij^(1) · W^(1)·h_j)
多头(K个头):
h_i' = ||_{k=1}^K σ(Σ α_ij^(k) · W^(k)·h_j)
或者(最后一层):
h_i' = σ(1/K Σ_{k=1}^K Σ α_ij^(k) · W^(k)·h_j)
好处:
- 不同头关注不同特征
- 类似 CNN 的多通道
- 增强表达能力
可视化多头注意力:
┌────────────────────────────────────────┐
│ 多头注意力(3个头) │
├────────────────────────────────────────┤
│ │
│ Head 1: 关注"年龄"相似性 │
│ B(25岁) ──0.8──→ A(24岁) │
│ C(50岁) ──0.2──→ │
│ │
│ Head 2: 关注"职业"相似性 │
│ B(工程师) ──0.3──→ A(工程师) │
│ C(教师) ──0.7──→ │
│ │
│ Head 3: 关注"地域"相似性 │
│ B(北京) ──0.6──→ A(北京) │
│ C(上海) ──0.4──→ │
│ │
│ 最终:拼接或平均三个头的输出 │
│ │
└────────────────────────────────────────┘
5.4 GNN 架构对比
┌────────────────────────────────────────────────────────┐
│ 主流 GNN 架构对比 │
├────────────────────────────────────────────────────────┤
│ │
│ 模型 │ 聚合方式 │ 特点 │ 复杂度 │
│ ─────────┼──────────┼───────────────┼────────────│
│ GCN │ 均值 │ 简单高效 │ O(E) │
│ │ │ 谱方法 │ │
│ ─────────┼──────────┼───────────────┼────────────│
│ GraphSAGE│ 采样 │ 可扩展 │ O(K×N) │
│ │ │ Inductive │ K=采样数 │
│ ─────────┼──────────┼───────────────┼────────────│
│ GAT │ 注意力 │ 自适应权重 │ O(E×K) │
│ │ │ 可解释性强 │ K=头数 │
│ ─────────┼──────────┼───────────────┼────────────│
│ GIN │ SUM │ 表达力最强 │ O(E) │
│ │ │ WL test │ │
│ │
└────────────────────────────────────────────────────────┘
6. 图的表示学习
6.1 什么是图表示学习?
目标:
将高维的图数据(节点、边、图)
映射到低维的向量空间(embedding)
原始图:
A ─── B
│ │
C ─── D
embedding:
A → [0.2, 0.8, 0.5, ...] (d维向量)
B → [0.3, 0.7, 0.6, ...]
C → [0.1, 0.9, 0.4, ...]
D → [0.25, 0.75, 0.55, ...]
好处:
✅ 可以用传统机器学习算法
✅ 保留图的结构信息
✅ 降维,节省计算
6.2 好的 embedding 应该满足什么?
原则1:结构相似性
图中相近的节点,embedding 也应该相近
A ─── B embedding(A) ≈ embedding(B)
原则2:语义相似性
属性相似的节点,embedding 应该相近
人A(医生) embedding(人A) ≈ embedding(人B)
人B(医生)
原则3:任务相关性
embedding 应该有利于下游任务
例如:节点分类任务
同类节点的 embedding 应该聚在一起
可视化:
【原始图】
●A────●B 同一社区
│ │
●C────●D
远距离
●E────●F 另一社区
│ │
●G────●H
【embedding 空间】(降到2维)
●A ●B
●C ●D ← 第一社区聚在一起
(距离远)
●E ●F
●G ●H ← 第二社区聚在一起
保留了图的社区结构!
6.3 Node2Vec(经典方法)
核心思想:
用随机游走采样节点序列
类似 Word2Vec,学习节点 embedding
算法流程:
1. 在图上随机游走,生成节点序列
例如:A → B → D → C → A → ...
2. 把节点序列看作"句子"
节点看作"单词"
3. 用 Skip-gram 模型训练
最大化:P(context | node)
优点:
- 无监督学习
- 灵活的游走策略(BFS vs DFS)
缺点:
- 不是端到端学习
- 不能处理新节点(transductive)
随机游走的策略:
【BFS 风格】(breadth-first)
优先探索近邻
A
╱│╲
B C D 先访问 A 的所有邻居
\│/ 再深入
E
路径:A → B → C → D → E → ...
捕捉:局部社区结构(micro-view)
【DFS 风格】(depth-first)
优先深入
A
│
B
│
C 一直深入
│
D
路径:A → B → C → D → ...
捕捉:全局结构角色(macro-view)
【Node2Vec】结合两者
用参数 p, q 控制:
- p:返回概率(避免回到刚访问的节点)
- q:BFS vs DFS 偏好
6.4 GNN 的表示学习
GNN vs Node2Vec:
Node2Vec:
- 基于随机游走
- 独立训练每个节点
- 不共享参数
GNN:
- 基于消息传递
- 所有节点共享权重矩阵
- 端到端训练
- 可以处理新节点(inductive)
选择:
- 图静态、无节点特征 → Node2Vec
- 图动态、有节点特征 → GNN
- 大规模图 → GraphSAGE (GNN变体)
7. 实际应用场景
7.1 社交网络分析
┌────────────────────────────────────────┐
│ 社交网络应用 │
├────────────────────────────────────────┤
│ │
│ 任务1:好友推荐 │
│ 给定:社交图 │
│ 预测:哪些用户可能成为朋友? │
│ 方法:链接预测(Link Prediction) │
│ │
│ 任务2:社区发现 │
│ 给定:社交图 │
│ 目标:找出紧密连接的用户群体 │
│ 方法:图聚类(Graph Clustering) │
│ │
│ 任务3:影响力传播 │
│ 给定:社交图 + 用户行为 │
│ 预测:信息如何在网络中传播? │
│ 方法:时序图神经网络 │
│ │
└────────────────────────────────────────┘
可视化:好友推荐
当前好友关系:
你 ────── 张三 ────── 李四
│ │
│ │
王五 ────── 赵六 ────── 李四
分析:
- 你和李四有2个共同好友(张三、王五)
- GNN 学习到:共同好友多 → 可能认识
推荐:
你 ─────?─────→ 李四 (推荐成为好友)
7.2 推荐系统
┌────────────────────────────────────────┐
│ 推荐系统应用 │
├────────────────────────────────────────┤
│ │
│ 异质图:用户-物品二部图 │
│ │
│ 用户1 ──购买──→ 商品A │
│ │ ↑ │
│ 浏览 购买 │
│ ↓ │ │
│ 商品B ←──购买── 用户2 │
│ │
│ GNN 方法: │
│ 1. 用户和商品都有 embedding │
│ 2. 通过消息传递更新 embedding │
│ 3. 预测用户对未交互商品的兴趣 │
│ │
│ 优势: │
│ - 利用高阶连接(朋友的朋友) │
│ - 缓解冷启动问题 │
│ - 可解释性(通过注意力权重) │
│ │
└────────────────────────────────────────┘
实际案例:Pinterest(图钉)
Pinterest 用 PinSage(基于 GraphSAGE)做推荐:
图结构:
Pin(图片) ─────→ Board(画板)
│ │
相似性 包含
│ │
Pin(图片) ←───── User(用户)
做法:
1. 构建 Pin-Pin 图(相似图片连接)
2. 用 GraphSAGE 学习 Pin 的 embedding
3. 对于用户喜欢的 Pin,推荐相似的 Pin
效果:
比传统方法提升 20% 点击率
7.3 药物发现
┌────────────────────────────────────────┐
│ 药物发现中的 GNN │
├────────────────────────────────────────┤
│ │
│ 任务1:分子性质预测 │
│ 分子 → 图 │
│ 节点:原子 │
│ 边:化学键 │
│ 预测:毒性、溶解度、生物活性 │
│ │
│ 任务2:药物-靶点相互作用预测 │
│ 输入:药物分子 + 蛋白质结构 │
│ 输出:是否会结合?结合强度? │
│ 应用:虚拟筛选,加速新药开发 │
│ │
│ 任务3:化学反应预测 │
│ 输入:反应物分子 │
│ 输出:可能的产物 │
│ 方法:图到图的生成模型 │
│ │
└────────────────────────────────────────┘
可视化:分子图
阿司匹林分子:
O
║
H₃C─C─O─●─●─●─●─COOH
│ │ │ │
●─●─●─●
转换为图:
节点:C, H, O 原子
边:单键、双键
节点特征:原子类型、电荷、杂化态等
边特征:键类型、键长等
GNN 任务:
输入:分子图
输出:是否有药效?毒性如何?
7.4 交通预测
┌────────────────────────────────────────┐
│ 交通流量预测 │
├────────────────────────────────────────┤
│ │
│ 问题设定: │
│ 节点:路口/路段 │
│ 边:道路连接 │
│ 节点特征:历史流量、时间、天气等 │
│ 目标:预测未来流量 │
│ │
│ 方法:时空图神经网络(STGNN) │
│ 空间维度:用 GNN 捕捉路网结构 │
│ 时间维度:用 RNN/TCN 捕捉时序模式 │
│ │
│ 应用: │
│ - 交通拥堵预警 │
│ - 路径规划优化 │
│ - 信号灯控制 │
│ │
└────────────────────────────────────────┘
时空图的可视化:
【空间图】路网结构
路口A ─── 路口B
│ │
│ │
路口C ─── 路口D
【时间序列】某个路口的流量
流量
↑
100│ ╱╲ ╱╲
50│ ╱ ╲╱ ╲
0└──────────────────→ 时间
早 中 晚 夜
【时空图神经网络】
同时考虑:
1. 路口A的流量受邻近路口影响(空间)
2. 流量有周期性规律(时间)
7.5 知识图谱
┌────────────────────────────────────────┐
│ 知识图谱应用 │
├────────────────────────────────────────┤
│ │
│ 结构: │
│ (实体1, 关系, 实体2) 三元组 │
│ 例如:(乔布斯, 创立, 苹果公司) │
│ │
│ 任务1:知识图谱补全 │
│ 给定:(?, 创立, 特斯拉) │
│ 预测:? = 马斯克 │
│ 方法:链接预测 │
│ │
│ 任务2:实体对齐 │
│ 不同知识图谱中的相同实体对齐 │
│ 例如:维基百科的"北京" = 百度百科的"北京" │
│ │
│ 任务3:问答系统 │
│ 问题:"谁创立了苹果公司?" │
│ 在知识图谱上推理,得到答案 │
│ │
└────────────────────────────────────────┘
知识图谱示例:
创立
乔布斯 ─────→ 苹果公司
│ │
出生于 总部
│ │
↓ ↓
旧金山 ←────── 库比蒂诺
位于
GNN 应用:
1. 学习实体和关系的 embedding
2. 预测缺失的三元组
3. 多跳推理(A→B→C 传递关系)
8. 代码实现示例
8.1 使用 PyTorch Geometric
# 安装
# pip install torch-geometric
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
# 1. 加载数据集(Cora 论文引用网络)
dataset = Planetoid(root='/tmp/Cora', name='Cora')
data = dataset[0]
print(f'数据集: {dataset}')
print(f'节点数: {data.num_nodes}')
print(f'边数: {data.num_edges}')
print(f'特征维度: {data.num_node_features}')
print(f'类别数: {dataset.num_classes}')
# 数据结构:
# data.x: 节点特征矩阵 [num_nodes, num_features]
# data.edge_index: 边索引 [2, num_edges]
# data.y: 节点标签 [num_nodes]
# 2. 定义 GCN 模型
class GCN(torch.nn.Module):
def __init__(self, num_features, num_classes):
super().__init__()
# 第一层 GCN
self.conv1 = GCNConv(num_features, 16)
# 第二层 GCN
self.conv2 = GCNConv(16, num_classes)
def forward(self, data):
x, edge_index = data.x, data.edge_index
# 第一层
x = self.conv1(x, edge_index)
x = F.relu(x)
x = F.dropout(x, p=0.5, training=self.training)
# 第二层
x = self.conv2(x, edge_index)
return F.log_softmax(x, dim=1)
# 3. 创建模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = GCN(dataset.num_node_features, dataset.num_classes).to(device)
data = data.to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
# 4. 训练
def train():
model.train()
optimizer.zero_grad()
out = model(data)
loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
return loss.item()
# 5. 测试
def test():
model.eval()
out = model(data)
pred = out.argmax(dim=1)
# 训练集准确率
train_correct = pred[data.train_mask] == data.y[data.train_mask]
train_acc = int(train_correct.sum()) / int(data.train_mask.sum())
# 测试集准确率
test_correct = pred[data.test_mask] == data.y[data.test_mask]
test_acc = int(test_correct.sum()) / int(data.test_mask.sum())
return train_acc, test_acc
# 6. 训练循环
for epoch in range(1, 201):
loss = train()
if epoch % 20 == 0:
train_acc, test_acc = test()
print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}, '
f'Train Acc: {train_acc:.4f}, Test Acc: {test_acc:.4f}')
8.2 从零实现简单 GCN
import torch
import torch.nn as nn
import numpy as np
class SimpleGCN(nn.Module):
"""
简单的 GCN 实现(教学用)
"""
def __init__(self, num_features, num_classes, hidden_dim=16):
super().__init__()
# 两层 GCN
self.weight1 = nn.Linear(num_features, hidden_dim)
self.weight2 = nn.Linear(hidden_dim, num_classes)
def normalize_adjacency(self, A):
"""
归一化邻接矩阵
à = D^(-1/2) A D^(-1/2)
"""
# 添加自环
A_hat = A + torch.eye(A.size(0), device=A.device)
# 计算度矩阵
D = torch.diag(A_hat.sum(dim=1))
# D^(-1/2)
D_inv_sqrt = torch.pow(D, -0.5)
D_inv_sqrt[torch.isinf(D_inv_sqrt)] = 0.
# D^(-1/2) A D^(-1/2)
A_norm = D_inv_sqrt @ A_hat @ D_inv_sqrt
return A_norm
def forward(self, X, A):
"""
前向传播
X: 节点特征 [num_nodes, num_features]
A: 邻接矩阵 [num_nodes, num_nodes]
"""
# 归一化邻接矩阵
A_norm = self.normalize_adjacency(A)
# 第一层
H = A_norm @ X # 聚合邻居
H = self.weight1(H) # 线性变换
H = torch.relu(H) # 激活
# 第二层
H = A_norm @ H
H = self.weight2(H)
return torch.log_softmax(H, dim=1)
# 使用示例
if __name__ == "__main__":
# 构造一个小图
num_nodes = 5
num_features = 3
num_classes = 2
# 节点特征(随机)
X = torch.randn(num_nodes, num_features)
# 邻接矩阵
A = torch.tensor([
[0, 1, 1, 0, 0], # 节点0连接1,2
[1, 0, 1, 1, 0], # 节点1连接0,2,3
[1, 1, 0, 1, 1], # 节点2连接0,1,3,4
[0, 1, 1, 0, 1], # 节点3连接1,2,4
[0, 0, 1, 1, 0], # 节点4连接2,3
], dtype=torch.float)
# 标签
y = torch.tensor([0, 0, 1, 1, 1])
# 创建模型
model = SimpleGCN(num_features, num_classes)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
# 训练
for epoch in range(100):
optimizer.zero_grad()
# 前向传播
out = model(X, A)
# 计算损失
loss = nn.functional.nll_loss(out, y)
# 反向传播
loss.backward()
optimizer.step()
if (epoch + 1) % 20 == 0:
# 预测
pred = out.argmax(dim=1)
acc = (pred == y).float().mean()
print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}, Acc: {acc:.4f}')
8.3 图注意力网络(GAT)实现
import torch
import torch.nn as nn
import torch.nn.functional as F
class GATLayer(nn.Module):
"""
图注意力层
"""
def __init__(self, in_features, out_features, dropout=0.6, alpha=0.2):
super().__init__()
self.in_features = in_features
self.out_features = out_features
# 权重矩阵
self.W = nn.Parameter(torch.empty(size=(in_features, out_features)))
nn.init.xavier_uniform_(self.W.data)
# 注意力参数
self.a = nn.Parameter(torch.empty(size=(2*out_features, 1)))
nn.init.xavier_uniform_(self.a.data)
self.leakyrelu = nn.LeakyReLU(alpha)
self.dropout = nn.Dropout(dropout)
def forward(self, h, adj):
"""
h: 节点特征 [N, in_features]
adj: 邻接矩阵 [N, N]
"""
# 线性变换
Wh = torch.mm(h, self.W) # [N, out_features]
# 计算注意力系数
a_input = self._prepare_attentional_mechanism_input(Wh)
e = self.leakyrelu(torch.matmul(a_input, self.a).squeeze(2))
# 只保留有边的注意力
zero_vec = -9e15 * torch.ones_like(e)
attention = torch.where(adj > 0, e, zero_vec)
# Softmax
attention = F.softmax(attention, dim=1)
attention = self.dropout(attention)
# 加权聚合
h_prime = torch.matmul(attention, Wh)
return h_prime, attention
def _prepare_attentional_mechanism_input(self, Wh):
"""
准备注意力机制的输入
"""
N = Wh.size()[0]
# 扩展维度
Wh_repeated_in_chunks = Wh.repeat_interleave(N, dim=0)
Wh_repeated_alternating = Wh.repeat(N, 1)
# 拼接
all_combinations_matrix = torch.cat([
Wh_repeated_in_chunks,
Wh_repeated_alternating
], dim=1)
return all_combinations_matrix.view(N, N, 2 * self.out_features)
class GAT(nn.Module):
"""
多头图注意力网络
"""
def __init__(self, num_features, num_classes, hidden=8, num_heads=8, dropout=0.6):
super().__init__()
# 第一层:多头注意力
self.attentions = nn.ModuleList([
GATLayer(num_features, hidden, dropout)
for _ in range(num_heads)
])
# 第二层:单头注意力
self.out_att = GATLayer(hidden * num_heads, num_classes, dropout)
self.dropout = nn.Dropout(dropout)
def forward(self, x, adj):
# 第一层:多头拼接
x = self.dropout(x)
x = torch.cat([att(x, adj)[0] for att in self.attentions], dim=1)
x = F.elu(x)
# 第二层
x = self.dropout(x)
x, attention = self.out_att(x, adj)
return F.log_softmax(x, dim=1), attention
# 使用示例
if __name__ == "__main__":
# 创建模型
model = GAT(num_features=10, num_classes=3, hidden=8, num_heads=4)
# 随机数据
num_nodes = 20
X = torch.randn(num_nodes, 10)
A = torch.randint(0, 2, (num_nodes, num_nodes)).float()
# 前向传播
output, attention = model(X, A)
print(f"输出形状: {output.shape}") # [20, 3]
print(f"注意力权重形状: {attention.shape}") # [20, 20]
9. 进阶话题
9.1 Over-smoothing 问题
问题:
GNN 层数过多时,所有节点的表示会趋于相同
原因:
每一层都聚合邻居信息
多层后,所有节点都看到了整个图
表示变得无法区分
可视化:
初始 → 2层 → 4层 → 8层
● ● ● ●
╱│╲ ╱│╲ ╱│╲ ╱│╲
●─●─● ●─●─● ●─●─● ●─●─●
表示:
不同 略相似 很相似 几乎相同
h_i ≠ h_j → h_i ≈ h_j → h_i ≈ h_j ≈ h_k
解决方案:
方法1:跳跃连接(Skip Connection)
h_i^(k) = h_i^(k-1) + GNN(h_i^(k-1))
保留初始信息
方法2:Dropout + DropEdge
随机丢弃边,防止过度平滑
方法3:预训练 + 微调
先用浅层GNN,再精调
方法4:图正则化
添加正则项,鼓励节点表示差异化
方法5:采样邻居
不聚合所有邻居(GraphSAGE)
9.2 GNN 的可解释性
问题:
GNN 是黑盒模型,难以理解预测依据
解决方案:
方法1:可视化注意力权重(GAT)
优势:
- 直接显示哪些邻居重要
- 可以画热力图
示例:
节点A的注意力分布:
B: ████████ 80% ← 最重要
C: ██ 15%
D: █ 5%
结论:A的预测主要受B影响
方法2:GNNExplainer
思想:
找到最小的子图,能够解释预测结果
算法:
1. 遮盖掉某些边
2. 看预测是否改变
3. 找到最关键的边
可视化:
●───●───●
╲ │ ╱ 完整图
● ●
●───● ●
│ 关键子图(解释预测)
● ●
方法3:反事实解释
问题:
如果改变某个边/特征,预测会如何变化?
例子:
原图:A-B-C,预测A是"研究生"
反事实:去掉A-B边,预测A变成"本科生"
结论:A-B的连接是关键!
9.3 动态图神经网络
问题:
真实世界的图是动态变化的
- 社交网络:用户不断建立/删除好友
- 交通网络:流量实时变化
- 知识图谱:新知识不断加入
传统GNN:
只能处理静态图
动态GNN:
处理时序变化的图
方法1:时序图神经网络(TGNN)
思想:
结合GNN和RNN/LSTM
架构:
时刻1 时刻2 时刻3
G_1 → G_2 → G_3
↓ ↓ ↓
GNN GNN GNN
↓ ↓ ↓
h_1 → h_2 → h_3
└─────LSTM─────┘
空间:GNN捕捉图结构
时间:LSTM捕捉演化
方法2:连续时间动态图
事件流:
t=0.5: 用户A关注B
t=1.2: 用户B发帖子
t=2.1: 用户C点赞
...
方法:
- 每个事件触发更新
- 用时间编码(time encoding)
- 持续学习(continual learning)
9.4 异质图神经网络
异质图:
多种类型的节点和边
例子:学术网络
节点类型:论文、作者、会议
边类型:写作、发表、引用
挑战:
- 不同类型节点的特征空间不同
- 不同类型边的语义不同
解决:异质图神经网络(HAN, HGT)
HAN(Heterogeneous Graph Attention Network)
核心思想:
1. 元路径(Meta-path)
定义不同类型节点间的路径
例如:作者-论文-作者(合作关系)
作者-论文-会议(研究领域)
2. 节点级注意力
聚合同一元路径上的邻居
3. 语义级注意力
融合不同元路径的信息
可视化:
元路径1: A─论文─A (合作者)
元路径2: A─论文─会议 (领域)
对作者A:
1. 沿元路径1聚合
2. 沿元路径2聚合
3. 用注意力融合两种信息
10. 常见问题解答
Q1: GNN 和 CNN 的区别?
CNN(卷积神经网络):
- 适用于:规则网格数据(图像)
- 卷积核:固定大小(如3×3)
- 邻居:固定数量(8个)
- 平移不变性
GNN(图神经网络):
- 适用于:不规则图数据
- "卷积":动态,取决于邻居数
- 邻居:变化的(1个、10个、100个...)
- 置换不变性
可以理解为:
CNN是GNN的特例(用于网格图)
Q2: GNN 可以处理多大的图?
小图(<10K节点):
所有GNN都可以
可以存整个邻接矩阵
中等图(10K-1M节点):
用采样方法(GraphSAGE)
minibatch训练
大图(>1M节点):
必须用采样 + 分布式训练
例如:Pinterest有数十亿节点
技巧:
1. 邻居采样(固定K个邻居)
2. 层采样(每层采样)
3. 子图采样(训练时只用子图)
4. 分布式GNN(多机多卡)
Q3: 如何选择 GNN 架构?
决策树:
1. 图的规模?
小 (<10K) → GCN/GAT 都可以
大 (>1M) → GraphSAGE
2. 是否有节点特征?
有 → 直接用GNN
无 → 先用Node2Vec预训练
3. 需要可解释性?
是 → GAT(可视化注意力)
否 → GCN(更简单快速)
4. 图是否动态?
是 → 动态GNN(TGNN)
否 → 静态GNN
5. 是否异质图?
是 → HAN/HGT
否 → GCN/GAT/GraphSAGE
Q4: GNN 的训练很慢怎么办?
加速技巧:
1. 硬件加速
- 用GPU(10-100倍加速)
- 多GPU并行
2. 采样技巧
- 邻居采样(GraphSAGE)
- 层采样(FastGCN)
- 子图采样(ClusterGCN)
3. 模型简化
- 减少层数(2-3层通常够用)
- 减少隐藏维度
- 去掉非必要组件
4. 训练技巧
- 预计算邻接矩阵的归一化
- 缓存中间结果
- 混合精度训练(FP16)
5. 分布式训练
- 数据并行
- 模型并行
Q5: GNN 会过拟合吗?如何防止?
会!尤其是小图上。
防止过拟合的方法:
1. Dropout
- 节点 Dropout
- 边 Dropout(DropEdge)
2. 正则化
- L2 正则
- 图正则化
3. 早停(Early Stopping)
- 监控验证集性能
- 性能不再提升时停止
4. 数据增强
- 添加边(增加连接)
- 删除边(提高鲁棒性)
- 节点特征加噪声
5. 模型集成
- 训练多个GNN
- 投票或平均预测
6. 预训练
- 在大图上预训练
- 在小图上微调
Q6: 如何处理新加入的节点(Inductive Learning)?
问题:
训练时没见过的节点,如何预测?
Transductive方法(不行):
- GCN:必须重新训练整个图
- Node2Vec:每个节点独立embedding
Inductive方法(可以):
- GraphSAGE:学习聚合函数,不是固定embedding
- GAT:注意力机制泛化到新节点
关键:
学习的是"如何聚合邻居"
而不是"每个节点的embedding"
例子:
训练:节点A, B, C
测试:新节点D
GraphSAGE:
1. 看D的邻居(假设是A, B)
2. 用学到的聚合函数处理A, B
3. 得到D的embedding
📚 总结
核心要点
图神经网络(GNN):
✅ 处理图结构数据的深度学习方法
✅ 通过消息传递机制学习节点/图表示
✅ 结合了图论和神经网络
核心思想:
节点 → 聚合邻居 → 更新自己 → 多轮传递
三大任务:
1. 节点分类(Node Classification)
2. 链接预测(Link Prediction)
3. 图分类(Graph Classification)
常用架构:
- GCN:简单高效,谱方法
- GraphSAGE:可扩展,采样聚合
- GAT:注意力机制,可解释
学习路径
入门阶段(1-2周):
□ 理解图的基本概念
□ 掌握消息传递机制
□ 实现简单的GCN
进阶阶段(1-2月):
□ 学习主流GNN架构(GCN, GAT, GraphSAGE)
□ 在公开数据集上实验
□ 理解over-smoothing等问题
高级阶段(3-6月):
□ 异质图、动态图
□ 大规模图的处理
□ 最新研究论文
□ 实际应用项目
推荐资源
入门教程:
1. Stanford CS224W - 图机器学习
2. Distill.pub - GNN可视化教程
3. PyTorch Geometric 官方教程
论文阅读:
1. GCN: "Semi-Supervised Classification with GCN"
2. GraphSAGE: "Inductive Representation Learning"
3. GAT: "Graph Attention Networks"
代码库:
1. PyTorch Geometric(最流行)
2. DGL(Deep Graph Library)
3. Spektral(Keras-based)
实战项目:
1. Cora论文分类(入门)
2. 社交网络链接预测(进阶)
3. 分子性质预测(高级)
未来方向
当前研究热点:
1. 大规模图的高效训练
2. 动态图、时序图
3. 图生成(Graph Generation)
4. 图Transformer
5. GNN + LLM 结合
6. 可解释性
7. 鲁棒性和对抗攻击
应用前沿:
1. 药物发现(AI制药)
2. 蛋白质结构预测
3. 推荐系统
4. 交通预测
5. 金融风控
6. 知识图谱
祝你在图深度学习的道路上越走越远!🚀
最后更新:2025年11月
作者:Echo
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)