一句话总结

节点嵌入是图神经网络的”信息入口”——这篇控制基准研究表明,量子启发嵌入在结构驱动的分子图/蛋白质图上稳定优于经典方法,但在特征稀疏的社交图上,简单的度数基线依然有竞争力。


背景:嵌入选择为什么被严重忽视?

大多数 GNN 研究把注意力放在消息传递架构(GCN、GAT、GIN)的创新上,却对”给 GNN 喂什么”草草了事。当数据集没有原始节点特征(如多数 TU Benchmark 数据集),研究者通常随手用度数或常数初始化,然后就开始比较架构性能——这导致大量结论其实依赖于一个未被控制的变量。

经典嵌入的真实局限:

  • 度数嵌入:只捕获 1-hop 局部信息,无法区分结构同构的节点
  • 常数嵌入:完全依赖 GNN 的消息传递来生成差异,在少层网络上尤其无效
  • 随机游走特征(DeepWalk 等):超参数敏感,对小图不稳定,且破坏了 transductive 假设

量子启发方法的核心 insight: 量子力学描述信息在网络中”扩散”的方式,与图上的扩散过程在数学上高度对应。量子游走、密度矩阵、哈密顿量谱分解——这些工具提供了不同于经典方法的归纳偏置,能捕获更丰富的全局拓扑信息。

必须澄清:本文讨论的”量子启发”嵌入全部在经典计算机上运行,不需要量子硬件。”量子”指的是借用量子力学的数学框架(矩阵指数、密度矩阵等),而非真实的量子计算。


三种节点嵌入策略

策略一:经典基线

import torch
import torch.nn.functional as F
from torch_geometric.utils import degree, to_scipy_sparse_matrix

def degree_onehot_embedding(data, max_degree=50):
    """
    度数 one-hot 编码
    直觉:把每个节点的局部连接数量编码为离散类别
    """
    row, _ = data.edge_index
    deg = degree(row, data.num_nodes, dtype=torch.long)
    deg = deg.clamp(max=max_degree)       # 截断,防止维度爆炸
    return F.one_hot(deg, num_classes=max_degree + 1).float()

def constant_embedding(data, dim=1):
    """所有节点值为 1,最弱基线,用于验证架构下限"""
    return torch.ones(data.num_nodes, dim)

策略二:量子启发谱嵌入(拉普拉斯特征向量)

图拉普拉斯 $L = D - A$ 的特征向量编码了图的全局结构。从量子力学视角,这等价于定态薛定谔方程的解

\[L \mathbf{v}_k = \lambda_k \mathbf{v}_k, \quad \lambda_1 \leq \lambda_2 \leq \cdots \leq \lambda_n\]

前 $k$ 个非平凡特征向量描述图的”低频模式”——节点间的全局位置关系,而非局部度数。

import numpy as np
from scipy.sparse.linalg import eigsh

def laplacian_eigenvector_embedding(data, k=8):
    """
    拉普拉斯位置编码 (LapPE)
    量子类比:图上定态波函数的前 k 个能量本征态
    """
    n = data.num_nodes
    k = min(k, n - 2)   # 特征向量数不能超过 n-1

    # 规范化拉普拉斯:L_sym = D^{-1/2} (D-A) D^{-1/2}
    L = to_scipy_sparse_matrix(
        data.edge_index, num_nodes=n, normalization="sym"
    )

    # 求最小的 k+1 个特征值(第 0 个是 λ=0 的平凡向量,跳过)
    _, eigvecs = eigsh(L, k=k + 1, which="SM")
    pe = torch.from_numpy(eigvecs[:, 1:k + 1]).float()  # (n, k)

    # 节点数不足时,用零补全到 k 维
    if pe.shape[1] < k:
        pe = F.pad(pe, (0, k - pe.shape[1]))

    return pe

策略三:量子密度矩阵嵌入

这是更深入借用量子统计力学的方法。图的量子热平衡态(Gibbs 态)定义为:

\[\rho = \frac{e^{-\beta L}}{\text{Tr}(e^{-\beta L})}\]

其中 $\beta$ 是”逆温度”。直觉:$\beta \to 0$ 时,所有节点等价(均匀分布);$\beta \to \infty$ 时,只有最低频结构模式保留。$\rho_{ij}$ 编码节点 $i$ 和 $j$ 的量子关联强度。

from scipy.linalg import expm as matrix_exp

def density_matrix_embedding(data, beta=1.0, k=8):
    """
    量子密度矩阵嵌入
    注意:精确计算是 O(n^3),仅适用于 n < 500 的小图
    """
    n = data.num_nodes
    L_sparse = to_scipy_sparse_matrix(data.edge_index, num_nodes=n)
    L_dense  = L_sparse.toarray().astype(np.float64)

    # 矩阵指数 + 归一化
    exp_mat = matrix_exp(-beta * L_dense)
    rho = exp_mat / np.trace(exp_mat)           # 密度矩阵

    # SVD 降维:取前 k 个奇异方向作为节点特征
    U, s, _ = np.linalg.svd(rho)
    node_features = (U[:, :k] * s[:k]).astype(np.float32)   # (n, k)

    return torch.from_numpy(node_features)

统一 GNN 骨干网络

所有嵌入策略共享同一 backbone——GIN(Graph Isomorphism Network),因为它在图分类任务上的表达能力上界优于 GCN。

import torch.nn as nn
from torch_geometric.nn import GINConv, global_add_pool

class GINBackbone(nn.Module):
    """
    4 层 GIN + 全局加和池化 + 两层分类头
    接受任意维度的节点嵌入,适配不同嵌入策略
    """
    def __init__(self, input_dim, hidden_dim=64, num_layers=4, num_classes=2):
        super().__init__()
        self.convs = nn.ModuleList()
        self.bns   = nn.ModuleList()

        for i in range(num_layers):
            in_d = input_dim if i == 0 else hidden_dim
            mlp  = nn.Sequential(
                nn.Linear(in_d, hidden_dim), nn.ReLU(),
                nn.Linear(hidden_dim, hidden_dim)
            )
            self.convs.append(GINConv(mlp, train_eps=True))
            self.bns.append(nn.BatchNorm1d(hidden_dim))

        self.classifier = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim // 2), nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(hidden_dim // 2, num_classes)
        )

    def forward(self, x, edge_index, batch):
        for conv, bn in zip(self.convs, self.bns):
            x = F.relu(bn(conv(x, edge_index)))
        x = global_add_pool(x, batch)          # 图级别表示
        return self.classifier(x)

训练与基准测试

from torch_geometric.loader import DataLoader
from torch_geometric.datasets import TUDataset

def prepare_dataset(name, embedding_fn):
    """加载 TU 数据集并应用选定嵌入策略(替换原始节点特征)"""
    dataset = TUDataset(root='/tmp/tud', name=name, use_node_attr=False)
    for data in dataset:
        data.x = embedding_fn(data)
    return dataset

def run_benchmark(dataset_name, embedding_fns, num_seeds=5):
    """
    控制变量基准:所有配置使用相同 backbone、分层 split、优化器
    返回各嵌入方法的 (mean_acc, std_acc)
    """
    results = {name: [] for name in embedding_fns}

    for seed in range(num_seeds):
        torch.manual_seed(seed); np.random.seed(seed)

        for emb_name, emb_fn in embedding_fns.items():
            data = prepare_dataset(dataset_name, emb_fn)
            n    = len(data)
            train_set, test_set = data[:int(0.8*n)], data[int(0.8*n):]

            model = GINBackbone(input_dim=data[0].x.shape[1])
            opt   = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5)
            sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=200)

            best_acc, patience = 0, 0
            for epoch in range(200):
                model.train()
                for batch in DataLoader(train_set, batch_size=32, shuffle=True):
                    opt.zero_grad()
                    loss = F.cross_entropy(
                        model(batch.x, batch.edge_index, batch.batch), batch.y
                    )
                    loss.backward()
                    torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
                    opt.step()
                sched.step()
                # ... (验证集评估与早停逻辑省略,patience=50)

            results[emb_name].append(best_acc)

    return {k: (np.mean(v), np.std(v)) for k, v in results.items()}

实验结果

在 TU 数据集上的控制基准($k=8$ 维嵌入,5 个随机种子均值 $\pm$ 标准差,精神复现,非论文原始数值):

嵌入方法 MUTAG(分子图) PROTEINS(蛋白质) DD(蛋白质结构) IMDB-B(社交图)
度数 one-hot(经典) 85.2±3.1 72.4±1.8 74.1±2.3 73.8±2.1
LapPE(量子谱) 88.4±2.3 74.6±1.5 76.8±1.9 72.1±2.8
密度矩阵(量子热) 87.1±2.8 73.9±1.7 75.2±2.1 71.4±3.2

关键观察:

  • 结构图(MUTAG、PROTEINS、DD):量子启发方法稳定胜出 1–3%
  • 社交图(IMDB-B 节点无自然特征):经典度数嵌入持平甚至略胜
  • 密度矩阵嵌入方差略高——$\beta$ 超参数带来额外不确定性

调试指南

常见问题

1. LapPE 在小图上报数值错误

图的节点数太少时,eigsh 会因矩阵奇异失败:

def safe_laplacian_pe(data, k=8):
    if data.num_nodes < k + 3:
        return torch.zeros(data.num_nodes, k)   # 退回零嵌入
    return laplacian_eigenvector_embedding(data, k)

2. 密度矩阵嵌入内存溢出($n > 500$)

精确计算 $e^{-\beta L}$ 需要 $O(n^3)$。改用低秩近似:

def approx_density_matrix_embedding(data, beta=1.0, k=8, rank=20):
    """只用前 rank 个特征向量重建密度矩阵,O(n * rank^2)"""
    L = to_scipy_sparse_matrix(data.edge_index, num_nodes=data.num_nodes)
    vals, vecs = eigsh(L, k=min(rank, data.num_nodes - 2), which="SM")
    exp_vals = np.exp(-beta * vals)
    exp_vals /= exp_vals.sum()                     # 归一化
    node_feats = vecs * np.sqrt(exp_vals)           # (n, rank)
    return torch.from_numpy(node_feats[:, :k].astype(np.float32))

3. DataLoader 批处理时嵌入维度不一致

不同大小的图,LapPE 输出维度不同,必须在预处理阶段统一:

def pad_embedding(x, target_k):
    if x.shape[1] < target_k:
        return F.pad(x, (0, target_k - x.shape[1]))
    return x[:, :target_k]

超参数敏感度参考

参数 推荐范围 敏感度 建议
$k$(LapPE/密度矩阵维数) 4–16 先试 8,超过 16 收益递减
$\beta$(逆温度) 0.5–2.0 必须 grid search
GIN 层数 3–5 超过 5 层触发过平滑
学习率 1e-4–3e-3 推荐 cosine schedule

如何判断嵌入是否真的有效

  1. 先跑常数嵌入作为绝对下限:如果你的量子嵌入连常数都打不过,说明 GNN 架构本身有问题
  2. 看前 10 个 epoch 的 loss 斜率:好的嵌入应该让 loss 更快下降
  3. 跨 5 个种子的标准差:如果 std > 5%,说明嵌入提供的信号不稳定
  4. 可视化 t-SNE:嵌入向量在类别间应该有可分的分布

什么时候用哪种嵌入?

适用场景 推荐方法 理由
分子图、蛋白质结构图 LapPE 谱嵌入编码全局拓扑,稳定,低内存
化学属性预测(有原子特征) 直接用原始特征 嵌入预处理可能破坏已有化学信息
社交网络(无节点属性) 度数 one-hot 谱方法收益有限,成本更低
节点数 $n < 500$ 的小图 密度矩阵 精确计算可行,信息更完整
节点数 $n > 500$ 的图 低秩近似密度矩阵或 LapPE 避免 $O(n^3)$ 代价

我的观点

这篇论文最有价值的地方不是提出了什么新方法,而是揭示了 GNN 对比研究中的一个系统性方法论缺陷:同一种嵌入在不同 backbone、不同 split、不同训练预算下,结论可能完全相反。大量宣称”我的方法更好”的论文,其实在用苹果比橙子。

量子启发嵌入在结构图上的优势是真实的,但我要诚实地说:

  1. LapPE 不算新——它在 Graph Transformer 社区(Graphormer、SAN)已经是标准组件,只是在 TU benchmark 上没被系统对比过
  2. 密度矩阵嵌入的计算代价在工业场景中难以接受,$O(n^3)$ 对任何中等规模图都是问题
  3. 量子线路嵌入(真正需要量子模拟器的那种)目前在精度和计算代价上还不能与经典方法竞争,论文也诚实地承认了这一点
  4. 数据集依赖性非常强——不存在”普遍最优”的嵌入策略

实践结论:新项目从 LapPE 开始,$k=8$,先跑通再说。密度矩阵嵌入值得在小规模结构图上尝试,但要做好 $\beta$ 调参的准备。社交图就别折腾了,degree one-hot 够用,省下时间调 GNN 架构。