深入浅出图神经网络GCN代码实战

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

深⼊浅出图神经⽹络GCN代码实战
GCN代码实战
书中5.6节的GCN代码实战做的是最经典Cora数据集上的分类,恰当⼜不恰当的类⽐Cora之于GNN就相当于MNIST之于机器学习。

有关Cora的介绍⽹上⼀搜⼀⼤把我就不赘述了,这⾥说⼀下Cora这个数据集对应的图是怎么样的。

Cora有2708篇论⽂,之间有引⽤关系共5429个,每篇论⽂作为⼀个节点,引⽤关系就是节点之间的边。

每篇论⽂有⼀个1433维的特征来表⽰某个词是否在⽂中出现过,也就是每个节点有1433维的特征。

最后这些论⽂被分为7类。

所以在Cora上训练的⽬的就是学习节点的特征及其与邻居的关系,根据已知的节点分类对未知分类的节点的类别进⾏预测。

知道这些应该就OK了,下⾯来看代码。

数据处理
注释⾥⾃⼰都写了代码引⽤⾃PyG我觉得就扫⼏眼就⾏了,因为现在常⽤的数据集两个GNN轮⼦(DGL和PyG)⾥都有,现在基本都是直接⽤,很少⾃⼰下原始数据再处理了,所以略过。

GCN层定义
回顾第5章中GCN层的定义:
X′=σ(˜L sym XW)
所以对于⼀层GCN,就是对输⼊X,乘⼀个参数矩阵W,再乘⼀个算好归⼀化后的“拉普拉斯矩阵”即可。

来看代码:
class GraphConvolution(nn.Module):
def __init__(self, input_dim, output_dim, use_bias=True):
super(GraphConvolution, self).__init__()
self.input_dim = input_dim
self.output_dim = output_dim
e_bias = use_bias
self.weight = nn.Parameter(torch.Tensor(input_dim, output_dim))
if e_bias:
self.bias = nn.Parameter(torch.Tensor(output_dim))
else:
self.register_parameter('bias', None)
self.reset_parameters()
def reset_parameters(self):
init.kaiming_uniform_(self.weight)
if e_bias:
init.zeros_(self.bias)
def forward(self, adjacency, input_feature):
support = torch.mm(input_feature, self.weight)
output = torch.sparse.mm(adjacency, support)
if e_bias:
output += self.bias
return output
def __repr__(self):
return self.__class__.__name__ + ' (' \
+ str(self.input_dim) + ' -> ' \
+ str(self.output_dim) + ')'
定义了⼀层GCN的输⼊输出维度和偏置,对于GCN层来说,每⼀层有⾃⼰的W,X是输⼊给的,˜L
sym是数据集算的,所以只需要定义⼀个weight矩阵,注意⼀下维度就⾏。

传播的时候只要按照公式X′=σ(˜L sym XW)进⾏⼀下矩阵乘法就好,注意⼀个trick:˜L sym是稀疏矩阵,所以先矩阵乘法得到XW,再⽤稀疏矩阵乘法计算˜L sym XW运算效率上更好。

GCN模型定义
知道了GCN层的定义之后堆叠GCN层就可以得到GCN模型了,两层的GCN就可以取得很好的效果(过深的GCN因为过度平滑的问题会导致准确率下降):
class GcnNet(nn.Module):
def __init__(self, input_dim=1433):
super(GcnNet, self).__init__()
self.gcn1 = GraphConvolution(input_dim, 16)
self.gcn2 = GraphConvolution(16, 7)
def forward(self, adjacency, feature):
h = F.relu(self.gcn1(adjacency, feature))
logits = self.gcn2(adjacency, h)
return logits
这⾥设置隐藏层维度为16,调到32,64,...都是可以的,我⾃⼰试的结果来说没有太⼤的区别。

从隐藏层到输出层直接将输出维度设置为分类的维度就可以得到预测分类。

传播的时候相⽐于每⼀层的传播只需要加上激活函数,这⾥选⽤ReLU。

训练
Processing math: 100%
定义模型、损失函数(交叉熵)、优化器:
model = GcnNet(input_dim).to(DEVICE)
criterion = nn.CrossEntropyLoss().to(DEVICE)
optimizer = optim.Adam(model.parameters(),
lr=LEARNING_RATE,
weight_decay=WEIGHT_DACAY)
具体的训练函数注释已经解释的很清楚:
def train():
loss_history = []
val_acc_history = []
model.train()
train_y = tensor_y[tensor_train_mask]
for epoch in range(EPOCHS):
logits = model(tensor_adjacency, tensor_x) # 前向传播
train_mask_logits = logits[tensor_train_mask] # 只选择训练节点进⾏监督
loss = criterion(train_mask_logits, train_y) # 计算损失值
optimizer.zero_grad()
loss.backward() # 反向传播计算参数的梯度
optimizer.step() # 使⽤优化⽅法进⾏梯度更新
train_acc, _, _ = test(tensor_train_mask) # 计算当前模型训练集上的准确率
val_acc, _, _ = test(tensor_val_mask) # 计算当前模型在验证集上的准确率
# 记录训练过程中损失值和准确率的变化,⽤于画图
loss_history.append(loss.item())
val_acc_history.append(val_acc.item())
print("Epoch {:03d}: Loss {:.4f}, TrainAcc {:.4}, ValAcc {:.4f}".format(
epoch, loss.item(), train_acc.item(), val_acc.item()))
return loss_history, val_acc_history
对应的测试函数:
def test(mask):
model.eval()
with torch.no_grad():
logits = model(tensor_adjacency, tensor_x)
test_mask_logits = logits[mask]
predict_y = test_mask_logits.max(1)[1]
accuarcy = torch.eq(predict_y, tensor_y[mask]).float().mean()
return accuarcy, test_mask_logits.cpu().numpy(), tensor_y[mask].cpu().numpy()
注意模型得到的分类不是one-hot的,⽽是对应不同种类的预测概率,所以要test_mask_logits.max(1)[1]取概率最⾼的⼀个作为模型预测的类别。

这些都写好之后直接运⾏训练函数即可。

有需要还可以对train_loss和validation_accuracy进⾏画图,书上也给出了相应的代码,⽐较简单不再赘述。

相关文档
最新文档