缘起
现在的大模型的输入多是文本和图片,针对三维模型的比较少。作为一名在三维建筑设计软件领域摸爬滚打多年的研发人员,如何才能让三维模型也能享受到这一波机器学习的红利呢?于是我四处搜索,发现了图神经网络。当然,我还尚未找到把图神经网络和三维模型结合进而落地的方案。
图神经网络的典型算法
图神经网络(Graph Neural Network,GNN)是一类专门用于处理图结构数据的神经网络,以下是一些典型的图神经网络算法:
图卷积网络(Graph Convolutional Network,GCN)
- 基本原理:GCN基于谱图理论,将卷积操作从传统的欧几里得空间拓展到图结构的非欧几里得空间。它通过对图的拉普拉斯矩阵进行特征分解,定义了在图上的卷积操作,以聚合节点的邻居信息来更新节点的特征表示。
- 应用场景:广泛应用于节点分类、图分类、链路预测等任务,如在社交网络中对用户进行兴趣分类,在生物网络中预测蛋白质的功能等。
图注意力网络(Graph Attention Network,GAT)
- 基本原理:引入了注意力机制,让节点在聚合邻居信息时能够自适应地分配不同的权重,即根据节点自身和邻居节点的特征来动态地确定每个邻居对当前节点的重要性,从而更有效地捕捉图中的重要信息。
- 应用场景:在处理具有复杂结构和异质性的图数据时表现出色,如在推荐系统中,能够根据用户与物品之间的交互关系,更精准地为用户推荐感兴趣的物品。
图自编码器(Graph Autoencoder,GAE)
- 基本原理:是一种无监督学习的图神经网络,由编码器和解码器组成。编码器将图的结构和节点特征映射到一个低维的潜在空间表示,解码器则根据潜在空间表示来重构原始的图结构或节点特征,通过最小化重构误差来学习图的有效表示。
- 应用场景:主要用于图数据的特征学习、节点嵌入、异常检测等任务,例如在网络安全领域,检测网络中的异常节点或恶意行为。
消息传递神经网络(Message Passing Neural Network,MPNN)
- 基本原理:基于消息传递的思想,节点通过在图中传递消息来更新自身的状态。在每一轮迭代中,节点接收来自邻居节点的消息,并根据这些消息和自身的当前状态来更新自己的状态,经过多轮迭代后,节点能够聚合到图中更远距离的信息。
- 应用场景:适用于各种图结构数据的任务,如在化学领域中对分子结构进行性质预测,在知识图谱中进行关系推理等。
图同构网络(Graph Isomorphism Network,GIN)
- 基本原理:旨在学习图的同构不变特征表示,通过设计特殊的聚合函数和更新机制,使得模型能够区分不同结构的图,同时对同构图具有不变性。它通过在节点特征上添加可学习的参数,来更灵活地捕捉图的结构信息。
- 应用场景:在图分类、图生成等任务中表现出良好的性能,尤其在处理具有复杂结构和不同规模的图数据时具有优势,可用于化学分子的相似性判断、社交网络社区结构的分析等。
以图卷积网络为例
下面以图卷积网络(GCN)为例,来看看如何具体进行实现。
图卷积网络核心原理与实现
GCNConv
是图卷积网络(GCN)中核心的卷积层模块,在 torch_geometric
库中已有实现。下面我们从原理和代码层面来深入了解它的实现方式。
原理
torch_geometric
中 GCNConv
的实现
代码示例
import torch
from torch_geometric.nn import GCNConv
# 模拟数据
# 假设有 4 个节点,每个节点有 3 个特征
x = torch.randn(4, 3)
# 边索引,模拟图的连接关系
edge_index = torch.tensor([[0, 1, 1, 2, 2, 3],
[1, 0, 2, 1, 3, 2]], dtype=torch.long)
# 定义 GCNConv 层,输入特征维度为 3,输出特征维度为 5
conv = GCNConv(3, 5)
# 进行卷积操作
out = conv(x, edge_index)
print(out.shape) # 输出形状应为 [4, 5]
代码解释
数据准备:
x
是节点特征矩阵,形状为[4, 3]
,表示有 4 个节点,每个节点有 3 个特征。edge_index
是边索引矩阵,形状为[2, 6]
,表示图中有 6 条边。
定义
GCNConv
层:GCNConv(3, 5)
表示创建一个输入特征维度为 3,输出特征维度为 5 的 GCN 卷积层。该层会自动初始化可学习的权重矩阵 (W)。
卷积操作:
conv(x, edge_index)
调用GCNConv
层的forward
方法,对输入的节点特征矩阵x
和边索引矩阵edge_index
进行卷积操作,得到输出特征矩阵out
,其形状为[4, 5]
。
自定义简单的 GCNConv
实现
import torch
import torch.nn as nn
import torch_sparse
class SimpleGCNConv(nn.Module):
def __init__(self, in_channels, out_channels):
super(SimpleGCNConv, self).__init__()
self.weight = nn.Parameter(torch.Tensor(in_channels, out_channels))
self.reset_parameters()
def reset_parameters(self):
nn.init.xavier_uniform_(self.weight)
def forward(self, x, edge_index):
# 添加自连接
num_nodes = x.size(0)
edge_index, _ = torch_sparse.add_self_loops(edge_index, num_nodes=num_nodes)
# 计算度矩阵的逆平方根
row, col = edge_index
deg = torch_sparse.sum(torch.ones_like(row), dim=0, size=num_nodes)
deg_inv_sqrt = deg.pow(-0.5)
deg_inv_sqrt[deg_inv_sqrt == float('inf')] = 0
# 计算归一化的邻接矩阵
adj = torch_sparse.SparseTensor(row=row, col=col, value=deg_inv_sqrt[row] * deg_inv_sqrt[col])
# 特征变换
x = torch.matmul(x, self.weight)
# 消息传递
out = torch_sparse.matmul(adj, x)
return out
# 测试自定义 GCNConv
x = torch.randn(4, 3)
edge_index = torch.tensor([[0, 1, 1, 2, 2, 3],
[1, 0, 2, 1, 3, 2]], dtype=torch.long)
conv = SimpleGCNConv(3, 5)
out = conv(x, edge_index)
print(out.shape) # 输出形状应为 [4, 5]
自定义实现解释
初始化:
- 在
__init__
方法中,定义可学习的权重矩阵self.weight
,并调用reset_parameters
方法对其进行初始化。
- 在
添加自连接:
- 使用
torch_sparse.add_self_loops
函数为边索引矩阵添加自连接,确保每个节点都能与自身进行信息传递。
- 使用
计算度矩阵的逆平方根:
- 通过
torch_sparse.sum
函数计算每个节点的度,然后对度取负的平方根,得到度矩阵的逆平方根。
- 通过
计算归一化的邻接矩阵:
- 使用
torch_sparse.SparseTensor
函数创建归一化的邻接矩阵,其元素为度矩阵逆平方根的乘积。
- 使用
特征变换:
- 使用
torch.matmul
函数将输入的节点特征矩阵x
与可学习的权重矩阵self.weight
相乘,实现特征变换。
- 使用
消息传递:
- 使用
torch_sparse.matmul
函数将归一化的邻接矩阵与变换后的特征矩阵相乘,完成消息传递过程,得到输出特征矩阵。
- 使用
通过以上步骤,我们实现了一个简单的 GCNConv
层,其功能与 torch_geometric
中的 GCNConv
类似。
核心公式的意义
上面代码的核心是实现这个公式,虽然从数学上推导可能是成立。但从物理意义上来说,这个公式有意义吗?
import torch
import torch.nn as nn
def gcn_layer_pytorch(H, A, W):
"""
实现 GCN 层的前向传播
:param H: 输入的节点特征矩阵,形状为 [N, F_in]
:param A: 邻接矩阵,形状为 [N, N]
:param W: 可学习的权重矩阵,形状为 [F_in, F_out]
:return: 输出的节点特征矩阵,形状为 [N, F_out]
"""
# 添加自连接
I = torch.eye(A.size(0))
A_hat = A + I
# 计算度矩阵
D_hat = torch.diag(torch.pow(torch.sum(A_hat, dim=1), -0.5))
# 计算归一化的邻接矩阵
A_norm = torch.mm(torch.mm(D_hat, A_hat), D_hat)
# 特征变换
H_transformed = torch.mm(H, W)
# 消息传递
H_next = torch.mm(A_norm, H_transformed)
# 应用激活函数(这里使用 ReLU)
H_next = torch.relu(H_next)
return H_next
# 示例数据
N = 4 # 节点数量
F_in = 3 # 输入特征维度
F_out = 2 # 输出特征维度
# 随机生成节点特征矩阵
H = torch.randn(N, F_in)
# 随机生成邻接矩阵
A = torch.randint(0, 2, size=(N, N)).float()
# 随机初始化权重矩阵
W = nn.Parameter(torch.randn(F_in, F_out))
# 进行 GCN 层的前向传播
H_next = gcn_layer_pytorch(H, A, W)
print("输出特征矩阵形状:", H_next.shape)
下面这些解释本身也不是太好懂。
图卷积网络(GCN)具有多方面的物理意义,主要体现在信号处理、信息传播与聚合、空间关系建模等方面,以下是具体解释:
从信号处理角度
- 图信号的滤波:在传统的信号处理中,卷积操作可以对信号进行滤波,提取信号中的特定频率成分。在图卷积网络中,也可以将图上的节点特征看作是一种图信号,GCN中的卷积操作类似于对图信号进行滤波。通过设计合适的卷积核(即权重矩阵),可以对图信号中的不同频率成分进行增强或抑制,从而提取出图信号中的关键特征。低频成分通常对应于图中较为平滑、全局的特征,而高频成分可能对应于图中的局部细节或噪声。GCN通过卷积操作能够有效地捕捉图信号中的低频特征,实现对图数据的平滑和去噪,突出图的整体结构和趋势。
- 频谱分析:GCN基于谱图理论,将图的拉普拉斯矩阵进行特征分解,得到图的频谱信息。拉普拉斯矩阵的特征值和特征向量可以看作是图的“频率”和“基函数”,类似于傅里叶变换中的频率和正弦/余弦函数。图卷积操作实际上是在图的频谱域上进行的,通过对频谱信息的处理来实现对图信号的变换和特征提取。这使得GCN能够从频谱的角度分析图数据,揭示图的内在结构和特征与频谱之间的关系,为理解图数据提供了一种新的视角。
从信息传播角度
- 邻居信息聚合:在图结构数据中,每个节点的状态往往与它的邻居节点密切相关。GCN的物理意义在于它提供了一种机制,使得每个节点能够聚合其邻居节点的信息。在每一层的卷积操作中,节点会收集来自其直接邻居的特征信息,并将这些信息与自身的特征进行融合,从而更新自己的特征表示。这种邻居信息的聚合过程可以看作是信息在图上的传播过程,随着网络层数的增加,节点能够逐渐聚合到更远距离的邻居信息,从而获取到图的更全局的结构信息。
- 信息扩散与传播动力学:可以类比为物理系统中的信息扩散或传播过程。例如,在一个社交网络中,用户的观点、信息或影响力可以通过用户之间的连接关系进行传播。GCN中的卷积操作模拟了这种信息在网络中的扩散过程,节点的特征更新类似于信息在节点之间的传递和更新。通过这种方式,GCN能够捕捉到信息在图结构中的传播规律和动态变化,从而对图数据中的各种现象和行为进行建模和预测。
从空间关系角度
- 空间结构建模:图中的节点之间存在着特定的空间关系,这种空间关系由图的边来定义。GCN能够通过卷积操作对图的空间结构进行建模,它考虑了节点的邻居节点的分布和特征,从而能够捕捉到图中节点之间的局部空间结构信息。例如,在一个图像的像素图中,相邻像素之间的关系可以通过图的边来表示,GCN可以利用这种图结构来学习图像中的局部空间特征,如边缘、纹理等。
- 几何特征提取:类似于在欧几里得空间中的卷积神经网络对图像等数据的几何特征进行提取,GCN在图结构的非欧几里得空间中也能够提取图的几何特征。它通过对图的局部结构和节点特征的处理,能够发现图中的一些几何模式和规律,如节点的聚集性、连通性等。这些几何特征对于理解图数据的性质和进行各种任务(如图分类、节点分类等)具有重要意义。
用pytorch写一个用图卷积网络对社交网络中对用户进行兴趣分类的例子
以下是一个使用 PyTorch 和 torch_geometric
库实现的图卷积网络(GCN)对社交网络中用户进行兴趣分类的例子。在这个例子中,我们会模拟一个简单的社交网络,为每个用户添加一些特征,并根据这些特征和社交关系进行兴趣分类。
代码实现
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.data import Data
import numpy as np
# 模拟社交网络数据
# 假设有 10 个用户
num_nodes = 10
# 每个用户有 5 个特征
num_features = 5
# 随机生成节点特征矩阵
x = torch.randn(num_nodes, num_features)
# 模拟社交关系(边索引)
# 这里简单生成一些边
edge_index = torch.tensor([[0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9],
[1, 0, 2, 1, 3, 2, 4, 3, 5, 4, 6, 5, 7, 6, 8, 7, 9, 8]], dtype=torch.long)
# 为每个用户分配一个兴趣类别(标签),假设共有 3 个兴趣类别
y = torch.tensor(np.random.randint(0, 3, num_nodes), dtype=torch.long)
# 划分训练集和测试集
train_mask = torch.zeros(num_nodes, dtype=torch.bool)
train_mask[:7] = True # 前 7 个节点作为训练集
test_mask = ~train_mask # 后 3 个节点作为测试集
# 创建图数据对象
data = Data(x=x, edge_index=edge_index, y=y, train_mask=train_mask, test_mask=test_mask)
# 定义 GCN 模型
class GCN(torch.nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels):
super(GCN, self).__init__()
self.conv1 = GCNConv(in_channels, hidden_channels)
self.conv2 = GCNConv(hidden_channels, out_channels)
def forward(self, x, edge_index):
x = self.conv1(x, edge_index)
x = F.relu(x)
x = F.dropout(x, training=self.training)
x = self.conv2(x, edge_index)
return F.log_softmax(x, dim=1)
# 初始化模型、优化器和损失函数
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = GCN(data.num_node_features, 16, 3).to(device)
data = data.to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)
# 训练函数
def train():
model.train()
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()
return loss.item()
# 测试函数
def test():
model.eval()
out = model(data.x, data.edge_index)
pred = out.argmax(dim=1)
test_correct = pred[data.test_mask] == data.y[data.test_mask]
test_acc = int(test_correct.sum()) / int(data.test_mask.sum())
return test_acc
# 训练模型
for epoch in range(200):
loss = train()
if (epoch + 1) % 10 == 0:
print(f'Epoch: {epoch + 1}, Loss: {loss:.4f}')
# 测试模型
test_acc = test()
print(f'Test Accuracy: {test_acc:.4f}')
代码解释
数据模拟:
- 随机生成节点特征矩阵
x
,表示每个用户的特征。 - 手动定义边索引
edge_index
,模拟用户之间的社交关系。 - 随机生成用户的兴趣类别标签
y
。 - 划分训练集和测试集,分别用
train_mask
和test_mask
表示。 - 创建
Data
对象,将节点特征、边索引、标签和掩码信息封装在一起。
- 随机生成节点特征矩阵
模型定义:
- 定义一个两层的 GCN 模型,包含两个
GCNConv
层,中间使用ReLU
激活函数和Dropout
防止过拟合。
- 定义一个两层的 GCN 模型,包含两个
训练过程:
- 在每个训练周期中,将模型设置为训练模式,进行前向传播计算损失,然后进行反向传播更新模型参数。
测试过程:
- 将模型设置为评估模式,进行前向传播得到预测结果,计算测试集的准确率。
注意事项
- 此代码中的数据是模拟生成的,实际应用中需要使用真实的社交网络数据。
- 可以根据需要调整模型的超参数,如学习率、隐藏层维度、训练周期等,以获得更好的性能。
总结
不知道第一个想出这个方法的人是谁,总之,为什么做了这样的矩阵计算,它就能用于学习了呢?其他图神经网络也是类似的,只是矩阵计算方式不同。也许,这就是学术研究,或者也是炼丹。如何从物理意义上解释这些算法的原理,似乎是给出了,但给出的内容本身也挺难懂的。学习AI的道路,任重道远!