Pytorch實踐之GNN無向異構圖邊分類任務

目錄

Graph Neural Network簡介

圖Global、Graph:U = (V, E)

圖的類型

圖的三類任務

圖神經網路:圖輸入,圖輸出

環境運行-Pycharm建置Pytorch、PyG

安裝Pytorch

安裝PyG(PyTorch Geometric)

資料準備-圖與資料集格式

一張圖Graph:torch_geometric.data.Data

處理成資料集.pt檔:仿 torch_geometric.data.Dataset 自訂義類別

模型原理

RGCN運行原理

解碼器運行原理

程式流程

參考資料


Graph Neural Network簡介

圖Global、Graph:U = (V, E)

b2ec178799b644efb47555053a3cc016.png

圖片來源:GNN/GCN-CSDN博客

圖的類型

  1. 有/無向:有向表示邊為從某一節點連向另一節點,無向表示兩節點為雙向連接。
  2. 同/異構:存在多種類型的節點或邊,或既有多種類型的節點亦有多種類型的邊。

圖的三類任務

  1. Graph-level task:整個圖的分類。例如預測某分子是否具有毒性、具有什麼氣味,或者這個關係圖屬於哪種方面的(家庭、學校、社會)。
  2. Node-level task:判斷某節點的性質、屬性,甚至是值。例如在人際關係圖中的某個人和誰比較親近、屬於哪一派。
  3. Edge-level task:節點之間的關係分類。

圖神經網路:圖輸入,圖輸出

7e1ce009b762446b97cbdea5fe241479.png

圖片來源:圖像化神經網路(1):Graph Neural Network小簡介


環境運行-Pycharm建置Pytorch、PyG

安裝Pytorch

[Pytorch]如何在Pycharm用pip3安装pytorch&如何查看/安装对应版本的CUDA Toolkit_pip install cudatoolkit-CSDN博客

參考上面鏈結照著做進行安裝。

第一步驟可以直接在Pycharm終端機輸入nvidia-smi命令就能夠確認顯卡驅動是否正確安裝了。

912ba39f8c1743e0b79f2b47992a1819.png

第三步(配置pip3環境變量)時可以先確認是否已經配置過了。

c511c58715b84d6790a66859ed0fdfea.png

最後一步驟測試:在環境工作資料夾裡新增一python檔,內容如下。

import torch

print(torch.__version__)

print(torch.cuda.is_available())    
# 用於檢查是否支援cuDNN。cuDNN是NVIDIA提供的用於深度神經網路的GPU加速庫。
# 這個函數會傳回一個布林值,表示目前PyTorch是否支援cuDNN。

print(torch.version.cuda) 

執行後看到以下結果就是安裝成功啦! 

93c7daf6c2ae4aaba7b57c5264c5f3e8.png

安裝PyG(PyTorch Geometric)

Installation — pytorch_geometric documentation

到上面網址選好自己的狀況,把Run那一欄他生成的程式碼複製到自己的Pycharm終端機執行。 

8a81f03649804aefbd560b44ee8fcb93.png

我的情況就是在pycharm終端機執行:

pip install torch_geometric # Optional dependencies: pip install torch_scatter torch_sparse torch_cluster torch_spline_conv -f https://data.pyg.org/whl/torch-2.1.0+cu121.html

看到下面的結果就是成功了! 

4780ad0e396446b89392beac42ba7a00.png


資料準備-圖與資料集格式

我的Graph

以一張圖片裡的每個電子元件作為節點,依從左至右從上至下的順序編號。每個電子元件的分類(one-hot encoding型態)作為節點特徵。邊分類(one-hot encoding型態)的涵義定義為任意倆元件為串聯還是並聯關係。由於節點和邊皆有多個類別,故屬於異構圖;由於一張電路圖圖片中任意兩元件間必有關係(即必有GNN圖中的edge),故屬於無項圖;目標是以GNN模型預測倆元件間的關係,也就是邊的分類,故屬於Edge- level task。

0683ef34ad1f4057b5481f1902dbb8e7.png

一張圖Graph:torch_geometric.data.Data

一個Data型別的資料包含三個參數:x-節點特徵、edge_index-邊索引、y-解答。本實作想要做邊分類,所以解答y就是放邊分類的標準答案。你可以將自己的節點和解答資料以如下格式分別存在.txt檔,後面資料集程式裡再將之讀出。

節點特徵

這裡每個節點特徵有5位,後續設定GNN的輸入通道數in_channels便是依照這個長度。

node_features = [ # 第一張圖節點特徵
                  [ [0, 1, 0, 0, 0],    # 第一張圖的第一個節點特徵 
                    [0, 0, 1, 0, 0],    # 第一張圖的第二個節點特徵 
                    [0, 0, 0, 0, 1], 
                    [0, 0, 1, 0, 0], 
                    [0, 0, 0, 1, 0] ], 
                  # 第二張圖節點特徵
                  [ [0, 1, 0, 0, 0], 
                    [0, 0, 0, 0, 1], 
                    [0, 0, 0, 0, 1] ],               
                  # ... 可以有更多圖的節點特徵
                ]

邊索引

每一張圖的邊索引會以二維串列表示。列出相連倆節點的編號就能夠表達出「邊」的意涵,所以你把這二維上下並排就可以看出每條邊:0 - 1、0 - 2、0 - 3、0 - 4、1 - 0、1 - 2、1 - 3、1 - 4...,這二維相同位置的編號就是有相關聯的兩個節點編號。build_dataset.py已經撰寫好自動依照每張圖的節點數量為每張圖生成邊索引的函式edgeprocess(),所以你不需要自己寫這裡的資料。

edge_lists = [ # 第一張圖的邊索引
               [ 
                 [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4],    # 所有源節點編號        
                 [1, 2, 3, 4, 0, 2, 3, 4, 0, 1, 3, 4, 0, 1, 2, 4, 0, 1, 2, 3]     # 所有目標節點編號 
               ],    
               # 第二張圖的邊索引 
               [ [0, 0, 1, 1, 2, 2], 
                 [1, 2, 0, 2, 0, 1] ]
             ]

邊分類解答:用one-hot encodeing表示

這裡每條邊用2位表示,後續設定GNN輸出通道數out_channels便是依據這個長度。

edge_label = [ # 第一張圖每個邊的分類,順序依照邊索引
               [ # 依序為0、1節點相連的邊分類;0、2節點相連的邊分類...
                 [0, 1], [1, 0], [0.1, 0.9], [0.1, 0.9], 
                 # 依序為1、0節點相連的邊分類;1、2節點相連的邊分類...
                 [0, 1], [1, 0], [0.1, 0.9], [0.1, 0.9],
                 # 由於是無向圖,0、1節點相連的邊即1、0節點相連的邊。
                 [1, 0], [1, 0], [1, 0], [1, 0],
                 [0.1, 0.9], [0.1, 0.9], [1, 0], [0, 1],
                 [0.1, 0.9], [0.1, 0.9], [1, 0], [0, 1] ],
               # 第二張圖每個邊的分類
               [ [0, 1], [0, 1],
                 [0, 1], [0, 1],
                 [0, 1], [0, 1] ],
             ]

由於edge_classification_model.py使用FastRGCNConvedge_type參數要求的邊分類是一維的,所以邊分類還需要另外處理成以一位直接表示是第幾類寫法的邊分類解答。

edge_type = [ # 第一張圖每個邊的分類,順序依照邊索引
               [0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0],
               # 第二張圖每個邊的分類
               [0, 0, 0, 0, 0, 0]
             ]

edge_classification_model.py class GCNlabelprocess()函式是在將one-hot encoding的邊分類轉成直接以一位數字表示屬於第幾類。你可以依據你的邊分類表示長度修改此段程式碼。

def labelprocess(self, edge_label):    
    edge_label_index = []
    for i, labellist in enumerate(edge_label):
        if labellist[1] > labellist[0]:    # 預設邊以兩位表示
            edge_label_index.append(0)  # [ , ]第1位大於第0位設為第0類的邊
        else:
            edge_label_index.append(1)  # 反之設為第1類的邊
   
    return edge_label_index

或是如果你有未one-hot encoding的邊分類解答,可以直接調用給FastRGCNConv,但要注意程式碼裡要如何取出該批次(batch)該張圖對應的edge type

處理成資料集.pt檔:仿 torch_geometric.data.Dataset 自訂義類別

記得在直接執行build_dataset.py前,要先在工作區建立mydataset資料夾。執行之後你應該可以工作區發現多了一個.pt檔,那就是你的圖資料庫!

get_node()函式:讀入你自訂的節點特徵。

get_edge()函式:讀入你的邊分類解答資訊。

edgeprocess()函式:生成邊索引。

get()函式:將一張圖的資料轉成torch.tensor的型別,然後將這些資料存入Data型別,成為一張圖。

最後在process()函式會將每張圖都處理成Data型別,存入data.pt檔案裡。

 build_dataset.py 完整程式碼:

import os.path as osp
import torch
from torch_geometric.data import Data
from torch_geometric.data.collate import collate
from itertools import permutations
import ast


class MyDataset():

    def __init__(self, root):
        if isinstance(root, str):
            root = osp.expanduser(osp.normpath(root))
        else:
            print('root problem')
        self.root = root
        self.__indices__ = None

        self.node_features = self.get_node()
        self.edge_label = self.get_edge()
        self.edge_lists = self.edgeprocess()
        self.num_features = len(self.node_features[0][0])
        self.num_classes = len(self.edge_label[0][0])

    def get_node(self):  # 讀入節點特徵資料
        file_path = 'node.txt'

        with open(file_path, 'r') as file:
            lines = file.readlines()

        return [ast.literal_eval(line) for line in lines]

    def get_edge(self):  # 讀入邊分類解答
        file_path = 'edge.txt'

        with open(file_path, 'r') as file:
            lines = file.readlines()

        return [ast.literal_eval(line) for line in lines]

    def edgeprocess(self):  # 生成邊索引:有哪些節點有連接。格式為[[所有源節點編號(以逗號隔開)], [所有目標節點編號]]
        edgelists = []
        for i in range(len(self.node_features)):  # 有幾張圖就執行幾次
            numbers = list(range(len(self.node_features[i])))  # 創建字串,內容為節點編號(例如有五個節點,就是[0, 1, 2, 3, 4, 5])
            str_numbers = ''.join(map(str, numbers))  # 把每個元素轉成字串,為了能夠執行permutations()的效果
            edge_lists = list(permutations(str_numbers, 2))  # 得到兩兩相連節點的標籤
            # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
            edge_lists = list(zip(*edge_lists))  # 將之改成.data裡的edge_index格式
            '''
            a = [1,2,3]
            b = [4,5,6]

            zipped = zip(a,b)     # 打包为元组的列表
            >>> [(1, 4), (2, 5), (3, 6)]
            zip(*zipped)          # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式
            >>> [(1, 2, 3), (4, 5, 6)]
            '''
            if edge_lists != []:
                for j in range(2):
                    edge_lists[j] = list(edge_lists[j])  # 把tuple改成list

            edgelists.extend([edge_lists])  # 把第i張圖的邊索引存入edge_lists的第i個位置

        # 把字串轉成int
        for sublist in edgelists:
            for i in range(len(sublist)):
                for j in range(len(sublist[i])):
                    sublist[i][j] = int(sublist[i][j])

        return edgelists

    # 返回數據裡面圖的數量
    def len(self):

        return len(self.node_features)

    def __len__(self):
        return len(self.node_features)

    def get(self, idx):
        # 獲取第idx個徒的所有資訊
        edge_list = torch.tensor(self.edge_lists[idx], dtype=torch.long)
        node_features = torch.tensor(self.node_features[idx], dtype=torch.float)
        edge_label = torch.tensor(self.edge_label[idx], dtype=torch.float)

        # torch_geometric.data.Data()
        data = Data(x=node_features, edge_index=edge_list, y=edge_label)  # 將第idx圖的所有參數存成data型別
        return data

    def __getitem__(self, idx):

        if isinstance(idx, int):  # 檢查idx是否是整數類型(int),若是便取得第idx個圖的data資料。
            data = self.get(idx)
        else:  # 若idx唯一個串列,依序取得每張圖的data格式資料
            data = [self.get(i) for i in idx]
        return data

    def tocollate(self, data_list):
        if len(data_list) == 1:
            return data_list[0], None

        data, slices, _ = collate(  # torch_geometric.data.collate.collate()
            data_list[0].__class__,
            data_list=data_list,
            increment=False,
            add_batch=False,
        )
        '''
        將input數據每一個特徵進行聚合得到一個大的Data數據,用返回的索引slices區分不同的數據。
        slices為字典類型,包含三個key值,對應著輸入數據的三個特徵,每個值是一個一維tensor,每個數字代表特徵在data的起始位置。
        '''
        return data, slices

    def process(self):  # 將數據處理好存入pt文件
        data_list = [self.__getitem__(i) for i in range(self.len())]  # 將每一張圖的數據轉成data格式依序儲存在data_list
        data, slices = self.tocollate(data_list)  # 將數據轉為pt檔儲存格式
        torch.save((data, slices), osp.join(self.root, 'data.pt'))  # 將檔案儲存在指定資料夾下,檔名為data.pt


def main():
    MyDataset(root='mydataset').process()


if __name__ == "__main__":
    main()

模型原理

處理邊分類任務的原理就是將邊對應的節點訊息聚合到邊上再加上分類器。本實作選用RGCN關係圖卷積神經網路:他用於處理節點同構、邊異構的異構圖情境。我們的節點其實是異構的,每一個是不同類型的電子元件,但也可以把它當成同構的-皆屬於電子元件只是特徵不同。

RGCN運行原理

圖卷積神經網路和其他神經網路一樣,每一次迭代會對所有節點逐次更新。每次更新時,會將與該節點以同一類別之邊連接的節點特徵送進同一個線性全連接層。如下圖,若綠色表示邊類別一、紅色表示邊類別為第二種,則以綠色邊連接的節點進入的全連接層與以紅色邊連接的節點特徵進入的全連接層為獨立的。最後會加上該節點本身的特徵,與兩個全連接層的結果進行聚合,也就是取mean平均/sum相加和,得到該節點的新特徵。這個特徵就蘊含著各類型邊對應的節點訊息之序和結果。

52a3f6a92733433691182d04ff056f8c.png

解碼器運行原理

前面提過,邊分類任務的原理就是將邊對應的節點訊息聚合到邊上再加上分類器。RGCN只幫我們做了一半:將邊對應的節點訊息聚合,但要怎麼把聚合的訊息聚合回邊上呢?由於圖神經網路也跟其他神經網路一樣,什麼樣式輸入就是什麼樣式輸出,儘管更新出來的圖中,每個節點已經蘊含我們要的資訊,但顯然是不匹配邊的數量的,如此就沒辦法將這個結果跟解答比對來訓練正確率。所以我們需要一個解碼器,把隱藏在RGCN產出圖裡的資訊放進邊做為邊的特徵,並加上分類器(也就是一個線性全連接層)預測邊的分類。

具體的做法是,將RGCN圖中的每個節點特徵依照邊索引的對應順序倆倆組合。在邊索引的章節有說過,圖用列出相連倆節點的編號來表達這兩個節點的連結,邊索引二維陣列相同位置的編號就是相連的兩個節點。所以我們現在將RGCN產出圖兩兩節點的特徵相接,組合成這連接這倆節點之邊的特徵。

例如,經過RGCN後的新節點0特徵為:

[ 0.7729,  0.4176, -0.3470, -0.5017, -0.3894]

新節點1特徵為:

[ 0.5777,  0.6314,  0.3491, -0.7999, -0.1461]

則邊0 - 1的特徵就是:

[ 0.7729,  0.4176, -0.3470, -0.5017, -0.3894,  0.5777,  0.6314,  0.3491, -0.7999, -0.1461 ]

 以此類推,我們就獲得根邊數量一樣多筆的資料了!就可以把些資料丟進去全連接層分類,獲得每條邊的預測分類值。

你也可以搭配著程式碼理解:

class Decoder(torch.nn.Module):
    def __init__(self, featurelen, edgeclasslen):
        super().__init__()
        self.classifier = torch.nn.Linear(featurelen*2, edgeclasslen)    # 分類器

    def forward(self, encodedata, edge_index):
        h = torch.cat([encodedata[edge_index[0]], encodedata[edge_index[1]]], 1)    # 將聚合結果放回邊
        y = self.classifier(h)
        return y

程式流程

這裡主要解釋模型的執行流程順序。

class GCN __init__主要設定RGCN和解碼器的輸出輸入通道數。RGCN輸入與輸出通道數即節點特徵長度;解碼器輸入通道數為節點特徵,輸出為邊分類長度。
class GCN(torch.nn.Module):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.conv = FastRGCNConv(in_channels=in_channels, out_channels=in_channels,
                             num_relations=out_channels, is_sorted=True)
        self.pred = Decoder(featurelen=self.in_channels, edgeclasslen=self.out_channels)

對應的主程式是這行:

# 載入模型
GCNmodel = GCN(in_channels=dataset.num_features, out_channels=dataset.num_classes)
print(GCNmodel)

在class GCN forward才真的進入神經網路。

class GCN(torch.nn.Module):

    def forward(self, x, edge_index, edge_label):
        labelindex = self.labelprocess(edge_label)
        labelindex = torch.tensor(labelindex, dtype=torch.long)
        encode_data_on_edge = self.conv(x=x, edge_index=edge_index, edge_type=labelindex)
        decode = self.pred(encodedata=encode_data_on_edge, edge_index=edge_index)
        return decode

    def labelprocess(self, edge_label):
        edge_label_index = []
        for i, labellist in enumerate(edge_label):
            if labellist[1] > labellist[0]:
                edge_label_index.append(0)
            else:
                edge_label_index.append(1)
        return edge_label_index

class Decoder(torch.nn.Module):
    def __init__(self, featurelen, edgeclasslen):
        super().__init__()
        self.classifier = torch.nn.Linear(featurelen*2, edgeclasslen)

    def forward(self, encodedata, edge_index):
        h = torch.cat([encodedata[edge_index[0]], encodedata[edge_index[1]]], 1)
        y = self.classifier(h)
        return y

class GCN forward是在train里執行的:

loss = train(train_loader, GCNmodel, optimizer)
def train(batchdata, model, op):
    total_loss = 0.0
    for batch in batchdata:
        optimizer.zero_grad()   # 在参数更新之后,梯度需要清零,以便进行下一轮的反向传播。
        x, edge_index, edge_label = batch.x, batch.edge_index, batch.y  # 请根据实际数据结构调整
        output = model(x, edge_index, edge_label)
        los = criterion(output, edge_label)    # 通过将模型输出和实际标签传递给损失函数,计算了模型在当前批次(或样本)上的损失值。
        los.backward()  # 计算损失函数关于模型参数的梯度。
        op.step()   # 根据梯度更新模型的参数,以最小化损失函数。
        total_loss += los.item()
    average_loss = total_loss / len(batchdata)
    return average_loss

model(x, edge_index, edge_label)就是在執行class GCN forward區塊,將該迭代(epoch)該批次(batch)的每張圖資訊分別輸入神經網路。

在驗證和測試的時候也是這樣的流程。

edge_classification_model.py 完整程式碼:

import torch
from torch_geometric.nn.conv import FastRGCNConv
from build_dataset import MyDataset
from torch.utils.data import random_split
from torch_geometric.loader import DataLoader
import matplotlib.pyplot as plt


class GCN(torch.nn.Module):
    def __init__(self, in_channels, out_channels):
        super().__init__()
        self.in_channels = in_channels    # 輸入通道數:即一節點的特徵長度
        self.out_channels = out_channels    # 輸出通道數:即邊類別表示所需的長度
        self.conv = FastRGCNConv(in_channels=in_channels, out_channels=in_channels,
                                 num_relations=out_channels, is_sorted=True)    # 設定RGCN捲積層
        self.pred = Decoder(featurelen=self.in_channels, edgeclasslen=self.out_channels)    # 設定解碼器參數並預測邊分類

    def forward(self, x, edge_index, edge_label):
        labelindex = self.labelprocess(edge_label)    # 取得RGCN()所需的邊分類解答
        labelindex = torch.tensor(labelindex, dtype=torch.long)    # labelprocess結果為串列,要改成torch.tensor資料
        encode_data_on_edge = self.conv(x=x, edge_index=edge_index, edge_type=labelindex)   # 執行RGCN捲積
        decode = self.pred(encodedata=encode_data_on_edge, edge_index=edge_index)   # 解碼並預測邊分類
        return decode

    def labelprocess(self, edge_label):    # 生成FastRGCNConv所需要的邊解答格式,以一位數表示邊屬於第幾類,而非one-hot encoding編碼的格式
        edge_label_index = []
        for i, labellist in enumerate(edge_label):
            if labellist[1] > labellist[0]:    # 預設邊以兩位表示
                edge_label_index.append(0)  # [ , ]第1位大於第0位設為第0類的邊
            else:
                edge_label_index.append(1)  # 反之設為第1類的邊
        return edge_label_index


class Decoder(torch.nn.Module):    # 解碼器
    def __init__(self, featurelen, edgeclasslen):
        super().__init__()
        self.classifier = torch.nn.Linear(featurelen*2, edgeclasslen)    # 邊的分類器

    def forward(self, encodedata, edge_index):
        h = torch.cat([encodedata[edge_index[0]], encodedata[edge_index[1]]], 1)    # 解碼,將結點聚合的資料聚合回邊上
        y = self.classifier(h)  # 進行邊分類預測
        return y


def train(batchdata, model, op):    # 訓練
    total_loss = 0.0
    for batch in batchdata:    # 依每個批次資料進行訓練
        optimizer.zero_grad()   # 在参數更新之后,梯度需要清零,以便進行下一輪的反向傳播。
        x, edge_index, edge_label = batch.x, batch.edge_index, batch.y
        output = model(x, edge_index, edge_label)
        los = criterion(output, edge_label)   # 將預測結果以criterion設定的損失函數計算損失值
        los.backward()    # 計算損失函數關於模型參數的梯度
        op.step()   # 根據梯度更新模型的參數
        total_loss += los.item()
    average_loss = total_loss / len(batchdata)
    return average_loss


@torch.no_grad()    # 上下文管理器,停用梯度追蹤。 提高效能並減少不必要的計算
def test(batchdata, model):
    model.eval()  # 設定模型為評估模式
    total_loss = 0.0

    for batch in batchdata:
        x, edge_index, edge_label = batch.x, batch.edge_index, batch.y

        # 模型推斷
        output = model(x, edge_index, edge_label)

        # 計算損失
        los = criterion(output, edge_label)
        total_loss += los.item()

    # 計算平均損失率
    average_loss = total_loss / len(batchdata)
    return average_loss


# 讀取資料庫
dataset = MyDataset(root='mydataset')
print()
print(f'Dataset: {dataset}:')
print(f'Number of graphs: {dataset.len()}')
print('======================')


# 分出訓練集、驗證集、測試集
train_dataset, val_dataset, test_dataset = random_split(dataset, [0.7, 0.2, 0.1])
print()
print('Number of graphs train_data:', len(train_dataset))
print('Number of graphs val_data:',  len(val_dataset))
print('Number of graphs test_data',  len(test_dataset))

# 把資料分批
train_loader = DataLoader(train_dataset, batch_size=10, shuffle=True)    # batch_size每次迭代的時候要隨機選擇幾個資料
val_loader = DataLoader(val_dataset, batch_size=10, shuffle=True)    # shuffle 參數用於指定是否對資料進行隨機打亂
test_loader = DataLoader(test_dataset, batch_size=10, shuffle=True)


# 載入模型
GCNmodel = GCN(in_channels=dataset.num_features, out_channels=dataset.num_classes)
print(GCNmodel)

# 定義儲存損失的串列
train_losses = []
val_losses = []
test_losses = []

# 執行模型訓練驗證和測試
optimizer = torch.optim.Adam(GCNmodel.parameters(), lr=0.01, weight_decay=5e-4)
criterion = torch.nn.CrossEntropyLoss()
num_epochs = 100    # 迭代次數
for epoch in range(num_epochs):
    loss = train(train_loader, GCNmodel, optimizer)
    val_los = test(val_loader, GCNmodel)
    test_los = test(test_loader, GCNmodel)
    print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}, Val loss: {val_los:.4f}, '
          f'Test loss: {test_los:.4f}')
    # 儲存每epoch的損失值
    train_losses.append(loss)
    val_losses.append(val_los)
    test_losses.append(test_los)

# 繪製圖表展現模型學習狀況
plt.figure(figsize=(10, 6))
plt.plot(range(1, num_epochs + 1), train_losses, label='Train Loss', marker='o')
plt.plot(range(1, num_epochs + 1), val_losses, label='Validation Loss', marker='o')
plt.plot(range(1, num_epochs + 1), test_losses, label='Test Loss', marker='o')

# 添加圖表的標題和標籤
plt.title('Training, Validation, and Test Loss Over Epochs')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
plt.show()

參考資料

圖像化神經網路(1):Graph Neural Network小簡介
RGCN - 知乎

5.2 边分类/回归 — DGL 1.1.3 documentation

PyCharm是一个流行的集成开发环境(IDE),用于Python开发。如果你想要在PyCharm 2024(假设版本还未正式发布,因为通常每年更新一次,且版本号可能会有所不同)中新建一个项目,可以按照以下步骤操作: 1. **打开PyCharm**: - 打开PyCharm软件,如果它是第一次启动,可能会让你选择工作区或者创建新项目。 2. **新建项目**: - 在PyCharm的欢迎界面或者"File"菜单中,找到并点击 "New Project" 或者 "Create New Project"。 3. **选择项目类型**: - 在弹出的窗口中,你可以看到模板选项,如 "Python Project", "Jupyter Notebook", "Django", 等等。根据你的需求,选择适合的项目类型。 4. **配置项目信息**: - 输入项目的名称、位置(通常是选择一个新的文件夹)、以及其他相关的设置,比如使用的Python解释器(如果你有多个Python版本)。 5. **选择框架或模块**: - 如果你选择了基于某个框架(如Django)的项目,需要勾选相应的选项,并完成额外的配置。 6. **完成创建**: - 阅读并接受许可协议,然后点击 "Finish" 或 "Create" 来创建新的项目。 7. **开始编码**: - 新建的项目会自动生成一些基本结构,现在你可以开始编写代码了。 请注意,由于实际版本可能会有一些变化,如果你是在最新版的PyCharm或其他版本上操作,上述步骤可能略有不同。在创建过程中遇到任何问题,PyCharm的官方文档会有详细的说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值