AI/ML 详解:图机器学习

Day 37 · 2026-06-23
面向:有编程经验的非 AI 方向工程师

前面 36 期讲的模型几乎都假设输入是序列(文本)或网格(图像)。但你最熟悉的很多数据其实是:服务调用拓扑、社交关系、知识图谱、数据库外键网络。这些数据的核心信息藏在连接里,不是藏在单个节点里。今天讲机器学习怎么直接吃下"图"这种结构——它的核心机制只有一个:让节点和邻居交换信息

消息传递Message Passing / GNN

核心机制GNN 基石
一句话类比

消息传递就是分布式系统里的 gossip 协议:每一轮,每个节点把自己的状态发给所有邻居,同时收集邻居发来的状态,聚合(aggregate)后更新自己。跑 K 轮,每个节点就"知道"了 K 跳以内整个邻域的信息——和 gossip 协议靠多轮传播让全网状态最终收敛,是同一个套路。

它解决什么问题 + 工作机制

图数据有个要命的特性:没有固定顺序,节点的邻居数量还不一样。文本能排成序列喂给 Transformer,图像能排成网格喂给 CNN,但图——节点 A 有 3 个邻居、节点 B 有 500 个邻居,你没法把它塞进一个固定形状的张量。早期做法是手工提取图特征(度数、中心性…),费力且丢信息。

消息传递神经网络(MPNN,Gilmer 等 2017)把所有图神经网络统一成一个框架:每一层做三件事——

第 k 层:节点 v 的一次更新

邻居 u₁邻居 u₂邻居 u₃
↓ ① Message:每个邻居生成消息 m = f(h_u)
m₁m₂m₃
↓ ② Aggregate:求和/平均/max(对顺序不敏感!)
聚合结果 = Σ mᵢ
↓ ③ Update:和自己旧状态合并 → 新状态
h_v 新 ← 跑 K 层 = 看 K 跳邻域

关键设计是聚合函数必须对顺序不敏感(permutation-invariant)——求和、平均、取最大,这些不管邻居以什么顺序进来,结果都一样。这正好解决了"图没有顺序"的难题。这就是所有 GNN 的母机制:GCN、GAT 不过是把"消息"和"聚合"两步换了不同公式而已。

代码示例
# 纯 numpy 演示一层消息传递的本质(不依赖框架)
import numpy as np
# A: 邻接矩阵(谁连谁)  H: 每个节点的特征向量
A = np.array([[0,1,1,0],[1,0,1,0],[1,1,0,1],[0,0,1,0]])
H = np.random.randn(4, 8)   # 4 个节点, 每个 8 维
W = np.random.randn(8, 16)  # 可学习的变换矩阵

# ① Message + ② Aggregate: A @ H 就是"把每个邻居的特征加起来"
agg = A @ H                  # (4,4)@(4,8) = (4,8) 邻居特征求和
# ③ Update: 线性变换 + 非线性激活
H_next = np.maximum(0, agg @ W)  # ReLU, 得到 (4,16) 新表示
# 叠两层 → 每个节点就融合了 2 跳邻域的信息
常见误区 + 实践场景
"层数越多看得越远越好"——错。GNN 堆太多层会过度平滑(over-smoothing):跑太多轮 gossip 后,所有节点的状态趋同,最后大家长得一模一样、无法区分。这和分布式系统里"过度同步导致信息熵丢失"是一个直觉。实践中 GNN 通常只有 2-4 层,远浅于 Transformer 的几十层。
📌 超级个体场景:把你维护的微服务调用图建模成图——节点是服务,边是调用关系,节点特征是 QPS/延迟/错误率。GNN 能学到"某个服务的健康状态如何被上下游影响",比孤立看单服务指标更能预测级联故障。
Takeaway + 思考题
💡 GNN 的全部魔法只有一句:节点反复和邻居交换并聚合信息。理解了 gossip,你就理解了 GNN。
🤔 gossip 协议追求"全网收敛到一致",而 GNN 怕"所有节点趋同(过度平滑)"——同一个机制,为什么一个把收敛当目标、一个把收敛当灾难?

图卷积网络Graph Convolutional Network (GCN)

谱方法简化最经典 GNN
一句话类比

GCN 的聚合就是带归一化的邻居平均——像数据库里对一对多关系做 AVG() 聚合,但多了一步按"双方度数"加权。直觉:一个邻居如果自己连了 1000 个节点,它发给你的消息可信度应该打折(它对谁都说同样的话);一个只连你的邻居,消息更"专属"、权重更高。

它解决什么问题 + 工作机制

GCN(Kipf & Welling 2016)是把复杂的"谱图卷积"理论一路简化到极致,最后得到一个干净到惊人的公式。上一张卡里我们用 A @ H 做朴素求和,但它有两个毛病:(1) 没把节点自己算进去;(2) 高度数节点的聚合值会爆炸。GCN 的修正只有一行:

H⁽ˡ⁺¹⁾ = σ( D̃ Ã D̃ H⁽ˡ⁾ W )

别被公式吓到,逐个拆——Ã = A + I:邻接矩阵加上单位矩阵,就是"把自己也当成自己的邻居"(self-loop),这样更新时不会丢掉自身信息。 是度矩阵(每个节点连了几条边)。 Ã D̃ 这一坨叫对称归一化:给每条边 i→j 的权重除以 √(deg_i · deg_j),让高度数节点的影响被压下去。W 是可学习权重,σ 是激活函数。整句话翻译成人话:每个节点 = 激活( 归一化加权的邻居(含自己)平均,再做一次线性变换 )

代码示例
# PyTorch Geometric: 工业界最常用的图学习库
import torch
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid

dataset = Planetoid(root="/tmp/Cora", name="Cora")  # 论文引用图
data = dataset[0]   # 2708 篇论文, 边=引用关系

class GCN(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.c1 = GCNConv(dataset.num_features, 16)
        self.c2 = GCNConv(16, dataset.num_classes)
    def forward(self, x, edge_index):
        x = torch.relu(self.c1(x, edge_index))  # 第1跳
        return self.c2(x, edge_index)            # 第2跳→分类
# 仅 2 层就能在 Cora 上达到 ~81% 节点分类准确率
常见误区 + 实践场景
"GCN 给每个邻居的权重是学出来的"——错。GCN 的边权重完全由图结构(度数)固定死,不随训练改变、也不区分邻居重要性。所有邻居在归一化后被"一视同仁"地平均。这个局限正是下一张卡 GAT 要解决的问题。
📌 决策辅助场景:把你的知识库笔记建成引用图(笔记=节点,互相引用=边),用 GCN 做半监督分类——你只手工标注 5% 的笔记主题,GCN 就能沿引用结构把标签"扩散"到其余 95%,自动归类整个知识库。
Takeaway + 思考题
💡 GCN = 自带 self-loop 的、按度数归一化的邻居平均 + 线性变换。一个公式背后是整套谱图理论的极简化。
🤔 GCN 用"度数"决定邻居权重,本质是假设"连接越少的邻居越重要"。这个假设在你的微服务图里成立吗?什么场景下它会严重误判?

图注意力网络Graph Attention Network (GAT)

注意力上图自适应权重
一句话类比

如果说 GCN 是"按固定规则平均所有邻居",GAT 就是给每个邻居装了一个智能加权的负载均衡器:权重不再由度数写死,而是根据"我和这个邻居有多相关"动态算出来。这正是 Day 1 学过的 attention 机制——只不过这次的"序列"是你的邻居集合。

它解决什么问题 + 工作机制

GCN 的死穴:所有邻居权重被结构固定。但现实里邻居重要性千差万别——你的社交网络里,最亲密的 1 个朋友比 200 个点赞之交对你的影响大得多。GAT(Veličković 等 2017)把 Transformer 的注意力搬到图上:对节点 i 的每个邻居 j,先算一个注意力系数 αᵢⱼ,表示"j 对 i 有多重要",再用它加权聚合。

机制三步:(1) 把 i、j 的特征拼起来过一个小网络,得到原始分数 eᵢⱼ;(2) 对 i 的所有邻居的分数做 softmax 归一化(保证权重和为 1,跟 attention 一模一样);(3) 按 αᵢⱼ 加权求和邻居特征。和 GCN 最大的区别:

中心节点 i 聚合 3 个邻居

GCN(权重由度数固定):
j₁ ×0.33j₂ ×0.33j₃ ×0.33 ← 一视同仁

GAT(权重学出来、随内容变):
j₁ ×0.7j₂ ×0.2j₃ ×0.1 ← α 由相关性决定
↑ 同一张图,GAT 能"重点关注"真正相关的邻居

和 Transformer 一样,GAT 也用多头注意力(multi-head):并行跑多套独立的注意力再拼接,稳定训练、捕捉邻居的多种关系。代价是计算量比 GCN 大——天下没有免费的自适应。

代码示例
from torch_geometric.nn import GATConv

# 把上一张卡的 GCNConv 直接换成 GATConv 即可
# heads=8: 8 个注意力头并行(类比 Transformer 多头)
self.c1 = GATConv(in_dim, 8, heads=8)       # 输出 8×8=64 维
self.c2 = GATConv(64, num_classes, heads=1)  # 最后一层单头

# forward 与 GCN 完全相同; 区别全在内部:
# GATConv 会为每条边学一个注意力系数 α, 而非用度数固定
# 训练后可导出 α 看模型"重点关注了哪些邻居"——天然可解释
常见误区 + 实践场景
"GAT 永远比 GCN 强,无脑选 GAT"——错。在同质性强(邻居大多同类)、结构信息已足够的图上,GCN 又快又好,GAT 多出来的注意力反而可能过拟合。注意力的价值在邻居异质、重要性差异大时才显现。选模型看数据特性,不看"谁更新"。
📌 跨学科思考场景:把你读过的论文/书建成引用与概念关联图,用 GAT 训练后,导出注意力系数 α,就能看到"模型认为哪些跨领域连接最关键"——这是一种用机器辅助发现你自己知识网络中高价值桥接点的方式。
Takeaway + 思考题
💡 GAT = GCN + attention:把"固定的度数权重"换成"学出来、随内容变的注意力权重",代价是算力,回报是表达力 + 可解释性。
🤔 GAT 的注意力系数让模型"可解释"——你能看到它关注了谁。但这种"关注"等于"因果重要"吗?高注意力的邻居,一定是真正影响结果的邻居吗?(想想 Day 36 因果 vs 相关)

图嵌入Graph Embedding

表示学习随机游走直推 vs 归纳
一句话类比

图嵌入就是给每个节点算一个稠密向量(embedding),让"图里挨得近的节点,向量空间里也挨得近"——本质和 Day 22 的语义搜索一样:把对象映射到向量空间,用距离表示相似度。区别只在"相似"的定义来自图结构而非文本语义。算出来的向量可以直接喂给任何下游模型(分类器、推荐系统、最近邻检索)。

它解决什么问题 + 工作机制

有时你不想训练端到端的 GNN,只想要一份现成的节点向量。node2vec(Grover & Leskovec 2016)的思路极其优雅,直接复用了 word2vec:在图上做随机游走(random walk)生成大量"节点序列",把序列当成"句子"、节点当成"词",套 word2vec 训练——经常一起出现在游走路径里的节点,向量就接近。它还用两个参数 p、q 控制游走偏向 BFS(广度,抓局部社区结构)还是 DFS(深度,抓全局角色)——你可以直接类比成爬虫的两种遍历策略。

但 node2vec 有个根本局限叫直推式(transductive):它为训练时见过的每个节点查表取向量,来一个新节点就抓瞎——必须整个重训。这在动态图(每天新增用户)里是致命的。GraphSAGE(Hamilton 等 2017)解决了它:不再为每个节点存向量,而是学一个聚合函数(采样邻居 + 聚合,本质还是消息传递),新节点来了,用它的邻居现场算向量即可——这叫归纳式(inductive)

直推 vs 归纳 —— 关键区别

node2vec = 查表:存好每个老节点的向量
  新节点来 查不到,必须重训整图

GraphSAGE = 学函数:存"怎么算"而非"算好的"
  新节点来 用其邻居现场算,秒出
代码示例
from torch_geometric.nn import Node2Vec
import torch

# 在图上做随机游走, 学每个节点的 embedding
model = Node2Vec(
    data.edge_index, embedding_dim=128,
    walk_length=20,      # 每条游走走 20 步
    context_size=10,     # word2vec 式的窗口
    p=1.0, q=1.0,        # p小偏BFS(局部), q小偏DFS(全局)
)
loader = model.loader(batch_size=128, shuffle=True)
# ... 标准训练循环 ...
emb = model()           # (N, 128) 每个节点一个向量
# 拿到向量后: 余弦相似度找"结构相似"的节点
常见误区 + 实践场景
"图嵌入只是 GNN 的简化版,能用 GNN 就别用嵌入"——不全对。node2vec 这类方法不需要节点特征,纯靠结构就能跑,在你只有连接关系、没有节点属性的冷启动场景(如纯社交关系图)反而更实用。而 GNN 通常需要节点特征才能发挥威力。
📌 个人项目场景:把你的浏览器书签 / 稍后读按"同一会话访问""互相超链接"建图,用 node2vec 算向量,再做聚类——自动发现你阅读兴趣的"隐藏社区",比手工打标签更能揭示你思维的真实结构。
Takeaway + 思考题
💡 图嵌入把"结构相似"翻译成"向量相近"。node2vec 靠随机游走(直推、查表);GraphSAGE 学聚合函数(归纳、能处理新节点)——动态图选后者。
🤔 node2vec 用 p、q 控制 BFS/DFS 偏好,等于在问"节点的身份由它的邻居社区定义,还是由它的结构角色定义"?这两种相似性,哪种更接近你心中"两个人很像"的含义?

深入资源Further Reading

深入思考Deep Questions

1. Transformer 其实是一种特殊的图神经网络——你能说出为什么吗?
把一句话的每个 token 看成一个节点,Transformer 的 self-attention 等价于在一张全连接图(每个 token 和所有 token 相连)上跑 GAT:注意力系数就是 GAT 的 αᵢⱼ,多头就是 multi-head GAT。换句话说,Transformer = 全连接图上的注意力 GNN,它放弃了"稀疏结构"这个先验(假设任意两个 token 都可能相关),用位置编码(Day 13)补回顺序信息。反过来看 GNN:它在 Transformer 基础上加了"只能和图里真实相连的邻居交互"这个强结构约束。这条等价性是近年的重要洞察——它解释了为什么图 Transformer(Graph Transformer)能把两个流派融合:在稀疏图上做注意力。对 BigCat 的意义:你已经懂的 attention,迁移到图上几乎零成本,只需把"序列里所有位置"换成"图里的邻居"。
2. 过度平滑(over-smoothing)限制 GNN 只能浅层(2-4 层),但 Transformer 能堆几十层——同样是"反复聚合信息",为什么命运不同?
差别在聚合的范围和方式。GNN 每层只聚合真实邻居,且聚合是"平均/求和"——多跑几轮,信息像墨水滴进水里一样扩散均匀,节点表示趋同(数学上趋向图的某种稳态/低频分量)。Transformer 每层是全连接 + 注意力加权,注意力能选择性地不平均(权重可以极度不均),加上残差连接(residual)和 LayerNorm 强力对抗趋同。缓解 GNN 过度平滑的手段也正是借鉴这些:残差连接(如 DeepGCN)、跳连、给注意力(GAT)以保留差异的能力。深层意涵:这是分布式系统里"同步 vs 一致性 vs 信息保留"三角的又一体现——你既想让信息传播,又不想传到所有人都变成同一个声音。gossip 协议把收敛当目标,GNN 把"恰到好处的不收敛"当艺术。
3. GAT 的注意力系数被宣传为"可解释"——但这种可解释性可信吗?
需要警惕。注意力权重高,只能说明模型在前向计算时把更多信息流分配给了这个邻居,这是相关性层面的描述,不等于因果重要性(呼应 Day 36)。已有研究指出注意力权重作为解释并不稳健:同样的预测,可能存在多组差异很大的注意力分布(解释不唯一);扰动高注意力的输入有时并不改变预测。所以正确姿势是:把 α 当成"模型行为的一个可观测信号",用于生成假设、debug、可视化,而不要当成"这个邻居因果上决定了结果"的证据。要验证因果,得做干预(删掉这条边看预测变不变),而不是只读权重。对追求"AI 超级个体"的人,这是关键素养:会用解释工具,更要知道每种解释能支撑什么结论、不能支撑什么。
4. 你的分布式系统经验里,哪些直觉能直接迁移到图学习,哪些会误导你?
能迁移的:(a) gossip/epidemic 协议 → 消息传递的多轮收敛直觉;(b) 一致性哈希/分区 → 大图的图采样与分布式 GNN 训练(GraphSAGE 的邻居采样本质是为了让单机放得下);(c) 缓存与冷热分层 → 图嵌入的查表(直推) vs 现算(归纳)权衡;(d) 级联故障 → GNN 用于预测故障传播。会误导的:(i) 分布式系统追求最终一致(全网一致),GNN 恰恰怕这个(过度平滑),目标相反;(ii) 你习惯节点是"同构的服务器",但图学习里节点高度异质,邻居重要性天差地别(这正是 GAT 的出发点);(iii) 分布式系统的边是"通信链路"、近似无语义,图学习的边常常本身带语义和权重(朋友 vs 同事 vs 陌生人),忽略边类型会丢大量信息(异质图/关系 GNN 专门处理这个)。迁移直觉是利器,但要逐条检验前提是否成立——这本身就是跨学科思考的训练。
5. 如果"图"是关系的数学,那把你的人生/知识建成图、用 GNN 分析,会揭示什么、又会丢失什么?
会揭示的:结构性洞察——你的知识网络里哪些概念是"高中心性"枢纽(删掉它整个理解就断裂)、哪些是孤岛(学了但没连进体系)、哪些跨领域桥接最稀缺却最有价值(佛学↔分布式↔复杂性科学之间的连接)。这些是单看"节点列表"(你读过的书清单)永远看不到的——价值在边里,不在点里,这正是图思维的核心。会丢失的:(a) 时间与因果——静态图抹掉了"先学 A 才能懂 B"的顺序和因果(需要时序图/因果图补,Day 35/36);(b) 语义的丰富性——一条边只是"相关",但"启发我"和"反驳我"是完全不同的关系,压成一条边就丢了;(c) 主体的内在体验——图能建模你知识的结构,但建模不了"理解某个概念时的那一下顿悟"。深层启示:图是关系的强大抽象,但抽象即简化。对追求人机协同的人,正确姿势是用图(和 GNN)扩展你看见结构的能力,同时清醒地守住那些无法被图化的部分——那往往才是"你之所以为你"的内核。