文章目录
- 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,图卷积神经网络,实际上跟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)猜测任意连续周期函数都可以写成三角函数之和。
其中为角速度,
是函数周期。
3.2 时域与频域
关于时域与频域的理解,参考文章《如何理解傅里叶变换公式?》中的讲解非常易懂。
3.2.1 时域
时域是描述一个数学函数或物理信号对时间的关系,这也是我们日常中最容易直观感受的一种域。
从我们学物理开始,很多物理量的定义都是跟时间相关的:
- 速度:位移与发生这个位移所用的时间之比
- 电流:单位时间里通过导体任一横截面的电量
- 功率:物体在单位时间内所做的功的多少
- …
比如一首歌的波形图,展现在时域上,如下图,是众多声音的叠加(人声、乐器声…)。如果我们需要放大人声,该如何操作呢?(保留问题)
3.2.2 频域
频域就是描述频率所用到的空间或者说坐标系。频率虽然比较抽象,但是在我们的生活中是无处不在的,只是我们很少直接提到这个专业名词。
对于波来说,频率是每秒波形重复的数量。声音是一种波;光具有波粒二象性,也具有电磁波的性质;更普遍的说,频率是物质每秒钟完成周期性变化的次数。比如家里用的交流电是50Hz,意思就是电压每秒完成50次振荡周期。
如果把函数的图像画在频域中,就是下面的样子,横坐标是频率
。
3.2.3 通过欧拉公式理解时域与频域及其转换
欧拉公式:
左侧是极坐标中的表示(其中长度用复数表示,水平方向为实部,竖直方向为虚部),右侧是平面坐标系。其中是复数的虚部。把频域中
向量的实部(即横坐标)映射的到时域中,得到的就是
;而虚部(即纵坐标)映射到时域中,得到的就是
(如下图)。
下图这两种角度,一个可以观察到旋转的频率,所以称为频域;一个可以看到流逝的时间,所以称为时域。
上图的频域是在极坐标中展示的,下面换一个角度,将频域展示在平面直角坐标系中。看下图,从左边看,就是上图时域;将函数分解为若干个正余弦波后,从右侧面看,就是频域(横坐标为频率)。
现在重谈 3.2.1 时域 中提到的歌曲波形图,如何放大其中的人声?
通过上图可以理解,将歌声的波分解为一个个的正余弦波,然后将其中人声的那个波扩大振幅,再重新将所有的波叠加起来。这样,我们就在不影响乐器声音的情况下,放大了人声。 所以说,一些在时域中很难处理的问题,转换到频域中会变得很简单。记住这个结论。
3.3 傅里叶变换
待续
3. GCN模型
假设我们有一张N个结点的图,每个结点都有自己的特征(一个m维向量),所有结点的特征向量组成一个矩阵
。记矩阵
为图的邻接矩阵,即若结点i与j之间有边相连,则
,否则为0。
输入数据:特征矩阵;邻接矩阵
GCN也是一个神经网络层,它的层与层之间的传播方式是:
其中,
,
是单位矩阵。
,即图的度矩阵(degree matrix),是对角矩阵。
是第
层的输出,
。
为激活函数。
不妨记,显然这是个常量,可以在模型开始之前就算出来的。则公式(1)可简写为:
公式(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