趋近智
仅使用 Python 和 NumPy 从头开始实现一个简化的图神经网络 (neural network)(GNN)层。这包括将消息传递机制的核心聚合和更新步骤直接转换为代码。其目的是通过实际应用,让 GNN 层的运作机制变得直观具体。
首先,我们需要一个图。让我们使用一个包含四个节点和四条边的简单小图。这个规模非常适合检查矩阵并通过手动计算进行验证。
一个具有四个节点及其连接关系的简单无向图。
我们的目标是编写一个函数,输入该图的结构和初始节点特征,并计算经过一层消息传递后的更新节点特征。
首先,我们将图的组成部分(结构和特征)表示为 NumPy 数组。正如我们在第 1 章中所学到的,这涉及邻接矩阵 A 和节点特征矩阵 X(我们也可以将其称为隐藏状态 H)。
假设我们节点的初始特征维度为 2。例如,这可能是某些初始特征提取或嵌入 (embedding)过程的结果。
import numpy as np
# 邻接矩阵 (A)
# 表示节点之间的连接。如果节点 i 和 j 相连,则 A[i, j] = 1。
A = np.array([
[0, 1, 1, 0],
[1, 0, 1, 0],
[1, 1, 0, 1],
[0, 0, 1, 0]
])
# 节点特征矩阵 (X 或 H)
# 每一行对应一个节点,每一列是一个特征。
# 这里我们有 4 个节点,每个节点有 2 个特征。
X = np.array([
[0.1, 0.2],
[0.3, 0.4],
[0.5, 0.6],
[0.7, 0.8]
])
# 我们还为 GNN 层定义一个权重矩阵。
# 该层将把 2 维特征转换为 3 维特征。
# 输入维度 = 2, 输出维度 = 3
W = np.random.rand(2, 3)
print("邻接矩阵 (A):\n", A)
print("\n节点特征 (X):\n", X)
print("\n权重 (W):\n", W)
消息传递的核心思想是聚合来自节点邻居的信息。同时为所有节点执行此聚合的一种直接且有效的方法是通过矩阵乘法。将邻接矩阵 A 与特征矩阵 X 相乘,正好可以得到我们需要的结果。
让我们看看这个运算产生了什么:
# 聚合邻居特征
M = A @ X
print("聚合特征 (M = A @ X):\n", M)
得到的矩阵 M 与 X 的维度相同。每一行 M[i] 是节点 i 的邻居特征向量 (vector)之和。例如,节点 0 与节点 1 和 2 相连。因此,M 的第一行是节点 1 的特征向量 ([0.3, 0.4]) 和节点 2 的特征向量 ([0.5, 0.6]) 之和,结果为 [0.8, 1.0]。这种单一的矩阵乘法高效地并行完成了所有节点的 AGGREGATE(聚合)步骤。
简单的聚合方式 A @ X 有两个缺点:
我们可以通过两个简单的修改来解决这些问题。
为了在聚合中包含节点自身的特征,我们只需为每个节点添加一个自环。在邻接矩阵方面,这意味着加上一个单位矩阵 I。
# 为邻接矩阵添加自环
A_hat = A + np.eye(A.shape[0])
print("带有自环的邻接矩阵 (A_hat):\n", A_hat)
现在,当我们计算 A_hat @ X 时,每个节点的聚合将包含其自身的特征以及邻居的特征。
为了解决度偏差问题,我们可以对聚合特征进行标准化。由图卷积网络(GCN)推广的一种常用技术是将特征除以每个节点的度。这实际上是计算邻居特征的平均值而不是总和。
对称标准化公式为:
这里, 是带有自环的邻接矩阵, 是从 计算得出的对角度矩阵。
让我们来实现它:
# 计算度矩阵 (D_hat)
D_hat = np.diag(np.sum(A_hat, axis=1))
print("度矩阵 (D_hat):\n", D_hat)
# 计算度矩阵的平方根倒数
# 我们添加一个极小值 epsilon 以避免孤立节点出现除以零的情况
D_hat_inv_sqrt = np.linalg.inv(D_hat)**0.5
# 标准化邻接矩阵
A_norm = D_hat_inv_sqrt @ A_hat @ D_hat_inv_sqrt
print("\n标准化邻接矩阵:\n", A_norm)
这个标准化的邻接矩阵 A_norm 现在可以用于执行更稳定且更有效的聚合。
聚合之后,UPDATE(更新)步骤会应用一个可学习的线性变换,随后是一个非线性激活函数 (activation function)。这与神经网络 (neural network)标准稠密层内部的操作完全相同。
我们简单的 GNN 层的完整公式为:
其中 是像 ReLU 这样的非线性激活函数。
让我们将此逻辑封装到一个可重用的 Python 函数中。该函数代表了一个完整的消息传递层。
def gnn_layer(A, X, W):
"""
执行一层图神经网络消息传递。
参数:
A (np.array): 图的邻接矩阵。
X (np.array): 节点特征矩阵。
W (np.array): 该层的权重矩阵。
返回:
np.array: 更新后的节点特征矩阵。
"""
# 1. 为邻接矩阵添加自环
A_hat = A + np.eye(A.shape[0])
# 2. 计算度矩阵
D_hat = np.diag(np.sum(A_hat, axis=1))
# 3. 计算度矩阵的平方根倒数
D_hat_inv_sqrt = np.linalg.inv(D_hat)**0.5
# 4. 标准化邻接矩阵
A_norm = D_hat_inv_sqrt @ A_hat @ D_hat_inv_sqrt
# 5. 执行聚合和更新步骤
# (A_norm @ X) 是邻居特征的聚合
# (... @ W) 是线性变换(更新)
H_prime = A_norm @ X @ W
# 6. 应用非线性激活函数 (ReLU)
H_next = np.maximum(0, H_prime)
return H_next
# 将我们的图数据通过 GNN 层运行
H_next = gnn_layer(A, X, W)
print("原始节点特征 (X):\n", X)
print("\n经过一层 GNN 层更新后的节点特征 (H_next):\n", H_next)
输出结果 H_next 是一个 4x3 的矩阵。每一行是对应节点的新特征向量 (vector)。这个新向量是通过对其自身及其邻居的特征进行标准化平均,然后将结果转换到新的高维空间 (high-dimensional space)中计算得出的。这个过程成功地基于每个节点的局部邻域更新了其表示。
通过从头实现这个层,你已经构建了许多强效 GNN 架构的基础组件。在下一章中,我们将看到这一逻辑如何构成图卷积网络(GCN)的基础,并研究 AGGREGATE 和 UPDATE 函数的其他变体。
这部分内容有帮助吗?
torch_geometric.nn.conv.GCNConv, PyTorch Geometric Developers, 2024 - PyTorch Geometric库中图卷积网络(GCN)层实现的官方文档,展示了核心逻辑如何转化为高性能深度学习框架。© 2026 ApX Machine Learning用心打造