2. 图的基本概念:节点、边、邻接矩阵、度矩阵

好了,咱们直接进入正题。图神经网络,说白了就是跟图打交道。你要是连图长什么样都说不清楚,那后面的模型根本没法聊。我最早接触图的时候,脑子里全是朋友圈的好友关系、蛋白质分子的结构,还有论文引用网络。这些东西抽象成数学结构,就是今天我们要讲的这几样东西。

2.1 节点与边:图的基本单元

图由什么组成?就两个核心元素:节点和边。

节点(也叫顶点,vertex):代表实体。一个人、一个化合物、一个网页,都可以是一个节点。
边(edge):代表关系。朋友关系、化学反应、超链接,都是边。

我个人习惯把节点想象成「点」,边想象成「线」。你在纸上画几个圈,再用线连起来,这就是一个图。

举个例子。我有一个社交网络的简化版:三个人,A认识B,B认识C,但A和C不认识。那么节点就是{A, B, C},边就是{(A,B), (B,C)}。

# 用Python表示一个简单图
# 节点集合
nodes = {'A', 'B', 'C'}

# 边集合(无向图,用元组表示)
edges = {('A', 'B'), ('B', 'C')}

这里有个小细节:边可以是有方向的,也可以是没有方向的。你想想看,微博上你关注了某大V,但大V不一定关注你,这就是有向边。而微信好友关系,你加了我我也加了你,这就是无向边。

属性 无向图 有向图
边方向 没有方向 有方向(箭头)
边表示 (u, v) 无序 (u → v) 有序
常见场景 朋友网络、分子结构 引用关系、关注关系

在实际项目中,我建议你一开始就把图的方向想清楚。我自己就吃过亏——有一次做论文引用网络分析,把有向边当无向边处理了,结果聚类效果一塌糊涂。

2.2 邻接矩阵:图的数据表示

图在计算机里怎么存?最直接的方式就是邻接矩阵。它就是一个方阵,行和列都对应节点。如果节点i和节点j之间有边,那矩阵的(i, j)位置就是1,否则是0。

就拿刚才三个人(A, B, C)的例子,邻接矩阵长这样:

# 邻接矩阵(无向图)
#     A  B  C
# A  [0, 1, 0]
# B  [1, 0, 1]
# C  [0, 1, 0]

# 用NumPy表示
import numpy as np

adjacency_matrix = np.array([
    [0, 1, 0],
    [1, 0, 1],
    [0, 1, 0]
])

为什么是0和1?因为我们只关心有没有边,不关心边的权重。如果是加权图,那就可以放权重值,比如通话时长、消息频率之类的。

注意!邻接矩阵有个致命的缺点:稀疏性。一个社交网络可能有上亿用户,但每个人的好友数量远小于总人数。结果就是矩阵里全是0,占用大量内存。我曾经用邻接矩阵存了一个百万节点的图,内存直接爆了。从那以后,小于一万节点我才用邻接矩阵,再大就得换别的方法了。

有向图的邻接矩阵不是对称的。看看这个例子,A→B,B→C,C→A:

# 有向图邻接矩阵
#     A  B  C
# A  [0, 1, 0]  A指向B
# B  [0, 0, 1]  B指向C
# C  [1, 0, 0]  C指向A

嗯,这里要注意:无向图邻接矩阵一定是对称的,有向图不一定。

2.3 度矩阵:节点的统计量

度矩阵?这个名字听着挺吓人。说白了,它就是一个对角矩阵,对角线上的值就是每个节点的度。

什么是度?无向图中,度就是跟这个节点相连的边的数量。刚才的例子,A有1条边(连B),B有2条边(连A和C),C有1条边(连B)。

# 度矩阵
#     A  B  C
# A  [1, 0, 0]
# B  [0, 2, 0]
# C  [0, 0, 1]

就这么简单。有向图里则分为出度和入度:出度是从这个节点出去的边数,入度是进入这个节点的边数。

你可能会问,度矩阵有什么用?我告诉你,在图神经网络里,度矩阵几乎处处用到。最经典的例子就是拉普拉斯矩阵:L = D - A。这个L在图的谱理论、图卷积网络里都是根基。

# 计算拉普拉斯矩阵
import numpy as np

A = np.array([[0,1,0],
              [1,0,1],
              [0,1,0]])

D = np.diag([1,2,1])  # 度矩阵

L = D - A  # 拉普拉斯矩阵
print(L)
# [[ 1, -1,  0],
#  [-1,  2, -1],
#  [ 0, -1,  1]]

我个人的一个经验:度矩阵在归一化的时候非常有用。比如做GCN时,会用 D^{-1/2} A D^{-1/2} 来做对称归一化,这可以防止深度模型里数值爆炸。你不妨自己用小的图试试。

2.4 本章知识体系全景

为了让你对这几个概念的关系一目了然,我画了一张结构图。注意看节点、边、邻接矩阵、度矩阵之间的逻辑链条。

图的基本概念关系图 图 (Graph) 节点 (Node/Vertex) 边 (Edge) 邻接矩阵 (Adjacency Matrix) 度矩阵 (Degree Matrix) 核心推导:L = D - A,拉普拉斯矩阵是GCN的数学基础

这张图展示了一个清晰的链条:图由节点和边组成,节点的关系可以用邻接矩阵记录,节点的连接数量由度矩阵统计,而这两个矩阵拼接起来,就得到了图神经网络最常用的拉普拉斯矩阵。

记住一句话:邻接矩阵描述谁跟谁有连接,度矩阵描述每个节点有多少连接,拉普拉斯矩阵描述连接之间的差异。 这三板斧,后面30%的GNN模型都在用。

2.5 实战小结与代码对照

我们最后用Python把这几个概念串起来。假设有一个小图,节点是0,1,2,3,边是(0,1), (1,2), (2,3), (3,0)。

import numpy as np

# 节点数
n = 4

# 手动构造邻接矩阵
A = np.zeros((n, n))
edges = [(0,1), (1,2), (2,3), (3,0), (1,3)]
for u, v in edges:
    A[u, v] = 1
    A[v, u] = 1  # 无向图

print("邻接矩阵 A:")
print(A)

# 度矩阵
D = np.diag(A.sum(axis=1))
print("\n度矩阵 D:")
print(D)

# 拉普拉斯矩阵
L = D - A
print("\n拉普拉斯矩阵 L:")
print(L)
邻接矩阵 A:
[[0. 1. 0. 1.]
 [1. 0. 1. 1.]
 [0. 1. 0. 1.]
 [1. 1. 1. 0.]]

度矩阵 D:
[[2. 0. 0. 0.]
 [0. 3. 0. 0.]
 [0. 0. 2. 0.]
 [0. 0. 0. 3.]]

拉普拉斯矩阵 L:
[[ 2. -1.  0. -1.]
 [-1.  3. -1. -1.]
 [ 0. -1.  2. -1.]
 [-1. -1. -1.  3.]]

你看,度矩阵的对角线是节点度数,拉普拉斯矩阵的对角线也是度数,但非对角线变成了-1。这里有个坑:如果你用有向图,度矩阵的处理方式完全不同。我自己刚开始做图数据时,就因为没分清有向和无向,导致拉普拉斯矩阵计算错误,模型训练一直不收敛。血的教训。

好了,这几个基本概念你脑子里应该清楚了。下节课我们会用Nextflow把这些图数据的管理和预处理组织起来,进入实战环节。记住,图的数学表示是GNN的起点,后面所有花式操作都绕不开这些基本元件。