【问题标题】:How to split the Cora dataset to train a GCN model only on training part?如何拆分 Cora 数据集以仅在训练部分训练 GCN 模型?
【发布时间】:2021-09-10 04:18:38
【问题描述】:

我正在 Cora 数据集上训练 GCN(图卷积网络)。

Cora 数据集具有以下属性:

Number of graphs: 1
Number of features: 1433
Number of classes: 7
Number of nodes: 2708
Number of edges: 10556
Number of training nodes: 140
Training node label rate: 0.05
Is undirected: True

Data(edge_index=[2, 10556], test_mask=[2708], train_mask=[2708], val_mask=[2708], x=[2708, 1433], y=[2708])

由于我的代码很长,我只把我的代码的相关部分放在这里。首先,我将 Cora 数据集拆分如下:

def to_mask(index, size):
    mask = torch.zeros(size, dtype=torch.bool)
    mask[index] = 1
    return mask

def cora_splits(data, num_classes):
    indices = []

    for i in range(num_classes):
        # returns all indices of the elements = i from data.y tensor
        index = (data.y == i).nonzero().view(-1)

        # returns a random permutation of integers from 0 to index.size(0).
        index = index[torch.randperm(index.size(0))]

        # indices is a list of tensors and it has a length of 7
        indices.append(index)

    # select 20 nodes from each class for training
    train_index = torch.cat([i[:20] for i in indices], dim=0)

    rest_index = torch.cat([i[20:] for i in indices], dim=0)
    rest_index = rest_index[torch.randperm(len(rest_index))]

    data.train_mask = to_mask(train_index, size=data.num_nodes)
    data.val_mask = to_mask(rest_index[:500], size=data.num_nodes)
    data.test_mask = to_mask(rest_index[500:], size=data.num_nodes)

    return data

train 如下(取自here,稍作修改):


def train(model, optimizer, data, epoch):
    t = time.time()
    model.train()
    optimizer.zero_grad()
    output = model(data)
    loss_train = F.nll_loss(output[data.train_mask], data.y[data.train_mask])
    acc_train = accuracy(output[data.train_mask], data.y[data.train_mask])
    loss_train.backward()
    optimizer.step()

    loss_val = F.nll_loss(output[data.val_mask], data.y[data.val_mask])
    acc_val = accuracy(output[data.val_mask], data.y[data.val_mask])

def accuracy(output, labels):
    preds = output.max(1)[1].type_as(labels)
    correct = preds.eq(labels).double()
    correct = correct.sum()
    return correct / len(labels)

当我在 10 次运行中运行 200 个 epoch 时,我获得了:

tensor([0.7690, 0.8030, 0.8530, 0.8760, 0.8600, 0.8550, 0.8850, 0.8580, 0.8940, 0.8830])

Val Loss: 0.5974, Test Accuracy: 0.854 ± 0.039

张量中的每个值都属于每次运行的模型准确度,所有这 10 次运行的平均准确度为 0.854,std ± 0.039。

可以观察到,从第一次运行到第 10 次运行的准确度大幅提高。因此,我认为该模型过度拟合。过拟合的一个原因是,在代码中,模型在训练时已经看到了测试数据,因为在 train 函数中,有一行 output = model(data) 所以模型是在整个数据上训练的。我打算只在部分数据(类似于data[data.train_mask])上训练我的模型,但问题是我无法通过data[data.train_mask],因为forward 模型的forward 函数@ 987654335@ (来自this repository):

def forward(self, data):
        x, edge_index = data.x, data.edge_index
        x = F.relu(self.conv1(x, edge_index))
        for conv in self.convs:
            x = F.relu(conv(x, edge_index))
        x = F.relu(self.lin1(x))
        x = F.dropout(x, p=0.5, training=self.training)
        x = self.lin2(x)
        return F.log_softmax(x, dim=-1)

如果我将data[data.train_mask] 传递给GCN 模型,那么在上述forward 函数中的x, edge_index = data.x, data.edge_index、x 和edge_index 无法从data[data.train_mask] 中检索到。因此,我需要找到一种方法来拆分 Cora 数据集,以便我可以将其中的特定部分与节点、边缘索引和其他属性一起传递给模型。我的问题是怎么做?

此外,非常感谢任何关于 k 折交叉验证的建议。

【问题讨论】:

  • 请注意,vanilla GCN 处理 transductive 学习,在训练期间可以看到测试数据(不知道标签)。您所说的是 inductive 学习,您可能需要更改更多代码(例如,使用 minibatches)以使其表现得相当好。有关示例,请参阅 GraphSage。
  • @hkchengrex 谢谢。我检查了您提供的链接以及相应的代码(GitHub 存储库)和citation_eval.pyhere。他们使用的数据集是“Protein-Protein Interactions”和“Reddit”,它们的结构与 Cora 数据集不同。 Cora 数据集的挑战在于它只包含一个图,创建小批量并不简单。
  • 是的。就其本质而言,单图 Cora 更适合于转换设置。您可能需要考虑您真正想要实现的目标,即更改数据集或更改设置。

标签: python python-3.x validation neural-network pytorch


【解决方案1】:

我猜你对transductive learning 的性质有点困惑,你提出的问题实际上并没有解决你面临的问题。

可以观察到,从第一次运行到第 10 次的准确度 正在大幅增加。因此,我认为模型是 过拟合。

不一定,当您的模型从训练样本中学习时,提高测试准确性可能是一种正常行为。由于损失函数的复杂性和非凸性,学习可以持续几十个epoch。判断过度拟合的最佳信号是当您的训练准确度提高但测试准确度显着下降时。

过拟合的一个原因是在代码中,测试数据已经 模型在训练时间内看到的,因为在训练函数中, 有一条线 output = model(data) 所以模型是在 整个数据。

模型确实在训练中看到了整个图(邻接矩阵),但它只看到训练集中节点的标签,对测试集中节点的标签一无所知。这正是转导式学习所做的。

最后,如果您 100% 确定要避免 转导学习 的范式,那么您可能需要编写自己的拆分算法来实现这一目标。但我想提醒一下,在现实世界的用例中,转导非常适合。一个例子是预测社交网络用户之间的潜在联系,我们将整个网络结构作为输入,并希望简单地运行边缘预测——>转导。因此,避免它没有多大意义。


根据您的任务,您可以看看 Stellargraph 的 EdgeSplitter 类 (docs) 和 scikit-learn 的 train_test_split 函数 (docs) 如何实现拆分。

节点分类

如果您的任务是节点分类任务,Node classification with Graph Convolutional Network (GCN) 是一个很好的示例,说明如何加载数据并进行训练-测试-拆分。它以 Cora 数据集为例。最重要的步骤如下:

dataset = sg.datasets.Cora()
display(HTML(dataset.description))
G, node_subjects = dataset.load()

train_subjects, test_subjects = model_selection.train_test_split(
    node_subjects, train_size=140, test_size=None, stratify=node_subjects
)
val_subjects, test_subjects = model_selection.train_test_split(
    test_subjects, train_size=500, test_size=None, stratify=test_subjects
)

train_gen = generator.flow(train_subjects.index, train_targets)
val_gen = generator.flow(val_subjects.index, val_targets)
test_gen = generator.flow(test_subjects.index, test_targets)

基本上和普通分类任务的train-test-split是一样的,只不过我们这里分割的是节点。

边缘分类

如果你的任务是边缘分类,你可以看看这个Link prediction example: GCN on the Cora citation dataset。 train-test-split 最相关的代码是

# Define an edge splitter on the original graph G:
edge_splitter_test = EdgeSplitter(G)

# Randomly sample a fraction p=0.1 of all positive links, and same number of negative links, from G, and obtain the
# reduced graph G_test with the sampled links removed:
G_test, edge_ids_test, edge_labels_test = edge_splitter_test.train_test_split(
    p=0.1, method="global", keep_connected=True
)

# Define an edge splitter on the reduced graph G_test:
edge_splitter_train = EdgeSplitter(G_test)

# Randomly sample a fraction p=0.1 of all positive links, and same number of negative links, from G_test, and obtain the
# reduced graph G_train with the sampled links removed:
G_train, edge_ids_train, edge_labels_train = edge_splitter_train.train_test_split(
    p=0.1, method="global", keep_connected=True
)

# For training we create a generator on the G_train graph, and make an 
# iterator over the training links using the generator’s flow() method:

train_gen = FullBatchLinkGenerator(G_train, method="gcn")
train_flow = train_gen.flow(edge_ids_train, edge_labels_train)
test_gen = FullBatchLinkGenerator(G_test, method="gcn")
test_flow = train_gen.flow(edge_ids_test, edge_labels_test)

这里EdgeSplitter类(docs)后面的分割算法比较复杂,需要在分割的同时保持图的结构,比如保持图的连通性。更多详情,请参阅EdgeSplitter的源代码

【讨论】:

    猜你喜欢
    • 2019-05-01
    • 1970-01-01
    • 2020-10-26
    • 2021-12-08
    • 2015-10-25
    • 1970-01-01
    • 1970-01-01
    • 2018-06-04
    • 2018-08-18
    相关资源
    最近更新 更多