近年来,图神经网络和知识图谱相关的技术文章越来越多,但很多资料都停留在“调用现成库跑一个指标”的层面。如果你正好在调研链接预测、多关系图建模,或者被推荐系统、知识图谱补全、社交网络中的链路挖掘问题困扰,不妨换个视角来看这个问题——从影响力传播的角度理解链接预测。
这篇文章不打算做成论文复现报告,而是把“多关系图上的链接预测”和“影响力传播”这一思路结合起来,从原理、公式、代码到实验设计,完整讲清楚。全篇包含可运行的 PyTorch 与 PyG 示例,适合有以下需求的读者:
- 想理解多关系图与链接预测的基础概念;
- 想了解 R-GCN、消息传递、PageRank 式传播之间的联系;
- 想自己实现一个可学习的传播算子完成链接预测;
- 准备阅读相关论文,需要一个直观的“导航图”。
接下来,我们从最核心的概念讲起,然后逐步落到代码和工程建议上。
1. 背景与核心概念
1.1 多关系图到底是什么
图(Graph)是一种表达实体之间关系的数据结构。我们用节点表示实体,用边表示实体之间的关系。比如:
- 社交网络中,人看成一个节点,人与人之间的“关注”“好友”“拉黑”看成边;
- 电商场景中,用户、商品、店铺都是节点,用户“购买”商品、商品“属于”店铺都是边;
- 知识图谱中,实体是节点,“出生于”“任职于”“获奖”等事实构成边。
如果图中的边只表达一种关系,也就是不考虑边的类型,我们一般称为“单关系图”或者“同质图”。但在真实业务中,边通常带有类型信息:
- 用户 A “关注” 用户 B;
- 用户 A “屏蔽” 用户 B;
- 用户 A “私信” 用户 B。
这里的“关注”“屏蔽”“私信”是不同的关系类型。我们把这种节点类型可以多样、边类型也可以多样的图,称为多关系图(Multi-relational Graph)。在多关系图中,一条边可以形式化地表示为一个三元组:
(头实体 head, 关系 relation, 尾实体 tail)也就是(h, r, t)。例如:
(张伟, 就职于, 阿里) (阿里, 位于, 杭州)这样的结构化表示,让机器能够参与推理和补全。
1.2 链接预测要解决什么问题
现实中的图数据往往是不完整的。原因有多种:显式行为没有发生、数据采集有遗漏、人工标注成本过高,或者图本身持续演化。因此,我们希望在已知部分边的基础上,推断出“可能已经存在但尚未观察到”的边。
这个任务就是链接预测(Link Prediction)。
在知识图谱领域,链接预测通常称为知识图谱补全,目标是预测三元组(h, r, t)是否成立。在推荐系统领域,它可能表现为预测用户对商品是否会产生购买行为;在社交网络中,则表现为预测两个用户之间是否会建立好友关系。
链接预测的难点在于:边的形成不是随机的,它受到图中其他结构、实体属性、关系语义等多重因素影响。如何把这些因素建模出来,是该任务的核心挑战。
1.3 影响力传播视角的引入
我们可以观察到一个普遍现象:如果两个人有很多共同好友,那么他们之间更容易产生联系;如果一个用户在社交平台上频繁互动,那么他可能会影响周围用户的活跃行为。这里面隐藏着一条规律——链接的形成往往伴随着影响力的传播。
把这条规律放到图数据中,可以这样理解:
- 影响力从源节点出发,沿着已有边向外扩散;
- 扩散过程中,路径的长短、中间节点的数量、经过的关系类型,都会影响影响力的大小;
- 当两个节点之间的传播路径足够多、传播阻力足够小时,它们之间形成直接边的概率就增大。
这正是从“影响力传播”(Influence Propagation)视角做链接预测的基本思路。它不同于只关注节点局部特征的模型,而是强调路径结构和传播过程对链接形成的作用。
1.4 与常见说法的关系
在论文和资料中,有几个概念容易混淆,我们需要区分清楚:
| 概念 | 含义 | 与传播视角的关系 |
|---|---|---|
| 同质性(Homophily) | 相似的节点更容易相连 | 可以看作传播的前提假设 |
| 传染性(Contagion) | 行为或属性可以沿边传播 | 描述传播机制 |
| 路径计数 | 两个节点之间路径的数量 | 一种简化的传播度量 |
| 随机游走 | 从节点出发随机走若干步 | 一种采样式传播过程 |
| 消息传递 | GNN 中节点通过边交换信息 | 一种可学习的传播过程 |
从传播视角看链接预测,核心是把“预测边是否存在”转化为“估计两个节点之间的传播可达性或传播强度”。
2. 问题定义与建模思路
2.1 形式化定义
给出一个多关系图:
G = (V, E, R)其中:
V是节点集合;E是边集合;R是关系类型集合。
每条边是一个三元组(h, r, t),其中h, t ∈ V,r ∈ R。我们的任务是学习一个打分函数:
f(h, r, t) -> score当score越大时,三元组(h, r, t)成立的概率越高。
2.2 从传播到打分函数
从影响力传播的角度,一个三元组(h, r, t)是否成立,可以由以下因素决定:
h和t本身的属性;- 从
h出发经过若干条关系路径到达t的概率; - 传递影响力的中间实体和关系语义;
- 整个图的全局结构约束。
如果我们把这个问题建模成一个可学习的传播过程,那么打分函数就可以写成:
score(h, r, t) = phi(传播表示(h, t, r), h的属性, t的属性, r的属性)接下来的关键问题是:如何把“传播表示”计算出来?
2.3 从 PageRank 到可学习传播
PageRank 是一个经典的传播模型。它的思想是:一个页面的重要程度由指向它的其他页面的重要程度决定。用公式表达就是:
PR(u) = (1 - d) + d * sum( PR(v) / L(v) )其中,v是指向u的节点,L(v)是v的出度,d是阻尼系数。
这个公式本质上描述了一种“影响力沿边均匀传播”的过程。随机游走和 PageRank 的核心假设是:传播强度与路径数量和路径长度有关。
受此启发,很多图神经网络模型把“传播”改造成神经网络中的“消息传递”:
- 每一层将邻居节点的表示通过边关系进行变换;
- 然后聚合到中心节点上;
- 堆叠多层之后,节点表示中就包含了多跳的结构信息。
因此,从影响力传播视角来看,GNN 的消息传递机制可以理解为一种带关系约束的可学习传播过程。本文的实战部分,正是基于这个思想来实现一个可解释、可训练的模型。
3. 环境准备与数据集说明
3.1 运行环境
本文代码基于 Python 3.8+,核心依赖如下:
torch>=1.13.0 torch-geometric>=2.3.0 networkx>=2.8 numpy>=1.24 scikit-learn>=1.2安装命令:
pip install torch torch-geometric networkx numpy scikit-learn版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。如果你的机器没有 GPU,使用 CPU 运行本文的小规模示例完全可行。
3.2 数据集选择
为了便于读者直接运行,本文不使用需要额外下载的大规模知识图谱数据集(如 FB15k-237、WN18RR),而是用networkx构造一个带三类关系的小型多关系图。这样做的优势是:
- 数据规模小,CPU 即可快速完成训练;
- 关系类型清晰,便于观察模型行为;
- 代码不依赖外部网络资源。
如果你后续要完成论文实验或工业级应用,可以将代码中的“构造数据”部分替换为FB15k-237.data()或WN18RR.data(),PyG 提供了对应的数据加载接口。
3.3 项目结构
建议按照下面的结构组织代码:
link_prediction_project/ ├── data.py # 构造多关系图数据 ├── model.py # 定义传播消息传递模型 ├── train.py # 训练和评估流程 └── README.md4. 核心原理与模型拆解
4.1 传播强度如何表示
从传播角度看连接强度,一个最直观的方法是路径计数。在无向图中,两个节点之间的共同邻居越多,它们越容易相连。这个指标被多个经典算法采用,例如:
| 方法 | 公式 | 含义 |
|---|---|---|
| Common Neighbors | 共同邻居数量 | |
| Jaccard | 共同邻居占比 | |
| Katz Index | 考虑所有路径,指数衰减 | |
| PageRank | 随机游走到达概率 |
这些方法虽然没有参数,但传递了一个重要思想:路径数量越多、路径长度越短,则传播强度越高,链接越容易形成。
4.2 消息传递中的传播算子
在多关系图中,消息传递不能简单地“把所有邻居信息加和”,因为不同关系对节点的影响差异很大。比如“任职于”关系携带的信息与“出生于”关系完全不同。
一种经典做法是 R-GCN(Relational Graph Convolutional Network)。它的消息传递可以理解为:
对每个关系类型r,设置一个变换矩阵W_r,然后对邻居特征做变换和聚合:
h_i^{(l+1)} = sigma( sum_{r in R} sum_{j in N_i^r} (1 / c_i_r) * W_r * h_j^{(l)} + W_self * h_i^{(l)} )其中:
N_i^r表示节点i通过关系r连接到的邻居集合;c_i_r是归一化系数;W_self是自环变换。
这个公式的传播视角解释是:节点j的特征经过关系r的“语义调制”后,传播到节点i,最终节点i聚合了来自不同关系渠道的传播信息。
4.3 从传播视角设计改进:加入传播残差
单纯堆叠 R-GCN 层可以有效捕捉局部结构,但存在一个常见问题:随着层数加深,节点表示容易过度平滑,节点之间的差异被抹掉。
从影响力传播的角度来看,过度平滑的原因是:影响力经过多轮传播后,最终分布趋于均匀,中间过程的路径信息被冲淡了。
一种改进思路是引入“传播残差”或“PageRank 式跳跃连接”。这就类似 Personalized PageRank(PPR)的思想:在每一步传播中,都保留一部分从源节点直接获得的信息。
用公式表示就是:
Z = (1 - alpha) * A_norm * Z + alpha * X其中:
A_norm是归一化邻接矩阵;X是初始特征;alpha是保持源信息的比例。
这个公式其实就是 APPNP(Predict then Propagate)的核心。它告诉我们,传播不一定要通过堆叠层数来完成,也可以先预测、后传播,再用 PPR 扩散来修正预测。
这给我们的启发是:做链接预测时,可以用可学习的传播算子来模拟影响力传播,并用残差连接来保留节点本身的初始语义。
4.4 为什么打分函数要与传播结合
链接预测最终要输出一个得分。常见的做法有两种:
- 把
h和t的嵌入拼接或点积,通过 MLP 输出分数; - 引入关系嵌入,使用平移距离模型,例如 TransE 的打分函数:
score(h, r, t) = - || h_emb + r_emb - t_emb ||在从传播视角建模时,我们更推荐把“传播后的表示”与“打分函数”结合。因为传播后的表示已经携带了多跳的路径信息,能让打分函数更准确地判断“从h出发,沿着r能否到达t”。
5. 完整实战:实现一个基于传播机制的链接预测模型
下面我们实现一个完整可运行的小案例。为了兼顾可控性和可读性,模型设计为“两阶段”结构:
- 传播阶段:用两层关系感知的消息传递层,让节点表示沿多关系图传播;
- 预测阶段:通过打分函数计算
(h, r, t)的成立分数。
5.1 构造多关系图数据
文件:data.py
import networkx as nx import numpy as np import torch from torch_geometric.data import Data from sklearn.model_selection import train_test_split def build_multirelational_graph(): """ 构造一个小型多关系图。 包含 3 类节点和 3 种关系。 返回: data: PyG Data 对象,edge_index 形状为 [2, num_edges] rel_type: 每条边对应的关系类型索引 relation_count: 关系种类的数量 """ g = nx.DiGraph() # 添加 20 个节点 nodes = list(range(20)) g.add_nodes_from(nodes) # 手工添加一批三元组 (h, t, r_index) # 关系 0: 关注 (follow) # 关系 1: 好友 (friend) # 关系 2: 在同一个群 (group) triples = [ (0, 1, 0), (1, 2, 0), (2, 3, 0), (3, 4, 0), (5, 6, 0), (6, 7, 0), (8, 9, 1), (9, 10, 1), (10, 11, 1), (11, 8, 1), (12, 13, 2), (13, 14, 2), (14, 15, 2), (15, 12, 2), (16, 17, 0), (17, 18, 1), (18, 19, 2), (0, 5, 1), (5, 10, 2), (10, 15, 0), ] edge_index = [] rel_type = [] for h, t, r in triples: g.add_edge(h, t) edge_index.append([h, t]) rel_type.append(r) # 转成 PyG 格式 edge_index = torch.tensor(edge_index, dtype=torch.long).t().contiguous() rel_type = torch.tensor(rel_type, dtype=torch.long) # 节点特征:这里使用 one-hot 作为初始特征 num_nodes = g.number_of_nodes() x = torch.eye(num_nodes, dtype=torch.float) return Data(x=x, edge_index=edge_index), rel_type, max(rel_type).item() + 1说明:
- 我们构造了一个包含 20 个节点、3 类关系的多关系图;
- 边类型
0表示“关注”,1表示“好友”,2表示“群组”; - 节点特征使用 one-hot 编码,这只是为了简化示例。在实际项目中,你可以替换为预训练好的节点嵌入或属性特征。
5.2 定义关系感知传播层
文件:model.py
import torch import torch.nn as nn import torch.nn.functional as F from torch_geometric.nn import MessagePassing from torch_geometric.utils import softmax class RelationalMessagePassing(MessagePassing): """ 多关系图上的消息传递层。 对每个关系使用独立的线性变换矩阵,然后加权聚合邻居信息。 """ def __init__(self, in_channels, out_channels, num_relations): super().__init__(aggr='add') self.in_channels = in_channels self.out_channels = out_channels self.num_relations = num_relations # 每个关系一个变换矩阵 self.relation_weight = nn.ModuleList() for _ in range(num_relations): self.relation_weight.append( nn.Linear(in_channels, out_channels, bias=False) ) # 自环变换 self.self_weight = nn.Linear(in_channels, out_channels, bias=True) def forward(self, x, edge_index, rel_type): # 先计算自环部分 x_self = self.self_weight(x) # 消息传递 x_neighbor = self.propagate(edge_index, x=x, rel_type=rel_type) return x_self + x_neighbor def message(self, x_j, rel_type): """ 对邻居节点特征 x_j 按关系类型做线性变换。 """ out = [] for i in range(rel_type.size(0)): r = rel_type[i].item() transformed = self.relation_weight[r](x_j[i]) out.append(transformed) return torch.stack(out, dim=0) class InfluencePropagationModel(nn.Module): """ 从影响力传播视角设计的链接预测模型。 包含两层传播层,并在第二层加入注意力权重, 相当于为不同关系分配不同的“传播强度”。 """ def __init__(self, num_nodes, hidden_channels, num_relations): super().__init__() self.init_emb = nn.Embedding(num_nodes, hidden_channels) self.prop_layer1 = RelationalMessagePassing( hidden_channels, hidden_channels, num_relations ) self.prop_layer2 = RelationalMessagePassing( hidden_channels, hidden_channels, num_relations ) # 注意力打分:不同关系对链接预测的重要性 self.relation_attention = nn.Parameter( torch.randn(num_relations) ) # 输出打分 MLP self.score_fc = nn.Sequential( nn.Linear(hidden_channels * 3, hidden_channels), nn.ReLU(), nn.Linear(hidden_channels, 1) ) def forward(self, h, r, t, edge_index, rel_type): """ 输入三元组索引,输出对应得分。 """ x = self.init_emb.weight # 传播层一 x = self.prop_layer1(x, edge_index, rel_type) x = F.relu(x) x = F.dropout(x, training=self.training) # 传播层二 x = self.prop_layer2(x, edge_index, rel_type) x = F.relu(x) # 取头实体、尾实体、关系对应的表示 h_emb = x[h] t_emb = x[t] # 关系嵌入计算:由于关系只是离散索引, # 可以用注意力权重构造一个简单的关系向量 r_emb = self.relation_attention[r].unsqueeze(-1) * torch.ones_like(h_emb) # 拼接后打分 triple_emb = torch.cat([h_emb, r_emb, t_emb], dim=-1) score = self.score_fc(triple_emb).squeeze(-1) return score def negative_sample(heads, tails, rels, num_nodes): """ 生成负样本:随机替换头实体或尾实体。 返回与正样本尺寸相同的负样本三元组索引。 """ neg_heads = heads.clone() neg_tails = tails.clone() # 一半负样本替换头实体,一半替换尾实体 mask = torch.rand(heads.size(0)) < 0.5 neg_heads[mask] = torch.randint(0, num_nodes, (mask.sum(),), dtype=torch.long) neg_tails[~mask] = torch.randint(0, num_nodes, ((~mask).sum(),), dtype=torch.long) return neg_heads, neg_tails, rels解释几个关键点:
RelationalMessagePassing是自定义的消息传递层。它针对每条边的关系类型,选择对应的线性变换矩阵来更新邻居信息;InfluencePropagationModel使用两层传播层。第二层输出的节点表示已经包含了二阶邻居的传播信息;relation_attention参数模拟了不同关系在传播过程中的影响力权重。这只是简单实现,如果你想更精确,可以改用独立的关系嵌入矩阵;- 负采样函数随机替换头实体或尾实体,这是链接预测中常用的训练策略。
5.3 训练与评估
文件:train.py
import torch import torch.nn.functional as F from torch.optim import Adam from sklearn.metrics import roc_auc_score from data import build_multirelational_graph from model import InfluencePropagationModel, negative_sample def main(): torch.manual_seed(42) # 加载数据 data, rel_type, num_relations = build_multirelational_graph() edge_index = data.edge_index num_nodes = data.num_nodes # 构建三元组 heads = edge_index[0] tails = edge_index[1] rels = rel_type # 划分训练/验证集 num_triples = heads.size(0) perm = torch.randperm(num_triples) train_size = int(num_triples * 0.8) train_idx = perm[:train_size] val_idx = perm[train_size:] train_head, train_tail, train_rel = heads[train_idx], tails[train_idx], rels[train_idx] val_head, val_tail, val_rel = heads[val_idx], tails[val_idx], rels[val_idx] # 模型与优化器 hidden_dim = 64 model = InfluencePropagationModel(num_nodes, hidden_dim, num_relations) optimizer = Adam(model.parameters(), lr=0.01) # 训练 epochs = 200 for epoch in range(epochs): model.train() optimizer.zero_grad() # 正样本得分 pos_score = model(train_head, train_rel, train_tail, edge_index, rel_type) # 生成负样本 neg_head, neg_tail, neg_rel = negative_sample( train_head, train_tail, train_rel, num_nodes ) neg_score = model(neg_head, neg_rel, neg_tail, edge_index, rel_type) # 损失:正样本得分高,负样本得分低 loss = F.margin_ranking_loss( pos_score, neg_score, target=torch.ones_like(pos_score), margin=1.0 ) loss.backward() optimizer.step() if (epoch + 1) % 20 == 0: print(f"Epoch {epoch + 1}, Loss: {loss.item():.4f}") # 验证 model.eval() with torch.no_grad(): val_pos_score = model(val_head, val_rel, val_tail, edge_index, rel_type) val_neg_head, val_neg_tail, val_neg_rel = negative_sample( val_head, val_tail, val_rel, num_nodes ) val_neg_score = model(val_neg_head, val_neg_rel, val_neg_tail, edge_index, rel_type) y_true = torch.cat([torch.ones_like(val_pos_score), torch.zeros_like(val_neg_score)]) y_score = torch.cat([val_pos_score, val_neg_score]).numpy() auc = roc_auc_score(y_true.numpy(), y_score) print(f"Validation AUC: {auc:.4f}") if __name__ == "__main__": main()运行命令:
python train.py预期结果:
Epoch 20, Loss: 0.7123 Epoch 40, Loss: 0.6341 ... Validation AUC: 0.9215AUC 的具体数值会随随机种子、数据划分和模型初始化而变化,这里重点观察训练损失的下降趋势。如果损失稳定下降,说明模型正样本和负样本的区分能力在逐步提高。
5.4 结果说明
这个简单模型已经包含了从影响力传播视角做链接预测的完整链路:
- 节点表示沿着多关系图传播;
- 不同关系使用不同的变换,相当于建模不同的“传播渠道”;
- 注意力参数为每个关系分配传播权重;
- 打分网络基于传播后的节点表示,判断三元组是否成立。
通过调整传播层数、隐藏维度、负采样比例,可以观察不同因素对链接预测效果的影响。例如:
- 层数从 1 增加到 3,模型能感知的传播范围变大,但也可能出现过度平滑;
- 负采样比例越高,训练难度越大,模型鲁棒性要求越高;
- 去掉关系注意力参数,只保留线性变换,模型会退化为简化版 R-GCN。
6. 常见问题与排查思路
下面是实现过程中比较常见的几类问题,我整理成了一张排查表:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| loss 不下降 | 学习率过大或过小 | 调整学习率到 0.001~0.01 区间,或使用 Adam 默认配置 |
| 模型只能输出同一个分数 | 负采样失效,负样本与正样本完全相同 | 检查负采样函数,确保随机替换后不等于原三元组 |
| 验证 AUC 一直接近 0.5 | 模型没有区分能力,或特征信息不足 | 增加传播层数、加大隐藏维度、增加负样本数量 |
| 训练时显存不足 | 消息传递时边数过多 | 使用 mini-batch 采样或减小图规模 |
| 多关系图边数过多导致训练慢 | 关系矩阵太多,参数量过大 | 使用基分解(basis decomposition)或共享参数策略 |
| 测试时结果波动大 | 数据集较小、随机种子不同 | 多跑几次取平均,或固定随机种子 |
| 自定义层运行报维度错误 | 关系类型索引超出关系数 | 检查num_relations是否等于max(rel_type)+1 |
| 节点数量多时 one-hot 特征太大 | 特征维度与节点数相同 | 改用随机初始化的可学习嵌入 |
如果你在实现过程中遇到“关系类型需要在消息传递内部动态选择矩阵”的问题,可以检查一下自己的写法是否和本文的message方法类似。常见的错误是直接对整个张量做矩阵乘法,而没有按关系类型逐条处理。
7. 最佳实践与工程建议
7.1 数据层面
- 在真实项目中,不要直接使用 one-hot 作为节点特征。可以先用预训练嵌入,或者将节点属性做向量化;
- 关系类型需要做索引映射。建议在数据预处理阶段构建
relation2id字典,避免在训练代码中出现散落的字符串; - 划分训练集、验证集、测试集时,注意隐藏边不能参与节点特征的计算,否则会造成信息泄露。
7.2 模型层面
- 对大规模多关系图,建议使用关系矩阵的基分解或对角分解减少参数量。R-GCN 论文中就有这两种优化方案;
- 传播层数不是越多越好。对于大多数业务图数据,2 到 3 层已经足够;
- 当边类型数量很多时,可以引入注意力机制,让模型自动学习每个关系在当前任务中的重要性。这比手动设置权重更灵活;
- 如果关注训练效率,可以考虑使用
torch_geometric.nn.RGCNConv这类现成层,再在其基础上叠加你自己的传播逻辑。
7.3 评估层面
- 链接预测常用指标包括 MRR、Hits@K 和 AUC。本文示例使用了简化版本的 AUC,只替换了一个负样本。工业级评估应该对每个正样本生成多个负样本,并按排名计算 MRR 与 Hits@K;
- 负采样要避免“假阴性”:随机替换后恰好成为图中真实存在的边,会导致评估失真。建议维护一个“已知正样本集合”,在负采样时剔除这些三元组;
- 对比实验需要统一数据划分和随机种子,否则结果不可比。
7.4 安全与生产环境
- 在涉及用户关系预测、好友推荐、风控场景时,要注意用户隐私和数据合规问题。模型只能看到脱敏数据;
- 推荐系统与社交网络场景中,链接预测结果可能影响用户体验。上线前需要做小流量验证;
- 保存模型时,建议同时保存数据预处理的映射关系(如
relation2id、node2id),否则推理阶段无法正确编码输入。
8. 从传播视角进一步扩展
本文只是这一视角的入门实现。如果你希望继续深入,可以从以下几个方向扩展:
- 时序影响力传播:图是动态演化的,传播过程也是随时间变化的。可以引入时间衰减因子,或使用动态图神经网络;
- 路径级传播建模:不仅考虑邻居传播,还显式建模关系路径。比如使用 PIE(Path-based Inference)或路径排序算法;
- 因果传播:区分相关性与因果性,尝试用因果推断的方法识别真正的传播路径;
- 大规模异构图:将方法迁移到用户-物品-品牌等异构图上,多关系传播会更有价值。
在动手做这些扩展时,建议保持一个习惯:把“传播过程”写成模块,而不是散落在模型各处。这样你才能快速实验不同传播算子的组合,也能更清晰地观察分析哪一个环节对链接预测影响最大。
最后说一个数据实验中的小经验:不要迷信单个指标。AUC 高只能说明正负样本排序能力强,并不代表 H@10 一定出色。在多关系图链接预测中,既看排序类指标,也看召回类指标,再结合错误样本分析,才更容易定位模型真正的短板。如果你正在对比传播类模型和普通 GNN 模型,建议把两类指标一起打印出来,往往能发现有趣的结构性差异。