excel中如何对矩阵得对角线进行求和_实战 | 如何入门图卷积网络GCN

点击上方“计算机视觉life”,选择“星标”

快速获得最新干货

我们面对的很多数据其实是图(graph),图在生活中无处不在,如社交网络,知识图谱,蛋白质结构等。在2020年这个寒冬,窝在家里的小编终于打算入门GNN(Graph Neural Networks)中的分支:GCN(Graph Convolutional Networks)

6ff58664368e5dbdb698f06623aecf78.png

图的概念

对于图,我们习惯上用表示。这里是图中节点的集合,而为边的集合,这里记图的节点数为。一个中有3个比较重要的矩阵:

  • 邻接矩阵:adjacency matrix,用来表示节点间的连接关系,这里我们假定是0-1矩阵;
  • 度矩阵:degree matrix,每个节点的度指的是其连接的节点数,这是一个对角矩阵,其中对角线元素;
  • 特征矩阵:用于表示节点的特征,,这里F是特征的维度;

数学表示是比较抽象的,下面是一个实例:

c596527f7a48425e07ddf3086e7462df.png图1 图以及邻接矩阵(来源:stanford cs224w)

注意左图是无向图,而右图是有向图,前者的邻接矩阵是对称的,而后者是不对称的。

相比图像和文本,图这种拓扑结构是较复杂的:任意的节点数以及节点间的复杂关系:

0249a2757e617011c5448803a67922ce.png图2 图与图像和文本的结构对比(来源:stanford cs224w)

这种复杂性给神经网络在图上的应用带来了一定困难,但是我们依然有解决办法。

学习新特征

深度学习中最重要的是学习特征:随着网络层数的增加,特征越来越抽象,然后用于最终的任务。对于图任务来说,这点同样适用,我们希望深度模型从图的最初始特征出发学习到更抽象的特征,比如学习到了某个节点的高级特征,这个特征根据图结构融合了图中其他节点的特征,我们就可以用这个特征用于节点分类或者属性预测。那么图网络就是要学习新特征,用公式表达就是:

这里k指的是网络层数,就是网络第k层的特征,其中。那么现在的问题是如何学习,我们可以从CNN中得到启发:

db0dd2bbe554783217fbc00b738531a1.png图3 CNN与图学习类比(来源:stanford cs224w)

这是一个简单的3x3卷积层,每个新特征的学习是这样的:对其领域(3x3局部空间)的特征进行变换(),然后求和()。类比到图学习上,每个节点的新特征可以类似得到:对该节点的邻域节点特征进行变换,然后求和。用公式表达就是:

这里的是学习权重,维度为,而是激活函数,比如是ReLU,这是神经网路的基本单元。上述公式其实就是对领域内节点特征求和,这里:

其中邻接矩阵是0-1矩阵,当节点j与节点i连接时,,所以节点i的新特征就是其邻接节点的特征和。

其实我们可以将上述学习分成三个部分:

  • 变换(transform):对当前的节点特征进行变换学习,这里就是乘法规则(Wx);
  • 聚合(aggregate):聚合领域节点的特征,得到该节点的新特征,这里是简单的加法规则;
  • 激活(activate):采用激活函数,增加非线性。

其实这就算是图卷积(graph convolution)了,首先这里的权重是所有节点共享的,类比于CNN中的参数共享;另外可以将节点的邻居节点看成感受野,随着网络层数的增加,感受野越来越大,即节点的特征融合了更多节点的信息。直观的图卷积示意图如下:

2530d14731f85a374ac4244d1a8af54a.png图4 图卷积的示意图 (来源:https://www.jianshu.com/p/2fd5a2454781)

图卷积

上述的加法规则只是一个简单实现,其存在两个问题:首先在计算新特征时没有考虑自己的特征,这肯定是个重大缺陷;另外采用加法规则时,对于度大的节点特征越来越大,而对于度小的节点却相反,这可能导致网络训练过程中梯度爆炸或者消失的问题。

针对第一个问题,我们可以给图中每个节点增加自连接,实现上可以直接改变邻接矩阵:

针对第二个问题,我们可以对邻接矩阵进行归一化,使得的每行和值为1,在实现上我们可以乘以度矩阵的逆矩阵:,这里的度矩阵是更新后重新计算的。这样我们就得到:

相比加法规则,这种聚合方式其实是对领域节点特征求平均,这里:

由于,所以这种聚合方式其实就是求平均,对领域节点的特征是求平均值,这样就进行了归一化,避免求和方式所造成的问题。

更进一步地,我们可以采用对称归一化来进行聚合操作,这就是论文1中所提出的图卷积方法:

这种新的聚合方法不再是单单地对邻域节点特征进行平均,这里:

可以看到这种聚合方式不仅考虑了节点i的度,而且也考虑了邻居节点j的度,当邻居节点j的度较大时,而特征反而会受到抑制。

这种图卷积方法其实谱图卷积的一阶近似(first-order approximation of spectral graph convolutions),关于更多的数学证明比较难理解,这里不做展开,详情可见论文。

定义了图卷积,我们只需要将图卷积层堆积起来就构成了图卷积网络GCN:

dd44a2ae4b4d9c43e5a42d6451ec86c5.png图5 GCN示意图

其实图神经网路(GNN,Graph Neural Network)是一个庞大的家族,如果按照分类,其可以分成以下类型:

d76a5bb76483e03040f651a6c0ec66e2.png图6 GNN分类

可以看到GCN只是其中的一个很小的分支,我们上面所述的GCN其实是属于谱图卷积。更多关于GNN的学习,可以阅读这三篇综述文章:

  • Graph Neural Networks: A Review of Methods and Application https://arxiv.org/pdf/1812.08434.pdf
  • A Comprehensive Survey on Graph Neural Networks https://arxiv.org/pdf/1901.00596.pdf
  • Deep Learning on Graphs: A Survey https://arxiv.org/pdf/1812.04202.pdf

GCN的PyTorch实现

虽然GCN从数学上较难理解,但是它的实现是非常简单的,值得注意的一点是一般情况下邻接矩阵是稀疏矩阵,所以我们在实现矩阵乘法时,采用稀疏运算会更高效。这里我们参考论文作者的官方实现。首先是图卷积层的实现:

    import torch
import torch.nn as nn


class GraphConvolution(nn.Module):
"""GCN layer"""

def __init__(self, in_features, out_features, bias=True):
super(GraphConvolution, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.weight = nn.Parameter(torch.Tensor(in_features, out_features))
if bias:
self.bias = nn.Parameter(torch.Tensor(out_features))
else:
self.register_parameter('bias', None)

self.reset_parameters()

def reset_parameters(self):
nn.init.kaiming_uniform_(self.weight)
if self.bias is not None:
nn.init.zeros_(self.bias)

def forward(self, input, adj):
support = torch.mm(input, self.weight)
output = torch.spmm(adj, support)
if self.bias is not None:
return output + self.bias
else:
return output

def extra_repr(self):
return 'in_features={}, out_features={}, bias={}'.format(
self.in_features, self.out_features, self.bias is not None
)

对于GCN,只需要将图卷积层堆积起来就可以,这里我们实现一个两层的GCN:

class GCN(nn.Module):
"""a simple two layer GCN"""
def __init__(self, nfeat, nhid, nclass):
super(GCN, self).__init__()
self.gc1 = GraphConvolution(nfeat, nhid)
self.gc2 = GraphConvolution(nhid, nclass)

def forward(self, input, adj):
h1 = F.relu(self.gc1(input, adj))
logits = self.gc2(h1, adj)
return logits

这里的激活函数采用ReLU,后面我们将用这个网络实现一个图中节点的半监督分类任务。

半监督分类实例

这里给出的是GCN论文中的一个半监督分类任务,官方代码也给出这个任务。我们要处理的数据集是cora数据集,该数据集是一个论文图,共2708个节点,每个节点都是一篇论文,所有样本点被分为7类别:

Case_Based, Genetic_Algorithms, Neural_Networks, Probabilistic_Methods, Reinforcement_Learning, Rule_Learning, Theory

每篇论文都由一个1433维的词向量表示,即节点特征维度为1433。词向量的每个特征都对应一个词,取0表示该特征对应的词不在论文中,取1则表示在论文中。每篇论文都至少引用了一篇其他论文,或者被其他论文引用,这是一个连通图,不存在孤立点。

这里的任务是给定图中某些节点的类别,然后训练一个网络能够预测其它节点标签,所以这里一个半监督学习任务。我们建立一个两层GCN来解决这个问题:

从结构上看,中间层用于提出特征,而最后一层的节点特征用于分类任务(送入softmax,计算交叉熵):

00dc1a675c7b6a9980847788882fdb29.png图7 两层GCN用于分类任务

数据的提取,论文官方实现已经给出,我们只需要load就可以:

# https://github.com/tkipf/pygcn/blob/master/pygcn/utils.py
adj, features, labels, idx_train, idx_val, idx_test = load_data(path="./data/cora/")

值得注意的有两点,一是论文引用应该是单向图,但是在网络时我们要先将其转成无向图,或者说建立双向引用,我发现这个对模型训练结果影响较大:

# build symmetric adjacency matrix
adj = adj + adj.T.multiply(adj.T > adj) - adj.multiply(adj.T > adj)

另外官方实现中对邻接矩阵采用的是普通均值归一化,当然我们也可以采用对称归一化方式:

def normalize_adj(adj):
"""compute L=D^-0.5 * (A+I) * D^-0.5"""
adj += sp.eye(adj.shape[0])
degree = np.array(adj.sum(1))
d_hat = sp.diags(np.power(degree, -0.5).flatten())
norm_adj = d_hat.dot(adj).dot(d_hat)
return norm_adj

这里我们只采用图中140个有标签样本对GCN进行训练,每个epoch计算出这些节点特征,然后计算loss:

    loss_history = []
val_acc_history = []
for epoch in range(epochs):
model.train()
logits = model(features, adj)
loss = criterion(logits[idx_train], labels[idx_train])

train_acc = accuracy(logits[idx_train], labels[idx_train])

optimizer.zero_grad()
loss.backward()
optimizer.step()

val_acc = test(idx_val)
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()))

只需要训练200个epoch,我们就可以在测试集上达到80%左右的分类准确,GCN的强大可想而知:de4f6e6873bb040865f98775bb4a8b33.png图8 训练收敛曲线

结语

GCN只是GNN中的冰山一角,这可能连入门都不算,但是千里之行始于足下。

参考

  1. Semi-Supervised Classification with Graph Convolutional Networks https://arxiv.org/abs/1609.02907
  2. How to do Deep Learning on Graphs with Graph Convolutional Networks https://towardsdatascience.com/how-to-do-deep-learning-on-graphs-with-graph-convolutional-networks-7d2250723780
  3. Graph Convolutional Networks http://tkipf.github.io/graph-convolutional-networks
  4. Graph Convolutional Networks in PyTorch https://github.com/tkipf/pygcn
  5. 回顾频谱图卷积的经典工作:从ChebNet到GCN https://www.jianshu.com/p/2fd5a2454781
  6. 图数据集之cora数据集介绍- 用pyton处理 - 可用于GCN任务 https://blog.csdn.net/yeziand01/article/details/93374216
交流群

欢迎加入公众号读者群一起和同行交流,目前覆盖SLAM、三维视觉、传感器自动驾驶、计算摄影、检测、分割、识别、医学影像、GAN算法竞赛等微信群,请扫描下面微信号加群,备注:”昵称+学校/公司+研究方向“,例如:”张三 + 上海交大 + 视觉SLAM“。请按照格式备注,否则不予通过。添加成功后会根据研究方向邀请进入相关微信群。请勿在群内发送广告,否则会请出群,谢谢理解~

0cca102c2b8cd03e61030321980c63b8.png

投稿、合作也欢迎联系:simiter@126.com

a74863a54dcfd363ab3d1414d0f6f4c9.png

长按关注计算机视觉life

推荐阅读

最全综述 | 医学图像处理

最全综述 | 图像分割算法

最全综述 | 图像目标检测

目标检测技术二十年综述

综述 | CVPR2019目标检测方法进展 参加 CVPR 2019 技术见闻总结

现在投身于计算机视觉是否明智?

如何激怒一个自动驾驶(无人驾驶、智能汽车)爱好者?

原来CNN是这样提取图像特征的。。。

计算机视觉方向简介 | 人体骨骼关键点检测综述

计算机视觉方向简介 | 人脸识别中的活体检测算法综述

计算机视觉方向简介 | 目标检测最新进展总结与展望

计算机视觉方向简介 | 人脸表情识别

计算机视觉方向简介 | 人脸颜值打分

计算机视觉方向简介 | 深度学习自动构图

计算机视觉方向简介 | 基于RGB-D的3D目标检测

计算机视觉方向简介 | 人体姿态估计

给优秀的自己点个赞   4323bc6a7337d4213cbb060c8f167858.gif

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用Pytorch Geometric (PyG)库训练卷积网络(GCN)进行骨骼识别的代码示例: ```python import torch import torch.nn.functional as F from torch_geometric.datasets import Human36M from torch_geometric.nn import GCNConv # 加载数据集 train_dataset = Human36M(root='/path/to/dataset', train=True) test_dataset = Human36M(root='/path/to/dataset', train=False) # 定义卷积网络模型 class GCN(torch.nn.Module): def __init__(self): super(GCN, self).__init__() self.conv1 = GCNConv(54, 128) # 第一层GCN卷积 self.conv2 = GCNConv(128, 128) # 第二层GCN卷积 self.fc1 = torch.nn.Linear(128, 64) # 全连接层 self.fc2 = torch.nn.Linear(64, 32) # 全连接层 self.fc3 = torch.nn.Linear(32, 17) # 全连接层 def forward(self, x, edge_index): # x: 特征向量 # edge_index: 的邻接矩阵 x = F.relu(self.conv1(x, edge_index)) # GCN卷积层1 x = F.relu(self.conv2(x, edge_index)) # GCN卷积层2 x = F.relu(self.fc1(x)) # 全连接层1 x = F.relu(self.fc2(x)) # 全连接层2 x = self.fc3(x) # 全连接层3, 输出17维向量 return x # 实例化模型 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = GCN().to(device) # 定义损失函数和优化器 criterion = torch.nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 训练模型 model.train() for epoch in range(50): train_loss = 0.0 for batch in train_dataset: x, edge_index, y = batch.x.to(device), batch.edge_index.to(device), batch.y.to(device) optimizer.zero_grad() out = model(x, edge_index) loss = criterion(out, y) loss.backward() optimizer.step() train_loss += loss.item() * batch.num_graphs train_loss /= len(train_dataset) print('Epoch: {:03d}, Train Loss: {:.7f}'.format(epoch, train_loss)) # 测试模型 model.eval() test_loss = 0.0 for batch in test_dataset: x, edge_index, y = batch.x.to(device), batch.edge_index.to(device), batch.y.to(device) out = model(x, edge_index) loss = criterion(out, y) test_loss += loss.item() * batch.num_graphs test_loss /= len(test_dataset) print('Test Loss: {:.7f}'.format(test_loss)) ``` 在这个示例,我们使用了Human3.6M数据集进行骨骼识别。该数据集包含了大量的人体骨骼姿态数据,每个姿态由17个关键点组成。我们使用GCN对每个关键点进行分类,输出17维向量,每一维代表一个关键点的分类得分。我们使用均方误差(MSE)作为损失函数,使用Adam优化器进行优化。在训练过程,我们使用了50个epoch进行训练,每个epoch遍历整个训练集。在测试过程,我们仅仅计算了测试集上的损失,没有进行预测。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值