日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【转】节点预测与边预测任务实践

發(fā)布時間:2024/9/18 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【转】节点预测与边预测任务实践 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

節(jié)點預測與邊預測任務(wù)實踐

引言

在此小節(jié)我們將利用PlanetoidPubMed數(shù)據(jù)集類,來實踐節(jié)點預測與邊預測任務(wù)。

注:邊預測任務(wù)實踐中的代碼來源于link_pred.py。

節(jié)點預測任務(wù)實踐

之前我們學習過由2層GATConv組成的圖神經(jīng)網(wǎng)絡(luò),現(xiàn)在我們重定義一個GAT圖神經(jīng)網(wǎng)絡(luò),使其能夠通過參數(shù)來定義GATConv的層數(shù),以及每一層GATConv的out_channels。我們的圖神經(jīng)網(wǎng)絡(luò)定義如下:

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_listconv_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

由于我們的神經(jīng)網(wǎng)絡(luò)由多個GATConv順序相連而構(gòu)成,因此我們使用了torch_geometric.nn.Sequential容器,詳細內(nèi)容可見于官方文檔。

我們通過hidden_channels_list參數(shù)來設(shè)置每一層GATConv的outchannel,所以hidden_channels_list長度即為GATConv的層數(shù)。通過修改hidden_channels_list,我們就可構(gòu)造出不同的圖神經(jīng)網(wǎng)絡(luò)。

完整的代碼可見于codes/node_classification.py。請小伙伴們自行完成代碼中圖神經(jīng)網(wǎng)絡(luò)類的訓練、驗證和測試。

邊預測任務(wù)實踐

邊預測任務(wù),目標是預測兩個節(jié)點之間是否存在邊。拿到一個圖數(shù)據(jù)集,我們有節(jié)點屬性x,邊端點edge_index。edge_index存儲的便是正樣本。為了構(gòu)建邊預測任務(wù),我們需要生成一些負樣本,即采樣一些不存在邊的節(jié)點對作為負樣本邊,正負樣本數(shù)量應平衡。此外要將樣本分為訓練集、驗證集和測試集三個集合。

PyG中為我們提供了現(xiàn)成的采樣負樣本邊的方法,train_test_split_edges(data, val_ratio=0.05, test_ratio=0.1),其

  • 第一個參數(shù)為torch_geometric.data.Data對象,
  • 第二參數(shù)為驗證集所占比例,
  • 第三個參數(shù)為測試集所占比例。

該函數(shù)將自動地采樣得到負樣本,并將正負樣本分成訓練集、驗證集和測試集三個集合。它用train_pos_edge_index、train_neg_adj_mask、val_pos_edge_index、val_neg_edge_index、test_pos_edge_index和test_neg_edge_index,六個屬性取代edge_index屬性。

注意train_neg_adj_mask與其他屬性格式不同,其實該屬性在后面并沒有派上用場,后面我們?nèi)匀恍枰M行一次訓練集負樣本采樣。

下面我們使用Cora數(shù)據(jù)集作為例子,進行邊預測任務(wù)說明。

獲取數(shù)據(jù)集并進行分析

首先是獲取數(shù)據(jù)集并進行分析

import os.path as ospfrom torch_geometric.utils import negative_sampling from torch_geometric.datasets import Planetoid import torch_geometric.transforms as T from torch_geometric.utils import train_test_split_edgesdataset = Planetoid('dataset', 'Cora', transform=T.NormalizeFeatures()) data = dataset[0] data.train_mask = data.val_mask = data.test_mask = data.y = None # 不再有用print(data.edge_index.shape) # torch.Size([2, 10556])data = train_test_split_edges(data)for key in data.keys:print(key, getattr(data, key).shape)# x torch.Size([2708, 1433]) # val_pos_edge_index torch.Size([2, 263]) # test_pos_edge_index torch.Size([2, 527]) # train_pos_edge_index torch.Size([2, 8976]) # train_neg_adj_mask torch.Size([2708, 2708]) # val_neg_edge_index torch.Size([2, 263]) # test_neg_edge_index torch.Size([2, 527]) # 263 + 527 + 8976 = 9766 != 10556 # 263 + 527 + 8976/2 = 5278 = 10556/2

我們觀察到訓練集、驗證集和測試集中正樣本邊的數(shù)量之和不等于原始邊的數(shù)量。這是因為,現(xiàn)在所用的Cora圖是無向圖,在統(tǒng)計原始邊數(shù)量時,每一條邊的正向與反向各統(tǒng)計了一次,訓練集也包含邊的正向與反向,但驗證集與測試集都只包含了邊的一個方向。

為什么訓練集要包含邊的正向與反向,而驗證集與測試集都只包含了邊的一個方向? 這是因為,訓練集用于訓練,訓練時一條邊的兩個端點要互傳信息,只考慮一個方向的話,只能由一個端點傳信息給另一個端點,而驗證集與測試集的邊用于衡量檢驗邊預測的準確性,只需考慮一個方向的邊即可。

邊預測圖神經(jīng)網(wǎng)絡(luò)的構(gòu)造

接下來構(gòu)造神經(jīng)網(wǎng)絡(luò)

import torch from torch_geometric.nn import GCNConvclass Net(torch.nn.Module):def __init__(self, in_channels, out_channels):super(Net, self).__init__()self.conv1 = GCNConv(in_channels, 128)self.conv2 = GCNConv(128, out_channels)def encode(self, x, edge_index):x = self.conv1(x, edge_index)x = x.relu()return self.conv2(x, edge_index)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)def decode_all(self, z):prob_adj = z @ z.t()return (prob_adj > 0).nonzero(as_tuple=False).t()

用于做邊預測的神經(jīng)網(wǎng)絡(luò)主要由兩部分組成:其一是編碼(encode),它與我們前面介紹的節(jié)點表征生成是一樣的;其二是解碼(decode),它根據(jù)邊兩端節(jié)點的表征生成邊為真的幾率(odds)。decode_all(self, z)用于推理(inference)階段,我們要對所有的節(jié)點對預測存在邊的幾率。

邊預測圖神經(jīng)網(wǎng)絡(luò)的訓練

定義單個epoch的訓練過程

def get_link_labels(pos_edge_index, neg_edge_index):num_links = pos_edge_index.size(1) + neg_edge_index.size(1)link_labels = torch.zeros(num_links, dtype=torch.float)link_labels[:pos_edge_index.size(1)] = 1.return link_labelsdef train(data, model, optimizer):model.train()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))optimizer.zero_grad()z = model.encode(data.x, data.train_pos_edge_index)link_logits = model.decode(z, data.train_pos_edge_index, neg_edge_index)link_labels = get_link_labels(data.train_pos_edge_index, neg_edge_index).to(data.x.device)loss = F.binary_cross_entropy_with_logits(link_logits, link_labels)loss.backward()optimizer.step()return loss

通常,存在邊的節(jié)點對的數(shù)量往往少于不存在邊的節(jié)點對的數(shù)量。我們在每一個epoch的訓練過程中,都進行一次訓練集負樣本采樣。采樣到的樣本數(shù)量與訓練集正樣本相同,但不同epoch中采樣到的樣本是不同的。這樣做,我們既能實現(xiàn)類別數(shù)量平衡,又能實現(xiàn)增加訓練集負樣本的多樣性。在負樣本采樣時,我們傳遞了train_pos_edge_index為參數(shù),于是negative_sampling()函數(shù)只會在訓練集中不存在邊的節(jié)點對中采樣。get_link_labels()函數(shù)用于生成完整訓練集的標簽。

注:在訓練階段,我們應該只見訓練集,對驗證集與測試集都是不可見的。所以我們沒有使用所有的邊,而是只用了訓練集正樣本邊。

定義單個epoch驗證與測試過程

@torch.no_grad() def test(data, model):model.eval()z = model.encode(data.x, data.train_pos_edge_index)results = []for prefix in ['val', 'test']:pos_edge_index = data[f'{prefix}_pos_edge_index']neg_edge_index = data[f'{prefix}_neg_edge_index']link_logits = model.decode(z, pos_edge_index, neg_edge_index)link_probs = link_logits.sigmoid()link_labels = get_link_labels(pos_edge_index, neg_edge_index)results.append(roc_auc_score(link_labels.cpu(), link_probs.cpu()))return results

注:在驗證與測試階段,我們也應該只見訓練集,對驗證集與測試集都是不可見的。所以在驗證與測試階段,我們依然只用訓練集正樣本邊。

運行完整的訓練、驗證與測試

def main():device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')dataset = 'Cora'path = osp.join(osp.dirname(osp.realpath(__file__)), '..', 'data', dataset)dataset = Planetoid(path, dataset, transform=T.NormalizeFeatures())data = dataset[0]ground_truth_edge_index = data.edge_index.to(device)data.train_mask = data.val_mask = data.test_mask = data.y = Nonedata = train_test_split_edges(data)data = data.to(device)model = Net(dataset.num_features, 64).to(device)optimizer = torch.optim.Adam(params=model.parameters(), lr=0.01)best_val_auc = test_auc = 0for epoch in range(1, 101):loss = train(data, model, optimizer)val_auc, tmp_test_auc = test(data, model)if val_auc > best_val_auc:best_val_auc = val_auctest_auc = tmp_test_aucprint(f'Epoch: {epoch:03d}, Loss: {loss:.4f}, Val: {val_auc:.4f}, 'f'Test: {test_auc:.4f}')z = model.encode(data.x, data.train_pos_edge_index)final_edge_index = model.decode_all(z)if __name__ == "__main__":main()

完整的代碼可見于codes/edge_prediction.py。

結(jié)語

在完整的第6節(jié)內(nèi)容中,我們學習了

  • PyG中規(guī)定的使用數(shù)據(jù)的一般過程;
  • InMemoryDataset基類;
  • 一個簡化的InMemory數(shù)據(jù)集類;
  • 一個InMemory數(shù)據(jù)集類實例,以及使用該數(shù)據(jù)集類時會發(fā)生的一些過程;
  • 節(jié)點預測任務(wù)實踐;
  • 邊預測任務(wù)實踐。

我們需要重點關(guān)注**InMemory數(shù)據(jù)集類的運行流程與其四個方法的定義的規(guī)范**,同時我們還應該重點關(guān)注邊預測任務(wù)中的數(shù)據(jù)集劃分訓練集負樣本采樣,以及訓練、驗證與測試三個階段使用的邊

作業(yè)

  • 實踐問題一:嘗試使用PyG中的不同的網(wǎng)絡(luò)層去代替GCNConv,以及不同的層數(shù)和不同的out_channels,來實現(xiàn)節(jié)點分類任務(wù)。

  • 實踐問題二:在邊預測任務(wù)中,嘗試用torch_geometric.nn.Sequential容器構(gòu)造圖神經(jīng)網(wǎng)絡(luò)。

  • 思考問題三:如下方代碼所示,我們以data.train_pos_edge_index為實際參數(shù)來進行訓練集負樣本采樣,但這樣采樣得到的負樣本可能包含一些驗證集的正樣本與測試集的正樣本,即可能將真實的正樣本標記為負樣本,由此會產(chǎn)生沖突。但我們還是這么做,這是為什么?

    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))

參考資料

  • Sequential官網(wǎng)文檔:torch_geometric.nn.Sequential
  • 邊預測任務(wù)實踐中的代碼來源于link_pred.py

參考答案

思考問題三:
問題:我們以data.train_pos_edge_index為實際參數(shù)來進行訓練集負樣本采樣,但這樣采樣得到的負樣本可能包含一些驗證集的正樣本與測試集的正樣本,即可能將真實的正樣本標記為負樣本,由此會產(chǎn)生沖突。但我們還是這么做,這是為什么?

解答:
首先我們討論如果使用edge_index為實際參數(shù)會怎么樣?如果以edge_index為實際參數(shù),negative_sampling()函數(shù)采樣到的是真實的負樣本。以真實負樣本作為訓練集負樣本,訓練集負樣本就不會與驗證集正樣本有交集,也不會與測試集正樣本有交集。 理論上這種采樣方式產(chǎn)生的驗證集的評估結(jié)果和測試集的評估結(jié)果都會更好,實際也是如此。但我們不能采用這種訓練集負樣本采樣方式,這是為什么?

整個數(shù)據(jù)集的正負樣本邊可劃分為訓練集正樣本邊、驗證集正樣本邊、測試集正樣本邊和所有負樣本邊,共四個集合。在訓練邊預測圖神經(jīng)網(wǎng)絡(luò)時,我們要輸入所有訓練集邊的節(jié)點。如果訓練集由訓練集正樣本邊和所有的負樣本邊組成,那么有極大的可能性,所有的節(jié)點都要輸入給圖神經(jīng)網(wǎng)絡(luò)。一個節(jié)點只有在滿足以下的條件時,才一定不會在訓練階段被輸入給圖神經(jīng)網(wǎng)絡(luò):

  • 該節(jié)點與其他所有節(jié)點相連。如果該節(jié)點與某個節(jié)點不相連,那么此對節(jié)點間存在負樣本邊,所有負樣本邊都有可能被采樣,于是該節(jié)點可能會在訓練階段被輸入給圖神經(jīng)網(wǎng)絡(luò)。
  • 該節(jié)點所有的邊都被劃分到了驗證集或測試集。這種可能性非常小。
  • 當邊預測圖神經(jīng)網(wǎng)絡(luò)能夠感知所有的節(jié)點時,它也就能夠感知所有的正負樣本邊。在訓練階段,我們給邊預測圖神經(jīng)網(wǎng)絡(luò)輸入訓練集正樣本邊和所有真實負樣本邊,邊預測圖神經(jīng)網(wǎng)絡(luò)就相當于知道了訓練集與驗證集的正樣本邊,因為沒出現(xiàn)在訓練集正樣本與所有真實負樣本里的樣本即為訓練集或驗證集的正樣本。 采用這種數(shù)據(jù)采樣方式采樣得到的數(shù)據(jù)集,用于神經(jīng)網(wǎng)絡(luò)的訓練,訓練得到的神經(jīng)網(wǎng)絡(luò)會在“現(xiàn)在整個數(shù)據(jù)集”上過擬合,于是就降低了對將來未知的數(shù)據(jù)的泛化能力。于是在訓練階段,我們不能知道所有負樣本邊,那么我們只能知道所有訓練集正樣本邊。

    接著我們討論如果以data.train_pos_edge_index為實際參數(shù)來進行訓練集負樣本采樣結(jié)果會是怎么樣?以data.train_pos_edge_index為實際參數(shù)來進行訓練集負樣本采樣,也就是在非訓練集正樣本中采樣。非訓練集正樣本包含了所有的負樣本,和沒有出現(xiàn)在訓練集中的正樣本。雖然包含了沒有出現(xiàn)在訓練集中的正樣本,但其數(shù)量相對于所有的負樣本的數(shù)量要少得多。即便將真實的正樣本標記為負樣本會產(chǎn)生沖突,但這帶來影響相對較小。

    綜上,我們要以data.train_pos_edge_index為實際參數(shù)來進行訓練集負樣本采樣,也就是我們要在非訓練集正樣本中采樣訓練集負樣本。

    總結(jié)

    以上是生活随笔為你收集整理的【转】节点预测与边预测任务实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。