传统的网状结构
初学深度学习时候,常见的经典结构比如CNN、RNN
对于CNN来说,他的核心在于它的kernel,kernel是一个个小窗口,在图片上平移,通过卷积的方式来提取特征。这里的关键在于图片结构上的平移不变性:一个小窗口无论移动到图片的哪一个位置,其内部的结构都是一模一样的,因此CNN可以实现参数共享。这就是CNN的精髓所在。
对于RNN来说,它的对象是自然语言这样的序列信息,是一个一维的结构,RNN就是专门针对这些序列的结构而设计的,通过各种门的操作,使得序列前后的信息互相影响,从而很好地捕捉序列的特征。
但是他们处理的对象都是属于欧式空间的数据。然而对于一些不规则的结构,例如图形结构(拓扑结构)、树形结构,比如社交网络、化学分子结构、知识图谱等等,这样图的结构一般来说是十分不规则的,可以认为是无限维的一种数据,所以它没有平移不变性。每一个节点的周围结构可能都是独一无二的,这种结构的数据,就让传统的CNN、RNN瞬间失效。就没有很好的效果了。
GCN结构
假设我们手头有一批图数据,其中有N个节点(node),每个节点都有自己的特征,我们设这些节点的特征组成一个N×D维的矩阵X,然后各个节点之间的关系也会形成一个N×N维的矩阵A,也称为邻接矩阵(adjacency matrix)。X和A便是我们模型的输入。
GCN也是一个神经网络层,它的层与层之间的传播方式是:
这个公式中:
A波浪=A+I,I是单位矩阵
D波浪是A波浪的度矩阵(degree matrix),公式为
H是每一层的特征,对于输入层的话,H就是X
σ是非线性激活函数
实例展示
# -*- coding: utf-8 -*-
# 引入库函数
import dgl
import torch
import torch.optim as optim
import matplotlib.pyplot as plt
import torch.nn.functional as F
import torch.nn as nn
import dgl.function as fn
# device设备设定先使用cuda再用cpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
class GCNLayer(nn.Module):
def __init__(self, in_feats, out_feats, bias=True):
super(GCNLayer, self).__init__()
self.weight = nn.Parameter(torch.Tensor(in_feats, out_feats))
if bias:
self.bias = nn.Parameter(torch.zeros(out_feats))
else:
self.bias = None
self.reset_parameter()
def reset_parameter(self):
nn.init.xavier_uniform_(self.weight)
def forward(self, g, h):
with g.local_scope():
h = torch.matmul(h, self.weight)
g.ndata['h'] = h * g.ndata['norm']
g.update_all(message_func=fn.copy_u('h', 'm'),
reduce_func=fn.sum('m', 'h'))
h = g.ndata['h']
h = h * g.ndata['norm']
if self.bias is not None:
h = h + self.bias
return h
# GCN模型
class GCNModel(nn.Module):
def __init__(self, in_feats, h_feats, num_classes, bias=True):
super(GCNModel, self).__init__()
self.conv1 = GCNLayer(in_feats, h_feats, bias)
self.conv2 = GCNLayer(h_feats, num_classes, bias)
def forward(self, g, in_feat):
h = self.conv1(g, in_feat)
h = F.relu(h)
h = self.conv2(g, h)
return h
# 画图功能
def drawPlot(heights, fname, ylabel, legends=None):
# fname:save file name
plt.figure(figsize=(6, 5)) # 设置画布大小
x = [i for i in range(1, len(heights[0]) + 1)]
# 绘制训练集和测试集上的loss变化曲线子图
plt.xlabel("epoch")
plt.ylabel(ylabel)
for i in range(len(heights)):
plt.plot(x, heights[i])
if legends:
plt.legend(legends)
plt.savefig("images/{}".format(fname))
plt.show()
# 训练函数
def train(g, model, optimizer, loss_fn, epochs):
best_val_acc, best_test_acc = 0, 0
# 得到数据集的特征和标签
features, labels = g.ndata['feat'], g.ndata['label']
train_mask, val_mask, test_mask = g.ndata['train_mask'], g.ndata[
'val_mask'], g.ndata['test_mask']
train_acc_list, val_acc_list, test_acc_list = [], [], []
for e in range(epochs):
logits = model(g, features) # (N, label_nums)
pred = logits.argmax(1)
loss = loss_fn(logits[train_mask], labels[train_mask])
# cal trian acc
train_acc = (pred[train_mask] == labels[train_mask]).float().mean()
# cal val acc
val_acc = (pred[val_mask] == labels[val_mask]).float().mean()
# cal test acc
test_acc = (pred[test_mask] == labels[test_mask]).float().mean()
train_acc_list.append(train_acc.item())
val_acc_list.append(val_acc.item())
test_acc_list.append(test_acc.item())
# save result based on valid dataset
if best_val_acc < val_acc:
best_val_acc = val_acc
best_test_acc = test_acc
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (e + 1) % 10 == 0:
print(
'Epoch {}, loss: {:.3f}, train acc: {:.3f}, val acc: {:.3f} (best {:.3f}), test acc: {:.3f} (best {:.3f})'
.format(e + 1, loss, train_acc, val_acc, best_val_acc,
test_acc, best_test_acc))
drawPlot([train_acc_list, val_acc_list, test_acc_list], "accuracy.png",
"Acc", ["train", "val", "test"])
if __name__ == "__main__":
# 设置超参数
epochs = 200 # 训练轮次
hidden_size = 32 # 隐藏层结点个数
lr = 0.01 # 学习率
weight_decay = 5e-4 # 下降权重
# 加载数据集
dataset = dgl.data.CoraGraphDataset(raw_dir="../Datasets/DGL/")
g = dataset[0] # graph图状结构 有2708个结点 10556条边
# 删除和添加自闭环
g = dgl.remove_self_loop(g)
g = dgl.add_self_loop(g)
# degs保存的是图的邻接矩阵
degs = g.out_degrees().float()
# 开根号
norm = torch.pow(degs, -0.5)
# 把两点不相连的位置都填成0
norm[torch.isinf(norm)] = 0
g.ndata['norm'] = norm.unsqueeze(1)
# 定义模型
model = GCNModel(in_feats=g.ndata['feat'].shape[1],
h_feats=hidden_size,
num_classes=dataset.num_classes)
# 将数据都部署到gpu或者cpu上
if torch.cuda.is_available():
g = g.to(device)
model = model.to(device)
# 优化器
optimizer = optim.Adam(model.parameters(),
lr=lr,
weight_decay=weight_decay)
# 损失函数
loss_fn = nn.CrossEntropyLoss()
# 训练
train(g, model, optimizer, loss_fn, epochs)