5.8 KiB
图神经网络
图表示学习的本质是把节点映射成低维连续稠密的向量。这些向量通常被称为 嵌入(Embedding),它们能够捕捉节点在图中的结构信息和属性信息,从而用于下游任务(如节点分类、链接预测、图分类等)。
- 低维:将高维的原始数据(如邻接矩阵或节点特征)压缩为低维向量,减少计算和存储开销。
- 连续:将离散的节点或图结构映射为连续的向量空间,便于数学运算和捕捉相似性。
- 稠密:将稀疏的原始数据转换为稠密的向量,每个维度都包含有意义的信息。
对图数据进行深度学习的“朴素做法”
把图的邻接矩阵和节点特征“直接拼接”成固定维度的输入,然后将其送入一个深度神经网络(全连接层)进行学习。
这种做法面临重大问题,导致其并不可行:
-
O(|V|^2)
参数量 ,参数量庞大 -
无法适应不同大小的图 ,需要固定输入维度
-
对节点顺序敏感 ,节点编号顺序一变,输入就完全变样,但其实图的拓扑并没变(仅节点编号/排列方式不同)。
A —— B | | D —— C
矩阵 1(顺序 $[A,B,C,D]$):
M_1 = \begin{pmatrix} 0 & 1 & 0 & 1\\ 1 & 0 & 1 & 0\\ 0 & 1 & 0 & 1\\ 1 & 0 & 1 & 0 \end{pmatrix}.
矩阵 2(顺序 $[C,A,D,B]$):
M_2 = \begin{pmatrix} 0 & 0 & 1 & 1 \\ 0 & 0 & 1 & 1 \\ 1 & 1 & 0 & 0 \\ 1 & 1 & 0 & 0 \end{pmatrix}.
两个矩阵完全不同,但它们对应的图是相同的(只不过节点的顺序改了)。
邻居聚合
计算图
在图神经网络里,通常每个节点v
都有一个局部计算图,用来表示该节点在聚合信息时所需的所有邻居(及邻居的邻居……)的依赖关系。
- 直观理解
- 以节点
v
为根; - 1-hop 邻居在第一层,2-hop 邻居在第二层……
- 逐层展开直到一定深度(例如 k 层)。
- 这样形成一棵“邻域树”或“展开图”,其中每个节点都需要从其子节点(邻居)获取特征进行聚合。
- 以节点
例子
在图神经网络中,每一层的计算通常包括以下步骤:
-
聚合(Aggregation):将邻居节点的特征聚合起来(如求和、均值、最大值等)。
-
变换(Transformation):将聚合后的特征通过一个神经网络(如 MLP)进行非线性变换。
A
|
B
/ \
C D
假设每个节点的特征是一个二维向量:
- 节点
A
的特征:h_A = [1.0, 0.5]
- 节点
B
的特征:h_B = [0.8, 1.2]
- 节点
C
的特征:h_C = [0.3, 0.7]
- 节点
D
的特征:h_D = [1.5, 0.9]
第 1 层更新:$A^{(0)} \to A^{(1)}$
-
节点
A
的 1-hop 邻居:只有 $B$。 -
聚合(示例:自 + sum 邻居):
z_A^{(1)} \;=\; A^{(0)} + B^{(0)} \;=\; [1.0,\,0.5] + [0.8,\,1.2] \;=\; [1.8,\,1.7].
-
MLP 变换:用一个MLP映射
z_A^{(1)}
到 2 维输出:A^{(1)} \;=\; \mathrm{MLP_1}\bigl(z_A^{(1)}\bigr).
- (数值略,可想象
\mathrm{MLP}([1.8,1.7]) \approx [1.9,1.1]
之类。)
- (数值略,可想象
结果:A^{(1)}
包含了 A 的初始特征 + B 的初始特征信息。
第 2 层更新:$A^{(1)} \to A^{(2)}$
为了让 A 获得 2-hop 范围($C, D$)的信息,需要先让 B 在第 1 层就吸收了 C, D
的特征,从而 B^{(1)} 蕴含 C, D
信息。然后 A 在第 2 层再从 B^{(1)} 聚合。
-
节点 B 在第 1 层(简要说明)
- 邻居:
\{A,C,D\}
- 聚合:
z_B^{(1)} = B^{(0)} + A^{(0)} + C^{(0)} + D^{(0)}
- MLP 变换:$B^{(1)} = \mathrm{MLP}\bigl(z_B^{(1)}\bigr)$。
- 此时 B^{(1)} 已经包含了
C, D
的信息。
- 邻居:
-
节点
A
的第 2 层聚合-
邻居:$B$,但此时要用 B^{(1)}(它已吸收 C、D)
-
聚合:
z_A^{(2)} = A^{(1)} + B^{(1)}.
-
MLP 变换:
A^{(2)} = \mathrm{MLP_2}\bigl(z_A^{(2)}\bigr).
-
结果:A^{(2)}
就包含了 2-hop 范围的信息,因为 B^{(1)} 中有 C, D
的贡献。
GNN 的层数就是节点聚合邻居信息的迭代次数,对应了“节点感受 k-hop 邻域”的深度。每层中,节点会用上一层的邻居表示进行聚合并经过可学习的变换,最终得到本层新的节点表示。
同一层里,所有节点共享一组参数(同一个 MLP 或线性变换)
public boolean hasCycle(ListNode head) {
// 如果链表为空或者只有一个节点,直接返回无环
if (head == null || head.next == null) {
return false;
}
// 用 originalHead 保存最初的头节点
ListNode originalHead = head;
// 从 head.next 开始遍历,先把 head 与链表分离
ListNode cur = head.next;
ListNode pre = head;
// 断开 head 和后面的连接
head.next = null;
while (cur != null) {
// 如果当前节点又指回了 originalHead,则说明出现环
if (cur == originalHead) {
return true;
}
// 反转指针
ListNode temp = cur.next;
cur.next = pre;
// 移动 pre 和 cur
pre = cur;
cur = temp;
}
// 走到空指针,说明无环
return false;
}