md_files/科研/图神经网络.md
2025-03-18 12:46:59 +08:00

189 lines
5.8 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 图神经网络
图表示学习的本质是把节点映射成低维连续稠密的向量。这些向量通常被称为 **嵌入Embedding**,它们能够捕捉节点在图中的结构信息和属性信息,从而用于下游任务(如节点分类、链接预测、图分类等)。
- **低维**:将高维的原始数据(如邻接矩阵或节点特征)压缩为低维向量,减少计算和存储开销。
- **连续**:将离散的节点或图结构映射为连续的向量空间,便于数学运算和捕捉相似性。
- **稠密**:将稀疏的原始数据转换为稠密的向量,每个维度都包含有意义的信息。
### 对图数据进行深度学习的“朴素做法”
把图的邻接矩阵和节点特征“直接拼接”成固定维度的输入,然后将其送入一个深度神经网络(全连接层)进行学习。
![image-20250316142412685](D:\folder\test\output\image-20250316142412685.png)
这种做法面临重大问题,导致其**并不可行**
1. **$O(|V|^2)$ 参数量** ,参数量庞大
2. **无法适应不同大小的图** ,需要固定输入维度
3. **对节点顺序敏感** ,节点编号顺序一变,输入就完全变样,但其实图的拓扑并没变(仅节点编号/排列方式不同)。
```
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 层)。
- 这样形成一棵“邻域树”或“展开图”,其中每个节点都需要从其子节点(邻居)获取特征进行聚合。
![image-20250316152729679](D:\folder\test\output\image-20250316152729679.png)
![image-20250316152836156](D:\folder\test\output\image-20250316152836156.png)
**例子**
在图神经网络中,每一层的计算通常包括以下步骤:
1. **聚合Aggregation**:将邻居节点的特征聚合起来(如求和、均值、最大值等)。
2. **变换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)}$**
1. **节点 $A$ 的 1-hop 邻居**:只有 $B$。
2. **聚合**(示例:自 + sum 邻居):
$$
z_A^{(1)} \;=\; A^{(0)} + B^{(0)}
\;=\; [1.0,\,0.5] + [0.8,\,1.2]
\;=\; [1.8,\,1.7].
$$
3. **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)}** 聚合。
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$ 的信息。
2. **节点 $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;
}
```