首頁>技術>

譯者:朱先忠

引言

深度學習為對非結構化數據進行預測開闢了一個全新的可能性世界。如今,人們常用卷積神經網絡(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 ogbpip install torch_geometric1.2.

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

複製
import osimport torchimport torch.nn.functional as Ffrom tqdm import tqdmfrom torch_geometric.loader import NeighborLoaderfrom torch.optim.lr_scheduler import ReduceLROnPlateaufrom torch_geometric.nn import MessagePassing, SAGEConvfrom ogb.nodeproppred import Evaluator, PygNodePropPredDataset1.2.3.4.5.6.7.

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

下載過程非常簡單:

複製
target_dataset = "ogbn-arxiv"#我們將把ogbn-arxiv下載到當前示例工程的"networks"文件夾下dataset = PygNodePropPredDataset(name=target_dataset, root="networks")1.2.

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

複製
data = dataset[0]1.

如果我們通過調試跟蹤看一下這個變量,我們會看到如下結果:

複製
Data(num_nodes=169343, edge_index=[2, 1166243], x=[169343, 128], node_year=[169343, 1], y=[169343, 1])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"]1.2.3.4.5.

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

我們將使用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)1.2.3.4.5.

注意,我們把訓練數據加載器中的數據以隨機方式打亂次序,但沒有打亂總加載器中數據的次序。此外,訓練加載程序的鄰節點數定義為網絡每層的數量。因為我們將在這裡使用兩層網絡,所以我們將其設置為兩個值為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_1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.

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

我們必須定義網絡的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 = 100optimizer = torch.optim.Adam(model.parameters(), lr=0.03)scheduler = ReduceLROnPlateau(optimizer, "max", patience=7)1.2.3.4.5.

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

複製
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))1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

在這個函數中,我們從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}")1.2.3.4.5.6.7.8.9.10.11.12.13.14.

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

結論

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

12
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • AVAX、MATIC、ATOM 價格分析:它們會繼續上漲嗎?