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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

PYG教程【五】链路预测

發(fā)布時(shí)間:2024/9/18 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 PYG教程【五】链路预测 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

鏈路預(yù)測(cè)是網(wǎng)絡(luò)科學(xué)里面的一個(gè)經(jīng)典任務(wù),其目的是利用當(dāng)前已獲取的網(wǎng)絡(luò)數(shù)據(jù)(包含結(jié)構(gòu)信息和屬性信息)來(lái)預(yù)測(cè)網(wǎng)絡(luò)中會(huì)出現(xiàn)哪些新的連邊。

本文計(jì)劃利用networkx包中的網(wǎng)絡(luò)來(lái)進(jìn)行鏈路預(yù)測(cè),因?yàn)槟壳癙yTorch Geometric包中封裝的網(wǎng)絡(luò)還不夠多,而很多網(wǎng)絡(luò)方便用networkx包生成或者處理。

環(huán)境配置

首先,安裝一個(gè)工具包,DeepSNAP。這個(gè)包提供了networkx到PyTorch Geometric的接口,可以方便地將networkx中的網(wǎng)絡(luò)轉(zhuǎn)換成PyTorch Geometric所要求的數(shù)據(jù)格式。DeepSNAP有兩種安裝方法:

第一種安裝方法:

pip install deepsnap

第二種安裝方法:

$ git clone https://github.com/snap-stanford/deepsnap $ cd deepsnap $ pip install .

鏈路預(yù)測(cè)

使用圖神經(jīng)網(wǎng)絡(luò)進(jìn)行鏈路預(yù)測(cè)包含以下基本步驟:

  • 導(dǎo)入圖數(shù)據(jù)
  • 分割數(shù)據(jù)集(劃分訓(xùn)練邊、測(cè)試邊)
  • 標(biāo)注正邊、采樣負(fù)邊
  • 訓(xùn)練神經(jīng)網(wǎng)絡(luò)
  • 測(cè)試模型效果
  • 鏈路預(yù)測(cè)最開(kāi)始是一個(gè)無(wú)監(jiān)督學(xué)習(xí)任務(wù),即根據(jù)已經(jīng)看到的網(wǎng)絡(luò)結(jié)構(gòu)(或者其他屬性信息)來(lái)推斷未知連邊是否存在,但是這樣的話就比較難以驗(yàn)證。只有在動(dòng)態(tài)網(wǎng)絡(luò)(或稱(chēng)時(shí)序網(wǎng)絡(luò))中才會(huì)有這樣的數(shù)據(jù)以供實(shí)驗(yàn)驗(yàn)證,可以用前一段時(shí)間的網(wǎng)絡(luò)結(jié)構(gòu)來(lái)預(yù)測(cè)后一段時(shí)間的網(wǎng)絡(luò)結(jié)構(gòu)。然而,很多網(wǎng)絡(luò)沒(méi)有時(shí)間信息,在這樣的網(wǎng)絡(luò)中如何驗(yàn)證呢?

    后來(lái),學(xué)者提出了用有監(jiān)督的方式來(lái)進(jìn)行鏈路預(yù)測(cè),也就是將其視為二分類(lèi)任務(wù),將網(wǎng)絡(luò)中存在的邊都視為正樣本(即正邊),不存在的連邊都當(dāng)作負(fù)樣本(即負(fù)邊)。然后,將這些邊分為兩部分,一部分為訓(xùn)練集,一部分為測(cè)試集。訓(xùn)練集和測(cè)試集中都包含正邊和負(fù)邊,目的是在訓(xùn)練集上訓(xùn)練出一個(gè)模型能夠準(zhǔn)確分類(lèi)這兩種邊,然后再在測(cè)試集上驗(yàn)證效果。

    然而,大多數(shù)網(wǎng)絡(luò)都是稀疏的,也就是說(shuō)存在邊的數(shù)量差不多是節(jié)點(diǎn)數(shù)量的幾倍左右,而網(wǎng)絡(luò)中不存在的邊的數(shù)量差不多是節(jié)點(diǎn)數(shù)量的平方(在無(wú)向網(wǎng)絡(luò)中,不存在邊的數(shù)量等于(n?1)n/2?m(n?1)n/2?m(n?1)n/2?m( n ? 1 ) n / 2 ? m (n-1)n/2-m(n?1)n/2?m(n?1)n/2?m(n?1)n/2?m(n?1)n/2?m,其中nnn為節(jié)點(diǎn)數(shù),mmm 為邊數(shù))。這樣不存邊的數(shù)量就遠(yuǎn)遠(yuǎn)大于存在邊的數(shù)量,在有監(jiān)督學(xué)習(xí)中就意味著負(fù)樣本遠(yuǎn)大于正樣本,類(lèi)別極其不平衡。怎么解決這個(gè)問(wèn)題呢?大家很自然地想到了負(fù)采樣,就是每次訓(xùn)練的時(shí)候隨機(jī)抽取與正樣本等比例的負(fù)樣本,這樣就避免了類(lèi)別不平衡。

    訓(xùn)練結(jié)束后,就可以用測(cè)試集中的正邊和負(fù)邊來(lái)驗(yàn)證模型的效果了。

    代碼

    import networkx as nx from deepsnap.graph import Graph import torch import torch.nn.functional as F from sklearn.metrics import roc_auc_score from torch_geometric.utils import negative_sampling from torch_geometric.nn import GCNConv from torch_geometric.utils import train_test_split_edgesG = nx.karate_club_graph() data = Graph(G) # 將networkx中的graph對(duì)象轉(zhuǎn)化為torch_geometric的Data對(duì)象 data.num_features = 3 data.edge_attr = None# 構(gòu)造節(jié)點(diǎn)特征矩陣(原網(wǎng)絡(luò)不存在節(jié)點(diǎn)特征) data.x = torch.ones((data.num_nodes, data.num_features), dtype=torch.float32)# 分割訓(xùn)練邊集、驗(yàn)證邊集(默認(rèn)占比0.05)以及測(cè)試邊集(默認(rèn)占比0.1) data = train_test_split_edges(data)# 構(gòu)造一個(gè)簡(jiǎn)單的圖卷積神經(jīng)網(wǎng)絡(luò)(兩層),包含編碼(節(jié)點(diǎn)嵌入)、解碼(分?jǐn)?shù)預(yù)測(cè))等操作 class Net(torch.nn.Module):def __init__(self):super(Net, self).__init__()self.conv1 = GCNConv(data.num_features, 128)self.conv2 = GCNConv(128, 64)def encode(self):x = self.conv1(data.x, data.train_pos_edge_index)x = x.relu()return self.conv2(x, data.train_pos_edge_index)def decode(self, z, pos_edge_index, neg_edge_index):edge_index = torch.cat([pos_edge_index, neg_edge_index], dim=-1) # 將正樣本與負(fù)樣本拼接 shape:[2,272]logits = (z[edge_index[0]] * z[edge_index[1]]).sum(dim=-1)return logitsdef decode_all(self, z):prob_adj = z @ z.t()return (prob_adj > 0).nonzero(as_tuple=False).t()# 將模型和數(shù)據(jù)送入設(shè)備 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model, data = Net().to(device), data.to(device) # 指定優(yōu)化器 optimizer = torch.optim.Adam(params=model.parameters(), lr=0.01)# 將訓(xùn)練集中的正邊標(biāo)簽設(shè)置為1,負(fù)邊標(biāo)簽設(shè)置為0 def get_link_labels(pos_edge_index, neg_edge_index):E = pos_edge_index.size(1) + neg_edge_index.size(1)link_labels = torch.zeros(E, dtype=torch.float, device=device)link_labels[:pos_edge_index.size(1)] = 1.return link_labels# 訓(xùn)練函數(shù),每次訓(xùn)練重新采樣負(fù)邊,計(jì)算模型損失,反向傳播誤差,更新模型參數(shù) def train():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), # 負(fù)采樣數(shù)量根據(jù)正樣本force_undirected=True,) # 得到負(fù)采樣shape: [2,136]neg_edge_index = neg_edge_index.to(device)optimizer.zero_grad()z = model.encode() # 利用正樣本訓(xùn)練學(xué)習(xí)得到每個(gè)節(jié)點(diǎn)的特征 shape:[34, 64]link_logits = model.decode(z, data.train_pos_edge_index, neg_edge_index) # [272] 利用正樣本和負(fù)樣本 按位相乘 求和 (z[edge_index[0]] * z[edge_index[1]]).sum(dim=-1)link_labels = get_link_labels(data.train_pos_edge_index, neg_edge_index) # [272] 前136個(gè)是1,后136個(gè)是0loss = F.binary_cross_entropy_with_logits(link_logits, link_labels) # binary_cross_entropy_with_logits會(huì)自動(dòng)計(jì)算link_logits的sigmoidloss.backward()optimizer.step()return loss# 測(cè)試函數(shù),評(píng)估模型在驗(yàn)證集和測(cè)試集上的預(yù)測(cè)準(zhǔn)確率 @torch.no_grad() def test():model.eval()perfs = []for prefix in ["val", "test"]:pos_edge_index = data[f'{prefix}_pos_edge_index']neg_edge_index = data[f'{prefix}_neg_edge_index']z = model.encode()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)perfs.append(roc_auc_score(link_labels.cpu(), link_probs.cpu()))return perfs# 訓(xùn)練模型,每次訓(xùn)練完,輸出模型在驗(yàn)證集和測(cè)試集上的預(yù)測(cè)準(zhǔn)確率 best_val_perf = test_perf = 0 for epoch in range(1, 11):train_loss = train()val_perf, tmp_test_perf = test()if val_perf > best_val_perf:best_val_perf = val_perftest_perf = tmp_test_perflog = 'Epoch: {:03d}, Loss: {:.4f}, Val: {:.4f}, Test: {:.4f}'print(log.format(epoch, train_loss, best_val_perf, test_perf))# 利用訓(xùn)練好的模型計(jì)算網(wǎng)絡(luò)中剩余所有邊的分?jǐn)?shù) z = model.encode() final_edge_index = model.decode_all(z)

    首先查看原始數(shù)據(jù)信息:

    data: Graph(G=[], club=[34], # 總共34個(gè)節(jié)點(diǎn)edge_label_index=[2, 156], # 總共156個(gè)邊name=[], node_label_index=[34], num_features=[1], test_neg_edge_index=[2, 7], # 測(cè)試集 負(fù)樣本鄰接矩陣test_pos_edge_index=[2, 7], # 測(cè)試集 正樣本鄰接矩陣train_neg_adj_mask=[34, 34], # 訓(xùn)練集 負(fù)樣本鄰接矩陣train_pos_edge_index=[2, 136], # 訓(xùn)練集 正樣本鄰接矩陣val_neg_edge_index=[2, 3], # 驗(yàn)證集 負(fù)樣本鄰接矩陣val_pos_edge_index=[2, 3], # 驗(yàn)證集 正樣本鄰接矩陣x=[34, 3] # 節(jié)點(diǎn)屬性 )

    輸出情況如下:

    Epoch: 001, Loss: 0.8969, Val: 0.3333, Test: 0.9796 Epoch: 002, Loss: 0.6772, Val: 0.3333, Test: 0.9796 Epoch: 003, Loss: 0.6933, Val: 0.3333, Test: 0.9796 Epoch: 004, Loss: 0.7107, Val: 0.3333, Test: 0.9796 Epoch: 005, Loss: 0.6960, Val: 0.3333, Test: 0.9796 Epoch: 006, Loss: 0.6905, Val: 0.3333, Test: 0.9796 Epoch: 007, Loss: 0.6896, Val: 0.3333, Test: 0.9796 Epoch: 008, Loss: 0.6837, Val: 0.3333, Test: 0.9796 Epoch: 009, Loss: 0.6834, Val: 0.3333, Test: 0.9796 Epoch: 010, Loss: 0.6840, Val: 0.3333, Test: 0.9796

    訓(xùn)練集中的負(fù)樣本是每次隨機(jī)采樣得到的(第51-55行),而驗(yàn)證集和測(cè)試集中的負(fù)樣本邊則在第14行就已經(jīng)固定了,所以結(jié)果中訓(xùn)練集上的loss一直在變化,而驗(yàn)證集和測(cè)試集上的AUC得分沒(méi)有變化,因?yàn)槲覀兊臄?shù)據(jù)量太小導(dǎo)致的。如果換成Cora數(shù)據(jù)集效果會(huì)好點(diǎn)。

    更詳細(xì)的介紹,請(qǐng)看這篇文章。

    總結(jié)

    以上是生活随笔為你收集整理的PYG教程【五】链路预测的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

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