前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >图神经网络整理

图神经网络整理

作者头像
算法之名
发布2022-05-06 10:30:08
5200
发布2022-05-06 10:30:08
举报
文章被收录于专栏:算法之名算法之名算法之名

关于图的概念可以参考图论整理 ,这里不再赘述。

图神经网络Graph neural networks(GNNs)是深度学习在图领域的基本方法,它既不属于CNN,也不属于RNN。CNN和RNN能做的事情,GNN都能做。CNN、RNN不能做的事情,GNN也能做。

在上图的左边,我们用不同的节点和有关系的边会构成一个图。在右侧的图上,我们会看到一系列的数字,对于一个节点,我们用N来表示,对于边用E来表示。而在右边出现的这一串数字,我们用X(属性、特征)来表示。属性或者是特征在不同的领域有不同的定义或者是方式,比如在CNN领域,我们会用各种各样的卷积网络,经过若干层得到一个feature map,这些feature map就是特征。在RNN领域,我们将文本转化为词向量(embedding),那么这些词向量就是特征。而在GNN这里,它都可以代替。所以可以有万物皆GNN。

信息传递(Message Passing)

如果节点和边的关系完整,那么图就可以在节点之间进行信息传递。这个过程也称为邻居间的聚合(Neighborhood Aggregation),在上图中,某一个参考节点(Reference Node)周边的邻居,都通过边的指向,向该节点进行信息传递。

传递过去之后,都会在参考节点中发生一次信息的聚合。信息的聚合有很多种,在GCNN中使用加权平均的方式来聚合。通过邻居节点的聚合,我们把信息全部收集了起来,再完成一次更新,那么这个参考节点的特征就发生了一次变化。这个是图神经网络核心的概念。

图任务(Graph Tasks)

上图是一个节点的分类任务,在左边的部分,有些节点有颜色,有些节点没颜色。在训练的过程中,我们希望用有颜色的节点(归类过的节点)和边的连接关系来推导出没有颜色的节点属于什么颜色。比如在推荐系统中,面对不同种类兴趣爱好的用户,随着他们关系网络的建立,我们对其需要进行精准推送,就需要去进行该种图任务的建立。

关系预测任务,在上图的左边的节点间有些是实线边,有些是虚线边。我们希望知道这些虚线边能否成为实线边。比如在同样作为音乐迷的一群人,有些是周杰伦迷,有些是张学友迷,但是他们彼此之间是否有关系,这个不得而知。此时我们想知道他们是否有其他彼此的共同偶像来确定他们是否存在关系。

GNN为什么难

对于CNN来说,无论是图像分类,目标检测,语义分割还是姿态检测,输入的都是一副图像

那么图像都是由像素组成的,每两个相邻像素之间的距离是相等的,它们的排列是有序的,网格化的。

对于RNN来说,每一段话都是由一个一个字组成的,它依然是有序的

但是对于GNN来说

用GNN的眼光来看CNN和RNN简直就是弟弟,GNN所处理的不再是这种规则的欧拉数据(Euclidean data),而是上图中左边这种完全无序的数据。

每一个GNN的图没有一个固定的格式,虽然上面的两个图的邻接矩阵是一样的,但是考虑到边的位置,那么它们就是不一样的图。

图神经网络很难可视化,它不像图像或者声音那样有可以确定的样子和波形,过于抽象,错综复杂。

GNN可以很方便处理关系(人际关系等)、同时可以把很多复杂的问题简化成一个比较简单的表示,或者是从其他视角上来进行转换。比如说推荐系统、知识图谱。

CNN和GNN的对比

从本质来说,CNN和GNN都是聚合邻域信息的运算,但是它们作用的对象是完全不同的。

上图的左边是GCN的某一层的数学表示,CNN相较于GCN中的卷积计算,它们最大的区别是CNN没有显式的描述邻接矩阵,但是实际计算的时候依然需要数据之间的关系。

感受野,从模型的层面来说,感受野会随着模型层数的增加而变大,每多一层卷积的运算,中间一层就能更多的融合进更外面一层的信息。在a图的CNN中的中间层是一个3*3的卷积核,往下扩大就是一个5*5的卷积核,再往下扩大的话可以扩大到7*7、9*9。类似的b图的GCN中,在上面的一层,我们可以从中心节点开始融合,先融合一阶邻居,随着层数的增加,可以扩大到二阶邻居。比方说老师回答了同学a的问题,那么老师和同学a就构成了一阶邻居,那么同学a弄懂之后又告诉了同学b,老师和同学b并没有发生直接关系,那么老师和同学b就构成了二阶邻居。所以在GCN中,感受野也会随着卷积层的增加而增大。

上图的a图和b图展示了CNN的两大任务,一个是图像分类,一个是图像分割。c图是不同的人的大脑脑波信号,虽然它们大致一样,但是不同的人的脑波图结构中的节点特征不一样,通过这些不一样的特征,也可以对正常人和患病的人进行一个分类。在d图中,可以对图结构中的每一个节点也可以进行分类,我们可以把每一个节点类比于图像分割中每一个像素的分类。

GNN的应用

一切皆是图

图神经网络可以应用于物理上,也可以应用于化学上或者生物上。

上图是一个目标检测的场景,当我们对图像中的目标进行目标框检测出来之后,我们想分析目标之间的关系。比如说图像中有人骑在马上,狗仰望着人,人俯视着狗等等这些关系。我们将图像中的目标当作节点,目标之间的关系当作边,通过这样的方式来构成一张图(Graph)。场景图可以将关系非常复杂的图像简化成一个关系非常明确的图。场景图有很多很多的应用,如图像合成、图像检索、视觉推理等。

这个是在文字上的应用,这个小猫很可爱。对于每一个文字我们可以构成一个节点,通过每一个节点研究每一个文字之间的关系来构成一个图。通过此来研究这句话每个单词的内容。

社交网络,代表着人或组织之间的社会关系,比如上图展示了一个在线的社交用户的关注网络,用户为节点,用户之间的关系是否关注作为边。我们可以研究用户的重要性排名,或者是否给某个用户推荐另外一个用户等。当然我们可以不光把人作为节点还可以把很多媒体对象补充到社交网络当中,比如段视频、小故事等等,都可以作为一个节点,然后构成一个新的图,称之为异构图。

这是一个属性图,张三作为一个人的节点,他属于某一个公司(组织节点),张三爱好科技(话题节点),张三和李四(用户节点)是朋友,这个就叫异构图。

交通网络,每一个站点作为节点,站与站是否联通作为边来构成一个图。

这是一个电子商务的结构图,业务数据可以用用户、商品等来作为节点来描述。它们的关系比较复杂,比方说用户可以浏览一个商品,可以收藏一个商品,可以购买一个商品,那么这边就可以进行分类,每个边代表不同的属性。在推荐系统中,用户与商品之间的关系反映了用户的消费偏好。

图卷积神经网络(Graph Convolution Networks)

GCN是一种可以直接作用于图上的卷积神经网络,它能够提取图的特征。GCN可以解决图中节点的分类问题(node classification)。在上图中有5个节点,谁和谁相连是知道的。每一个节点都有一串数字,对应图的特征。我们还知道红色和蓝色的节点各属于一类。还有2个节点,我们不知道它们属于哪一类。

我们再来看一下这两张图,对于这个公式

,如果去掉A',F是特征,可以用x来代替就是Z=σ(Wx+b),那么它就是一个CNN中的神经元的计算公式,而这个A‘是图的结构。其实对于CNN来说,也可以加上这个A',但由于每一个图像像素彼此之间的联系都是一样的,所以A'可以省略;但是对于GCN来说就不一样了,因为每个图结构节点彼此之间的连接都不一样。

GCN最后的输出是这个样子的,l是层的意思,

代表下一层的输出,其中

代表的是A',

是当前层。

是可训练参数,至于b就不要了。

在上图中,我们已知了几个节点和它们的特征以及所有蓝色节点的label,而绿色节点的标签是未知的,我们将要判别类别的节点(图中的绿色节点)周围的邻居节点的特征全部汇聚到该节点以及包含它自己的特征计算一个平均值,得到了一组新的特征值

,再将这组新的特征值送入到一个类似CNN的神经网络中,最后输出一个二分类的概率值,通过这个概率值来判定该节点属于哪个分类。

对于GCN的这个神经网络,我们只能说它是类似于CNN的卷积神经网络,但它们并不一样。第一步,对于原始图结构的每一个节点,我们都会进行一次邻居节点以及其本身节点的特征的平均值,也就是说这个平均值的数量是有多少个节点就有多少个平均值。此时我们得到的新的特征图的结构跟原始特征图是一样的,只不过每一个节点的特征值不一样。然后再来一遍。

GCN中图的表示

在图论中我们知道,我们可以用邻接矩阵和邻接列表来表示图,邻接列表是一种类似于链表的数据结构。

这是一个有向无权图,它用邻接列表可以表示为

但是这种邻接列表是一种计算机数据结构的表达方式,不是一种数学表达,所以我们在GCN中真正要使用的只有邻接矩阵。邻接矩阵其实就是一种one-hot编码

如果在邻接矩阵中,1的数量比较少,我们称为稀疏图,反之则称为稠密图。如果全是1,则称为完全稠密图。

度矩阵(Degree Matrix)

度矩阵也是一个n*n,但是只有主对角线上有值的图的矩阵。度指的是无向图相邻顶点的边数,当然在有向图中,我们有出度和入度。

上面的这个无向图的度矩阵为

这里需要说明的是,在无向图中如果存在自环边,则度数需要加1,因为它包含了自己的出度和入度,所以1这个节点的度数为4。

消息传递(Message Passing)

在上图中,我们如何判定A和B节点的关系呢?它们都在一个图中,并且经过了千丝万缕的联系。

在上面的图结构中,我们知道了它的邻接矩阵、度矩阵以及每一个节点的特征(3*1)。第一步,我们需要更新邻接矩阵。

这里的A是邻接矩阵,λI是单位矩阵,

是更新后的邻接矩阵。

因为原始的邻接矩阵只有邻居节点的信息,没有自己的信息,这样会造成信息丢失(特征的丢失),所以这里需要恢复自己的信息,也就是加上一个单位矩阵。第二步,更新度矩阵。

这里也是原度矩阵加上一个单位矩阵,得到新的度矩阵

。然后对新的度矩阵取逆

神经网络对输入的数据规模特别敏感,一般来说我们希望对所有的向量进行归一化处理,就是在本身的矩阵中乘以一个对角线矩阵。这样就可以减小规模。从数学角度上来说,要根据节点的度数对诸多向量来减少规模,度矩阵本身就是一个对角线矩阵,本身也反映了图的结构。此时我们直接对度矩阵取逆再做归一化操作。

在上图中,我们将新度矩阵的逆

与新的邻接矩阵

相乘,再乘以特征X,就有

在这里,我们将

作为

的一个常数因子(Scale Factor),虽然新邻接矩阵

是一个比较复杂,颇具规模的矩阵,但是新度矩阵的逆

是一个对角矩阵,除了对角线有数以外,其他都是0,这样它们相乘后,实际上就是降低了数据的规模。根据矩阵乘法的性质,它相当于对新邻接矩阵

的每一行做了一次归一化操作。但是对新邻接矩阵

的每一列却没有处理,这时可以再乘以一个

这样就有了

对新邻接矩阵

的每一列做了一次归一化处理。这样就同时对新邻接矩阵

的行和列都做了归一化处理。由于前后都乘了一次

,齐次化了两次,为了进一步减小规模,我们对

进行开方,上式就变成了

这就是

的由来,而A'就是

由于是做分类,最终我们需要再套一个softmax。

同样,它的损失函数跟CNN是一样的,为交叉熵(cross-entropy error)损失函数

GCN的代码基础

先从最简单建图开始

import dgl
import torch
import networkx as nx
import matplotlib.pyplot as plt

if __name__ == '__main__':

    g = dgl.DGLGraph()
    # 建立一个10个节点的图
    g.add_nodes(10)
    # 建立一个从1到3指向0的有向边
    for i in range(1, 4):
        g.add_edge(i, 0)
    # 用列表的方法建立从4到6指向0的有向边
    src = list(range(4, 7))
    dst = [0] * 3
    g.add_edges(src, dst)
    # 用张量的形式建立从7到9指向0的有向边
    src = torch.tensor([7, 8, 9])
    g.add_edges(src, 0)
    nx.draw(g.to_networkx(), with_labels=True)
    plt.show()

运行结果

给节点赋特征值

x = torch.rand(10, 3)
print(x)
# 直接将x设为图节点的特征
g.ndata['x'] = x
print(g.nodes[:].data['x'])

运行结果

tensor([[0.6127, 0.9122, 0.8311],
        [0.5199, 0.1088, 0.8050],
        [0.5773, 0.6138, 0.5709],
        [0.1529, 0.4860, 0.2772],
        [0.7792, 0.6176, 0.2838],
        [0.8339, 0.7008, 0.7178],
        [0.6055, 0.8056, 0.2257],
        [0.1695, 0.1825, 0.0911],
        [0.5644, 0.9415, 0.6474],
        [0.9301, 0.2012, 0.0065]])
tensor([[0.6127, 0.9122, 0.8311],
        [0.5199, 0.1088, 0.8050],
        [0.5773, 0.6138, 0.5709],
        [0.1529, 0.4860, 0.2772],
        [0.7792, 0.6176, 0.2838],
        [0.8339, 0.7008, 0.7178],
        [0.6055, 0.8056, 0.2257],
        [0.1695, 0.1825, 0.0911],
        [0.5644, 0.9415, 0.6474],
        [0.9301, 0.2012, 0.0065]])

修改特征值

# 修改特征值
g.nodes[0].data['x'] = torch.zeros(1, 3)
g.nodes[[1, 2, 3]].data['x'] = torch.ones(3, 3)
g.nodes[torch.tensor([4, 5, 6])].data['x'] = torch.zeros((3, 3))
print(g.nodes[:].data['x'])

运行结果

tensor([[0.0000, 0.0000, 0.0000],
        [1.0000, 1.0000, 1.0000],
        [1.0000, 1.0000, 1.0000],
        [1.0000, 1.0000, 1.0000],
        [0.0000, 0.0000, 0.0000],
        [0.0000, 0.0000, 0.0000],
        [0.0000, 0.0000, 0.0000],
        [0.1695, 0.1825, 0.0911],
        [0.5644, 0.9415, 0.6474],
        [0.9301, 0.2012, 0.0065]])
  • 同构图
import dgl
import torch
import networkx as nx
import matplotlib.pyplot as plt

if __name__ == '__main__':

    u = torch.tensor([0, 0, 0, 1])
    v = torch.tensor([1, 2, 3, 3])
    g = dgl.graph((u, v))
    nx.draw(g.to_networkx(), with_labels=True)
    plt.show()

    g.ndata['x'] = torch.ones(g.num_nodes(), 3)
    print(g.nodes[:].data['x'])

运行结果

tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])
  • 异构图
import dgl
import torch

if __name__ == '__main__':

    graph_data = {
        ('user0', 'follows', 'user1'): (torch.tensor([0, 1]), torch.tensor([1, 2])),
        ('user0', 'plays', 'game0'): (torch.tensor([0, 2]), torch.tensor([2, 3])),
        ('user0', 'plays', 'game1'): (torch.tensor([0, 3]), torch.tensor([2, 3])),
        ('user1', 'plays', 'game1'): (torch.tensor([1]), torch.tensor([2])),
        ('user1', 'plays', 'game2'): (torch.tensor([3]), torch.tensor([2]))
    }
    # 构建异构图
    g = dgl.heterograph(graph_data)
    # 打印节点索引
    print(g.nodes('user0'))
    print(g.nodes('game2'))
    # 打印节点类型
    print('ntypes', g.ntypes)
    # 打印边类型
    print('etypes', g.etypes)
    # 设置节点特征
    g.nodes['user0'].data['hv'] = torch.ones(4, 2)
    # 打印节点特征
    print(g.nodes['user0'].data['hv'])

运行结果

tensor([0, 1, 2, 3])
tensor([0, 1, 2])
ntypes ['game0', 'game1', 'game2', 'user0', 'user1']
etypes ['follows', 'plays', 'plays', 'plays', 'plays']
tensor([[1., 1.],
        [1., 1.],
        [1., 1.],
        [1., 1.]])
  • GCN图神经网络
import dgl
import dgl.function as fn
import torch
import torch.nn as nn
import torch.nn.functional as F
from dgl.data import citation_graph as citegrh
import networkx as nx
import numpy as np
import time
import matplotlib.pyplot as plt

class NodeApplyModule(nn.Module):
    # 使用全连接层来更新节点特征
    def __init__(self, in_feats, out_feats, activation):
        super(NodeApplyModule, self).__init__()
        self.linear = nn.Linear(in_feats, out_feats)
        self.activation = activation

    def forward(self, nodes):
        h = self.linear(nodes.data['h'])
        if self.activation is not None:
            h = self.activation(h)
        return {'h': h}

class GCN(nn.Module):

    def __init__(self, in_feats, out_feats, activation):
        super(GCN, self).__init__()
        self.apply_mod = NodeApplyModule(in_feats, out_feats, activation)

    def forward(self, g, feature):
        # 给节点赋特征值
        g.ndata['h'] = feature
        # 在全部节点上执行消息传递并计算平均特征值
        g.update_all(msg, reduce)
        # 将所有的平均特征值(2708*1433)送入全连接网络
        g.apply_nodes(func=self.apply_mod)
        return g.ndata.pop('h')

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.gcn1 = GCN(1433, 16, F.relu)
        self.gcn2 = GCN(16, 7, None)

    def forward(self, g, features):
        # 2708*16
        x = self.gcn1(g, features)
        # 2708*7
        x = self.gcn2(g, x)
        return x

def load_cora_data():
    data = citegrh.load_cora()
    features = torch.FloatTensor(data.features)
    labels = torch.LongTensor(data.labels)
    train_mask = torch.BoolTensor(data.train_mask)
    test_mask = torch.BoolTensor(data.test_mask)
    g = data.graph
    g.remove_edges_from(nx.selfloop_edges(g))
    g = dgl.DGLGraph(g)
    g.add_edges(g.nodes(), g.nodes())
    print(features.size())
    nx.draw(g.to_networkx(), with_labels=True)
    plt.show()
    return g, features, labels, train_mask, test_mask

def evaluate(model, g, features, labels, mask):
    model.eval()
    with torch.no_grad():
        logits = model(g, features)
        logits = logits[mask]
        labels = labels[mask]
        _, indices = torch.max(logits, dim=1)
        correct = torch.sum(indices == labels)
        return correct.item() * 1.0 / len(labels)

if __name__ == '__main__':

    # 发送节点特征,用m来标识消息是源节点
    msg = fn.copy_src(src='h', out='m')
    # 邻居节点和当前节点特征平均值
    reduce = fn.mean(msg='m', out='h')
    net = Net()
    g, features, labels, train_mask, test_mask = load_cora_data()
    optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
    dur = []
    for epoch in range(1300):
        if epoch >= 3:
            t0 = time.time()
        logits = net(g, features)
        # 在softmax的基础上再进行一次log运算
        logp = F.log_softmax(logits, 1)
        # F.nll_loss不包含softmax的交叉熵函数
        # nn.CrossEntropyLoss为包含softmax的交叉熵函数
        loss = F.nll_loss(logp[train_mask], labels[train_mask])
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if epoch >= 3:
            dur.append(time.time() - t0)
        acc = evaluate(net, g, features, labels, test_mask)
        print("Epoch {:05d} | loss {:.4f} | Test Acc {:.4f} | Time(s) {:.4f}".format(
            epoch, loss.item(), acc, np.mean(dur)
        ))

运行结果

部分日志

NumNodes: 2708
  NumEdges: 10556
  NumFeats: 1433
  NumClasses: 7
  NumTrainingSamples: 140
  NumValidationSamples: 500
  NumTestSamples: 1000
torch.Size([2708, 1433])
...
Epoch 01282 | loss 0.0626 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01283 | loss 0.0625 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01284 | loss 0.0623 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01285 | loss 0.0622 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01286 | loss 0.0621 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01287 | loss 0.0619 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01288 | loss 0.0618 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01289 | loss 0.0617 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01290 | loss 0.0616 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01291 | loss 0.0614 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01292 | loss 0.0613 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01293 | loss 0.0612 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01294 | loss 0.0610 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01295 | loss 0.0609 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01296 | loss 0.0608 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01297 | loss 0.0607 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01298 | loss 0.0605 | Test Acc 0.7860 | Time(s) 0.0315
Epoch 01299 | loss 0.0604 | Test Acc 0.7860 | Time(s) 0.0315

回忆一般的CNN网络,我们只需要送一张图片到网络中去提取特征图(feature map),我们可以把图片理解成原始特征,而在图GCN网络中,我们不仅仅要将原始特征送进网络,还要带上图的结构才能进行前向运算。

logits = net(g, features)

经过这一步,系统会自动的去计算图的邻接矩阵、度矩阵,然后经过一系列的计算去获取图卷积神经网络的最终特征值再去做softmax进行分类

logp = F.log_softmax(logits, 1)

而在

# 发送节点特征,用m来标识消息是源节点
msg = fn.copy_src(src='h', out='m')
# 邻居节点和当前节点特征平均值
reduce = fn.mean(msg='m', out='h')

以及

def forward(self, g, feature):
    # 给节点赋特征值
    g.ndata['h'] = feature
    # 在全部节点上执行消息传递并计算平均特征值
    g.update_all(msg, reduce)
    # 将所有的平均特征值(2708*1433)送入全连接网络
    g.apply_nodes(func=self.apply_mod)
    return g.ndata.pop('h')

说明,此处是对每一个节点都获取周围邻居的特征以及自己的特征来计算一个平均特征值,再将这些平均特征值送入到全连接网络中。2708*1433指的是2708个节点,每个节点都有1433个特征。它经过了两次全连接层

self.gcn1 = GCN(1433, 16, F.relu)
self.gcn2 = GCN(16, 7, None)

第一次是将1433个神经元与16个神经元相连,并且带有激活函数。第二次是将16个神经元与7个神经元相连,因为做分类,就没有使用激活函数。但是这里需要注意的是,它依然是将2708*16个特征重新放入图节点中作为新的特征值,再进行一次消息传递计算平均值才送入全连接层的,这跟第一次是一样的。

当然我们也可以不写的这么复杂,使用dgl的内置图卷积GraphConv一步到位,它的作用跟上面的代码是一样的。

import dgl
import torch
import torch.nn as nn
import torch.nn.functional as F
from dgl.data import citation_graph as citegrh
import networkx as nx
import numpy as np
import time
import matplotlib.pyplot as plt
from dgl.nn.pytorch import GraphConv

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.gcn1 = GraphConv(1433, 16)
        self.gcn2 = GraphConv(16, 7)

    def forward(self, g, features):
        # 2708*16
        x = self.gcn1(g, features)
        x = torch.relu(x)
        # 2708*7
        x = self.gcn2(g, x)
        return x

def load_cora_data():
    data = citegrh.load_cora()
    features = torch.FloatTensor(data.features)
    labels = torch.LongTensor(data.labels)
    train_mask = torch.BoolTensor(data.train_mask)
    test_mask = torch.BoolTensor(data.test_mask)
    g = data.graph
    g.remove_edges_from(nx.selfloop_edges(g))
    g = dgl.DGLGraph(g)
    g.add_edges(g.nodes(), g.nodes())
    print(features.size())
    nx.draw(g.to_networkx(), with_labels=True)
    plt.show()
    return g, features, labels, train_mask, test_mask

def evaluate(model, g, features, labels, mask):
    model.eval()
    with torch.no_grad():
        logits = model(g, features)
        logits = logits[mask]
        labels = labels[mask]
        _, indices = torch.max(logits, dim=1)
        correct = torch.sum(indices == labels)
        return correct.item() * 1.0 / len(labels)

if __name__ == '__main__':

    net = Net()
    g, features, labels, train_mask, test_mask = load_cora_data()
    optimizer = torch.optim.Adam(net.parameters(), lr=1e-3)
    dur = []
    for epoch in range(1300):
        if epoch >= 3:
            t0 = time.time()
        logits = net(g, features)
        # 在softmax的基础上再进行一次log运算
        logp = F.log_softmax(logits, 1)
        # F.nll_loss不包含softmax的交叉熵函数
        # nn.CrossEntropyLoss为包含softmax的交叉熵函数
        loss = F.nll_loss(logp[train_mask], labels[train_mask])
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        if epoch >= 3:
            dur.append(time.time() - t0)
        acc = evaluate(net, g, features, labels, test_mask)
        print("Epoch {:05d} | loss {:.4f} | Test Acc {:.4f} | Time(s) {:.4f}".format(
            epoch, loss.item(), acc, np.mean(dur)
        ))

图时空网络

行为识别与分析(Human Acitivity Recognition)

行为识别与分析的目的是为了从未知的视频帧中识别出人体动作的类型,它是一种分类任务。

时空图卷积网络(Spatial Temporal Graph Convolution Networks)

时空图卷积网络简称ST-GCN,训练帧非常多,导致非常耗能,考虑计算力的话,并不是一种特别好的方法。但它的思想值得借鉴。它是基于一系列的骨架图来做分析。它的输入是一系列连续帧的图,然后进行姿态估计。姿态估计的介绍可以参考人体姿态检测概述 。通过姿态估计得到关键点,之后通过这一系列的关键点,最后对行为进行分类(Action Classification),在上图的最右边,我们看到跑的概率是最大的,我们就把这一系列的图像帧定义为"跑"。

  • ST-GCN

G = (V, E)

节点:人体关节点

边:有两种连接方法,一起构成了整个图的边

  1. 根据空间结构构建的人体骨骼连接,这是自然的边
  2. 在时间序列上,将相邻两帧的相同关节点连接起来,构成时序边

一般来说,图有三大任务:1、节点分类;2、图分类;3、边预测。

  • 分区策略(Partitioning Strategies)

分区策略是为了构造卷积运算,比如

对于一张骨骼帧,身体关键点是用蓝色点表示的,焦点的感受野是用范围为1的区域红色虚线表示。红色的点称为焦点。

进一步,我们将所有焦点的邻居节点以及焦点本身标记为相同颜色的节点,上图为绿色。这里称为Uni-labeling partitioning strategy。

分区距离,我们将焦点本身的距离定义为0,而邻居节点的距离定义为1。

空间配置分区,所有的邻居节点会根据焦点(绿色的点)到重心点(黒叉)的距离比较来划分,向心距离短的会被划分为蓝色,向心距离长的会被划分为黄色。

我们把所有关节点的平均坐标作为骨架重心。但是一般如果所有关节点中在胸部有点的话,我们一般将胸部作为重心点。但是如果没有胸部关节点的话,我们一般采用脖子作为重心点。在同一片焦点区域中,焦点到重心点的向心距离,我们定义为0;而焦点的邻居节点到重心点的距离如果小于焦点到重心点的距离,则该向心距离,我们定义为1;焦点的邻居节点到重心点的距离如果大于焦点到重心点的距离,则该向心距离,我们定义为2;这里需要注意向心距离不是实际距离,它们是两个概念。

动结图卷积网络(Actional-Structural Graph Convolutional Networks)

动结图卷积网络简称AS-GCN。

上图是一个基于时间帧的动作输入,它分成了动作的连接(Actional Links)和结构的连接(Structural Links)。在右边的部分成了两部分,一部分是AS-GCN,一部分是ST-GCN。但不论是哪部分,每个关节点都有一个红色的圆的区域,这个圆的区域代表着当前关节点运动的强烈程度,如果该圆的红色区域越大,颜色越深就代表该关节点的运动越强烈。对于图中的矩形框框住的手和脚进行比较,我们会发现对于AS-GCN来说,它的运动比较强烈,而对于ST-GCN来说,它的运动很小。

对于动作的连接和结构的连接来说,结构的连接很好理解,它就是关节点的连线。而对于动作的连接来说,蓝色的线代表的是骨骼节点的连线,而黄色的线代表人体物理上不同节点之间的依赖关系,如果黄线越粗代表两个节点之间的关系越强,如果黄线越细代表在当前动作下两个节点之间的联系越浅。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-05-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 图卷积神经网络(Graph Convolution Networks)
  • 图时空网络
相关产品与服务
灰盒安全测试
腾讯知识图谱(Tencent Knowledge Graph,TKG)是一个集成图数据库、图计算引擎和图可视化分析的一站式平台。支持抽取和融合异构数据,支持千亿级节点关系的存储和计算,支持规则匹配、机器学习、图嵌入等图数据挖掘算法,拥有丰富的图数据渲染和展现的可视化方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档