PyG搭建GCN模型实现节点分类GCNConv参数详解
作者:Cyril_KI 发布时间:2022-08-22 19:58:49
前言
在上一篇文章PyG搭建GCN前的准备:了解PyG中的数据格式中,大致了解了PyG中的数据格式,这篇文章主要是简单搭建GCN来实现节点分类,主要目的是了解PyG中GCN的参数情况。
模型搭建
首先导入包:
from torch_geometric.nn import GCNConv
模型参数:
in_channels:输入通道,比如节点分类中表示每个节点的特征数。
out_channels:输出通道,最后一层GCNConv的输出通道为节点类别数(节点分类)。
improved:如果为True表示自环增加,也就是原始邻接矩阵加上2I而不是I,默认为False。
cached:如果为True,GCNConv在第一次对邻接矩阵进行归一化时会进行缓存,以后将不再重复计算。
add_self_loops:如果为False不再强制添加自环,默认为True。
normalize:默认为True,表示对邻接矩阵进行归一化。
bias:默认添加偏置。
于是模型搭建如下:
class GCN(torch.nn.Module):
def __init__(self, num_node_features, num_classes):
super(GCN, self).__init__()
self.conv1 = GCNConv(num_node_features, 16)
self.conv2 = GCNConv(16, num_classes)
def forward(self, data):
x, edge_index = data.x, data.edge_index
x = self.conv1(x, edge_index)
x = F.relu(x)
x = F.dropout(x, training=self.training)
x = self.conv2(x, edge_index)
x = F.relu(x)
x = F.dropout(x, training=self.training)
x = F.softmax(x, dim=1)
return x
输出一下模型:
data = Planetoid(root='/data/CiteSeer', name='CiteSeer')model = GCN(data.num_node_features, data.num_classes).to(device)print(model)GCN(
(conv1): GCNConv(3703, 16)
(conv2): GCNConv(16, 6)
)
输出为:
GCN( (conv1): GCNConv(3703, 16) (conv2): GCNConv(16, 6))GCN(
(conv1): GCNConv(3703, 16)
(conv2): GCNConv(16, 6)
)
1. 前向传播
查看官方文档中GCNConv的输入输出要求:
可以发现,GCNConv中需要输入的是节点特征矩阵x和邻接关系edge_index,还有一个可选项edge_weight。因此我们首先:
x, edge_index = data.x, data.edge_index
x = self.conv1(x, edge_index)
x = F.relu(x)
x = F.dropout(x, training=self.training)
此时我们不妨输出一下x及其size:
tensor([[0.0000, 0.1630, 0.0000, ..., 0.0000, 0.0488, 0.0000],
[0.0000, 0.2451, 0.1614, ..., 0.0000, 0.0125, 0.0000],
[0.1175, 0.0262, 0.2141, ..., 0.2592, 0.0000, 0.0000],
...,
[0.0000, 0.0000, 0.0000, ..., 0.0000, 0.1825, 0.0000],
[0.0000, 0.1024, 0.0000, ..., 0.0498, 0.0000, 0.0000],
[0.0000, 0.3263, 0.0000, ..., 0.0000, 0.0000, 0.0000]],
device='cuda:0', grad_fn=<FusedDropoutBackward0>)
torch.Size([3327, 16])
此时的x一共3327行,每一行表示一个节点经过第一层卷积更新后的状态向量。
那么同理,由于:
self.conv2 = GCNConv(16, num_classes)
所以经过第二层卷积后:
x = self.conv2(x, edge_index)x = F.relu(x)x = F.dropout(x, training=self.training)x = self.conv2(x, edge_index)
x = F.relu(x)
x = F.dropout(x, training=self.training)
此时得到的x的size应该为:
torch.Size([3327, 6])
即每个节点的维度为6的状态向量。
由于我们需要进行6分类,所以最后需要加上一个softmax:
x = F.softmax(x, dim=1)
dim=1表示对每一行进行运算,最终每一行之和加起来为1,也就表示了该节点为每一类的概率。输出此时的x:
tensor([[0.1607, 0.1727, 0.1607, 0.1607, 0.1607, 0.1846], [0.1654, 0.1654, 0.1654, 0.1654, 0.1654, 0.1731], [0.1778, 0.1622, 0.1733, 0.1622, 0.1622, 0.1622], ..., [0.1659, 0.1659, 0.1659, 0.1704, 0.1659, 0.1659], [0.1667, 0.1667, 0.1667, 0.1667, 0.1667, 0.1667], [0.1641, 0.1641, 0.1658, 0.1766, 0.1653, 0.1641]], device='cuda:0', grad_fn=<SoftmaxBackward0>)tensor([[0.1607, 0.1727, 0.1607, 0.1607, 0.1607, 0.1846],
[0.1654, 0.1654, 0.1654, 0.1654, 0.1654, 0.1731],
[0.1778, 0.1622, 0.1733, 0.1622, 0.1622, 0.1622],
...,
[0.1659, 0.1659, 0.1659, 0.1704, 0.1659, 0.1659],
[0.1667, 0.1667, 0.1667, 0.1667, 0.1667, 0.1667],
[0.1641, 0.1641, 0.1658, 0.1766, 0.1653, 0.1641]], device='cuda:0',
grad_fn=<SoftmaxBackward0>)
2. 反向传播
在训练时,我们首先利用前向传播计算出输出:
out = model(data)
out即为最终得到的每个节点的6个概率值,但在实际训练中,我们只需要计算出训练集的损失,所以损失函数这样写:
loss = loss_function(out[data.train_mask], data.y[data.train_mask])
然后计算梯度,反向更新!
3. 训练
训练的完整代码:
def train(): optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) loss_function = torch.nn.CrossEntropyLoss().to(device) model.train() for epoch in range(500): out = model(data) optimizer.zero_grad() loss = loss_function(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() print('Epoch {:03d} loss {:.4f}'.format(epoch, loss.item()))def train():
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
loss_function = torch.nn.CrossEntropyLoss().to(device)
model.train()
for epoch in range(500):
out = model(data)
optimizer.zero_grad()
loss = loss_function(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
print('Epoch {:03d} loss {:.4f}'.format(epoch, loss.item()))
4. 测试
我们首先需要算出模型对所有节点的预测值:
model(data)
此时得到的是每个节点的6个概率值,我们需要在每一行上取其最大值:
model(data).max(dim=1)
输出一下:
torch.return_types.max(
values=tensor([0.9100, 0.9071, 0.9786, ..., 0.4321, 0.4009, 0.8779], device='cuda:0',
grad_fn=<MaxBackward0>),
indices=tensor([3, 1, 5, ..., 3, 1, 5], device='cuda:0'))
返回的第一项是每一行的最大值,第二项为最大值在这一行中的索引,我们只需要取第二项,那么最终的预测值应该写为:
_, pred = model(data).max(dim=1)
然后计算预测精度:
correct = int(pred[data.test_mask].eq(data.y[data.test_mask]).sum().item())
acc = correct / int(data.test_mask.sum())
print('GCN Accuracy: {:.4f}'.format(acc))
完整代码
完整代码中实现了论文中提到的四种数据集,代码地址:PyG-GCN。
来源:https://blog.csdn.net/Cyril_KI/article/details/123457698
猜你喜欢
- 上一章节我们学习了如何生成 word 文档以及在文档行中添加各种内容,今天我们基于上一章节的内容进行添砖加瓦 —>
- .asa是文件后缀名,它是Active Server Application的首字母缩写。Global.asa文件可以管理在ASP应用中两个
- 前言:在Python里面,只要类型对象实现了__iter__,那么它的实例对象就被称为可迭代对象(Iterable),比如字符串、元组、列表
- 使用Keras如果要使用大规模数据集对网络进行训练,就没办法先加载进内存再从内存直接传到显存了,除了使用Sequence类以外,还可以使用迭
- 最近开始在项目中使用Quickwork For Asp,虽然该框架是自己独立完成的,不过功能没做过详细的总结,所以很多参数总是会弄错,毕竟鱼
- 简介:上文中已经介绍如何安装Pycharm已经环境变量的配置。现在软件已经安装成功,现在就开始动手做第一个Python项目。第一个“Hell
- 1. 使用 in 和 not inin 和 not in 在 Python 中是很常用的关键字,我们将它们归类为 成员运算符。使用这两个成员
- 因为 GAE 在国内访问不便,所以平时有一些小应用,我都会放在 SAE 上面, 虽然 SAE 还有很多缺陷,但算是上手比较容易的一个了,最起
- 官方文档https://developers.weixin.qq.com/miniprogram/dev/devtools/download
- 本文实例讲述了python中黄金分割法实现方法。分享给大家供大家参考。具体实现方法如下:''' a,b = brac
- Python计算的位数在电脑上做了一个实验,看看python能计算到多少位,一下是结果。x = math.sqrt((3))print (&
- 在这里我们介绍两个拼接数组的方法:np.vstack():在竖直方向上堆叠np.hstack():在水平方向上平铺import numpy
- ORM 江湖曾几何时,程序员因为惧怕SQL而在开发的时候小心翼翼的写着sql,心中总是少不了恐慌,万一不小心sql语句出错,搞坏了数据库怎么
- 彩 * 像转换为灰度图像第一种方式通过 imread 读取图像的时候直接设置参数为 0 ,自动转换彩 * 像为灰度图像第二种方式,可以通过 sp
- 1.合并1.1 结构合并将两个结构相同的数据合并1.1.1 concat函数函数配置:concat([dataFrame1, dataFra
- 时间戳的问题我们的微博应用的一个忽略了很久的问题就是日间和日期的显示。直到现在,我们在我们的User和Post对象中使用Pyth
- scrapy 框架结构思考scrapy 为什么是框架而不是库?scrapy是如何工作的?项目结构在开始爬取之前,必须创建一个新的Scrapy
- 那你也许会问及,怎样获取当前系统日期的最大时间值,如yyyy-MM-dd 23:59:59.997。 我们可以使用DATEADD函数,来实现
- $array=explode(separator,$string); $string=implode(glue,$array);使用和理解这
- 技巧1XmlNode.InnerText与.InnerXml的区别是:前者将内容中的<和>分别变成<和>,因此,希望