最近需要訓練圖卷積神經網絡(Graph Convolution Neural Network, GCNN),在配置GCNN環境上總結了一些經驗。
我覺得對于初學者而言,圖神經網絡的訓練會有2個難點:
①環境配置
②數據集制作
一、環境配置
我最初光想到要給GCNN配環境就覺得有些困難,感覺相比于目標檢測、分類識別這些任務用規則數據,圖神經網絡的模型、數據都是圖,所以內心覺得會比較難。
我之前更有一個誤區,就是覺得不規則結構的圖數據不能用CUDA進行并行加速。實際上,圖,在電腦里也是以張量這種規則結構數據存在的,完全能用CUDA進行加速計算,訓練GCN前配置CUDA完全OK。
以下是我配置的環境,可用CUDA成功運行link_pred.py
幾個關鍵包的版本:
torch? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 2.4.1
torch-geometric ? ? ? ? ? ? ? 2.3.1
torchaudio? ? ? ? ? ? ? ? ? ? ? ?2.4.1
torchvision? ? ? ? ? ? ? ? ? ? ? ?0.14.0
torchviz? ? ? ? ? ? ? ? ? ? ? ? ? ? 0.0.2pandas? ? ? ? ? ? ? ? ? ? ? ? ? ? ?1.0.3
numpy? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1.20.0
?CUDA: 11.8
注意要先安裝好CUDA,顯示了:
?
再安裝GPU版本的torch,不然python檢測安裝的是cpu版本的torch。這時,就得卸載重新安裝了
環境配置成功:
print(torch.__version__)
print(torch.cuda.is_available())
如果CUDA環境安裝失敗,會打印:
2.4.1+cpu
False
其實只安裝torch和CUDA還好,如果你的python中有numpy和pandas可能解決版本之間的沖突會耗費不少時間,我就是在numpy和pandas版本上試了很久,最終找到現在的版本是相互兼容的。
CUDA的版本切換可以參考我的另一篇博客:
CUDA版本切換
二、數據集制作
掌握圖數據集制作的關鍵在于掌握slices切片:
for ...data = Data(x=X, edge_index=Edge_index, edge_label_index=Edge_label_index, edge_label=Edge_label)data_list.append(data)
data_, slices = self.collate(data_list) # 將不同大小的圖數據對齊,填充
torch.save((data_, slices), self.processed_paths[0])
和CNN不同的是,GCN沒有樣本維度,需要把所有樣本拼成一張大圖喂給GCN進行訓練?
數據集生成代碼:
#作者:zhouzhichao
#創建時間:2025/5/30
#內容:生成200個樣本的PYG數據集import h5py
import hdf5storage
import numpy as np
import torch
from torch_geometric.data import InMemoryDataset, Data
from torch_geometric.utils import negative_samplingbase_dir = "D:\\無線通信網絡認知\\論文1\\experiment\\直推式拓撲推理實驗\\拓撲生成\\200樣本\\"N = 30
grapg_size = N
train_n = 31
M = 3000class graph_data(InMemoryDataset):def __init__(self, root, signals=None, tp_list = None, transform=None, pre_transform=None):# self.Signals = Signals# self.Tp_list = Tp_listself.signals = signalsself.tp_list = tp_listsuper().__init__(root, transform, pre_transform)# self.data, self.slices = torch.load(self.processed_paths[0])self.data = torch.load(self.processed_paths[0])# 返回process方法所需的保存文件名。你之后保存的數據集名字和列表里的一致@propertydef processed_file_names(self):return ['gcn_data.pt']# 生成數據集所用的方法def process(self):# data_list = []# for k in range(200):# signals = self.Signals[:, :, k]# tp_list = np.array(mat_file[self.Tp_list[0, k]])signals = self.signalstp_list =self.tp_list# tp = Tp[:,:,k]X = torch.tensor(signals, dtype=torch.float)# 所有的邊Edge_index = torch.tensor(tp_list, dtype=torch.long)# 所有的邊1標簽edge_label = np.ones((tp_list.shape[1]))# edge_label = np.zeros((tp_list.shape[1]))Edge_label = torch.tensor(edge_label, dtype=torch.float)neg_edge_index = negative_sampling(edge_index=Edge_index, num_nodes=grapg_size,num_neg_samples=Edge_index.shape[1], method='sparse')# 拼接正負樣本索引# c = 0# for i in range(31):# for i in range(31):# if torch.equal(Edge_index[:, i], neg_edge_index[:, i]):# c = c + 1# print("c: ",c)Edge_label_index = Edge_indexperm = torch.randperm(Edge_index.size(1))Edge_index = Edge_index[:, perm]Edge_index = Edge_index[:, :train_n]Edge_label_index = torch.cat([Edge_label_index, neg_edge_index],dim=-1,)# 拼接正負樣本Edge_label = torch.cat([Edge_label,Edge_label.new_zeros(neg_edge_index.size(1))], dim=0)# Edge_label = torch.cat([# Edge_label,# Edge_label.new_ones(neg_edge_index.size(1))# ], dim=0)data = Data(x=X, edge_index=Edge_index, edge_label_index=Edge_label_index, edge_label=Edge_label)torch.save(data, self.processed_paths[0])# data_list.append(data)# data_, slices = self.collate(data_list) # 將不同大小的圖數據對齊,填充# torch.save((data_, slices), self.processed_paths[0])for snr in [0,20,40]:print("snr: ", snr)mat_file = h5py.File(base_dir + str(N) + '_nodes_dataset_snr-' + str(snr) + '_M_' + str(M) + '.mat', 'r')# mat_file = hdf5storage.loadmat(base_dir + str(N) + '_nodes_dataset_snr-' + str(snr) + '_M_' + str(M) + '.mat', 'r')# 獲取數據集Signals = mat_file["Signals"][()]# signals = np.swapaxes(signals, 1, 0)Tp = mat_file["Tp"][()]Tp_list = mat_file["Tp_list"][()]# tp_list = tp_list - 1# 關閉文件# mat_file.close()# graph_data("gcn_data")# n = Signals.shape[2]n = 10for i in range(n):signals = Signals[:,:,i]tp_list = np.array(mat_file[Tp_list[0, i]])root = "gcn_data-"+str(i)+"_N_"+str(N)+"_snr_"+str(snr)+"_train_n_"+str(train_n)+"_M_"+str(M)graph_data(root, signals = signals, tp_list = tp_list)print("")print("...圖數據生成完成...")
訓練代碼:
#作者:zhouzhichao
#創建時間:25年5月29日
#內容:統計圖中有關系節點和無關系節點的GCN特征歐式距離import sys
import torch
import random
import numpy as np
import pandas as pd
from torch_geometric.nn import GCNConv
from sklearn.metrics import roc_auc_score
sys.path.append('D:\無線通信網絡認知\論文1\experiment\直推式拓撲推理實驗\GCN推理')
from gcn_dataset import graph_data
print(torch.__version__)
print(torch.cuda.is_available())mode = "gcn"class Net(torch.nn.Module):def __init__(self):super().__init__()self.conv1 = GCNConv(Input_L, 1000)self.conv2 = GCNConv(1000, 20)def encode(self, x, edge_index):x1 = self.conv1(x, edge_index)x1_1 = x1.relu()x2 = self.conv2(x1_1, edge_index)x2_2 = x2.relu()return x2_2def decode(self, z, edge_label_index):# 節點和邊都是矩陣,不同的計算方法致使:節點->節點,節點->邊# nodes_relation = (z[edge_label_index[0]] * z[edge_label_index[1]]).sum(dim=-1)# distances = torch.norm(z[edge_label_index[0]] - z[edge_label_index[1]], dim=-1)distance_squared = torch.sum((z[edge_label_index[0]] - z[edge_label_index[1]]) ** 2, dim=-1)# print("distance_squared: ",distance_squared)return distance_squareddef decode_all(self, z):prob_adj = z @ z.t() # 得到所有邊概率矩陣return (prob_adj > 0).nonzero(as_tuple=False).t() # 返回概率大于0的邊,以edge_index的形式@torch.no_grad()def test(self,input_data):model.eval()z = model.encode(input_data.x, input_data.edge_index)out = model.decode(z, input_data.edge_label_index).view(-1)out = 1 - outN = 30
train_n = 31
M = 3000
# snr = -20
# for train_n in range(1,51):
# for M in range(3000, 499, -100):
for snr in [0,20,40]:print("snr: ", snr)for I in range(10):root = "gcn_data-"+str(I)+"_N_"+str(N)+"_snr_"+str(snr)+"_train_n_"+str(train_n)+"_M_"+str(M)gcn_data = graph_data(root)Input_L = gcn_data.x.shape[1]model = Net()# model = Net().to(device)optimizer = torch.optim.Adam(params=model.parameters(), lr=0.01)criterion = torch.nn.BCEWithLogitsLoss()def train():model.train()optimizer.zero_grad()z = model.encode(gcn_data.x, gcn_data.edge_index)# out = model.decode(z, train_data.edge_label_index).view(-1).sigmoid()out = model.decode(z, gcn_data.edge_label_index).view(-1)out = 1 - outloss = criterion(out, gcn_data.edge_label)loss.backward()optimizer.step()return lossmin_loss = 99999count = 0#早停for epoch in range(10000):loss = train()if loss<min_loss:min_loss = losscount = 0count = count + 1if count>100:breakprint("epoch: ",epoch," loss: ",round(loss.item(),2), " min_loss: ",round(min_loss.item(),2))z = model.encode(gcn_data.x, gcn_data.edge_index)out = model.decode(z, gcn_data.edge_label_index).view(-1)list_0 = []list_1 = []for i in range(len(gcn_data.edge_label)):true_label = gcn_data.edge_label[i].item()euclidean_distance_value = out[i].item()if true_label==1:list_1.append(euclidean_distance_value)if true_label==0:list_0.append(euclidean_distance_value)minlength = min(len(list_1), len(list_0))list_1 = random.sample(list_1, minlength)list_0 = random.sample(list_0, minlength)value = list_1 + list_0large_class = list(np.full(len(value), snr))small_class = list(np.full(len(list_1), 1)) + list(np.full(len(list_0), 0))data = {'large_class': large_class,'small_class': small_class,'value': value}# 創建一個 DataFramedf = pd.DataFrame(data)## # 保存到 Excel 文件file_path = 'D:\無線通信網絡認知\論文1\大修意見\圖聚類、閾值相似性圖實驗補充\\' + mode + '_similarity_' + str(snr) + 'db_'+str(I)+'.xlsx'df.to_excel(file_path, index=False)