目录
  1. 1. 一、感知机
    1. 1.1. 1.1 单层感知机
    2. 1.2. 1.2 感知机的局限性
  2. 2. 二、多层感知机(MLP)
    1. 2.1. 2.1 结构与万能逼近定理
    2. 2.2. 2.2 前向传播
    3. 2.3. 2.3 表示能力与深度
  3. 3. 三、反向传播
    1. 3.1. 3.1 计算图与链式法则
    2. 3.2. 3.2 梯度消失与梯度爆炸
  4. 4. 四、激活函数
    1. 4.1. 4.1 各类激活函数
    2. 4.2. 4.2 激活函数选择指南
    3. 4.3. 4.3 GELU 详解
  5. 5. 五、损失函数
    1. 5.1. 5.1 回归损失
    2. 5.2. 5.2 分类损失
    3. 5.3. 5.3 对比损失(Contrastive Loss)
  6. 6. 六、优化算法
    1. 6.1. 6.1 SGD
    2. 6.2. 6.2 Adam
    3. 6.3. 6.3 AdamW
  7. 7. 七、权重初始化
    1. 7.1. 7.1 Xavier 初始化(Glorot Initialization)
    2. 7.2. 7.2 He 初始化(Kaiming Initialization)
    3. 7.3. 7.3 在 PyTorch 中
  8. 8. 八、正则化
    1. 8.1. 8.1 L1/L2 正则化
    2. 8.2. 8.2 Dropout
    3. 8.3. 8.3 Batch Normalization
    4. 8.4. 8.4 数据增强
  9. 9. 九、学习率调度
    1. 9.1. 9.1 常用调度策略
    2. 9.2. 9.2 Warmup
  10. 10. 十、这些基础在 GNN 中的应用
  11. 11. 十一、优化景观:鞍点与局部极小点
    1. 11.1. 11.1 鞍点为何普遍
    2. 11.2. 11.2 对训练的启示
  12. 12. 十二、Batch Normalization 的数学推导
    1. 12.1. 12.1 正向传播
    2. 12.2. 12.2 反向传播(梯度穿过 BN 层)
    3. 12.3. 12.3 BN 为何有效:几种理论解释
  13. 13. 十三、Dropout 的集成学习视角
    1. 13.1. 13.1 Dropout 等价于指数级模型集成
    2. 13.2. 13.2 Dropout 的变体
  14. 14. 十四、训练实用技巧
    1. 14.1. 14.1 学习率查找器(LR Finder)
    2. 14.2. 14.2 梯度裁剪(Gradient Clipping)
    3. 14.3. 14.3 早停(Early Stopping)
    4. 14.4. 14.4 过拟合检查清单
    5. 14.5. 14.5 欠拟合检查清单
  15. 15. 面试/自查问题
【GNN原理解析】神经网络基础

神经网络是深度学习的基础构建块。GCN、GAT、GraphSAGE 等 GNN 模型的每一层本质上都是一个消息传递加一个前馈神经网络层。本文将系统梳理理解 GNN 所需的神经网络基础知识:感知机、多层感知机、反向传播、激活函数、损失函数、优化器和正则化策略。

一、感知机

1.1 单层感知机

感知机(Perceptron, Rosenblatt 1958)是最简单的神经网络模型:

y = sign(w · x + b)

其中 w 是权重向量,b 是偏置项,sign 是符号函数。

感知机是一个线性二分类器。它只能对线性可分的数据进行分类。对于 XOR 等非线性可分问题,单层感知机无能为力。这也直接导致了 1969 年 Minsky & Papert 对神经网络研究的悲观论断和”AI 寒冬”。

1.2 感知机的局限性

感知机的决策边界是一个超平面 w·x + b = 0。无法学习 XOR 函数:

XOR(0,0) = 0
XOR(0,1) = 1
XOR(1,0) = 1
XOR(1,1) = 0

在二维平面上,XOR 的四个点不能被任何一条直线分离。解决方法是引入隐藏层(多层感知机)。

二、多层感知机(MLP)

2.1 结构与万能逼近定理

一个具有一个隐藏层的 MLP:

h = σ(W₁ x + b₁)
y = W₂ h + b₂

万能逼近定理(Universal Approximation Theorem):具有至少一个隐藏层和任意”压缩”激活函数(如 sigmoid)的前馈网络,只要隐藏层有足够多的神经元,就能以任意精度逼近任何 Borel 可测函数。

推论:深度(层数)比宽度(每层神经元数)更有效地提升表达能力。一个深层窄网络可以用比浅层宽网络少得多的参数实现相同的逼近精度。

2.2 前向传播

import torch
import torch.nn as nn
import torch.nn.functional as F

class MLP(nn.Module):
def __init__(self, input_dim, hidden_dims, output_dim, dropout=0.0):
super().__init__()
layers = []
prev_dim = input_dim
for hidden_dim in hidden_dims:
layers.append(nn.Linear(prev_dim, hidden_dim))
layers.append(nn.BatchNorm1d(hidden_dim))
layers.append(nn.ReLU())
if dropout > 0:
layers.append(nn.Dropout(dropout))
prev_dim = hidden_dim
layers.append(nn.Linear(prev_dim, output_dim))
self.net = nn.Sequential(*layers)

def forward(self, x):
return self.net(x)

# 示例:输入 100 维,2 个隐藏层,输出 3 类
model = MLP(input_dim=100, hidden_dims=[256, 128], output_dim=3)
x = torch.randn(32, 100) # batch=32
y = model(x) # (32, 3)

2.3 表示能力与深度

深度的重要性可以用电路复杂度来类比。一个 d 层的网络可以表达需要 O(2^d) 个神经元的浅层网络才能表达的函数。这在 GNN 中也是类似的:更深的 GNN 覆盖更大的感受野,但也面临过平滑问题。

三、反向传播

3.1 计算图与链式法则

反向传播(Backpropagation, Rumelhart et al. 1986)是训练神经网络的核心算法。它基于计算图和链式法则。

对于一个简单的两层网络:

z₁ = W₁ x + b₁        (线性变换)
a₁ = σ(z₁) (激活函数)
z₂ = W₂ a₁ + b₂ (线性变换)
ŷ = softmax(z₂) (输出层)
L = -log ŷ_y (交叉熵损失,y 是真实标签)

反向传播逐层计算梯度:

∂L/∂z₂ = ŷ - one_hot(y)                    # softmax + CE 的简洁梯度
∂L/∂W₂ = ∂L/∂z₂ · a₁^T # 外积
∂L/∂b₂ = ∂L/∂z₂
∂L/∂a₁ = W₂^T · ∂L/∂z₂
∂L/∂z₁ = ∂L/∂a₁ ⊙ σ'(z₁) # 逐元素乘法
∂L/∂W₁ = ∂L/∂z₁ · x^T
∂L/∂b₁ = ∂L/∂z₁

3.2 梯度消失与梯度爆炸

当网络很深时,梯度通过链式法则反复相乘。考虑 L 层网络:

∂L/∂W₁ = ∂L/∂z_L · Π_{k=2}^{L-1} (W_k^T · diag(σ'(z_{k-1})))

如果激活函数的导数 |σ’(z)| < 1(如 sigmoid: σ’(z) ≤ 0.25),则梯度指数衰减 → **梯度消失**。如果权重矩阵的奇异值 > 1,则梯度指数增长 → 梯度爆炸

解决方案

  • 使用 ReLU(导数恒为 1)替代 sigmoid/tanh
  • Batch Normalization(稳定各层输入的分布)
  • 残差连接(提供梯度高速通道)
  • 权重初始化(Xavier/He 初始化)
  • 梯度裁剪(Clip gradients)

四、激活函数

4.1 各类激活函数

函数 公式 导数 特点
Sigmoid σ(x)=1/(1+e^{-x}) σ(x)(1-σ(x)) 输出 (0,1),梯度消失风险
Tanh (e^x-e^{-x})/(e^x+e^{-x}) 1-tanh²(x) 输出 (-1,1),零中心
ReLU max(0,x) 0 if x<0 else 1 计算快,缓解梯度消失
LeakyReLU max(αx,x), α=0.01 0.01 if x<0 else 1 解决 dying ReLU
PReLU max(αx,x), α 可学习 α if x<0 else 1 α 通过反向传播学习
ELU x if x>0 else α(e^x-1) 1 if x>0 else ELU(x)+α 负值有界,均值接近 0
GELU x·Φ(x) Transformer/现代 CNN 首选
Swish x·σ(βx) Google 提出,自门控

4.2 激活函数选择指南

  • 隐藏层默认选择:ReLU → LeakyReLU → GELU(现代网络的趋势)
  • 输出层:回归(无激活/ReLU)、二分类(Sigmoid)、多分类(Softmax)、多标签(Sigmoid)
  • GNN 中:通常使用 ReLU 或 ELU。GCN 原始论文使用 ReLU,GAT 使用 ELU

4.3 GELU 详解

GELU 在 Transformer(BERT/GPT)和现代 CNN(ConvNeXt)中被广泛使用:

GELU(x) = x · Φ(x) = x · 0.5 (1 + erf(x/√2))

其中 Φ(x) 是标准正态分布的累积分布函数。GELU 可以被理解为一种概率性的激活:以 Φ(x) 的概率保留输入 x,以 1-Φ(x) 的概率输出 0。这种自门控机制比 ReLU 的硬阈值更平滑,通常能带来小幅的性能提升。

五、损失函数

5.1 回归损失

损失 公式 特点
MSE (1/N) Σ(y_i-ŷ_i)² 对异常值敏感(梯度与误差成正比)
MAE (1/N) Σ y_i-ŷ_i
Huber 组合 MSE 和 MAE 小误差用 MSE,大误差用 MAE

5.2 分类损失

二分类交叉熵

L = -[y log(ŷ) + (1-y) log(1-ŷ)]

多分类交叉熵

L = -Σ_c y_c log(ŷ_c)

Focal Loss(处理类别不平衡):

FL(p_t) = -α_t (1-p_t)^γ log(p_t)

其中 γ ≥ 0 是聚焦参数(γ=0 时退化为 CE),(1-p_t)^γ 降低易分类样本的权重,让模型专注于难分类样本。

5.3 对比损失(Contrastive Loss)

在表示学习和 GNN 中常用:

L = y·d² + (1-y)·[m-d]₊²

其中 d 是样本对的距离,y=1 表正样本对,m 是 margin。正样本对距离应小,负样本对距离应大于 m。

六、优化算法

6.1 SGD

θ_{t+1} = θ_t - η ∇L(θ_t)

简单但收敛慢。加入 Momentum:

v_{t+1} = β v_t + (1-β) ∇L(θ_t)
θ_{t+1} = θ_t - η v_{t+1}

Momentum 在梯度方向一致时加速(如峡谷地形),在梯度震荡时平滑更新。

6.2 Adam

Adam(Kingma & Ba, 2015)结合了 Momentum 和 RMSProp:

m_t = β₁ m_{t-1} + (1-β₁) g_t       # 一阶矩(指数移动平均)
v_t = β₂ v_{t-1} + (1-β₂) g_t² # 二阶矩
m̂_t = m_t / (1-β₁^t) # 偏差修正
v̂_t = v_t / (1-β₂^t) # 偏差修正
θ_t = θ_{t-1} - η · m̂_t / (√v̂_t + ε)

超参数:η=0.001, β₁=0.9, β₂=0.999, ε=1e-8。

6.3 AdamW

AdamW(Loshchilov & Hutter, 2019)将权重衰减从 L2 正则化中解耦:

# Adam 中的 L2 正则化(错误实现):
g_t = ∇L(θ_t) + λ θ_t # 权重衰减混入了动量

# AdamW(正确实现):
g_t = ∇L(θ_t)
# ... Adam 的 m_t, v_t 更新 ...
θ_t = θ_{t-1} - η · m̂_t/(√v̂_t+ε) - η·λ·θ_{t-1} # 权重衰减独立于动量

AdamW 是现代 Transformer 和 GNN 训练的首选优化器,显著优于 Adam。

七、权重初始化

7.1 Xavier 初始化(Glorot Initialization)

适用于 sigmoid/tanh 激活函数。保持各层激活值和梯度的方差不变:

W ~ U( -√(6/(n_in+n_out)), √(6/(n_in+n_out)) )

W ~ N( 0, 2/(n_in+n_out) )

7.2 He 初始化(Kaiming Initialization)

适用于 ReLU 激活函数(ReLU 将一半输入置零,方差需要加倍):

W ~ N( 0, 2/n_in )

W ~ U( -√(6/n_in), √(6/n_in) )

7.3 在 PyTorch 中

import torch.nn.init as init

def init_weights(m):
if isinstance(m, nn.Linear):
init.kaiming_uniform_(m.weight, mode='fan_in', nonlinearity='relu')
if m.bias is not None:
init.zeros_(m.bias)
elif isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')

model.apply(init_weights)

八、正则化

8.1 L1/L2 正则化

L1(Lasso):鼓励稀疏性(权重精确为零):

Ω(θ) = λ Σ_i |θ_i|

L2(Ridge/Weight Decay):鼓励小权重(但不为零):

Ω(θ) = λ/2 Σ_i θ_i²

8.2 Dropout

训练时以概率 p 随机丢弃神经元:

class Dropout(nn.Module):
def forward(self, x):
if self.training:
mask = (torch.rand_like(x) > self.p).float()
return x * mask / (1 - self.p) # 缩放以保持期望不变
return x

“inverted dropout”中的 / (1-p) 确保测试时不需要缩放。现代实践中,dropout 主要用于全连接层;卷积层通常使用 BatchNorm 提供正则化,不需要 dropout。

8.3 Batch Normalization

对每个 mini-batch 的每个通道做标准化:

μ_B ← 1/m Σ_i x_i
σ²_B ← 1/m Σ_i (x_i - μ_B)²
x̂_i ← (x_i - μ_B) / √(σ²_B + ε)
y_i ← γ x̂_i + β

γ 和 β 是可学习的参数,使得网络可以在需要时恢复原始的分布。BN 在训练时使用 mini-batch 统计量,在测试时使用全体训练数据的移动平均统计量。

其他归一化方法

  • Layer Normalization:在特征维度上归一化(而不是 batch 维度),RNN/Transformer 首选,GNN 中也常用
  • Instance Normalization:每个样本每个通道独立归一化,风格迁移任务
  • Group Normalization:将通道分组,每组内归一化,小 batch 时效果好
  • GraphNorm:GNN 专用的归一化,考虑图的度数信息

8.4 数据增强

常见策略:随机裁剪、水平翻转、颜色抖动、随机旋转、Cutout/Random Erasing、Mixup/CutMix(样本混合)。

在 GNN 中对应:DropEdge(随机丢弃边)、Feature Masking(随机遮蔽节点特征)、Node Dropping(随机删除节点)、Subgraph Sampling。

九、学习率调度

9.1 常用调度策略

# StepLR:每 step_size 个 epoch 乘以 gamma
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)

# CosineAnnealing:余弦退火
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)

# ReduceLROnPlateau:验证损失不降时降低 lr
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.5, patience=5
)

# OneCycleLR:先线性上升再余弦下降
scheduler = torch.optim.lr_scheduler.OneCycleLR(
optimizer, max_lr=0.01, total_steps=total_steps
)

9.2 Warmup

在训练开始时使用较小的学习率,线性或指数增加到目标学习率:

def warmup_scheduler(optimizer, warmup_steps, target_lr):
for step in range(warmup_steps):
lr = target_lr * (step + 1) / warmup_steps
for param_group in optimizer.param_groups:
param_group['lr'] = lr

Warmup 在 Transformer 训练中至关重要,在 GNN 的大批量训练中也推荐使用。

十、这些基础在 GNN 中的应用

基础概念 在 GNN 中的应用
MLP GCN/GAT/GraphSAGE 每层都有一个 MLP 做特征变换
反向传播 端到端训练 GNN 的核心算法
ReLU/ELU GNN 层的激活函数
Cross Entropy 节点分类和图分类的损失函数
Adam/AdamW GNN 训练的默认优化器
初始化 GCN 层的 weight 初始化(通常用 Glorot)
Dropout GCN 的特征 dropout,DropEdge 是边的 dropout
BatchNorm/LayerNorm GNN 的归一化层(LayerNorm 在 GNN 中更常用)
残差连接 防止 GNN 过平滑的关键技术(GCNII, JK-Net)

十一、优化景观:鞍点与局部极小点

在高维神经网络优化中,一个反直觉的发现是:真正的局部极小点(local minima)在高维空间中其实很少见,更常见的是鞍点(saddle points)

11.1 鞍点为何普遍

在 $d$ 维参数空间中,一个临界点是局部极小点要求 Hessian 矩阵的所有 $d$ 个特征值都为正。在随机矩阵理论中,临界点的 Hessian 特征值近似服从 Wigner 半圆律,正负各半。因此获得一个全正 Hessian 的概率约为 $\sim e^{-O(d)}$(指数级小)。实际上,在高维神经网络中,绝大多数一阶临界点都是鞍点——部分方向上是局部最小值,其他方向上是局部最大值。

为什么训练会陷入鞍点附近缓慢前进? 在鞍点附近,某些方向的梯度极其微小,SGD 在鞍点附近的”平原”上会非常缓慢地漂移。Momentum 和 Adam 通过累积历史梯度能加速逃离鞍点。

11.2 对训练的启示

  • SGD + Momentum 是逃离鞍点的有效策略(Momentum 在平坦方向持续加速)
  • 学习率不能太小:在鞍点附近,梯度接近零,太小会导致训练停滞
  • 损失函数曲面并非想象中的”布满坑洞”,而是”山谷与平原交错的复杂地形”

十二、Batch Normalization 的数学推导

12.1 正向传播

对于 mini-batch $\mathcal{B} = {x_1, x_2, …, x_m}$:

$$ \mu_{\mathcal{B}} = \frac{1}{m} \sum_{i=1}^{m} x_i $$

$$ \sigma_{\mathcal{B}}^2 = \frac{1}{m} \sum_{i=1}^{m} (x_i - \mu_{\mathcal{B}})^2 $$

$$ \hat{x}_i = \frac{x_i - \mu_{\mathcal{B}}}{\sqrt{\sigma_{\mathcal{B}}^2 + \epsilon}} $$

$$ y_i = \gamma \hat{x}_i + \beta \equiv \text{BN}_{\gamma, \beta}(x_i) $$

12.2 反向传播(梯度穿过 BN 层)

对于输出端的梯度 $\frac{\partial L}{\partial y_i}$,穿过 BN 层的梯度为:

梯度对输入的导数

$$ \frac{\partial L}{\partial x_i} = \frac{\gamma}{m \sqrt{\sigma_{\mathcal{B}}^2 + \epsilon}} \left[ m \frac{\partial L}{\partial y_i} - \sum_{j=1}^{m} \frac{\partial L}{\partial y_j} - \hat{x}_i \sum_{j=1}^{m} \frac{\partial L}{\partial y_j} \hat{x}_j \right] $$

推导分为三步:先求 $\frac{\partial L}{\partial \hat{x}i}$,再通过 $\hat{x}i$ 对 $\mu{\mathcal{B}}$ 和 $\sigma{\mathcal{B}}^2$ 的依赖,应用链式法则,最终组合为对 $x_i$ 的梯度。

梯度对 $\gamma$ 和 $\beta$ 的导数

$$ \frac{\partial L}{\partial \gamma} = \sum_{i=1}^{m} \frac{\partial L}{\partial y_i} \cdot \hat{x}_i $$

$$ \frac{\partial L}{\partial \beta} = \sum_{i=1}^{m} \frac{\partial L}{\partial y_i} $$

12.3 BN 为何有效:几种理论解释

理论 解释
Internal Covariate Shift(原始论文) BN 减少了各层输入的分布偏移,使各层学习更稳定
Loss Landscape Smoothing(平滑景观) BN 使得损失函数曲面更平滑,梯度更可预测,允许使用更大的学习率
Gradient Predictive(梯度可预测) BN 减少了梯度对参数尺度变化的敏感性(参数缩放不影响 BN 后的输出)
Implicit Regularization(隐式正则化) BN 在 mini-batch 中引入噪声(统计量估计误差),有轻微的正则化效果

十三、Dropout 的集成学习视角

13.1 Dropout 等价于指数级模型集成

标准 Dropout 在训练时以概率 $p$ 随机使神经元输出为零,测试时不做 Dropout 但将输出乘以 $(1-p)$(保持期望一致)。从集成学习角度看,每次前向传播使用的是一个不同的子网络(被 Dropout mask 选中的神经元集合)。因此,整个训练过程实际上是在训练 $2^n$($n$ 为神经元总数)个子网络的集合。

测试时的加权平均:测试时不使用 Dropout 的全网络输出,几何平均意义上近似等于所有子网络预测的加权几何平均:

$$ p_{\text{ensemble}}(y \mid x) \approx \prod_{\mu} p_{\mu}(y \mid x)^{1/|\mu|} $$

其中 $\mu$ 是子网络 mask。

13.2 Dropout 的变体

变体 方法 适用场景
Standard Dropout 独立丢弃每个神经元 全连接层
Spatial Dropout 整通道丢弃 卷积层
DropConnect 随机丢弃权重(而非神经元输出) 实验性
DropEdge 随机丢弃图的边 GNN
DropNode 随机丢弃图的节点 GNN
Concrete Dropout 自动学习 dropout 率 $p$ 需要自适应的场景

十四、训练实用技巧

14.1 学习率查找器(LR Finder)

Leslie Smith 提出的 LR Finder 帮助自动找到合适的学习率:

Algorithm: LR Finder

1. 选择一个极小的初始学习率 (e.g., 1e-7)
2. 每 batch 将 LR 指数增长 (e.g., factor=1.5)
3. 记录 loss vs LR 曲线
4. 找到 loss 下降最快区间的中点作为 base LR:
- loss 还没有开始下降 → LR 太小
- loss 震荡/上升 → LR 太大
- 选择 loss 下降最陡峭区域的 1/10 ~ 1/2 作为初始 LR
# 简化的 LR Finder 伪代码
def find_lr(model, train_loader, optimizer, init_lr=1e-7, final_lr=10):
lr_logs, loss_logs = [], []
lr = init_lr
for batch in train_loader:
lr_logs.append(lr)
loss = train_one_batch(model, batch, optimizer, lr)
loss_logs.append(loss.item())
lr *= 1.1 # 指数增长
if loss > 4 * min(loss_logs) or lr > final_lr:
break
# 选择 loss 最陡下降区域
smoothed_loss = smooth(loss_logs)
best_lr = lr_logs[argmin(smoothed_loss)] / 10
return best_lr

14.2 梯度裁剪(Gradient Clipping)

梯度裁剪限制梯度的最大范数,防止梯度爆炸:

# norm-based clipping(最常用)
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

# value-based clipping(不常用,可能导致梯度方向偏离)
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=1.0)

在 RNN、Transformer 和深层 GNN 中,梯度裁剪通常是必须的

14.3 早停(Early Stopping)

早停是防止过拟合最经典且有效的方法:

best_val_loss = float('inf')
patience_counter = 0
best_model_state = None

for epoch in range(epochs):
train_loss = train_one_epoch(...)
val_loss = validate(...)

if val_loss < best_val_loss:
best_val_loss = val_loss
patience_counter = 0
best_model_state = copy.deepcopy(model.state_dict())
else:
patience_counter += 1

if patience_counter >= patience:
print(f"Early stopping at epoch {epoch}")
model.load_state_dict(best_model_state)
break

14.4 过拟合检查清单

当模型出现过拟合时,按顺序排查:

  1. 添加更多数据/增强:最强力的手段
  2. 降低模型容量:减少层数或隐藏单元
  3. 增加正则化强度:weight_decay, dropout
  4. 早停:监控验证损失
  5. Batch Normalization:自带轻微正则化
  6. 检查数据泄漏:训练集和验证集是否存在交集

14.5 欠拟合检查清单

  1. 训练更久:增大 epoch
  2. 增加模型容量:增加层数/宽度
  3. 降低正则化强度
  4. 增大 batch size 或调整学习率
  5. 检查数据质量:标签是否正确?特征是否有信息量?

面试/自查问题

Q1:为什么 ReLU 比 sigmoid 更好?

  1. 缓解梯度消失:sigmoid 的导数是 σ(x)(1-σ(x)),最大仅 0.25,且 |x| 较大时导数接近 0。ReLU 在 x>0 时导数恒为 1
  2. 计算快:ReLU 只需 max(0,x),无需指数运算
  3. 稀疏激活:x<0 时输出为 0,使网络有稀疏性(类似生物神经元),可能有正则化效果
  4. 但 ReLU 也有问题:dying ReLU(x<0 时梯度为 0,神经元”死亡”),Leaky ReLU/ELU 等变体解决此问题

Q2:Adam 为何需要偏差修正?

Adam 初始化时 m_0=0, v_0=0。在最初的几步中,指数移动平均 m_t 和 v_t 偏向 0。这不公平地惩罚了初始的参数更新。偏差修正 m̂_t = m_t/(1-β₁^t) 补偿了初始步骤的偏置。随着 t→∞,修正因子趋于 1,因此只在训练初期显著。

Q3:GNN 为什么通常使用 LayerNorm 而非 BatchNorm?

  1. GNN 处理的图大小不一,batch 内图 size 差异大,BN 的 batch 统计不稳定
  2. BN 要求每个 sample 的每个特征通道有相同的含义,这在可变大小的图中不成立
  3. LN 在特征维度上归一化,与节点数无关,对图大小不敏感
  4. 实验证明 LN 在大多数 GNN 任务上优于 BN

Q4:SGD 在高维优化中为什么经常比 Adam 泛化更好?

这是一个经验发现(Wilson et al., 2017)但机理仍有争议的现象。主要解释:

  1. Adam 的自适应学习率使得模型在训练后期”太灵活”,容易过度适应训练数据的噪声模式(而 SGD 的均匀步长提供了一种隐式正则化)
  2. SGD 倾向于收敛到更”平坦”的极小值(flat minima),而 Adam 可能收敛到更”尖锐”的极小值(sharp minima)。平坦的极小值通常有更好的泛化能力,因为参数微扰不会显著改变损失
  3. 在 CV 任务中常使用 SGD + Momentum,在 NLP/GNN 中 Adam/AdamW 仍是主流——实践中 AdamW 经过充分调参也能达到 SGD 的泛化水平(甚至更好)

Q5:深层 GNN 中提到的”过平滑”(over-smoothing)是什么?神经网络基础能提供什么解决思路?

过平滑是指当 GNN 堆叠过多层时,所有节点的表示趋于相同(收敛到图的全局平均或 stationary distribution),丢失了节点间的区分性信息。

从神经网络基础的角度看,解决方案包括:

  • 残差连接(Residual Connection):$H^{(l+1)} = H^{(l)} + \text{GNNConv}(H^{(l)})$,为初始特征信息提供恒等映射的路径
  • JK-Net(Jumping Knowledge Networks):将每一层的输出保留,最后拼接/池化所有层的表示,相当于灵活的多尺度”DenseNet-style”跳跃连接
  • DropEdge:随机丢弃边(类比 Dropout 的正则化效果),减缓过平滑的发生速度
  • GCNII:引入 Initial Residual(每层与初始表示 $H^{(0)}$ 的残差连接)+ Identity Mapping(类似 ResNet 的恒等映射),使得即使 64 层的 GNN 也能有效训练

Q6:BatchNorm 和 LayerNorm 在数学推导上的本质区别是什么?

两者的核心区别在于归一化的维度不同:

  • BatchNorm:对 mini-batch 维度做归一化。对于形状 $(N, C, H, W)$ 的张量,对每个通道独立地在 $(N, H, W)$ 上求均值和方差(即每个 channel 一对 $\mu, \sigma$)。BN 的统计量与 batch size N 高度相关,在 N 较小时不稳定。
  • LayerNorm:对特征/层维度做归一化。对于形状 $(N, D)$ 的张量(D 是特征维度),对每个样本独立地在 $(D,)$ 上求均值和方差(即每个样本一对 $\mu, \sigma$)。LN 不依赖 batch size,因此在 batch 较小时更稳定,且在 RNN/Transformer/GNN 中更自然(序列长度可变,batch 内 padding 多)。

形式化地,对于 LayerNorm 输入 $x \in \mathbb{R}^{B \times D}$:

$$ \mu_b = \frac{1}{D} \sum_{d=1}^{D} x_{bd}, \quad \sigma_b^2 = \frac{1}{D} \sum_{d=1}^{D} (x_{bd} - \mu_b)^2 $$

$$ \hat{x}_{bd} = \frac{x_{bd} - \mu_b}{\sqrt{\sigma_b^2 + \epsilon}}, \quad y_{bd} = \gamma_d \hat{x}_{bd} + \beta_d $$

Q7:什么是”梯度检查”(Gradient Checking),如何用它来验证反向传播实现的正确性?

梯度检查是验证数值计算的梯度与反向传播解析梯度一致的诊断工具。方法如下:

def gradient_check(model, x, y, epsilon=1e-5, tolerance=1e-4):
# 1. 通过反向传播计算所有参数的梯度
loss = criterion(model(x), y)
loss.backward()
grad_analytic = {n: p.grad.clone() for n, p in model.named_parameters()}

# 2. 逐参数做数值梯度计算(双侧差分)
grad_numerical = {}
for name, param in model.named_parameters():
grad_numerical[name] = torch.zeros_like(param)
it = np.nditer(param.detach().numpy(), flags=['multi_index'])
while not it.finished:
idx = it.multi_index

old_val = param[idx].item()
param[idx] = old_val + epsilon
loss_plus = criterion(model(x), y).item()

param[idx] = old_val - epsilon
loss_minus = criterion(model(x), y).item()

param[idx] = old_val # 恢复原值
grad_numerical[name][idx] = (loss_plus - loss_minus) / (2 * epsilon)
it.iternext()

# 3. 比较两者(相对误差)
for name in grad_analytic:
diff = torch.abs(grad_analytic[name] - grad_numerical[name])
norm = torch.abs(grad_analytic[name]) + torch.abs(grad_numerical[name])
rel_error = diff / (norm + 1e-8)
max_rel_error = rel_error.max().item()
assert max_rel_error < tolerance, \
f"{name}: max relative error {max_rel_error:.2e} > {tolerance}"
print("Gradient check passed!")

注意事项:使用双侧差分($\frac{f(x+\epsilon)-f(x-\epsilon)}{2\epsilon}$)而非单侧差分,误差为 $O(\epsilon^2)$ 而非 $O(\epsilon)$。$\epsilon$ 通常取 $10^{-5}$ 左右。梯度检查应在简化模型上运行(少量权重、不使用 Dropout/BN),因为 Dropout 引入随机性、BN 在训练和测试模式下的行为不同。

文章作者: Leo·Cheung
文章链接: http://tufusi.com/2022/03/22/%E3%80%90GNN%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90%E3%80%91%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E5%9F%BA%E7%A1%80/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ONE·PIECE
打赏
  • 微信
  • 支付宝

评论