文章目录

  • 1. 背景
  • 2. 优秀文章
  • 3. 傅里叶级数与傅里叶变换
  • 3.1 傅里叶级数的定义
  • 3.2 时域与频域
  • 3.2.1 时域
  • 3.2.2 频域
  • 3.2.3 通过欧拉公式理解时域与频域及其转换
  • 3.3 傅里叶变换
  • 3. GCN模型
  • 4. GCN模型实现(pytorch 1.60)


1. 背景

卷积神经网络(CNN)的输入是图片等具有欧几里得结构的图结构,也就是矩阵形式,很容易在其上做卷积操作。而对于图来说,没有一个通用的结构框架,也就是非欧几里得结构Non Euclidean Structure。 在图上没法直接做卷积,那怎么提取图的特征呢?答案是图卷积网络(GCN)

GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_pytorch

GCN,图卷积神经网络,实际上跟CNN的作用一样,就是一个特征提取器,只不过它的对象是图数据。GCN精妙地设计了一种从图数据中提取特征的方法,从而让我们可以使用这些特征去对图数据进行节点分类(node classification)、图分类(graph classification)、边预测(link prediction),还可以顺便得到图的嵌入表示(graph embedding),可见用途广泛。因此现在人们脑洞大开,让GCN到各个领域中发光发热。

2. 优秀文章

[1] 图卷积神经网络(GCN)

避开复杂的数学推导,直接从结论上理解GCN,以及如何使用。给这篇文章点一百个赞!

[2] 如何理解 Graph Convolutional Network(GCN)?

[3] 如何理解傅立叶级数公式?

理解傅里叶级数必看!展示动图,形象把傅里叶级数的物理意义描述清晰;以及通俗证明傅里叶级数在时域中的正交基。

[4] 如何理解傅里叶变换公式?

3. 傅里叶级数与傅里叶变换

3.1 傅里叶级数的定义

让·巴普蒂斯·约瑟夫·傅里叶男爵(1768 -1830)猜测任意连续周期函数都可以写成三角函数之和。

GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_02

其中GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_03为角速度,GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_04是函数周期。

GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_05
GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_图卷积网络_06

3.2 时域与频域

关于时域与频域的理解,参考文章《如何理解傅里叶变换公式?》中的讲解非常易懂。

3.2.1 时域

时域是描述一个数学函数或物理信号对时间的关系,这也是我们日常中最容易直观感受的一种域。

从我们学物理开始,很多物理量的定义都是跟时间相关的:

  • 速度:位移与发生这个位移所用的时间之比
  • 电流:单位时间里通过导体任一横截面的电量
  • 功率:物体在单位时间内所做的功的多少

比如一首歌的波形图,展现在时域上,如下图,是众多声音的叠加(人声、乐器声…)。如果我们需要放大人声,该如何操作呢?(保留问题)

GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_07

3.2.2 频域

频域就是描述频率所用到的空间或者说坐标系。频率虽然比较抽象,但是在我们的生活中是无处不在的,只是我们很少直接提到这个专业名词。

对于波来说,频率是每秒波形重复的数量。声音是一种波;光具有波粒二象性,也具有电磁波的性质;更普遍的说,频率是物质每秒钟完成周期性变化的次数。比如家里用的交流电是50Hz,意思就是电压每秒完成50次振荡周期。

如果把函数GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_图卷积网络_08的图像画在频域中,就是下面的样子,横坐标是频率GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_09

GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_10

3.2.3 通过欧拉公式理解时域与频域及其转换

欧拉公式:
GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_频域_11

左侧是极坐标中的表示(其中长度用复数表示,水平方向为实部,竖直方向为虚部),右侧是平面坐标系。其中GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_12是复数的虚部。把频域中GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_13向量的实部(即横坐标)映射的到时域中,得到的就是GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_14;而虚部(即纵坐标)映射到时域中,得到的就是GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_图卷积网络_15(如下图)。

下图这两种角度,一个可以观察到旋转的频率,所以称为频域;一个可以看到流逝的时间,所以称为时域

GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_16

上图的频域是在极坐标中展示的,下面换一个角度,将频域展示在平面直角坐标系中。看下图,从左边看,就是上图时域;将函数分解为若干个正余弦波后,从右侧面看,就是频域(横坐标为频率)。

GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_频域_17

现在重谈 3.2.1 时域 中提到的歌曲波形图,如何放大其中的人声?

通过上图可以理解,将歌声的波分解为一个个的正余弦波,然后将其中人声的那个波扩大振幅,再重新将所有的波叠加起来。这样,我们就在不影响乐器声音的情况下,放大了人声。 所以说,一些在时域中很难处理的问题,转换到频域中会变得很简单。记住这个结论。

3.3 傅里叶变换

待续

3. GCN模型

假设我们有一张N个结点的图,每个结点都有自己的特征GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_pytorch_18(一个m维向量),所有结点的特征向量组成一个矩阵GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_19。记矩阵GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_频域_20为图的邻接矩阵,即若结点i与j之间有边相连,则GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_21,否则为0。

输入数据:特征矩阵GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_19;邻接矩阵GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_频域_20

GCN也是一个神经网络层,它的层与层之间的传播方式是:
GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_24

其中,

  1. GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_25GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_频域_26是单位矩阵。
  2. GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_pytorch_27,即图的度矩阵(degree matrix),是对角矩阵。
  3. GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_频域_28是第GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_图卷积网络_29层的输出,GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_时域_30
  4. GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_pytorch_31为激活函数。

不妨记GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_32,显然这是个常量,可以在模型开始之前就算出来的。则公式(1)可简写为:
GCN 图卷积网络 实践 pytorch (空手道俱乐部数据集)_深度学习_33

公式(2)就是GCN常用模型,这是图卷积网络的一种fast approximate形式,较早的两个版本是SEMI-SUPERVISED CLASSIFICATION WITH GRAPH CONVOLUTIONAL NETWORKS。

对于不需要去了解数学原理、只想应用GCN来解决实际问题的人来说,只需要知道这个GCN设计了一个牛逼的公式,用这个公式就可以很好地提取图的特征。这就够了,毕竟不是什么事情都需要知道内部原理,这是根据需求决定的。

4. GCN模型实现(pytorch 1.60)

import torch
import torch.nn as nn
import torch.nn.functional as F
import networkx as nx


class GCN(nn.Module):
    def __init__(self, A, dim_in, dim_out):
        super(GCN, self).__init__()

        A = A + torch.eye(A.shape[0])  # A = A+I
        D = torch.diag(torch.pow(A.sum(dim=1), -0.5))  # D = D^-1/2
        self.A = D @ A @ D  # \hat A

        self.fc1 = nn.Linear(dim_in, dim_in, bias=False)
        self.fc2 = nn.Linear(dim_in, dim_in // 2, bias=False)
        self.fc3 = nn.Linear(dim_in // 2, dim_out, bias=False)

    def forward(self, X):
        X = F.relu(self.fc1(self.A @ X))
        X = F.relu(self.fc2(self.A @ X))
        return self.fc3(self.A @ X)


def train(model, X):
    opt = torch.optim.Adam(model.parameters())
    for i in range(150):  # 训练150个轮次
        model.train()
        y_pred = F.softmax(model(X), dim=1)
        loss = (-y_pred.log().gather(1, Y.view(-1, 1)))  # cross entropy
        loss = loss.masked_select(Y_mask).mean()  # 仅保留可供训练的样本

        opt.zero_grad()  # 梯度清零
        loss.backward()  # 反向传播计算梯度
        opt.step()  # 根据梯度信息更新所有可训练参数

        model.eval()
        if i % 20 == 0:  # 验证一下,跟真实标签比较
            _, idx = y_pred.max(1)
            print('每个人的预测类别:', idx)
            print('准确率:', float((idx == realY).sum()) / N)


if __name__ == '__main__':
    G = nx.karate_club_graph()  # 获得空手道俱乐部数据,34个人
    A = torch.Tensor(nx.adjacency_matrix(G).todense())  # 获取邻接矩阵
    N = A.shape[0]  # 结点个数
    feature_dim = 34  # 每个结点的特征维度
    X = torch.eye(N, feature_dim)  # 初始每个结点的特征向量

    # 假设我们已知第0个人属于0集团,第N-1个人属于第1集团。仅用这两个样本用来训练
    Y = torch.zeros(N, 1).long()
    Y[0][0] = 0
    Y[N - 1][0] = 1
    Y_mask = torch.zeros(N, 1, dtype=torch.bool)  # 标记哪个结点样本是可以作为训练数据的
    Y_mask[0][0] = 1
    Y_mask[N - 1][0] = 1

    # 整张图真实的空手道俱乐部人员的分类标签
    class0idx = [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 16, 17, 19, 21]
    realY = torch.Tensor([0 if i in class0idx else 1 for i in range(N)])

    model = GCN(A, dim_in=feature_dim, dim_out=2)
    train(model, X)

150次迭代后的输出:

每个人的预测类别: tensor([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
准确率: 1.0