如何來構建神經網路?看看這篇乾貨

大雄45發表於2022-09-09
導讀 在這篇文章中,您將學習圖神經網路如何工作的基礎知識,以及如何使用Pytorch Geometric(PyG)庫和Open Graph Benchmark(OGB)庫並透過Python程式設計實現這樣一個圖神經網路。

如何來構建神經網路?看看這篇乾貨如何來構建神經網路?看看這篇乾貨
深度學習為對非結構化資料進行預測開闢了一個全新的可能性世界。如今,人們常用卷積神經網路(CNN)處理影像資料,而採用遞迴神經網路(RNN)來處理文字資料,等等。

在過去幾年中,又出現了一類新的令人興奮的神經網路:圖神經網路(Graph Neural Networks,簡稱“GNN”)。顧名思義,這個網路型別專注於處理圖資料。

在這篇文章中,您將學習圖神經網路如何工作的基礎知識,以及如何使用Pytorch Geometric(PyG)庫和Open Graph Benchmark(OGB)庫並透過Python程式設計實現這樣一個圖神經網路。

注意,您可以在我的Github和Kaggle網站上找到本文提供的示例工程原始碼。

普通GNN的工作原理

隨著圖卷積網路(GCN)[見參考文獻1]的引入,GNN開始流行起來,該網路將CNN中的一些概念借用到了圖世界。這種網路的主要思想,也稱為訊息傳遞框架(Message-Passing Framework),多年來成為該領域的黃金標準。我們將在本文中探討這一概念。

訊息傳遞框架指出,對於圖中的每個節點,我們將做兩件事:

聚合來自其鄰節點的資訊
使用來自其上一層及其鄰節點聚合的資訊更新當前節點資訊
如何來構建神經網路?看看這篇乾貨如何來構建神經網路?看看這篇乾貨

訊息傳遞框架示意圖。來源:維基百科

上圖中顯示了訊息傳遞框架的工作原理。在GCN之後開發的許多架構側重於定義聚合和更新資料的最佳方式。

PyG和OGB簡介

PyG是Pytorch庫的擴充套件,它允許我們使用研究中已經建立的層快速實現新的圖神經網路架構。

OGB[見參考文獻2]是作為提高該領域研究質量的一種方式開發的,因為它提供了可使用的策劃圖,也是評估給定架構結果的標準方式,從而使提案之間的比較更加公平。

於是,我們可以將這兩個庫一起使用,一方面可以更容易地提出一個架構,另一方面也不必擔心資料獲取和評估機制的問題。

實現一個GNN專案

首先,讓我們安裝示例工程必需的庫。請注意,您必須首先安裝PyTorch:

pip install ogb
pip install torch_geometric

現在,讓我們匯入所需的方法和庫:

import os
import torch
import torch.nn.functional as Ffrom tqdm import tqdm
from torch_geometric.loader import NeighborLoader
from torch.optim.lr_scheduler import ReduceLROnPlateau
from torch_geometric.nn import MessagePassing, SAGEConv
from ogb.nodeproppred import Evaluator, PygNodePropPredDataset

接下來,第一步是從OGB下載資料集。我們將使用ogbn-arxiv網路,其中每個節點都是arxiv網站上的電腦科學論文,每個有向邊表示一篇論文引用了另一篇論文。我們的任務是:將每個節點分類為一個論文類別。

下載過程非常簡單:

target_dataset = 'ogbn-arxiv'#我們將把ogbn-arxiv下載到當前示例工程的'networks'資料夾下
dataset = PygNodePropPredDataset(name=target_dataset, root='networks')

其中,dataset變數是一個名為PygNodePropPredDataset的類的例項,該類特定於OGB庫。要將該資料集作為可在Pyrotch Geometric上使用的資料類進行訪問,我們只需執行以下操作:

data = dataset[0]

如果我們透過除錯跟蹤看一下這個變數,我們會看到如下結果:

Data(num_nodes=169343, edge_index=[2, 1166243], x=[169343, 128], node_year=[169343, 1], y=[169343, 1])

至此,我們已經準備好了節點數目、鄰接列表、網路的特徵向量、每個節點的年份資訊,並確定下目標標籤。

另外,ogbn-arxiv網路已經配備好了分別用於訓練、驗證和測試的分割資料子集。這是OGB提供的一種提高該網路研究再現性和質量的好方法。我們可以透過以下方式提取:

split_idx = dataset.get_idx_split() 
        
train_idx = split_idx['train']
valid_idx = split_idx['valid']
test_idx = split_idx['test']

現在,我們將定義兩個在訓練期間使用的資料載入器。第一個將僅載入訓練集中的節點,第二個將載入網路上的所有節點。

我們將使用Pytorch Geometric庫中的鄰節點載入函式NeighborLoader。該資料載入器為每個節點取樣給定數量的鄰節點。這是一種避免具有數千個節點的節點的RAM和計算時間癱瘓的方法。在本教程中,我們將在訓練載入程式上每個節點使用30個鄰節點。

train_loader = NeighborLoader(data, input_nodes=train_idx,
                              shuffle=True, num_workers=os.cpu_count() - 2,
                              batch_size=1024, num_neighbors=[30] * 2)total_loader = NeighborLoader(data, input_nodes=None, num_neighbors=[-1],
                               batch_size=4096, shuffle=False,
                               num_workers=os.cpu_count() - 2)

注意,我們把訓練資料載入器中的資料以隨機方式打亂次序,但沒有打亂總載入器中資料的次序。此外,訓練載入程式的鄰節點數定義為網路每層的數量。因為我們將在這裡使用兩層網路,所以我們將其設定為兩個值為30的列表。

現在是時候建立我們的GNN架構了。對於任何熟悉Pytorch的人來說,這應該都是平常的事情。

我們將使用SAGE圖層。這些層是在一篇很好的論文[見參考文獻3]中定義的,該論文非常細緻地介紹了鄰節點取樣的思想。幸運的是,Pytorch Geometric 庫已經為我們實現了這一層。

因此,與每個PyTorch架構一樣,我們必須定義一個包含我們將要使用的層的類:

class SAGE(torch.nn.Module):
    def __init__(self, in_channels,
                 hidden_channels, out_channels,
                 n_layers=2):
        
        super(SAGE, self).__init__()
        self.n_layers = n_layers    self.layers = torch.nn.ModuleList()
        self.layers_bn = torch.nn.ModuleList()    if n_layers == 1:
            self.layers.append(SAGEConv(in_channels, out_channels,   normalize=False))
        elif n_layers == 2:
            self.layers.append(SAGEConv(in_channels, hidden_channels, normalize=False))
             self.layers_bn.append(torch.nn.BatchNorm1d(hidden_channels))
            self.layers.append(SAGEConv(hidden_channels, out_channels, normalize=False))
        else:
           self.layers.append(SAGEConv(in_channels, hidden_channels, normalize=False))
              self.layers_bn.append(torch.nn.BatchNorm1d(hidden_channels))    for _ in range(n_layers - 2):
                self.layers.append(SAGEConv(hidden_channels,  hidden_channels, normalize=False))
                 self.layers_bn.append(torch.nn.BatchNorm1d(hidden_channels))
            
            self.layers.append(SAGEConv(hidden_channels, out_channels, normalize=False))
            
        for layer in self.layers:
            layer.reset_parameters()    def forward(self, x, edge_index):
        if len(self.layers) > 1:
            looper = self.layers[:-1]
        else:
            looper = self.layers
        
        for i, layer in enumerate(looper):
            x = layer(x, edge_index)
            try:
                x = self.layers_bn[i](x)
            except Exception as e:
                abs(1)
            finally:
                x = F.relu(x)
                x = F.dropout(x, p=0.5, training=self.training)
        
        if len(self.layers) > 1:
            x = self.layers[-1](x, edge_index)        return F.log_softmax(x, dim=-1), torch.var(x)
    
    def inference(self, total_loader, device):
        xs = []
        var_ = []
        for batch in total_loader:
            out, var = self.forward(batch.x.to(device), batch.edge_index.to(device))
            out = out[:batch.batch_size]
            xs.append(out.cpu())
            var_.append(var.item())
        
        out_all = torch.cat(xs, dim=0)
        
        return out_all, var_

讓我們一步一步地將上述程式碼分開解釋:

我們必須定義網路的in_channels數量,這個值代表資料集中的特徵數。out_channels代表我們試圖預測的類別的總數。隱藏通道引數idden_channels是一個我們可以定義的值,表示隱藏單元的數量。
我們可以設定網路的層數。對於每個隱藏層,我們新增一個批次歸一化層,然後重置每個層的引數。
forward方法執行正向過程的單個迭代。期間,獲得特徵向量和鄰接列表,並將其傳遞給SAGE層,然後將結果傳遞給批次歸一化層。此外,我們還應用ReLU非線性和衰減層進行正則化。
最後,推理方法(inference)將為資料集中的每個節點生成預測。我們將使用它進行驗證。
現在,讓我們定義模型的一些引數:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')model = SAGE(data.x.shape[1], 256, dataset.num_classes, n_layers=2)
model.to(device)
epochs = 100
optimizer = torch.optim.Adam(model.parameters(), lr=0.03)
scheduler = ReduceLROnPlateau(optimizer, 'max', patience=7)

現在,我們可以開始測試了,以驗證我們的所有預測:

def test(model, device):
    evaluator = Evaluator(name=target_dataset)
    model.eval()
    out, var = model.inference(total_loader, device)    y_true = data.y.cpu()
        y_pred = out.argmax(dim=-1, keepdim=True)    train_acc = evaluator.eval({
        'y_true': y_true[split_idx['train']],
        'y_pred': y_pred[split_idx['train']],
    })['acc']
    val_acc = evaluator.eval({
        'y_true': y_true[split_idx['valid']],
        'y_pred': y_pred[split_idx['valid']],
    })['acc']
    test_acc = evaluator.eval({
        'y_true': y_true[split_idx['test']],
        'y_pred': y_pred[split_idx['test']],
    })['acc']return train_acc, val_acc, test_acc, torch.mean(torch.Tensor(var))

在這個函式中,我們從OGB庫中例項化一個驗證器類Validator。這個類將負責驗證我們之前檢索到的每個分割的模型。這樣,我們將看到每個世代上的訓練、驗證和測試集的得分值。

最後,讓我們建立我們的訓練迴圈:

for epoch in range(1, epochs):
    model.train()    pbar = tqdm(total=train_idx.size(0))
    pbar.set_description(f'Epoch {epoch:02d}')    total_loss = total_correct = 0    for batch in train_loader:
        batch_size = batch.batch_size
        optimizer.zero_grad()        out, _ = model(batch.x.to(device), batch.edge_index.to(device))
        out = out[:batch_size]        batch_y = batch.y[:batch_size].to(device)
        batch_y = torch.reshape(batch_y, (-1,))        loss = F.nll_loss(out, batch_y)
        loss.backward()
        optimizer.step()        total_loss += float(loss)
        total_correct += int(out.argmax(dim=-1).eq(batch_y).sum())
        pbar.update(batch.batch_size)    pbar.close()    loss = total_loss / len(train_loader)
    approx_acc = total_correct / train_idx.size(0)    train_acc, val_acc, test_acc, var = test(model, device)
    
    print(f'Train: {train_acc:.4f}, Val: {val_acc:.4f}, Test: {test_acc:.4f}, Var: {var:.4f}')

這個迴圈將訓練我們的GNN的100個世代,如果我們的驗證得分連續7個世代沒有增長的話,它將提前停止訓練。

結論

總之,GNN是一類有趣的神經網路。今天,人們已經開發出了一些現成的工具來幫助我們開發這種解決方案。正如您在本文中所見到的,藉助Pytorch Geometric和OGB這兩個庫就可以輕鬆實現某些型別的圖的GNN設計。

 

原文來自:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2914184/,如需轉載,請註明出處,否則將追究法律責任。

相關文章