Task04 数据完整存储与内存的数据集类+节点预测与边预测任务

数据完整存储与内存的数据集类

对于占用内存有限的数据集如Planetoid的Cora等,我们可以将整个数据集的数据都存储到内存里。PyG为我们提供了方便的方式来构造数据完全存于内存的数据集类——InMemoryDataset类
InMemoryDataset

PyG使用数据集的一般过程

主要包括以下4步:

  1. 从网络上下载数据原始文件。
  2. 每一个图样本生成一个Data 对象。
  3. 过滤Data 对象。
  4. 保存Data 对象到文件。

并不一定需要执行每一步。

InMemoryDataset基类

CLASS InMemoryDataset(root: Optional[str] = None, transform: Optional[Callable] = None, pre_transform: Optional[Callable] = None, pre_filter: Optional[Callable] = None)

root:字符串类型,存储数据集的文件夹的路径(此路径自定义)。该文件夹下有两个文件夹(raw_dir(数据集原始文件)和processed_dir(处理后的数据))。如下图。
root
transform :函数类型,一个数据转换函数,它接收一个Data对象并
返回一个转换后的Data对象。
pre_transform :函数类型,一个数据转换函数,它接收一个Data对
象并返回一个转换后的Data对象。
pre_filter :函数类型,一个检查数据是否要保留的函数,它接收一
个Data对象,返回此 Data对象是否应该被包含在最终的数据集中。

Planetoid例子

class Planetoid(InMemoryDataset):
	url = 'https://github.com/kimiyoung/planetoid/raw/master/data'

    def __init__(self, root: str, name: str, split: str = "public",
                 num_train_per_class: int = 20, num_val: int = 500,
                 num_test: int = 1000, transform: Optional[Callable] = None,
                 pre_transform: Optional[Callable] = None):
        self.name = name

        super().__init__(root, transform, pre_transform)
        self.data, self.slices = torch.load(self.processed_paths[0])

        self.split = split
        assert self.split in ['public', 'full', 'random']

        if split == 'full':
            data = self.get(0)
            data.train_mask.fill_(True)
            data.train_mask[data.val_mask | data.test_mask] = False
            self.data, self.slices = self.collate([data])

        elif split == 'random':
            data = self.get(0)
            data.train_mask.fill_(False)
            for c in range(self.num_classes):
                idx = (data.y == c).nonzero(as_tuple=False).view(-1)
                idx = idx[torch.randperm(idx.size(0))[:num_train_per_class]]
                data.train_mask[idx] = True

            remaining = (~data.train_mask).nonzero(as_tuple=False).view(-1)
            remaining = remaining[torch.randperm(remaining.size(0))]

            data.val_mask.fill_(False)
            data.val_mask[remaining[:num_val]] = True

            data.test_mask.fill_(False)
            data.test_mask[remaining[num_val:num_val + num_test]] = True

            self.data, self.slices = self.collate([data])

    @property
    def raw_dir(self) -> str:
        return osp.join(self.root, self.name, 'raw')

    @property
    def processed_dir(self) -> str:
        return osp.join(self.root, self.name, 'processed')

    @property
    def raw_file_names(self) -> List[str]:
        names = ['x', 'tx', 'allx', 'y', 'ty', 'ally', 'graph', 'test.index']
        return [f'ind.{self.name.lower()}.{name}' for name in names]

    @property
    def processed_file_names(self) -> str:
        return 'data.pt'

    def download(self):
        for name in self.raw_file_names:
            download_url('{}/{}'.format(self.url, name), self.raw_dir)

    def process(self):
        data = read_planetoid_data(self.raw_dir, self.name)
        data = data if self.pre_transform is None else self.pre_transform(data)
        torch.save(self.collate([data]), self.processed_paths[0])

    def __repr__(self) -> str:
        return f'{self.name}()'

通过Planetoid例子来进行分析,要继承InMemoryDataset类来构造一个我们自己的数据集类。我们需要重写以下四个函数:raw_file_names(),processed_file_names(),download()和process()。
InMemoryDataset类中显示为:

    @property
    def raw_file_names(self) -> Union[str, List[str], Tuple]:
        r"""The name of the files to find in the :obj:`self.raw_dir` folder in
        order to skip the download."""
        raise NotImplementedError
    @property
    def processed_file_names(self) -> Union[str, List[str], Tuple]:
        r"""The name of the files to find in the :obj:`self.processed_dir`
        folder in order to skip the processing."""
        raise NotImplementedError
    def download(self):
        r"""Downloads the dataset to the :obj:`self.raw_dir` folder."""
        raise NotImplementedError
    def process(self):
        r"""Processes the dataset to the :obj:`self.processed_dir` folder."""
        raise NotImplementedError

节点预测与边预测任务

节点预测任务

节点预测任务以是节点的值为主。去继承一个以GAT为主图神经网络,需要重写__init__()和forward()函数。如下:

class GAT(torch.nn.Module):
    def __init__(self, num_features, hidden_channels_list, num_classes):
        super(GAT, self).__init__()
        torch.manual_seed(12345)
        hns = [num_features] + hidden_channels_list
        conv_list = []
        for idx in range(len(hidden_channels_list)):
            conv_list.append((GATConv(hns[idx], hns[idx+1]), 'x, edge_index -> x'))
            conv_list.append(ReLU(inplace=True),)

        self.convseq = Sequential('x, edge_index', conv_list)
        self.linear = Linear(hidden_channels_list[-1], num_classes)

    def forward(self, x, edge_index):
        x = self.convseq(x, edge_index)
        x = F.dropout(x, p=0.5, training=self.training)
        x = self.linear(x)
        return x

其中Sequential为PyG的。如下图:
Sequential

边预测任务

边预测任务,目标是预测两个节点之间是否存在边。与节点预测任务不同的是需要重写的函数不同。需要重写以下四个函数__init__()、encode()、decode()和decode_all()函数。
encode()部分:

    def encode(self, x, edge_index):
        x = self.convseq(x, edge_index)
        x = F.dropout(x, p=0.5, training=self.training)
        x = self.linear(x)
        return x

decode()部分:

    def decode(self, z, pos_edge_index, neg_edge_index):
        edge_index = torch.cat([pos_edge_index, neg_edge_index], dim=-1)
        return (z[edge_index[0]] * z[edge_index[1]]).sum(dim=-1)

decode_all()部分:

    def decode_all(self, z):
        prob_adj = z @ z.t()
        return (prob_adj > 0).nonzero(as_tuple=False).t()

在通过常规的神经网络训练的范式即可完成。

作业

问题一

尝试使用PyG中的不同的网络层去代替GCNConv,以及不同的层数和不同的out_channels ,来实现节点分类任务。

此处选择使用GCNConv来代替GATConv,隐藏层有200->100变成200->100->100->20。
原始结果:
gAT
作业结果:
在这里插入图片描述
在这里插入图片描述

问题二

在边预测任务中,尝试用torch_geometric.nn.Sequential容器构造图神经网络。
修改代码处

    def __init__(self, num_features, hidden_channels_list, num_classes):
        super(Net, self).__init__()
        torch.manual_seed(12345)
        hns = [num_features] + hidden_channels_list
        conv_list = []
        for idx in range(len(hidden_channels_list)):
          conv_list.append((GATConv(hns[idx], hns[idx+1]), 'x, edge_index -> x')) 
          conv_list.append(nn.ReLU(inplace=True))
        self.convseq = Sequential('x, edge_index', conv_list)
        self.linear = nn.Linear(hidden_channels_list[-1], num_classes)

隐藏层设置为[128,64,32,32,8]。
作业结果为:
1
2

问题三

以data.train_pos_edge_index为实际参数来进行训练集负样本采样,但这样采样得到的负样本可能包含一些验证集的正样本与测试集的正样本,即可能将真实的正样本标记为负样本,由此会产生冲突。

加循环判断:

    flag = 1
    while flag:
        if (len(train_neg_edge_set & val_pos_edge_set) > 0) or (len(train_neg_edge_set & test_pos_edge_set) > 0):
            # 训练集负样本与验证集负样本存在交集,或训练集负样本与测试集负样本存在交集
          neg_edge_index = negative_sampling(
              edge_index=data.train_pos_edge_index,
              num_nodes=data.num_nodes,
              num_neg_samples=data.train_pos_edge_index.size(1))
        else:
          flag = 0

参考文献

[1] https://github.com/datawhalechina/team-learning-nlp
[2] https://pytorch-geometric.readthedocs.io/en/latest/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值