链接预测中训练集、验证集以及测试集的划分(以PyG的RandomLinkSplit为例)

1. 链接预测的基本概念

在图任务中,所谓链接预测,一般有两种含义:在静态网络中,链接预测用于发现缺失的链接,而在动态网络中,链接预测用于预测未来可能出现的链接。

之前学术界关于链接预测的方案主要可以分为三类:启发式方法(Heuristic Methods),Network Embedding的方法和图神经网络方法。

  • 启发式方法(Heuristic Methods): 主要考虑利用两个节点之间一些预先定义的特征来衡量节点之间的相似度,比如,节点的共同邻居(Common Neighbor),Jaccard相似度,Katz Index等,这样的方法假设存在边关系的节点之间存在某种特定的结构特性,但这样的假设不一定对任意网络都有效。
  • Network Embeddings方法: 使用基于游走的方法得到经过某个节点的多条路径,再通过Skip-Gram和CBOW的方式,对路径上mask的节点进行预测。这样的方法,没有直接将链接预测任务嵌入到有监督学习的流程中,并且无法较好的利用用户的节点属性,无法达到较好的预测精度。
  • 图神经网络(graph neural network,GNN): 主要分为两类,节点为中心的图神经网络模型(Node-centric GNN)和边为中心的图神经网络模型(Edge-centric GNN)。

在GNN链接预测中,我们一般将链接预测转换为一个二分类问题:图中存在的边我们称之为正样本,不存在的边我们称之为负样本。

一个现实的问题是:网络中存在的链接数往往都是远小于不存在的链接数的,也就是说图中的正样本数量远小于负样本数量,为了使模型训练较为均衡,我们通常先将正样本分为训练集、验证集和测试集,然后再分别从三个数据集中采样等同数量的负样本参与训练、验证以及测试。

2. 一些术语

在PyG的链接预测中,一共有4种类型的边:

  1. training message edges:用于GNN的消息传递和聚合的边。
  2. training supervision edges:分为正样本和负样本,在利用training message edges得到节点的向量表示后,利用training supervision edges进行有监督学习。
  3. validation edges:用于验证的边。
  4. testing edges:用于测试的边。

需要注意的是:validation edgestesting edges不能和训练阶段的两种边有交集。

一个问题:模型训练阶段中的training meaasge edgestraining supervision edges允许出现相同边吗?会不会造成数据泄露?

一般来说,在消息传递和监督中使用相同的边集可能会在训练阶段导致数据泄漏,但这取决于模型的能力。例如,GAE使用基于GCN的编码器和基于点积的解码器,编码器和解码器的能力都有限,因此模型的数据泄漏能力也有限。

以上内容来自PyG作者在一个issue中的回答:RandomLinkSplit 中的拆分错误 #3668

3. 实例

3.1 数据集介绍

这里以CiteSeer网络为例:Citeseer网络是一个引文网络,节点为论文,一共3327篇论文。论文一共分为六类:Agents、AI(人工智能)、DB(数据库)、IR(信息检索)、ML(机器语言)和HCI。如果两篇论文间存在引用关系,那么它们之间就存在链接关系。

加载数据:

dataset = Planetoid('data', name='CiteSeer')
print(dataset[0])

输出:

Data(x=[3327, 3703], edge_index=[2, 9104], y=[3327], train_mask=[3327], val_mask=[3327], test_mask=[3327])

x=[3327, 3703]表示一共有3327个节点,然后节点的特征维度为3703,这里实际上是去除停用词和在文档中出现频率小于10次的词,整理得到3703个唯一词。edge_index=[2, 9104],表示一共9104条edge,数据一共两行,每一行都表示节点编号。

3.2 RandomLinkSplit

得到数据集后,接下来我们需要划分训练集、验证集以及测试集。

划分的标准为:训练集中不能包含验证集和测试集中存在的链接,验证集中不能包含测试集中存在的链接。

利用PyG封装的RandomLinkSplit我们很容易实现数据集的划分。RandomLinkSplit的具体参数如下所示:
在这里插入图片描述
介绍几个常用的参数:

  1. num_val:验证集中边的比例,默认为0.1。
  2. num_test:测试集中边的比例,默认为0.1。
  3. is_undirected:如果为True,则假定图是无向图。
  4. add_negative_train_samples:是否为链接预测添加负训练样本,如果模型已经执行了负采样,则该选项应设置为False。一般我们设置为False,也就是训练集中不包含负样本,然后每一轮训练时在训练集中重新采样与正样本相同数量的负样本进行训练,这样可以保证每一轮训练中采样得到的负样本都是不一样的,可以有效提高模型泛化能力。默认为False,也就是不采样。
  5. neg_sampling_ratio:采样的正负样本的比例,默认为1。即验证集和测试集中正负样本个数一致。
  6. disjoint_train_ratio:如果设置为大于0的值,则不会为消息传递和监督共享训练边。

利用RandomLinkSplit对CiteSeer进行划分:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
transform = T.Compose([
    T.NormalizeFeatures(),
    T.ToDevice(device),
    T.RandomLinkSplit(num_val=0.1, num_test=0.1, is_undirected=True,
                      add_negative_train_samples=False),
])
dataset = Planetoid('data', name='CiteSeer', transform=transform)
train_data, val_data, test_data = dataset[0]

最终我们得到train_data, val_data, test_data

输出一下原始数据集和三个被划分出来的数据集:

Data(x=[3327, 3703], edge_index=[2, 9104], y=[3327], train_mask=[3327], val_mask=[3327], test_mask=[3327])
Data(x=[3327, 3703], edge_index=[2, 7284], y=[3327], train_mask=[3327], val_mask=[3327], test_mask=[3327], edge_label=[3642], edge_label_index=[2, 3642])
Data(x=[3327, 3703], edge_index=[2, 7284], y=[3327], train_mask=[3327], val_mask=[3327], test_mask=[3327], edge_label=[910], edge_label_index=[2, 910])
Data(x=[3327, 3703], edge_index=[2, 8194], y=[3327], train_mask=[3327], val_mask=[3327], test_mask=[3327], edge_label=[910], edge_label_index=[2, 910])

从上到下依次为原始数据集、训练集、验证集以及测试集。

可以发现,验证集和测试集中都有:

edge_label=[910], edge_label_index=[2, 910])

也就是说验证集和测试集中都一共包含了910条边,这里edge_label为01数据,其中正样本标签为1,负样本标签为0,输出一下edge_label之和:

print(val_data.edge_label.sum())
tensor(455., device='cuda:0')

910是455的两倍,说明验证集和测试集中正负样本的比例为1,这是因为neg_sampling_ratio默认为1。

对于训练集:

edge_index=[2, 7284], edge_label=[3642], edge_label_index=[2, 3642]
print(train_data.edge_label.sum())
tensor(3642., device='cuda:0')

说明训练集中包含了一共3642条边,并且这些边都是正样本,也就是原始图中存在的边,值得注意的是,由于设置了disjoint_train_ratio=0,那么训练集中消息传递和监督是会有重叠的,具体来讲就是训练过程中用于监督训练的边edge_label_index包含在了用于消息传递和聚合的边edge_index中。我们可以简单验证如下:

train_edge = [(train_data.edge_index[0][i].item(), train_data.edge_index[1][i].item()) for i in range(train_data.edge_index.size(1))]
train_label_edge = [(train_data.edge_label_index[0][i].item(), train_data.edge_label_index[1][i].item()) for i in range(train_data.edge_label_index.size(1))]
s = 0
for x in train_label_edge:
    if x in train_edge:
        s += 1
print('s=', s)

输出s=3642,说明edge_label_index完全包含在了edge_index中。

因此,原始图中一共9104条边,在0.1验证和0.1测试的比例下,验证集和测试集都分得了910个样本,这其中包括455个正样本和455个负样本,训练集分得了3642个正样本(负样本在训练时进行采样)。

而我们知道,9104的0.8和0.1分别为7283和910,这其中训练集一共得到了7284条边用于训练,这7284条边中的一半也就是3642条边用于有监督学习;测试集和验证集都得到了910条边(这里不全是正样本)。

  • 12
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyril_KI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值