目录
一.背景
CNN通过卷积conv来对某个空间区域的像素点进行加权求和,得到新的特征表示。 加权系数就是卷积核的参数,如下图所示。
CNN适用于规则2维矩阵数据 (每个像素点有上下左右相连)或1维序列数据(如语音,每个点左右相连) 来提取特征。CNN无法处理非欧几里得结构的数据, 因为传统的卷积没法处理节点关系多变的信息(没法固定尺寸进行设置卷积核)。
对于并不具备规则的空间结构,如社交网络、脑信号、分子结构等,可以利用图图卷积神经网络(GCN)提取图的特征信息与结构信息。
二.原理
图数据具有节点特征(每个节点都具有自己的向量表示)和结构特征(节点与节点间具有一定的联系, 即携带信息的边)。
GCN的核心思想是聚合来自节点局部邻域的特征和节点特征更新。GCN主要分为2类,1)基于顶点域或空间域 vertex domain(spatial domain);2)基于频域或谱域(spectral domain) 。顶点域可以类比到直接在图片的像素点上进行卷积,而频域可以类比到对图片进行傅里叶变换后,再进行卷积。在此,只讨论空间域GCN。
GCN的步骤主要为:
1. 初始化:为每个节点分配初始特征表示。
2. 邻居聚合:对于每个节点,将其自身特征与邻居节点的特征进行加权平均或拼接,得到聚合后的特征。
3. 特征转换:对聚合后的特征进行线性变换,以充分利用特征之间的关系。
4. 非线性激活:应用非线性激活函数,如ReLU,将线性变换后的特征映射到非线性空间。
5.循环迭代:重复进行邻居聚合、特征转换和非线性激活的步骤,直到达到所需的网络层数或收敛条件。
具体地,GCN第l+1层的特征矩阵具体计算方式如下:
其中,H是隐藏层的特征,σ是非线性激活函数,是邻接矩阵与单位矩阵的和。
D是度矩阵,其中对角线元素代表节点的度。
W是权重矩阵。
三.应用实例
使用Pytorch提供的GCN网络对Cora数据集进行分类。
1.Cora数据集
数据集共2708个样本,每个样本都是1篇科学论文,论文分为以下7类之一。
- 基于案例
- 遗传算法
- 神经网络
- 概率方法
- 强化学习
- 规则学习
- 理论
每篇论文都由1个1433维的词向量表示,词向量的每个元素都对应1个词,只有0(不在论文中),1(在论文中)2个取值。
每篇论文都至少引用1篇其他论文,或者被其他论文引用,没有任何1个样本与其他样板完全没联系。如果将样本看作图中的点,则是1个连通图,不存在孤立点。
(1)文件格式
cora.content是所有论文的独自的信息;cora.cites是论文之间的引用记录。
cora.content共有2708行,每行即1篇论文,由3部分组成,分别是论文编号、论文词向量、论文的类别。
cora.cites共5429行,每1行有2个论文编号,表示第1个编号的论文先写,第2个编号的论文引用第1个编号的论文。
如果将论文看作图中的节点,那么5429行便是节点之间的5429条边。
2.GCN分类代码
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.datasets import Planetoid
from torch_geometric.transforms import NormalizeFeatures
# 加载并预处理Cora数据集
# dataset = Planetoid(root='data/planetoid', name='Cora', transform=NormalizeFeatures())
dataset = Planetoid(root='', name='Cora', transform=NormalizeFeatures())
class GCN(torch.nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(GCN, self).__init__()
self.conv1 = GCNConv(input_dim, hidden_dim)
self.conv2 = GCNConv(hidden_dim, output_dim)
def forward(self, x, edge_index):
print(f"first {x.shape}")
x = self.conv1(x, edge_index)
print(f"conv1 {x.shape}")
x = F.relu(x)
x = self.conv2(x, edge_index)
print(f"conv2 {x.shape}")
print(f"x[0] {x[0]}")
print(f"max x[0] {max(x[0])}")
return F.log_softmax(x, dim=1)
# 数据集信息
data = dataset[0]
# print(f"data shape {len(data)}")
input_dim = dataset.num_node_features
hidden_dim = 16
output_dim = dataset.num_classes
print(input_dim, output_dim)
# 初始化模型
model = GCN(input_dim, hidden_dim, output_dim)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
# 训练模型
model.train()
for epoch in range(100):
optimizer.zero_grad()
out = model(data.x, data.edge_index)
loss = F.nll_loss(out[data.train_mask], data.y[data.train_mask])
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f'Epoch {epoch}, Loss: {loss.item()}')
# 测试模型
model.eval()
_, pred = model(data.x, data.edge_index).max(dim=1)
correct = (pred[data.test_mask] == data.y[data.test_mask]).sum()
accuracy = int(correct) / int(data.test_mask.sum())
print(f'Accuracy: {accuracy:.4f}')
(1) 数据集加载
Planetoid数据会因为网络问题无法直接下载,一直报错。可以通过Cora,将下载好的数据
放到根目录下Cora/raw
文件夹下,然后再运行代码。
(2) Planetoid中Cora数据集边数量不一致问题
cora.cites中显示有5429条边有,
但Planetoid中的Cora边数量5278,
原因在于cora.cites中存在重复边,详细信息如下。
'''data check'''
def data_check(cites_file):
# 读取引用关系数据
with open(cites_file, 'r') as f:
cites_lines = f.readlines()
edge_count = {}
self_loops = 0
# 解析边数据并统计出现次数
for line in cites_lines:
parts = line.strip().split()
cited = parts[0]
citing = parts[1]
# 检查自环
if cited == citing:
self_loops += 1
# 创建无向边的标准形式 (sorted tuple)
edge = tuple(sorted((cited, citing)))
if edge in edge_count:
edge_count[edge] += 1
else:
edge_count[edge] = 1
# 查找并打印重复边
repeated_edges = {edge: count for edge, count in edge_count.items() if count > 1}
print(f'Total edges (directed): {len(cites_lines)}')
print(f'Unique edges (undirected): {len(edge_count)}')
print(f'Self-loops: {self_loops}')
print(f'Number of repeated edges: {len(repeated_edges)}')
# 打印重复边及其出现次数
print('Repeated edges:')
top_info=list(repeated_edges.items())[:10]
for edge, count in top_info:
print(f'Edge {edge} appears {count} times')
cites_file='Cora/cora_origin/cora.cites'
data_check(cites_file)
'''data check'''
cora.cites中5429-5278=151条存在重复边。