本节摘要:聚合函数汇总邻域消息,必须满足置换不变性;求和保留规模信息、均值抹平规模信息、最大值只留最突出证据,池化与循环单元聚合更强但更贵。更新函数融合旧档案与聚合结果,从简单拼接投影到门控循环单元各有取舍。本节承接消息传递三步曲,把"消化工序"的选项表与选型逻辑讲透,为第三章各模型的差异化设计提供参照。
同样是把邻居证词汇总,用均值、用求和还是取最大,结论可能天差地别——聚合函数不是实现细节,而是模型归纳偏置的直接载体。上一节把图卷积的两条理论路线勘验完毕,本节回到三步曲的第二、三步:消息汇总用什么函数、档案改写用什么函数。选择之前先立规矩:聚合函数必须置换不变——邻居以任何顺序到场,汇总结果必须相同。这条规矩直接继承自第一章的教训:对编号与顺序敏感的算子,学到的不是结构规律而是排列噪声。均值、求和、最大值都天然满足;任何依赖邻居到达顺序的方案(比如按列表顺序做循环累积)都先天违规。
均值聚合把邻域消息取平均,等于给每个邻居相同的话语权,输出量级与邻居数无关。它适合"邻域的平均画像最有信息量"的场景:引文分类里一篇论文的主题由引用它的论文的平均主题决定。代价是抹平规模——三个高信用担保人与十个低信用关联者,均值后可能难以区分。求和聚合保留规模信息:邻居越多、同类证据越多,汇总值越大。它适合"数量本身就是证据"的场景:欺诈检测里关联的可疑账号越多越可疑、化学图里邻接原子类型计数决定分子性质。代价是输出量级随度数漂移,需要配合归一化或残差使用。最大值聚合只保留每个特征维度上最突出的消息,等价于"挑最刺眼的证据"。它对规模不敏感、对离群消息鲁棒,适合"一票否决"式判断(比如任一关联账号触发风控即整体可疑)。池化聚合先把每条消息过一个共享的小网络再求和或最大池化,表达力最强,GraphSAGE 的池化变体即此设计。循环单元聚合把邻居消息依次喂入门控循环单元,理论上能建模消息间的组合关系,但违背置换不变性的风险与计算开销都高,实践中仅在消息带顺序语义(动态图、会话图)时使用。
import numpy as np # 三条"证词":每条是二维向量(示意:可疑度、资金量) msgs = np.array([ [0.9, 0.2], # 邻居甲:高度可疑、小额 [0.1, 5.0], # 邻居乙:基本清白、大额 [0.7, 0.4], # 邻居丙:可疑、小额 ]) print("均值聚合:", msgs.mean(axis=0)) # [0.57, 1.87] 平均画像 print("求和聚合:", msgs.sum(axis=0)) # [1.7 , 5.6 ] 规模信息保留 print("最大值聚合:", msgs.max(axis=0)) # [0.9 , 5.0] 每维最突出证据 # 三种汇总给出三份不同画像:任务要"平均氛围"还是"证据总量"还是"极端信号",选型随之不同 # 规模敏感实验:再加三条低可疑证词,观察三种聚合的反应 more = np.array([[0.1, 0.2], [0.15, 0.3], [0.05, 0.1]]) allm = np.vstack([msgs, more]) print("扩充后 均值:", np.round(allm.mean(axis=0), 3)) # 可疑度被稀释 print("扩充后 求和:", np.round(allm.sum(axis=0), 3)) # 可疑度总量上升 print("扩充后 最大:", np.round(allm.max(axis=0), 3)) # 完全不变 # "多个弱证据累积成强信号"的任务必须用求和;"证据密度"任务用均值
这段实验揭示了选型的第一原则:先问任务的证据语义,再选聚合函数。判断"嫌疑人所处圈层的平均氛围"用均值;判断"嫌疑人沾染的可疑总量"用求和;判断"是否存在至少一条铁证"用最大值。第三章会看到这个原则如何落地:GCN 用归一化的均值类聚合,GraphSAGE 提供均值、池化等多路选择,GIN 则论证"只有求和聚合才能数清多重集合"——表达能力的分水岭正是聚合函数。
聚合结果到手后,节点要决定"旧档案与新证词如何合并",这就是更新函数的职责。拼接加投影最常见:把旧状态与聚合结果首尾相接,过一个线性层加激活——简单、稳定、参数可控,GCN 的做法可视为它的简化版(旧状态与聚合结果共享投影)。加权融合给旧状态与聚合结果各配一个可学习门控系数,适合"新旧证据分量随任务变化"的场景。门控循环单元把更新写成记忆读写:门控决定哪些旧信息保留、哪些新证词写入,表达力最强、参数最多,消息传递多层堆叠时退化为深层序列更新的情形,训练难度上升。残差更新保留旧状态作为基底、只学习增量,深层图网络里几乎是标配——它给梯度留了高速通道,第五章的深网络讨论会反复引用这条设计。
import numpy as np rng = np.random.default_rng(3) d = 4 h_old = rng.normal(size=d) # 旧档案 agg = rng.normal(size=d) # 聚合后的邻域证词 # 方案一:拼接投影 W_cat = rng.normal(size=(2*d, d)) * 0.4 h_cat = np.tanh(np.hstack([h_old, agg]) @ W_cat) # 方案二:门控加权融合(单变量门控,sigmoid 决定新旧比重) w_g, w_a = rng.normal(size=d), rng.normal(size=d) gate = 1 / (1 + np.exp(-(h_old @ w_g))) # 门控值在 0~1 之间 h_gate = gate * h_old + (1 - gate) * agg # 方案三:残差更新(只学增量) W_res = rng.normal(size=(d, d)) * 0.2 h_res = h_old + np.tanh(agg @ W_res) print("拼接投影:", np.round(h_cat, 3)) print("门控融合 (门控值≈%.2f):" % gate.mean(), np.round(h_gate, 3)) print("残差更新:", np.round(h_res, 3)) # 三份新档案各有侧重:投影可整体改写、门控显式权衡新旧、残差保守增量
把决策路径整理成问答清单:任务的证据语义决定聚合函数(规模证据选求和、密度证据选均值、极端证据选最大值),网络深度决定更新函数(浅层拼接投影够用,深层残差或门控保命)。另有两条横贯原则:其一,聚合与更新可以解耦设计——这正是 GraphSAGE 把聚合器做成可插拔选项的思路;其二,聚合函数的可区分性决定模型上限——均值与最大值聚合都会把不同的邻域多重集合映射到相同的汇总值(比如均值聚合下"一条强证据"与"两条减半的强证据"无法区分),求和配合同单射消息函数则可避开,这个论断的严格证明留给第三章 GIN 一节。
走访与消化的规程至此完备,下一节处理走访的最终产出——把节点的多层表示沉淀为可复用的嵌入档案,它是第四章一切结案任务的直接输入。