简介:这份资源面向图神经网络与链路预测方向的初学者及进阶学习者,聚焦在Cora和Citeseer两个经典学术文献网络上,用图卷积神经网络完成节点间未知引用关系的预测任务。包内共3个Python文件,压缩包约7KB,涵盖数据读取与图结构预处理、GCN模型搭建以及训练测试主流程,分别承担数据加载与邻接矩阵构造、多层图卷积与节点嵌入生成、损失函数与优化器配置等职责,结构紧凑便于按模块阅读。资源围绕词袋特征、邻接矩阵、负采样与AUC-ROC评估等关键环节展开,读者可借此理解GCN如何通过邻居信息聚合更新节点表示,并掌握链路预测从预处理到性能评估的完整实践路径。目前已有3070人学习,适合希望以最小代码量快速上手图学习实战的读者参考。1. 链路预测的“反直觉”起点:为什么 GCN 在 Cora 和 Citeseer 上不是“万能药”链路预测常被误解为“给两个节点算个相似度就完事”。但在 Cora 和 Citeseer 这类引文网络上,真正要预测的是:两篇论文之间是否存在一条尚未被观测到的引用边。这本质上是一个图结构上的二分类问题,而不是简单的节点相似度排序。GCN(图卷积神经网络)之所以被拿来解决它,是因为它能把节点特征和局部拓扑结构联合编码,让模型学到“什么样的节点对更可能相连”。但反直觉的地方在于:GCN 在 Cora 和 Citeseer 上的表现,并不总是碾压传统启发式方法(如共同邻居、Adamic-Adar)。原因在于这两个数据集规模小、特征稀疏、边密度低,GCN 的过平滑和过拟合风险会直接拉低链路预测的泛化能力。搞懂这个机制,你才能明白为什么“用 GCN 做链路预测”不是调个库就完事,而是要在编码方式、负采样、解码器设计上做出一系列取舍。这也是这份资源值得深挖的地方:它把 GCN 从节点分类拉到链路预测场景,逼你直面图神经网络在稀疏图上的真实边界。2. 从节点分类到链路预测:GCN 的编码器到底在学什么2.1 节点分类与链路预测的目标差异节点分类的目标是给每个节点分配一个标签,GCN 的每一层都在做“邻居特征聚合”,最终每个节点的表示会包含其局部子图的结构信息。而链路预测的目标是判断一对节点之间是否存在边,这意味着模型不能只学“节点像什么”,还要学“节点对之间为什么该连”。常见的实现思路是:先用 GCN 编码器把节点映射到低维向量,再用一个解码器(如内积、MLP)把两个向量映射成一个概率值。这个过程中,编码器学到的表示必须保留“连接模式”的信息,而不是仅仅保留“类别区分度”。在 Cora 和 Citeseer 上,节点特征维度分别是 1433 和 3703,但边数只有 5000 左右,这种“高维稀疏特征 + 低密度边”的组合,会让 GCN 的聚合过程非常依赖邻接矩阵的归一化方式。如果直接使用对称归一化,度数低的节点会被过度平滑,导致其表示趋近于邻居的均值,反而丢失了区分性。2.2 编码器结构:两层 GCN 为什么是常见选择在链路预测任务中,编码器通常采用两层 GCN,而不是更深。第一层把原始特征映射到隐藏维度(如 128 或 256),第二层再映射到输出维度(如 64 或 128)。两层的原因在于:链路预测需要的是“局部结构感知”的节点表示,而不是像节点分类那样需要捕获多跳邻域信息。更深的 GCN 会让节点表示过度平滑,尤其是在 Cora 和 Citeseer 这种小图上,三层的 GCN 就会让所有节点的表示趋近于相同,导致链路预测的 AUC 明显下降。常见的实现思路是:在两层之间加入 dropout 和 ReLU,但 dropout 率不能太高,否则会破坏稀疏特征中的关键信号。下面是一个典型的编码器实现片段:import torch import torch.nn as nn import torch.nn.functional as F from torch_geometric.nn import GCNConv class GCNEncoder(nn.Module): def __init__(self, in_dim, hidden_dim, out_dim, dropout=0.3): super().__init__() # 第一层:原始特征 -> 隐藏层 self.conv1 = GCNConv(in_dim, hidden_dim) # 第二层:隐藏层 -> 输出嵌入 self.conv2 = GCNConv(hidden_dim, out_dim) self.dropout = dropout def forward(self, x, edge_index): # 第一层聚合,激活后 dropout x = self.conv1(x, edge_index) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) # 第二层聚合,不加激活,直接输出嵌入 x = self.conv2(x, edge_index) return x这段代码里,GCNConv内部做了对称归一化的邻接矩阵乘法,dropout只在训练时生效。关键参数是hidden_dim和out_dim:在 Cora 上,hidden_dim=128、out_dim=64是比较稳的配置;在 Citeseer 上,由于特征维度更高,hidden_dim可以适当增大到 256,但out_dim不宜超过 128,否则解码器参数量会过大,容易过拟合。2.3 解码器设计:内积、MLP 与双线性解码器决定了如何把两个节点嵌入映射成边存在的概率。最简单的内积解码器直接计算两个向量的点积,再经过 sigmoid。这种方式的参数量为零,但表达能力有限,适合边密度较高的图。MLP 解码器把两个向量拼接后送入多层感知机,参数量大,容易在小图上过拟合。双线性解码器则引入一个可学习的矩阵,计算z_u^T W z_v,在表达能力和参数量之间取得平衡。在 Cora 和 Citeseer 上,常见的做法是先用内积做基线,如果 AUC 不理想再换成双线性。需要注意的是,解码器的输出必须经过 sigmoid 才能解释为概率,训练时用二元交叉熵损失。负采样策略也很关键:如果负样本从所有未观测边中均匀采样,模型会学到“大多数节点对不相连”的先验,导致预测偏向负类。常见的改进是采用“难负采样”,即优先选择那些在嵌入空间中距离较近但实际不相连的节点对。3. 在 Cora 和 Citeseer 上跑通链路预测:数据划分与训练细节3.1 数据集加载与边划分的坑Cora 和 Citeseer 是引文网络数据集,节点是论文,边是引用关系,特征是词袋向量。在链路预测任务中,不能直接使用节点分类的划分方式,而是要把边分成训练集、验证集和测试集。常见的做法是:随机抽取 85% 的边作为训练正样本,5% 作为验证正样本,10% 作为测试正样本;同时从不存在边的节点对中采样等量的负样本。但这里有一个容易翻车的地方:如果先划分边再构建邻接矩阵,训练时的邻接矩阵会包含验证集和测试集的边,造成信息泄露。正确的做法是:在训练阶段,只使用训练边的邻接矩阵;在验证和测试阶段,再逐步加入验证边和测试边。下面是一个边划分的示例:import numpy as np from torch_geometric.utils import to_undirected def split_edges(edge_index, num_nodes, val_ratio=0.05, test_ratio=0.1): # 转为无向边,避免重复 edge_index = to_undirected(edge_index) num_edges = edge_index.size(1) # 随机打乱边 perm = np.random.permutation(num_edges) val_size = int(num_edges * val_ratio) test_size = int(num_edges * test_ratio) train_size = num_edges - val_size - test_size train_edges = edge_index[:, perm[:train_size]] val_edges = edge_index[:, perm[train_size:train_size+val_size]] test_edges = edge_index[:, perm[train_size+val_size:]] return train_edges, val_edges, test_edges这段代码里,to_undirected会把有向边转成无向边,避免同一对节点出现两次。perm是随机排列,确保划分的随机性。关键点是:训练时只能用train_edges构建邻接矩阵,验证和测试时再分别加入对应的边。如果忽略这一点,验证集上的 AUC 会虚高,但测试集上会明显下降。3.2 负采样与训练循环负采样是链路预测训练的核心。对于 Cora 和 Citeseer,正样本边数大约 5000 左右,如果负采样比例是 1:1,那么每个 epoch 会有约 10000 条样本。负采样的实现通常是从所有节点对中随机抽取,但要注意排除已存在的边。下面是一个负采样函数:def negative_sampling(edge_index, num_nodes, num_neg_samples): # 已存在的边集合 existing = set() for i in range(edge_index.size(1)): u, v = edge_index[0, i].item(), edge_index[1, i].item() existing.add((u, v)) existing.add((v, u)) neg_edges = [] while len(neg_edges) < num_neg_samples: u = np.random.randint(0, num_nodes) v = np.random.randint(0, num_nodes) if u == v or (u, v) in existing: continue neg_edges.append((u, v)) return torch.tensor(neg_edges, dtype=torch.long).t()这个函数会不断采样直到凑够负样本数。注意u == v的情况要排除,因为自环通常不参与链路预测。训练循环里,每个 epoch 先用编码器得到所有节点的嵌入,然后对正负样本分别计算解码器输出,用二元交叉熵损失反向传播。优化器通常选 Adam,学习率在 0.01 左右,权重衰减用 5e-4 来抑制过拟合。在 Cora 上,训练 200 个 epoch 左右就能收敛;Citeseer 由于特征更稀疏,可能需要 300 个 epoch,并且要更早停止。3.3 评估指标:AUC 与 AP 的取舍链路预测常用的评估指标是 AUC 和 Average Precision(AP)。AUC 衡量的是模型对正负样本的排序能力,AP 则更关注正样本的排序质量。在 Cora 和 Citeseer 上,由于负样本是随机采样的,AUC 通常会被高估,因为随机负样本中很多是“容易区分”的。更严格的评估是使用“所有未观测边”作为负样本,但计算量太大。常见的折中方案是:在测试阶段,对每个正样本采样 100 个负样本,计算 AUC 和 AP,然后取平均。如果 AUC 在 0.9 以上但 AP 只有 0.7 左右,说明模型对正样本的排序不够好,可能需要调整解码器或增加负采样难度。4. 避坑与排查:GCN 链路预测在 Cora/Citeseer 上的常见翻车点4.1 现象:验证集 AUC 很高,测试集 AUC 暴跌原因:边划分时信息泄露,训练时的邻接矩阵包含了验证集或测试集的边。解决:严格按训练边构建邻接矩阵,验证和测试时再逐步加入对应边。检查方法是:在训练阶段打印邻接矩阵的边数,确认它等于训练边数。4.2 现象:模型输出所有节点对的概率都接近 0.5原因:过平滑导致节点嵌入趋同,解码器无法区分正负样本。解决:减少 GCN 层数到两层,降低 dropout 率,或者改用双线性解码器增加表达能力。检查方法是:计算节点嵌入的方差,如果方差小于 0.01,说明过平滑严重。4.3 现象:训练损失下降但 AUC 不升原因:负采样太简单,模型学到了“所有负样本都容易区分”的捷径。解决:采用难负采样,优先选择嵌入空间中距离近但不相连的节点对。检查方法是:统计负样本的平均嵌入距离,如果远小于正样本距离,说明负采样太简单。4.4 现象:Citeseer 上 AUC 明显低于 Cora原因:Citeseer 的特征维度更高但边更稀疏,GCN 的聚合过程容易受噪声影响。解决:对特征做 L2 归一化,或者在第一层 GCN 前加一个线性降维层。检查方法是:对比两个数据集上的节点度数分布,Citeseer 的平均度数更低,需要更小的隐藏维度。4.5 现象:训练时间过长,GPU 利用率低原因:负采样在 Python 循环中逐条采样,效率低。解决:用向量化方式批量采样,或者预先生成负样本池。检查方法是:用torch.cuda.Event计时,确认负采样耗时占比是否超过 50%。5. 设计取舍:为什么在小图上 GCN 不是链路预测的最优解在 Cora 和 Citeseer 上,GCN 链路预测的 AUC 通常能做到 0.85 到 0.92 之间,但传统启发式方法如 Adamic-Adar 在某些划分下也能达到 0.88 左右。这意味着 GCN 的优势并不绝对,尤其是在边密度低、特征稀疏的场景下。GCN 的真正价值在于:它能联合学习节点特征和结构信息,当图上有丰富的节点属性时,GCN 的表示能力会超过纯拓扑启发式方法。但在 Cora 和 Citeseer 上,词袋特征的信息量有限,GCN 的增益主要来自结构聚合,而不是特征编码。因此,一个实用的取舍是:先用启发式方法做基线,如果 AUC 已经满足需求,就不必上 GCN;如果需要端到端学习,并且图上有额外的节点特征,再考虑 GCN。另一个取舍是编码器深度:两层是性价比最高的选择,三层以上需要配合残差连接或 JKNet 才能避免过平滑。解码器方面,内积适合快速验证,双线性适合追求更高 AUC,但参数量会增加,需要更强的正则化。负采样策略上,均匀采样实现简单但容易高估性能,难负采样更接近真实场景但训练不稳定,需要调整学习率。这些取舍没有标准答案,取决于你对 AUC、训练时间和实现复杂度的权衡。我自己的习惯是:每次跑链路预测,先固定随机种子跑三次,取平均 AUC,再对比不同配置。如果三次的方差超过 0.02,说明划分或初始化不稳定,需要检查边划分和负采样。从那以后我每次做链路预测实验,都强制走一遍“训练边邻接矩阵检查 + 嵌入方差检查 + 负样本距离检查”,这三个检查能挡掉大部分翻车情况。希望帮到你。本文还有配套的精品资源,点击获取
简介:这份资源面向图神经网络与链路预测方向的初学者及进阶学习者,聚焦在Cora和Citeseer两个经典学术文献网络上,用图卷积神经网络完成节点间未知引用关系的预测任务。包内共3个Python文件,压缩包约7KB,涵盖数据读取与图结构预处理、GCN模型搭建以及训练测试主流程,分别承担数据加载与邻接矩阵构造、多层图卷积与节点嵌入生成、损失函数与优化器配置等职责,结构紧凑便于按模块阅读。资源围绕词袋特征、邻接矩阵、负采样与AUC-ROC评估等关键环节展开,读者可借此理解GCN如何通过邻居信息聚合更新节点表示,并掌握链路预测从预处理到性能评估的完整实践路径。目前已有3070人学习,适合希望以最小代码量快速上手图学习实战的读者参考。1. 链路预测的“反直觉”起点:为什么 GCN 在 Cora 和 Citeseer 上不是“万能药”链路预测常被误解为“给两个节点算个相似度就完事”。但在 Cora 和 Citeseer 这类引文网络上,真正要预测的是:两篇论文之间是否存在一条尚未被观测到的引用边。这本质上是一个图结构上的二分类问题,而不是简单的节点相似度排序。GCN(图卷积神经网络)之所以被拿来解决它,是因为它能把节点特征和局部拓扑结构联合编码,让模型学到“什么样的节点对更可能相连”。但反直觉的地方在于:GCN 在 Cora 和 Citeseer 上的表现,并不总是碾压传统启发式方法(如共同邻居、Adamic-Adar)。原因在于这两个数据集规模小、特征稀疏、边密度低,GCN 的过平滑和过拟合风险会直接拉低链路预测的泛化能力。搞懂这个机制,你才能明白为什么“用 GCN 做链路预测”不是调个库就完事,而是要在编码方式、负采样、解码器设计上做出一系列取舍。这也是这份资源值得深挖的地方:它把 GCN 从节点分类拉到链路预测场景,逼你直面图神经网络在稀疏图上的真实边界。2. 从节点分类到链路预测:GCN 的编码器到底在学什么2.1 节点分类与链路预测的目标差异节点分类的目标是给每个节点分配一个标签,GCN 的每一层都在做“邻居特征聚合”,最终每个节点的表示会包含其局部子图的结构信息。而链路预测的目标是判断一对节点之间是否存在边,这意味着模型不能只学“节点像什么”,还要学“节点对之间为什么该连”。常见的实现思路是:先用 GCN 编码器把节点映射到低维向量,再用一个解码器(如内积、MLP)把两个向量映射成一个概率值。这个过程中,编码器学到的表示必须保留“连接模式”的信息,而不是仅仅保留“类别区分度”。在 Cora 和 Citeseer 上,节点特征维度分别是 1433 和 3703,但边数只有 5000 左右,这种“高维稀疏特征 + 低密度边”的组合,会让 GCN 的聚合过程非常依赖邻接矩阵的归一化方式。如果直接使用对称归一化,度数低的节点会被过度平滑,导致其表示趋近于邻居的均值,反而丢失了区分性。2.2 编码器结构:两层 GCN 为什么是常见选择在链路预测任务中,编码器通常采用两层 GCN,而不是更深。第一层把原始特征映射到隐藏维度(如 128 或 256),第二层再映射到输出维度(如 64 或 128)。两层的原因在于:链路预测需要的是“局部结构感知”的节点表示,而不是像节点分类那样需要捕获多跳邻域信息。更深的 GCN 会让节点表示过度平滑,尤其是在 Cora 和 Citeseer 这种小图上,三层的 GCN 就会让所有节点的表示趋近于相同,导致链路预测的 AUC 明显下降。常见的实现思路是:在两层之间加入 dropout 和 ReLU,但 dropout 率不能太高,否则会破坏稀疏特征中的关键信号。下面是一个典型的编码器实现片段:import torch import torch.nn as nn import torch.nn.functional as F from torch_geometric.nn import GCNConv class GCNEncoder(nn.Module): def __init__(self, in_dim, hidden_dim, out_dim, dropout=0.3): super().__init__() # 第一层:原始特征 -> 隐藏层 self.conv1 = GCNConv(in_dim, hidden_dim) # 第二层:隐藏层 -> 输出嵌入 self.conv2 = GCNConv(hidden_dim, out_dim) self.dropout = dropout def forward(self, x, edge_index): # 第一层聚合,激活后 dropout x = self.conv1(x, edge_index) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) # 第二层聚合,不加激活,直接输出嵌入 x = self.conv2(x, edge_index) return x这段代码里,GCNConv内部做了对称归一化的邻接矩阵乘法,dropout只在训练时生效。关键参数是hidden_dim和out_dim:在 Cora 上,hidden_dim=128、out_dim=64是比较稳的配置;在 Citeseer 上,由于特征维度更高,hidden_dim可以适当增大到 256,但out_dim不宜超过 128,否则解码器参数量会过大,容易过拟合。2.3 解码器设计:内积、MLP 与双线性解码器决定了如何把两个节点嵌入映射成边存在的概率。最简单的内积解码器直接计算两个向量的点积,再经过 sigmoid。这种方式的参数量为零,但表达能力有限,适合边密度较高的图。MLP 解码器把两个向量拼接后送入多层感知机,参数量大,容易在小图上过拟合。双线性解码器则引入一个可学习的矩阵,计算z_u^T W z_v,在表达能力和参数量之间取得平衡。在 Cora 和 Citeseer 上,常见的做法是先用内积做基线,如果 AUC 不理想再换成双线性。需要注意的是,解码器的输出必须经过 sigmoid 才能解释为概率,训练时用二元交叉熵损失。负采样策略也很关键:如果负样本从所有未观测边中均匀采样,模型会学到“大多数节点对不相连”的先验,导致预测偏向负类。常见的改进是采用“难负采样”,即优先选择那些在嵌入空间中距离较近但实际不相连的节点对。3. 在 Cora 和 Citeseer 上跑通链路预测:数据划分与训练细节3.1 数据集加载与边划分的坑Cora 和 Citeseer 是引文网络数据集,节点是论文,边是引用关系,特征是词袋向量。在链路预测任务中,不能直接使用节点分类的划分方式,而是要把边分成训练集、验证集和测试集。常见的做法是:随机抽取 85% 的边作为训练正样本,5% 作为验证正样本,10% 作为测试正样本;同时从不存在边的节点对中采样等量的负样本。但这里有一个容易翻车的地方:如果先划分边再构建邻接矩阵,训练时的邻接矩阵会包含验证集和测试集的边,造成信息泄露。正确的做法是:在训练阶段,只使用训练边的邻接矩阵;在验证和测试阶段,再逐步加入验证边和测试边。下面是一个边划分的示例:import numpy as np from torch_geometric.utils import to_undirected def split_edges(edge_index, num_nodes, val_ratio=0.05, test_ratio=0.1): # 转为无向边,避免重复 edge_index = to_undirected(edge_index) num_edges = edge_index.size(1) # 随机打乱边 perm = np.random.permutation(num_edges) val_size = int(num_edges * val_ratio) test_size = int(num_edges * test_ratio) train_size = num_edges - val_size - test_size train_edges = edge_index[:, perm[:train_size]] val_edges = edge_index[:, perm[train_size:train_size+val_size]] test_edges = edge_index[:, perm[train_size+val_size:]] return train_edges, val_edges, test_edges这段代码里,to_undirected会把有向边转成无向边,避免同一对节点出现两次。perm是随机排列,确保划分的随机性。关键点是:训练时只能用train_edges构建邻接矩阵,验证和测试时再分别加入对应的边。如果忽略这一点,验证集上的 AUC 会虚高,但测试集上会明显下降。3.2 负采样与训练循环负采样是链路预测训练的核心。对于 Cora 和 Citeseer,正样本边数大约 5000 左右,如果负采样比例是 1:1,那么每个 epoch 会有约 10000 条样本。负采样的实现通常是从所有节点对中随机抽取,但要注意排除已存在的边。下面是一个负采样函数:def negative_sampling(edge_index, num_nodes, num_neg_samples): # 已存在的边集合 existing = set() for i in range(edge_index.size(1)): u, v = edge_index[0, i].item(), edge_index[1, i].item() existing.add((u, v)) existing.add((v, u)) neg_edges = [] while len(neg_edges) < num_neg_samples: u = np.random.randint(0, num_nodes) v = np.random.randint(0, num_nodes) if u == v or (u, v) in existing: continue neg_edges.append((u, v)) return torch.tensor(neg_edges, dtype=torch.long).t()这个函数会不断采样直到凑够负样本数。注意u == v的情况要排除,因为自环通常不参与链路预测。训练循环里,每个 epoch 先用编码器得到所有节点的嵌入,然后对正负样本分别计算解码器输出,用二元交叉熵损失反向传播。优化器通常选 Adam,学习率在 0.01 左右,权重衰减用 5e-4 来抑制过拟合。在 Cora 上,训练 200 个 epoch 左右就能收敛;Citeseer 由于特征更稀疏,可能需要 300 个 epoch,并且要更早停止。3.3 评估指标:AUC 与 AP 的取舍链路预测常用的评估指标是 AUC 和 Average Precision(AP)。AUC 衡量的是模型对正负样本的排序能力,AP 则更关注正样本的排序质量。在 Cora 和 Citeseer 上,由于负样本是随机采样的,AUC 通常会被高估,因为随机负样本中很多是“容易区分”的。更严格的评估是使用“所有未观测边”作为负样本,但计算量太大。常见的折中方案是:在测试阶段,对每个正样本采样 100 个负样本,计算 AUC 和 AP,然后取平均。如果 AUC 在 0.9 以上但 AP 只有 0.7 左右,说明模型对正样本的排序不够好,可能需要调整解码器或增加负采样难度。4. 避坑与排查:GCN 链路预测在 Cora/Citeseer 上的常见翻车点4.1 现象:验证集 AUC 很高,测试集 AUC 暴跌原因:边划分时信息泄露,训练时的邻接矩阵包含了验证集或测试集的边。解决:严格按训练边构建邻接矩阵,验证和测试时再逐步加入对应边。检查方法是:在训练阶段打印邻接矩阵的边数,确认它等于训练边数。4.2 现象:模型输出所有节点对的概率都接近 0.5原因:过平滑导致节点嵌入趋同,解码器无法区分正负样本。解决:减少 GCN 层数到两层,降低 dropout 率,或者改用双线性解码器增加表达能力。检查方法是:计算节点嵌入的方差,如果方差小于 0.01,说明过平滑严重。4.3 现象:训练损失下降但 AUC 不升原因:负采样太简单,模型学到了“所有负样本都容易区分”的捷径。解决:采用难负采样,优先选择嵌入空间中距离近但不相连的节点对。检查方法是:统计负样本的平均嵌入距离,如果远小于正样本距离,说明负采样太简单。4.4 现象:Citeseer 上 AUC 明显低于 Cora原因:Citeseer 的特征维度更高但边更稀疏,GCN 的聚合过程容易受噪声影响。解决:对特征做 L2 归一化,或者在第一层 GCN 前加一个线性降维层。检查方法是:对比两个数据集上的节点度数分布,Citeseer 的平均度数更低,需要更小的隐藏维度。4.5 现象:训练时间过长,GPU 利用率低原因:负采样在 Python 循环中逐条采样,效率低。解决:用向量化方式批量采样,或者预先生成负样本池。检查方法是:用torch.cuda.Event计时,确认负采样耗时占比是否超过 50%。5. 设计取舍:为什么在小图上 GCN 不是链路预测的最优解在 Cora 和 Citeseer 上,GCN 链路预测的 AUC 通常能做到 0.85 到 0.92 之间,但传统启发式方法如 Adamic-Adar 在某些划分下也能达到 0.88 左右。这意味着 GCN 的优势并不绝对,尤其是在边密度低、特征稀疏的场景下。GCN 的真正价值在于:它能联合学习节点特征和结构信息,当图上有丰富的节点属性时,GCN 的表示能力会超过纯拓扑启发式方法。但在 Cora 和 Citeseer 上,词袋特征的信息量有限,GCN 的增益主要来自结构聚合,而不是特征编码。因此,一个实用的取舍是:先用启发式方法做基线,如果 AUC 已经满足需求,就不必上 GCN;如果需要端到端学习,并且图上有额外的节点特征,再考虑 GCN。另一个取舍是编码器深度:两层是性价比最高的选择,三层以上需要配合残差连接或 JKNet 才能避免过平滑。解码器方面,内积适合快速验证,双线性适合追求更高 AUC,但参数量会增加,需要更强的正则化。负采样策略上,均匀采样实现简单但容易高估性能,难负采样更接近真实场景但训练不稳定,需要调整学习率。这些取舍没有标准答案,取决于你对 AUC、训练时间和实现复杂度的权衡。我自己的习惯是:每次跑链路预测,先固定随机种子跑三次,取平均 AUC,再对比不同配置。如果三次的方差超过 0.02,说明划分或初始化不稳定,需要检查边划分和负采样。从那以后我每次做链路预测实验,都强制走一遍“训练边邻接矩阵检查 + 嵌入方差检查 + 负样本距离检查”,这三个检查能挡掉大部分翻车情况。希望帮到你。本文还有配套的精品资源,点击获取