超大規(guī)模數(shù)據(jù)集類的創(chuàng)建

引言

本文是datawhale開源社區(qū)GNN組隊學(xué)習(xí)的筆記,絕大部分內(nèi)容出自其中困檩。
torch_geometric.data.InMemoryDataset類的使用是讓數(shù)據(jù)可全部儲存于內(nèi)存的數(shù)據(jù)集购啄,這些數(shù)據(jù)集對應(yīng)的數(shù)據(jù)集類在創(chuàng)建對象時就將所有數(shù)據(jù)都加載到內(nèi)存。然而在一些應(yīng)用場景中扇苞,數(shù)據(jù)集規(guī)模超級大,我們很難有足夠大的內(nèi)存完全存下所有數(shù)據(jù)不从。因此需要一個按需加載樣本到內(nèi)存的數(shù)據(jù)集類牙甫。

Dataset基類

在我們將學(xué)習(xí)為一個包含上千萬個圖樣本的數(shù)據(jù)集構(gòu)建一個數(shù)據(jù)集類。
在PyG中虽界,我們通過繼承torch_geometric.data.Dataset基類來自定義一個按需加載樣本到內(nèi)存的數(shù)據(jù)集類。
繼承torch_geometric.data.InMemoryDataset基類要實現(xiàn)的方法(raw_file_names(), processed_file_names(),download(),process())涛菠,繼承此基類同樣要實現(xiàn)莉御,此外還需要實現(xiàn)以下方法:

  1. len():返回數(shù)據(jù)集中的樣本的數(shù)量。
  2. get():實現(xiàn)加載單個圖的操作俗冻。注意:在內(nèi)部礁叔,getitem()返回通過調(diào)用get()來獲取Data對象,并根據(jù)transform參數(shù)對它們進(jìn)行選擇性轉(zhuǎn)換言疗。

對于無需下載數(shù)據(jù)集原文件的情況晴圾,我們不重寫download方法即可跳過下載。對于無需對數(shù)據(jù)集做預(yù)處理的情況噪奄,我們不重寫process方法即可跳過預(yù)處理死姚。

合并小圖組成大圖

圖可以有任意數(shù)量的節(jié)點和邊,它不是規(guī)整的數(shù)據(jù)結(jié)構(gòu)勤篮,因此對圖數(shù)據(jù)封裝成批的操作與對圖像和序列等數(shù)據(jù)封裝成批的操作不同都毒。PyTorch Geometric中采用的將多個圖封裝成批的方式是,將小圖作為連通組件(connected component)的形式合并碰缔,構(gòu)建一個大圖账劲。于是小圖的鄰接矩陣存儲在大圖鄰接矩陣的對角線上。大圖的鄰接矩陣、屬性矩陣瀑焦、預(yù)測目標(biāo)矩陣分別為:


合并后的大圖

此方法有以下關(guān)鍵的優(yōu)勢:

  • 依靠消息傳遞方案的GNN運(yùn)算不需要被修改腌且,因為消息仍然不能在屬于不同圖的兩個節(jié)點之間交換。
  • 沒有額外的計算或內(nèi)存的開銷榛瓮。例如铺董,這個批處理程序的工作完全不需要對節(jié)點或邊緣特征進(jìn)行任何填充。請注意禀晓,鄰接矩陣沒有額外的內(nèi)存開銷精续,因為它們是以稀疏的方式保存的,只保留非零項粹懒,即邊重付。
    小圖中的屬性拼接
    將小圖存儲到大圖中時需要對小圖的屬性做一些修改,一個最顯著的例子就是要對節(jié)點序號增值凫乖。在最一般的形式中确垫,PyTorch Geometric的DataLoader類會自動對edge_index張量增值,增加的值為當(dāng)前被處理圖的前面的圖的累積節(jié)點數(shù)量拣凹。比方說森爽,現(xiàn)在對第個圖的edge_index張量做增值恨豁,前面?zhèn)€圖的累積節(jié)點數(shù)量為嚣镜,那么對第個圖的edge_index張量的增值。增值后橘蜜,對所有圖的edge_index張量(其形狀為[2, num_edges])在第二維中連接起來菊匿。

然而,有一些特殊的場景中(如下所述)计福,基于需求我們希望能修改這一行為跌捆。PyTorch Geometric允許我們通過覆蓋torch_geometric.data.__inc__()torch_geometric.data.__cat_dim__()函數(shù)來實現(xiàn)我們希望的行為。

from torch_geometric.data import Data, DataLoader
import torch

class PairData(Data):
    #將兩個圖象颖,一個源圖G_s和一個目標(biāo)圖G_t佩厚,存儲在一個Data類中
    def __init__(self, edge_index_s, x_s, edge_index_t, x_t):
        super(PairData, self).__init__()
        self.edge_index_s = edge_index_s
        self.x_s = x_s
        self.edge_index_t = edge_index_t
        self.x_t = x_t
    
    #c重寫__inc__()兩個連續(xù)的圖的屬性之間的增量大小
    def __inc__(self, key, value):
        if key == 'edge_index_s':
            return self.x_s.size(0)
        if key == 'edge_index_t':
            return self.x_t.size(0)
        else:
            return super().__inc__(key, value)

# 定義邊索引矩陣
edge_index_s = torch.tensor([
    [0, 0, 0, 0],
    [1, 2, 3, 4],
])
#5個節(jié)點,16個特征
x_s = torch.randn(5, 16)  

edge_index_t = torch.tensor([
    [0, 0, 0],
    [1, 2, 3],
])
#4個節(jié)點 16個特征
x_t = torch.randn(4, 16)

data = PairData(edge_index_s, x_s, edge_index_t, x_t)
data_list = [data, data]

loader = DataLoader(data_list, batch_size=2)
batch = next(iter(loader))


print(batch)
#對應(yīng)小圖 未成功將bacth映射成小圖
print(batch.edge_index_s)
print(batch.edge_index_t)
'''
Batch(edge_index_s=[2, 8], edge_index_t=[2, 6], x_s=[10, 16], x_t=[8, 16])
tensor([[0, 0, 0, 0, 5, 5, 5, 5],
        [1, 2, 3, 4, 6, 7, 8, 9]])
tensor([[0, 0, 0, 4, 4, 4],
        [1, 2, 3, 5, 6, 7]])
'''

# 利用follow_batch屬性
loader = DataLoader(data_list, batch_size=2, follow_batch=['x_s', 'x_t'])
batch = next(iter(loader))

print(batch)
# Batch(edge_index_s=[2, 8], x_s=[10, 16], x_s_batch=[10], edge_index_t=[2, 6], x_t=[8, 16], x_t_batch=[8])
print(batch.x_s_batch)
# tensor([0, 0, 0, 0, 0, 1, 1, 1, 1, 1])

print(batch.x_t_batch)
# tensor([0, 0, 0, 0, 1, 1, 1, 1])

二部圖的增值
一般來說说订,不同類型的節(jié)點數(shù)量不需要一致抄瓦,于是二部圖的鄰接矩陣A \in \{0,1\}^{N \times M}可能不是平方矩陣,即可能有N \neq M陶冷。對二部圖的封裝成批過程中钙姊,edge_index 中邊的源節(jié)點與目標(biāo)節(jié)點做的增值操作應(yīng)是不同的。

class BipartiteData(Data):
    def __init__(self, edge_index, x_s, x_t):
        super(BipartiteData, self).__init__()
        self.edge_index = edge_index
        self.x_s = x_s
        self.x_t = x_t

def __inc__(self, key, value):
    if key == 'edge_index':
        #源埂伦、目標(biāo)節(jié)點增值
        return torch.tensor([[self.x_s.size(0)], [self.x_t.size(0)]])
    else:
        return super().__inc__(key, value)

edge_index = torch.tensor([
    [0, 0, 1, 1],
    [0, 1, 1, 2],
])
x_s = torch.randn(2, 16)  # 2 nodes.
x_t = torch.randn(3, 16)  # 3 nodes.

data = BipartiteData(edge_index, x_s, x_t)
data_list = [data, data]
loader = DataLoader(data_list, batch_size=2)
batch = next(iter(loader))

print(batch)
# Batch(edge_index=[2, 8], x_s=[4, 16], x_t=[6, 16])

#邊的源節(jié)點增值為2煞额,目標(biāo)節(jié)點增值為3
print(batch.edge_index)
# tensor([[0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 1, 2, 3, 4, 4, 5]])

超大圖數(shù)據(jù)實踐

import os
import os.path as osp

import pandas as pd
import torch
from ogb.utils import smiles2graph
from ogb.utils.torch_util import replace_numpy_with_torchtensor
from ogb.utils.url import download_url, extract_zip
from rdkit import RDLogger
from torch_geometric.data import Data, Dataset
import shutil
from torch_geometric.data import DataLoader
from tqdm import tqdm

RDLogger.DisableLog('rdApp.*')

class MyPCQM4MDataset(Dataset):

    def __init__(self, root):
        self.url = 'https://dgl-data.s3-accelerate.amazonaws.com/dataset/OGB-LSC/pcqm4m_kddcup2021.zip'
        super(MyPCQM4MDataset, self).__init__(root)

        filepath = osp.join(root, 'raw/data.csv.gz')
        data_df = pd.read_csv(filepath)
        self.smiles_list = data_df['smiles']
        self.homolumogap_list = data_df['homolumogap']

    @property
    def raw_file_names(self):
        return 'data.csv.gz'

    def download(self):
        path = download_url(self.url, self.root)
        extract_zip(path, self.root)
        os.unlink(path)
        shutil.move(osp.join(self.root, 'pcqm4m_kddcup2021/raw/data.csv.gz'), osp.join(self.root, 'raw/data.csv.gz'))

    def len(self):
        return len(self.smiles_list)

    def get(self, idx):
        smiles, homolumogap = self.smiles_list[idx], self.homolumogap_list[idx]
        graph = smiles2graph(smiles)
        assert(len(graph['edge_feat']) == graph['edge_index'].shape[1])
        assert(len(graph['node_feat']) == graph['num_nodes'])

        x = torch.from_numpy(graph['node_feat']).to(torch.int64)
        edge_index = torch.from_numpy(graph['edge_index']).to(torch.int64)
        edge_attr = torch.from_numpy(graph['edge_feat']).to(torch.int64)
        y = torch.Tensor([homolumogap])
        num_nodes = int(graph['num_nodes'])
        data = Data(x, edge_index, edge_attr, y, num_nodes=num_nodes)
        return data

    # 獲取數(shù)據(jù)集劃分
    def get_idx_split(self):
        split_dict = replace_numpy_with_torchtensor(torch.load(osp.join(self.root, 'pcqm4m_kddcup2021/split_dict.pt')))
        return split_dict


dataset = MyPCQM4MDataset('dataset2')
dataloader = DataLoader(dataset, batch_size=256, shuffle=True, num_workers=4)
for batch in tqdm(dataloader):
    pass
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子膊毁,更是在濱河造成了極大的恐慌胀莹,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婚温,死亡現(xiàn)場離奇詭異嗜逻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)缭召,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門栈顷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嵌巷,你說我怎么就攤上這事萄凤。” “怎么了搪哪?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵靡努,是天一觀的道長。 經(jīng)常有香客問我晓折,道長惑朦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任漓概,我火速辦了婚禮漾月,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胃珍。我一直安慰自己梁肿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布觅彰。 她就那樣靜靜地躺著吩蔑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪填抬。 梳的紋絲不亂的頭發(fā)上烛芬,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音飒责,去河邊找鬼赘娄。 笑死,一個胖子當(dāng)著我的面吹牛读拆,可吹牛的內(nèi)容都是我干的擅憔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼檐晕,長吁一口氣:“原來是場噩夢啊……” “哼暑诸!你這毒婦竟也來了蚌讼?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤个榕,失蹤者是張志新(化名)和其女友劉穎篡石,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體西采,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凰萨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了械馆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胖眷。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖霹崎,靈堂內(nèi)的尸體忽然破棺而出珊搀,到底是詐尸還是另有隱情,我是刑警寧澤尾菇,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布境析,位于F島的核電站,受9級特大地震影響派诬,放射性物質(zhì)發(fā)生泄漏劳淆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一默赂、第九天 我趴在偏房一處隱蔽的房頂上張望沛鸵。 院中可真熱鬧,春花似錦放可、人聲如沸谒臼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拾氓,卻和暖如春冯挎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咙鞍。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工房官, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人续滋。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓翰守,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疲酌。 傳聞我的和親對象是個殘疾皇子蜡峰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容