PyG-Sequential容器-设计序列图神经网络
引言
之前我们介绍过由2层GATConv
组成的神经网络,当我们需要定义一个复杂的序列图神经网络(上一层的输出将作为下一层的输入)时,可以使用torch.nn.Sequential
容器将多个层连接起来。本文将先介绍torch.nn.Sequential
容器,包括如何使用和相关的参数、标识;其次,将利用该容器设计一个基于GATConv
的序列神经网络,并将模型用于节点分类这一下游任务。
torch.nn.Sequential容器
torch.nn.Sequential
容器可用于定义顺序GNN 模型。 由于GNN运算符接受多个输入参数,torch_geometric.nn.Sequential
需要全局输入参数和单个运算符的函数头定义。 如果省略,中间模块将对其前一个模块的输出进行操作。
我们首先定义一个卷积层列表conv_list
,用于存序列卷积层,其中上一卷积层的输出将作为下一卷积层的输入。加载各个卷积层。
GATConv
初始化传入的参数'x, edge_index -> x'
为函数头,比如GATConv
的输入形参(x, edge_index
)和返回值(x
)。
然后利用Sequential('x, edge_index',conv_list)
容器将卷积层列表conv_list
包装起来,其中'x, edge_index'
是模型传入的形参。
from torch_geometric.nn import GATConv, Sequential
feature_num=1433 num_classes=7 hidden_channels_list=[feature_num,128,64,num_classes] for idx in range(len(hidden_channels_list)): conv_list.append((GATConv(hns[idx],hns[idx+1],'x, edge_index -> x'))) conv_list.append(ReLU(inplace=True),)
convseq=Sequential('x, edge_index',conv_list)
|
序列GAT完成节点分类任务
数据加载
数据加载有困难请参考之前的博客:图神经网络的下游任务1-利用节点特征进行节点分类 | 冬于的博客 (ifwind.github.io)
from torch_geometric.datasets import Planetoid from torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root='dataset/PlanetoidPubMed',transform=NormalizeFeatures()) print('dataset.num_features:', dataset.num_features) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') data = dataset[0].to(device)
|
设计Net
现在我们重定义一个GAT神经网络,使其能够通过参数定义GATConv
的层数,以及每一层GATConv
的out_channels
,该网络由多个GATConv
顺序相连而构成。
我们的神经网络模型定义如下:
import os.path as osp
import torch import torch.nn.functional as F from torch_geometric.nn import GATConv, Sequential from torch.nn import Linear, ReLU
class GAT(torch.nn.Module): def __init__(self, num_features, hidden_channels_list, num_classes): super(GAT, self).__init__() torch.manual_seed(12345) hns = [num_features] + hidden_channels_list conv_list = [] for idx in range(len(hidden_channels_list)): conv_list.append((GATConv(hns[idx], hns[idx+1]), 'x, edge_index -> x')) conv_list.append(ReLU(inplace=True),)
self.convseq = Sequential('x, edge_index', conv_list) self.linear = Linear(hidden_channels_list[-1], num_classes)
def forward(self, x, edge_index): x = self.convseq(x, edge_index) x = F.dropout(x, p=0.5, training=self.training) x = self.linear(x) return x
model = GAT(num_features=dataset.num_features, hidden_channels_list=[200, 100], num_classes=dataset.num_classes).to(device)
|
训练和测试
model=GAT(num_features=dataset.num_features,hidden_channels_list=[200,100],num_classes=dataset.num_classes).to(device) optimizer=torch.optim.Adam(model.parameters(),lr=0.01,weight_decay=5e-4) criterion=torch.nn.CrossEntropyLoss()
def train(model,data,optimizer,criterion): model.train() optimizer.zero_grad() out=model(data.x,data.edge_index) loss=criterion(out[data.train_mask],data.y[data.train_mask]) loss.backward() optimizer.step() return loss
def test(model,data): model.eval() out=model(data.x,data.edge_index) pred=out.argmax(dim=1) test_correct=pred[data.test_mask]==data.y[data.test_mask] test_acc=int(test_correct.sum())/int(data.test_mask.sum()) return test_acc
for epoch in range(1,101): loss=train(model,data,optimizer,criterion) print(f'Epoch:{epoch:03d}, Loss:{loss:.4f}')
test_acc=test(model,data) print(f'Test Accuracy:{test_acc:.4f}')
|
可视化
import matplotlib.pyplot as plt from sklearn.manifold import TSNE
def visualize(out,color): z = TSNE(n_components=2).fit_transform(out.detach().cpu().numpy()) plt.figure(figsize=(10,10)) plt.xticks([]) plt.yticks([])
plt.scatter(z[:,0],z[:,1],s=70,c=color,cmap='Set2') plt.show() out=model(data.x,data.edge_index) visualize(out,color=data.y)
|
实验结果
Test Accuracy: 0.7770
可视化结果: