非凸优化深度指南
引言:为什么非凸优化在人工智能时代至关重要
优化理论是现代数学与计算科学的交叉领域,其中非凸优化(Non-Convex Optimization)在过去二十年间经历了从理论冷门到实践热点的根本性转变。这一转变的核心驱动力正是深度学习的崛起——现代神经网络的损失函数 landscape 极其复杂,充满了无数局部最优、鞍点和平台区,而这恰恰是非凸优化理论研究的重点对象。
与凸优化不同,非凸优化问题通常无法在多项式时间内找到全局最优解。然而,这并不意味着非凸优化问题不可求解。事实上,深度学习的实践表明,通过适当的算法设计(如随机梯度下降及其变体),我们往往能够找到足够好的局部最优解,这些解在测试数据上表现出色的泛化能力。
本指南将系统性地介绍非凸优化的理论基础、关键概念、核心算法和前沿研究方向,特别强调这些理论工具在神经网络训练中的实际应用场景。无论你是希望深入理解深度学习训练 dynamics 的研究者,还是希望在工程实践中更有效地调优模型的工程师,本指南都将为你提供坚实的理论与实践基础。
第一部分:非凸优化的数学基础
1.1 优化问题的数学表述
定义 1.1.1(优化问题):一般优化问题可以表述为: 其中 称为目标函数(objective function), 称为决策变量或模型参数。
定义 1.1.2(全局最优与局部最优):
- 全局最优解(Global Minimum): 称为 的全局最优解,如果 对所有 成立
- 局部最优解(Local Minimum): 称为 的局部最优解,如果存在邻域 使得 对所有 成立
定义 1.1.3(严格局部最优): 是严格局部最优解,如果存在 使得:
定义 1.1.4(鞍点): 称为鞍点(saddle point),如果它既是局部最大值点也是局部最小值点,即: 其中 是两个不同方向上的子集。
1.2 凸性与非凸性
定义 1.2.1(凸函数):函数 称为凸函数,如果:
定义 1.2.2(严格凸函数): 称为严格凸的,如果上述不等式对 和 是严格的。
定义 1.2.3(非凸函数):不是凸函数的函数称为非凸函数。非凸函数可以有复杂的 landscape,包括局部最优、鞍点、plat 等等。
例 1.2.1(经典的非凸函数):
-
Rastrigin 函数(高维多模态函数): 这是一个经典的多模态测试函数,具有许多局部最优。
-
Himmelblau 函数(双峰函数): 这个函数有四个局部最优和一个鞍点。
-
Rosenbrock 函数(“香蕉函数”): 这个函数有一个弯曲的全局最优谷。
1.3 必要条件与充分条件
定理 1.3.1(一阶必要性):如果 是局部最优且 在 处可微,则:
定理 1.3.2(二阶必要性):如果 是局部最优且 在 处二阶可微,则:
定理 1.3.3(二阶充分性):如果 满足: 则 是严格局部最优解。
定义 1.3.1(驻点):满足 的点 称为驻点(critical point)。驻点包括局部最优、鞍点和平台区中心。
1.4 神经网络损失函数的特殊性质
现代神经网络,如CNN、 RNN、 Transformer 等,其损失函数具有以下特殊性质:
性质 1.4.1(规模化性质):许多神经网络的损失函数在某些参数方向上具有尺度不变性。例如,ReLU 网络的参数缩放可能不改变输出:
性质 1.4.2(过度参数化):现代大模型通常具有远多于数据点数量的参数。这种过度参数化可能导致:
- 每个局部最优实际上也是全局最优
- 所有局部最优具有相似的损失值
- 鞍点可以被容易地逃离
性质 1.4.3(景观对称性):神经网络存在大量对称性,例如交换同一层中不同神经元的位置不改变函数。这导致:
- 大量等价的局部最优
- 景观的高度简并性
第二部分:鞍点问题与逃离策略
2.1 鞍点的分类与性质
定义 2.1.1(鞍点的严格定义):设 二阶可微, 满足 。 是鞍点当且仅当 既有正特征值也有负特征值。
定理 2.1.1(鞍点的拓扑分类):
- 严格鞍点(Strict Saddle): 在某个方向上负曲率(),在其他方向上正曲率
- 退化鞍点(Degenerate Saddle): 包含零特征值
在深度学习中的意义:大量研究表明,神经网络训练早期遇到的” plateau “主要是由鞍点而非局部最优引起的。这可以从以下角度理解:
- 高维空间中,局部最优是稀有事件
- 随机矩阵理论预测高维随机 Hessian 在原点附近几乎肯定是鞍点
- 梯度噪声有助于逃离鞍点
2.2 逃离鞍点的算法策略
策略 2.2.1(梯度噪声):随机梯度 可以分解为: 其中 是采样噪声。噪声的协方差与负曲率方向对齐时,有助于逃离鞍点。
定理 2.2.1(噪声驱动的逃离):考虑带噪声的梯度下降: 其中 是均值为零、协方差为 的噪声。在适当条件下,逃离鞍点的速率正比于 。
策略 2.2.2(Hessian 信息):利用二阶信息逃离鞍点:
-
信赖域方法(Trust Region Methods):
-
共轭梯度法:通过 Hessian-vector 乘积快速求解二阶近似
-
负曲率方向下降(Negative Curvature Descent):找到负曲率方向 ()并沿该方向下降
策略 2.2.3(动量方法):
Heavy Ball 方法:
Nesterov 动量:
动量可以帮助算法越过小的鞍点和平台区。
2.3 鞍点检测方法
方法 2.3.1(特征值检测):通过检查 Hessian 的最小特征值判断鞍点:
# 检查 Hessian 的最小特征值
H = compute_hessian(x)
eigenvalues = np.linalg.eigvalsh(H)
min_eigenvalue = np.min(eigenvalues)
if min_eigenvalue < -threshold:
print("检测到鞍点")
elif abs(min_eigenvalue) < threshold:
print("接近退化点")方法 2.3.2(Griewank 函数法):使用 Griewank 函数在特定维度上的行为检测收敛到鞍点。
第三部分:局部最优的价值与”好”局部最优
3.1 高维空间中局部最优的稀缺性
定理 3.1.1(随机矩阵结果):设 是 对称随机矩阵,其特征值服从 Wigner 半圆分布(当元素独立同分布且满足某些条件时)。则:
- 原点附近()的特征值比例约为
- 当 很大时,原点附近几乎无特征值
推论 3.1.1:在高维空间中,严格鞍点(负特征值方向存在)几乎无处不在,而局部最优(所有特征值非负)极其稀少。
在深度学习中的含义:这解释了为什么神经网络训练时,我们很少遇到真正的局部最优——我们遇到的大多是鞍点或平台区。
3.2 局部最优与全局最优的等价性
定理 3.2.1(过度参数化的神经网络):考虑具有 个隐藏神经元和 个训练样本的两层神经网络。存在性结果:如果 足够大(),则每个局部最优都是全局最优。
定理 3.2.2(Chernodarov-Marcenko 定理):对于某些类型的神经网络,当参数数量远大于样本数量时,损失函数的局部最优值与全局最优值相等。
3.3 Flat Minima 与泛化能力
定义 3.3.1(Flat Minima):局部最优 称为 flat minima,如果 在 的一个邻域内变化缓慢,即 较小。
度量方法:
-
Sharpness:
-
Hessian 范数:
-
有效平坦度(Effective Flatness):通过扰动实验测量
定理 3.3.1(Keshar 猜想/实证):Flat minima 与良好的泛化能力相关。这一观察催生了以下研究方向:
-
Sharpness-Aware Minimization (SAM):
-
Entropy-SGD:在损失函数中加入局部熵项以寻找更平坦的区域
3.4 局部最优的多样性
定理 3.4.1(Loss Landscape 的复杂性):神经网络损失函数景观具有以下特点:
- 碗状谷:存在从输入到输出的单调路径
- Funnel 结构:存在漏斗状结构便于收敛
- 大量次优盆:存在许多具有相似损失值的不同盆
在实践中的意义:这意味着我们不需要找到”最好”的局部最优——任何”足够好”的局部最优都能提供良好的性能。
第四部分:梯度下降法的理论基础
4.1 梯度下降法的收敛性分析
算法 4.1.1(梯度下降法):
定理 4.1.1(凸函数的收敛性):设 是凸函数且 是 -Lipschitz 连续,即:
取固定步长 ,则:
定理 4.1.2(非凸函数的收敛性):对于一般非凸函数,梯度下降法满足: 其中 是梯度噪声方差。
定义 4.1.1(-近似最优): 称为 -近似最优,如果 。
4.2 梯度 Lipschitz 条件
定义 4.2.1(梯度 Lipschitz 连续):函数 称为 -smooth(或 是 -Lipschitz 连续),如果:
定理 4.2.1(Smoothness 性质):如果 是 -smooth,则:
这给出了 的二次上界,是分析收敛性的关键。
与 Hessian 的关系:如果 二阶可微,则 -smoothness 等价于 对所有 成立。
4.3 步长选择策略
策略 4.3.1(固定步长):,要求 保证收敛。
策略 4.3.2(递减步长):,满足 和 。
策略 4.3.3(线搜索):
- 回溯线搜索(Backtracking Line Search):从 开始,如果 ,则
- Armijo 条件:
策略 4.3.4(自适应步长):如 AdaGrad、RMSProp、Adam 等方法自动调节步长。
4.4 梯度下降的变体
变体 4.4.1(投影梯度下降):当约束存在时 :
变体 4.4.2(梯度扰动):
变体 4.4.3(随机梯度下降): 其中 是真实梯度的无偏估计。
第五部分:随机梯度下降法
5.1 随机梯度下降的理论框架
定义 5.1.1(随机优化问题):
假设 5.1.1(基本假设):
- A1(有界方差):
- A2(有界梯度):
- A3(Lipschitz 连续):
5.2 SGD 的收敛性分析
定理 5.2.1(SGD 收敛性):在上述假设下,取递减步长 ,则:
定理 5.2.2(带重球动量的 SGD):
在适当条件下,Heavy-ball SGD 可以达到 的收敛速率(在非凸情形下)。
5.3 学习率调度策略
策略 5.3.1(阶梯衰减):
策略 5.3.2(余弦退火):
策略 5.3.3(Warmup + Decay):
5.4 Mini-batch 的作用
定理 5.4.1(Mini-batch 方差缩减):设使用大小为 的 mini-batch:
实际考虑:
- 小 batch ():噪声大,有助于探索
- 大 batch ():噪声小,收敛稳定,但可能陷入 sharp minima
- 渐进 batch:初期小 batch,后期大 batch
第六部分:自适应学习方法
6.1 AdaGrad 算法
算法 6.1.1(AdaGrad):
优点:自动调节学习率,适合稀疏梯度 缺点:学习率持续衰减,长期训练可能停滞
6.2 RMSProp 算法
算法 6.2.1(RMSProp):
改进:使用指数移动平均替代累积,避免学习率过度衰减
6.3 Adam 算法
算法 6.3.1(Adam):
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) # 偏差校正
x_{t+1} = x_t - η * m̂_t / (√v̂_t + ε) # 更新
默认值:
6.4 收敛性比较
定理 6.4.1(Adam 的理论保证):在适当的假设下,Adam 可以达到 的 regret bound。
实践观察:
- Adam 通常收敛快,但泛化性能可能不如 SGD + Momentum
- AMSGrad 和 AdaBound 等变体试图结合两者的优点
- 近期研究表明,Adam 的泛化差距可能源于其自适应学习率过于激进
第七部分:二阶方法的理论基础
7.1 牛顿法
算法 7.1.1(牛顿法):
定理 7.1.1(局部收敛性):如果 是严格局部最优且 ,则牛顿法局部二阶收敛。
优点:收敛速度快 缺点:
- 需要计算 Hessian( 存储)
- 需要求解线性系统 ( 计算)
- Hessian 可能不正定(非凸问题)
7.2 拟牛顿法
核心思想:通过低秩更新逼近 Hessian,避免直接计算和求逆。
算法 7.2.1(BFGS): 其中 ,。
L-BFGS:使用 个历史 对近似 Hessian,避免存储完整矩阵。
7.3 信赖域方法
算法 7.3.1(信赖域):
更新规则:
- 如果 :接受步长,扩大信赖域
- 否则:拒绝步长,缩小信赖域
优点:无需线搜索,对曲率变化自适应
7.4 自然梯度法
定义 7.4.1(Fisher 信息矩阵):
算法 7.4.1(自然梯度下降):
优点:参数空间的几何感知更新 应用:Natural Evolution Strategies、TRPO、ACKTR
第八部分:收敛速率与复杂度下界
8.1 一阶方法的复杂度下界
定理 8.1.1(一阶 Oracle 下界):对于 -smooth 的非凸函数,存在函数使得任何一阶算法需要至少 次迭代才能达到 -近似最优。
证明思路:构造具有”香肠”结构的函数,每个 plateau 需要 步才能穿越。
8.2 二阶方法的改进上界
定理 8.2.1(二阶方法复杂度):使用 Hessian 信息,可以达到 的迭代复杂度。
定理 8.2.2(负曲率方向的作用):利用负曲率方向的算法可以逃离鞍点,避免被困在 维的平台区。
8.3 梯度范数作为收敛准则
替代准则:由于高维非凸问题中 可能表示鞍点,使用以下准则:
- Hessian 正定性检测:
- 方向导数检测:
- 噪声尺度检测:当 接近噪声水平时停止
第九部分:神经网络训练的优化视角
9.1 神经网络优化问题的结构
问题表述:
特点:
- 极大( 到 甚至更多)
- 非凸
- 梯度计算昂贵(需要前向传播所有数据)
- 通常使用随机近似
9.2 初始化策略
策略 9.2.1(Xavier/Glorot 初始化):
策略 9.2.2(He 初始化):
策略 9.2.3(LSUV):逐层进行单位方差正交化初始化。
9.3 归一化技术
批归一化(Batch Normalization):
层归一化(Layer Normalization):
权重归一化(Weight Normalization):
作用:改变损失 landscape,提高收敛速度和稳定性。
9.4 残差连接的作用
定义 9.4.1(残差连接):
几何解释:残差连接使得优化更容易,因为:
- 恒等映射提供了一条”高速公路”
- 梯度可以直接流过网络
- 有效降低了网络的 condition number
第十部分:现代优化算法实践
10.1 学习率区间与恢复
策略 10.1.1(退役(Warmdown)):从高学习率开始,逐步降低。
策略 10.1.2(周期学习率):
- 余弦退火
- 循环学习率(Cyclic Learning Rate)
策略 10.1.3(快照集成):在周期学习率的每个周期结束时保存模型,最后集成。
10.2 早停与泛化
策略 10.2.1(早停):
train_loss, val_loss = [], []
for epoch in range(max_epochs):
train_loss.append(train_step())
val_loss.append(validation())
if patience_counter > patience and val_loss[-1] > val_loss[-patience_counter-1]:
restore_weights()
break
策略 10.2.2(集成与微调):训练多个模型并集成,或在最后几个 epoch 降低学习率进行微调。
10.3 梯度裁剪
策略 10.3.1(梯度裁剪):
grad_norm = torch.norm(torch.stack([torch.norm(p.grad) for p in model.parameters()]))
if grad_norm > max_norm:
clip_coef = max_norm / grad_norm
for p in model.parameters():
p.grad.mul_(clip_coef)作用:防止梯度爆炸,稳定训练过程,特别是在 RNN 和 Transformer 训练中非常重要。
10.4 混合精度训练
策略 10.4.1(FP16/BF16 训练):
- 使用半精度浮点数加速计算
- 维护 FP32 主权重副本避免精度损失
- 使用动态损失缩放防止下溢
第十一部分:代码实现
11.1 Python 实现:基础优化器
"""
非凸优化的基础实现
包含:梯度下降、SGD、动量法等核心算法
"""
import numpy as np
from typing import Callable, Tuple, Optional, Dict
from dataclasses import dataclass
import matplotlib.pyplot as plt
@dataclass
class OptimizationResult:
"""优化结果的数据类"""
trajectory: np.ndarray
losses: np.ndarray
gradients_norm: np.ndarray
final_point: np.ndarray
final_loss: float
iterations: int
converged: bool
metadata: Dict
class FunctionWithGradient:
"""可微函数接口"""
def __init__(self, func: Callable, grad: Optional[Callable] = None,
hess: Optional[Callable] = None):
self.func = func
self.grad = grad
self.hess = hess
def numerical_gradient(self, x: np.ndarray, eps: float = 1e-8) -> np.ndarray:
"""数值梯度计算"""
n = len(x)
grad = np.zeros(n)
f0 = self.func(x)
for i in range(n):
x_plus = x.copy()
x_plus[i] += eps
grad[i] = (self.func(x_plus) - f0) / eps
return grad
def numerical_hessian(self, x: np.ndarray, eps: float = 1e-5) -> np.ndarray:
"""数值 Hessian 计算"""
n = len(x)
hess = np.zeros((n, n))
f0 = self.func(x)
for i in range(n):
for j in range(n):
x_pp = x.copy()
x_pp[i] += eps
x_pp[j] += eps
x_pm = x.copy()
x_pm[i] += eps
x_mp = x.copy()
x_mp[j] += eps
hess[i, j] = (self.func(x_pp) - self.func(x_pm)
- self.func(x_mp) + f0) / (eps * eps)
return hess
class GradientDescent:
"""梯度下降法"""
def __init__(self, func: Callable, grad: Optional[Callable] = None,
lr: float = 0.01, max_iter: int = 1000,
tol: float = 1e-6, verbose: bool = False):
self.func = func
self.grad = grad if grad else self._numerical_grad
self.lr = lr
self.max_iter = max_iter
self.tol = tol
self.verbose = verbose
def _numerical_grad(self, x: np.ndarray) -> np.ndarray:
eps = 1e-8
n = len(x)
grad = np.zeros(n)
f0 = self.func(x)
for i in range(n):
x_plus = x.copy()
x_plus[i] += eps
grad[i] = (self.func(x_plus) - f0) / eps
return grad
def optimize(self, x0: np.ndarray) -> OptimizationResult:
"""执行优化"""
x = x0.copy()
trajectory = [x.copy()]
losses = [self.func(x)]
gradients_norm = []
for iteration in range(self.max_iter):
g = self.grad(x)
grad_norm = np.linalg.norm(g)
gradients_norm.append(grad_norm)
if grad_norm < self.tol:
if self.verbose:
print(f"收敛于迭代 {iteration}")
break
x = x - self.lr * g
trajectory.append(x.copy())
losses.append(self.func(x))
return OptimizationResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
gradients_norm=np.array(gradients_norm),
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < self.tol,
metadata={'method': 'Gradient Descent', 'lr': self.lr}
)
class MomentumGD:
"""带动量的梯度下降(Heavy Ball)"""
def __init__(self, func: Callable, grad: Optional[Callable] = None,
lr: float = 0.01, momentum: float = 0.9,
max_iter: int = 1000, tol: float = 1e-6):
self.func = func
self.grad = grad if grad else self._numerical_grad
self.lr = lr
self.momentum = momentum
self.max_iter = max_iter
self.tol = tol
def _numerical_grad(self, x: np.ndarray) -> np.ndarray:
eps = 1e-8
n = len(x)
grad = np.zeros(n)
f0 = self.func(x)
for i in range(n):
x_plus = x.copy()
x_plus[i] += eps
grad[i] = (self.func(x_plus) - f0) / eps
return grad
def optimize(self, x0: np.ndarray) -> OptimizationResult:
x = x0.copy()
v = np.zeros_like(x)
trajectory = [x.copy()]
losses = [self.func(x)]
gradients_norm = []
for iteration in range(self.max_iter):
g = self.grad(x)
grad_norm = np.linalg.norm(g)
gradients_norm.append(grad_norm)
if grad_norm < self.tol:
break
v = self.momentum * v + self.lr * g
x = x - v
trajectory.append(x.copy())
losses.append(self.func(x))
return OptimizationResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
gradients_norm=np.array(gradients_norm),
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < self.tol,
metadata={'method': 'Momentum GD', 'lr': self.lr,
'momentum': self.momentum}
)
class NesterovMomentum:
"""Nesterov 动量法"""
def __init__(self, func: Callable, grad: Optional[Callable] = None,
lr: float = 0.01, momentum: float = 0.9,
max_iter: int = 1000, tol: float = 1e-6):
self.func = func
self.grad = grad if grad else self._numerical_grad
self.lr = lr
self.momentum = momentum
self.max_iter = max_iter
self.tol = tol
def _numerical_grad(self, x: np.ndarray) -> np.ndarray:
eps = 1e-8
n = len(x)
grad = np.zeros(n)
f0 = self.func(x)
for i in range(n):
x_plus = x.copy()
x_plus[i] += eps
grad[i] = (self.func(x_plus) - f0) / eps
return grad
def optimize(self, x0: np.ndarray) -> OptimizationResult:
x = x0.copy()
v = np.zeros_like(x)
trajectory = [x.copy()]
losses = [self.func(x)]
gradients_norm = []
for iteration in range(self.max_iter):
# Nesterov 预览步骤
g = self.grad(x - self.momentum * v)
grad_norm = np.linalg.norm(g)
gradients_norm.append(grad_norm)
if grad_norm < self.tol:
break
v = self.momentum * v + g
x = x - self.lr * v
trajectory.append(x.copy())
losses.append(self.func(x))
return OptimizationResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
gradients_norm=np.array(gradients_norm),
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < self.tol,
metadata={'method': 'Nesterov Momentum', 'lr': self.lr,
'momentum': self.momentum}
)
class AdaGrad:
"""AdaGrad 优化器"""
def __init__(self, func: Callable, grad: Optional[Callable] = None,
lr: float = 0.1, eps: float = 1e-8,
max_iter: int = 1000, tol: float = 1e-6):
self.func = func
self.grad = grad if grad else self._numerical_grad
self.lr = lr
self.eps = eps
self.max_iter = max_iter
self.tol = tol
def _numerical_grad(self, x: np.ndarray) -> np.ndarray:
eps = 1e-8
n = len(x)
grad = np.zeros(n)
f0 = self.func(x)
for i in range(n):
x_plus = x.copy()
x_plus[i] += eps
grad[i] = (self.func(x_plus) - f0) / eps
return grad
def optimize(self, x0: np.ndarray) -> OptimizationResult:
x = x0.copy()
G = np.zeros_like(x)
trajectory = [x.copy()]
losses = [self.func(x)]
gradients_norm = []
for iteration in range(self.max_iter):
g = self.grad(x)
grad_norm = np.linalg.norm(g)
gradients_norm.append(grad_norm)
if grad_norm < self.tol:
break
G += g ** 2
x = x - self.lr * g / (np.sqrt(G) + self.eps)
trajectory.append(x.copy())
losses.append(self.func(x))
return OptimizationResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
gradients_norm=np.array(gradients_norm),
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < self.tol,
metadata={'method': 'AdaGrad', 'lr': self.lr}
)
class Adam:
"""Adam 优化器"""
def __init__(self, func: Callable, grad: Optional[Callable] = None,
lr: float = 0.001, beta1: float = 0.9, beta2: float = 0.999,
eps: float = 1e-8, max_iter: int = 1000, tol: float = 1e-6):
self.func = func
self.grad = grad if grad else self._numerical_grad
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.eps = eps
self.max_iter = max_iter
self.tol = tol
def _numerical_grad(self, x: np.ndarray) -> np.ndarray:
eps = 1e-8
n = len(x)
grad = np.zeros(n)
f0 = self.func(x)
for i in range(n):
x_plus = x.copy()
x_plus[i] += eps
grad[i] = (self.func(x_plus) - f0) / eps
return grad
def optimize(self, x0: np.ndarray) -> OptimizationResult:
x = x0.copy()
m = np.zeros_like(x)
v = np.zeros_like(x)
trajectory = [x.copy()]
losses = [self.func(x)]
gradients_norm = []
for t in range(1, self.max_iter + 1):
g = self.grad(x)
grad_norm = np.linalg.norm(g)
gradients_norm.append(grad_norm)
if grad_norm < self.tol:
break
# 更新一阶矩和二阶矩
m = self.beta1 * m + (1 - self.beta1) * g
v = self.beta2 * v + (1 - self.beta2) * g ** 2
# 偏差校正
m_hat = m / (1 - self.beta1 ** t)
v_hat = v / (1 - self.beta2 ** t)
# 更新参数
x = x - self.lr * m_hat / (np.sqrt(v_hat) + self.eps)
trajectory.append(x.copy())
losses.append(self.func(x))
return OptimizationResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
gradients_norm=np.array(gradients_norm),
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < self.tol,
metadata={'method': 'Adam', 'lr': self.lr, 'beta1': self.beta1,
'beta2': self.beta2}
)
def compare_optimizers():
"""比较不同优化器的性能"""
print("=" * 60)
print("优化器性能比较")
print("=" * 60)
# 定义测试函数
# Rosenbrock 函数 (香蕉函数)
def rosenbrock(x):
return (1 - x[0])**2 + 100 * (x[1] - x[0]**2)**2
def rosenbrock_grad(x):
dx0 = -2 * (1 - x[0]) - 400 * x[0] * (x[1] - x[0]**2)
dx1 = 200 * (x[1] - x[0]**2)
return np.array([dx0, dx1])
# 测试函数
test_cases = [
("Rosenbrock", rosenbrock, rosenbrock_grad, np.array([-1.0, 1.0])),
]
optimizers = [
("GD", GradientDescent(rosenbrock, rosenbrock_grad, lr=0.001)),
("Momentum", MomentumGD(rosenbrock, rosenbrock_grad, lr=0.001, momentum=0.9)),
("Nesterov", NesterovMomentum(rosenbrock, rosenbrock_grad, lr=0.001, momentum=0.9)),
("AdaGrad", AdaGrad(rosenbrock, rosenbrock_grad, lr=0.5)),
("Adam", Adam(rosenbrock, rosenbrock_grad, lr=0.001)),
]
for name, func, grad, x0 in test_cases:
print(f"\n测试函数: {name}")
print("-" * 40)
print(f"初始点: {x0}")
print(f"初始值: {func(x0):.4f}")
results = []
for opt_name, optimizer in optimizers:
result = optimizer.optimize(x0.copy())
results.append((opt_name, result))
print(f"\n{opt_name}:")
print(f" 迭代次数: {result.iterations}")
print(f" 最终点: {result.final_point}")
print(f" 最终损失: {result.final_loss:.6f}")
print(f" 收敛: {result.converged}")
if __name__ == "__main__":
compare_optimizers()11.2 Python 实现:二阶方法与鞍点逃离
"""
二阶优化方法和鞍点逃离策略的实现
包含:牛顿法、拟牛顿法、信赖域方法等
"""
import numpy as np
from typing import Callable, Tuple, Optional, Dict
from dataclasses import dataclass
@dataclass
class SecondOrderResult:
"""二阶方法结果"""
trajectory: np.ndarray
losses: np.ndarray
eigenvalues_history: list
final_point: np.ndarray
final_loss: float
iterations: int
converged: bool
metadata: Dict
class NewtonMethod:
"""牛顿法"""
def __init__(self, func: Callable, grad: Callable, hess: Callable,
max_iter: int = 100, tol: float = 1e-6,
regularization: float = 0.0):
self.func = func
self.grad = grad
self.hess = hess
self.max_iter = max_iter
self.tol = tol
self.regularization = regularization
def optimize(self, x0: np.ndarray) -> SecondOrderResult:
x = x0.copy()
trajectory = [x.copy()]
losses = [self.func(x)]
eigenvalues_history = []
for iteration in range(self.max_iter):
g = self.grad(x)
grad_norm = np.linalg.norm(g)
if grad_norm < self.tol:
break
H = self.hess(x)
# 添加正则化确保正定
if self.regularization > 0:
H = H + self.regularization * np.eye(len(x))
# 谱分解
eigenvalues, eigenvectors = np.linalg.eigh(H)
eigenvalues_history.append(eigenvalues.copy())
# 尝试求解 H d = -g
try:
d = np.linalg.solve(H, -g)
except np.linalg.LinAlgError:
# Hessian 奇异,使用梯度方向
d = -g / np.linalg.norm(g)
# 线搜索
alpha = self._line_search(x, d, g)
x = x + alpha * d
trajectory.append(x.copy())
losses.append(self.func(x))
return SecondOrderResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
eigenvalues_history=eigenvalues_history,
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < self.tol,
metadata={'method': 'Newton', 'regularization': self.regularization}
)
def _line_search(self, x: np.ndarray, d: np.ndarray,
g: np.ndarray, alpha0: float = 1.0,
c: float = 1e-4, rho: float = 0.5) -> float:
"""Armijo 线搜索"""
alpha = alpha0
f0 = self.func(x)
while alpha > 1e-10:
f_new = self.func(x + alpha * d)
if f_new <= f0 + c * alpha * np.dot(g, d):
return alpha
alpha *= rho
return alpha
class BFGS:
"""BFGS 拟牛顿法"""
def __init__(self, func: Callable, grad: Callable,
max_iter: int = 100, tol: float = 1e-6):
self.func = func
self.grad = grad
self.max_iter = max_iter
self.tol = tol
def optimize(self, x0: np.ndarray) -> SecondOrderResult:
x = x0.copy()
n = len(x)
# 初始 Hessian 近似(单位矩阵)
B = np.eye(n)
g = self.grad(x)
trajectory = [x.copy()]
losses = [self.func(x)]
eigenvalues_history = []
for iteration in range(self.max_iter):
grad_norm = np.linalg.norm(g)
if grad_norm < self.tol:
break
# 求解 B d = -g
try:
d = np.linalg.solve(B, -g)
except np.linalg.LinAlgError:
d = -g
# 线搜索
alpha = self._line_search(x, d, g)
s = alpha * d
x_new = x + s
g_new = self.grad(x_new)
y = g_new - g
# BFGS 更新
if np.dot(y, s) > 1e-10: # 确保曲率条件
rho = 1.0 / np.dot(y, s)
I = np.eye(n)
B = (I - rho * np.outer(s, y)) @ B @ (I - rho * np.outer(y, s)) + rho * np.outer(s, s)
# 记录历史
eigenvalues, _ = np.linalg.eigh(B)
eigenvalues_history.append(eigenvalues.copy())
x = x_new
g = g_new
trajectory.append(x.copy())
losses.append(self.func(x))
return SecondOrderResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
eigenvalues_history=eigenvalues_history,
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < self.tol,
metadata={'method': 'BFGS'}
)
def _line_search(self, x: np.ndarray, d: np.ndarray,
g: np.ndarray, alpha0: float = 1.0,
c: float = 1e-4, rho: float = 0.5) -> float:
alpha = alpha0
f0 = self.func(x)
while alpha > 1e-10:
f_new = self.func(x + alpha * d)
if f_new <= f0 + c * alpha * np.dot(g, d):
return alpha
alpha *= rho
return alpha
class TrustRegionMethod:
"""信赖域方法"""
def __init__(self, func: Callable, grad: Callable, hess: Callable,
max_iter: int = 100, initial_delta: float = 1.0,
eta: float = 0.1, tol: float = 1e-6):
self.func = func
self.grad = grad
self.hess = hess
self.max_iter = max_iter
self.initial_delta = initial_delta
self.eta = eta
self.tol = tol
def _solve_trust_region(self, g: np.ndarray, H: np.ndarray,
delta: float) -> np.ndarray:
"""使用截断共轭梯度求解信赖域子问题"""
n = len(g)
d = np.zeros(n)
r = -g
# CG 迭代
for _ in range(n):
if np.linalg.norm(r) < 1e-10:
break
if np.linalg.norm(d) >= delta:
# 找到边界上的解
d = d * (delta / np.linalg.norm(d))
break
H_r = H @ r if np.linalg.norm(r) > 1e-10 else np.zeros(n)
# 使用有限差分近似 H r
eps = 1e-8
H_r = np.array([(self.hess(d + eps * r_i) - self.hess(d)) @ r / eps
if np.linalg.norm(r) > 1e-10 else 0
for r_i in range(n)])
alpha = np.dot(r, r) / (np.dot(r, H_r) + 1e-10)
d_new = d + alpha * r
if np.linalg.norm(d_new) >= delta:
# 插值到边界
a = np.dot(d_new - d, d_new)
b = 2 * np.dot(d, d_new - d)
c = np.dot(d, d) - delta**2
t = (-b + np.sqrt(b**2 - 4*a*c)) / (2*a)
d = d + t * (d_new - d)
break
r_new = r - alpha * H_r
beta = np.dot(r_new, r_new) / (np.dot(r, r) + 1e-10)
r = r_new
d = d_new
return d
def optimize(self, x0: np.ndarray) -> SecondOrderResult:
x = x0.copy()
delta = self.initial_delta
trajectory = [x.copy()]
losses = [self.func(x)]
eigenvalues_history = []
g = self.grad(x)
H = self.hess(x)
eigenvalues_history.append(np.linalg.eigvalsh(H))
for iteration in range(self.max_iter):
grad_norm = np.linalg.norm(g)
if grad_norm < self.tol:
break
# 求解信赖域子问题
d = self._solve_trust_region(g, H, delta)
# 计算实际与预测的改进比
actual_reduction = self.func(x) - self.func(x + d)
predicted_reduction = -np.dot(g, d) - 0.5 * np.dot(d, H @ d)
rho = actual_reduction / (predicted_reduction + 1e-10)
if rho > self.eta:
x = x + d
g = self.grad(x)
H = self.hess(x)
if rho > 0.75:
delta = min(2 * delta, 10.0)
else:
delta = delta / 2
# 记录历史
eigenvalues_history.append(np.linalg.eigvalsh(H))
trajectory.append(x.copy())
losses.append(self.func(x))
return SecondOrderResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
eigenvalues_history=eigenvalues_history,
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < self.tol,
metadata={'method': 'Trust Region'}
)
class SaddlePointEscaper:
"""鞍点逃离器"""
def __init__(self, func: Callable, grad: Callable, hess: Callable,
escaper: str = 'noise', noise_scale: float = 0.1):
self.func = func
self.grad = grad
self.hess = hess
self.escaper = escaper
self.noise_scale = noise_scale
def detect_saddle(self, x: np.ndarray, threshold: float = -0.01) -> Tuple[bool, np.ndarray]:
"""检测鞍点并返回逃离方向"""
eigenvalues, eigenvectors = np.linalg.eigh(self.hess(x))
min_eigenvalue = np.min(eigenvalues)
is_saddle = min_eigenvalue < threshold
# 负曲率方向
if is_saddle:
escape_direction = eigenvectors[:, np.argmin(eigenvalues)]
else:
escape_direction = None
return is_saddle, escape_direction, min_eigenvalue
def optimize_with_escape(self, x0: np.ndarray, max_iter: int = 100,
tol: float = 1e-6) -> SecondOrderResult:
x = x0.copy()
trajectory = [x.copy()]
losses = [self.func(x)]
eigenvalues_history = []
for iteration in range(max_iter):
g = self.grad(x)
grad_norm = np.linalg.norm(g)
eigenvalues, eigenvectors = np.linalg.eigh(self.hess(x))
eigenvalues_history.append(eigenvalues.copy())
if grad_norm < tol:
break
# 检测鞍点
is_saddle, escape_dir, min_eig = self.detect_saddle(x)
if is_saddle:
# 使用逃离策略
if self.escaper == 'neg_curvature':
# 沿负曲率方向逃离
x = x + 0.1 * escape_dir * np.abs(min_eig)
elif self.escaper == 'noise':
# 添加随机噪声
noise = np.random.randn(len(x)) * self.noise_scale
x = x + noise
# 梯度下降步骤
step_size = 0.01
x = x - step_size * g
trajectory.append(x.copy())
losses.append(self.func(x))
return SecondOrderResult(
trajectory=np.array(trajectory),
losses=np.array(losses),
eigenvalues_history=eigenvalues_history,
final_point=x,
final_loss=losses[-1],
iterations=len(losses) - 1,
converged=grad_norm < tol,
metadata={'method': f'Saddle Escape ({self.escaper})'}
)
def test_second_order_methods():
"""测试二阶方法"""
print("=" * 60)
print("二阶优化方法测试")
print("=" * 60)
# 定义测试函数
def func(x):
return (1 - x[0])**2 + 100 * (x[1] - x[0]**2)**2
def grad(x):
dx0 = -2 * (1 - x[0]) - 400 * x[0] * (x[1] - x[0]**2)
dx1 = 200 * (x[1] - x[0]**2)
return np.array([dx0, dx1])
def hess(x):
dxx = 2 - 400 * (x[1] - x[0]**2) + 800 * x[0]**2
dxy = -400 * x[0]
dyx = -400 * x[0]
dyy = 200
return np.array([[dxx, dxy], [dyx, dyy]])
x0 = np.array([-1.0, 1.0])
methods = [
("Newton", NewtonMethod(func, grad, hess, regularization=0.01)),
("BFGS", BFGS(func, grad)),
("Trust Region", TrustRegionMethod(func, grad, hess)),
("Saddle Escape (noise)", SaddlePointEscaper(func, grad, hess,
escaper='noise')),
]
for name, method in methods:
print(f"\n{name}:")
print("-" * 40)
result = method.optimize(x0.copy())
print(f" 迭代次数: {result.iterations}")
print(f" 最终点: {result.final_point}")
print(f" 最终损失: {result.final_loss:.8f}")
print(f" 收敛: {result.converged}")
if __name__ == "__main__":
test_second_order_methods()11.3 Python 实现:神经网络优化器
"""
神经网络优化器的 PyTorch 实现
包含:SGD、Momentum、Adam、RMSprop、AdamW 等
"""
import numpy as np
from typing import Dict, List, Optional, Tuple
import matplotlib.pyplot as plt
class SimpleNeuralNetwork:
"""简化神经网络用于优化测试"""
def __init__(self, input_dim: int, hidden_dim: int, output_dim: int):
np.random.seed(42)
self.W1 = np.random.randn(input_dim, hidden_dim) * 0.1
self.b1 = np.zeros(hidden_dim)
self.W2 = np.random.randn(hidden_dim, hidden_dim) * 0.1
self.b2 = np.zeros(hidden_dim)
self.W3 = np.random.randn(hidden_dim, output_dim) * 0.1
self.b3 = np.zeros(output_dim)
self.params = [self.W1, self.b1, self.W2, self.b2, self.W3, self.b3]
self.param_shapes = [p.shape for p in self.params]
def relu(self, x):
return np.maximum(0, x)
def relu_grad(self, x):
return (x > 0).astype(float)
def softmax(self, x):
exp_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
return exp_x / np.sum(exp_x, axis=-1, keepdims=True)
def forward(self, X):
self.z1 = X @ self.W1 + self.b1
self.a1 = self.relu(self.z1)
self.z2 = self.a1 @ self.W2 + self.b2
self.a2 = self.relu(self.z2)
self.z3 = self.a2 @ self.W3 + self.b3
self.output = self.softmax(self.z3)
return self.output
def backward(self, X, y, loss):
m = X.shape[0]
# 输出层梯度
dz3 = self.output.copy()
dz3[np.arange(m), y] -= 1
dz3 /= m
dW3 = self.a2.T @ dz3
db3 = np.sum(dz3, axis=0)
da2 = dz3 @ self.W3.T
dz2 = da2 * self.relu_grad(self.z2)
dW2 = self.a1.T @ dz2
db2 = np.sum(dz2, axis=0)
da1 = dz2 @ self.W2.T
dz1 = da1 * self.relu_grad(self.z1)
dW1 = X.T @ dz1
db1 = np.sum(dz1, axis=0)
self.grads = [dW1, db1, dW2, db2, dW3, db3]
return self.grads
def update(self, grads, lr):
for i, (param, grad) in enumerate(zip(self.params, grads)):
param -= lr * grad
self.params[i] = param
self.W1, self.b1, self.W2, self.b2, self.W3, self.b3 = self.params
def cross_entropy_loss(self, y_pred, y_true):
m = y_true.shape[0]
log_likelihood = -np.log(y_pred[np.arange(m), y_true] + 1e-8)
return np.mean(log_likelihood)
class SGDOptimizer:
"""随机梯度下降优化器"""
def __init__(self, params: List[np.ndarray], lr: float = 0.01,
momentum: float = 0.0, dampening: float = 0.0,
weight_decay: float = 0.0, nesterov: bool = False):
self.params = params
self.lr = lr
self.momentum = momentum
self.dampening = dampening
self.weight_decay = weight_decay
self.nesterov = nesterov
self.velocity = [np.zeros_like(p) for p in params]
def step(self, grads: List[np.ndarray]):
for i, (param, grad) in enumerate(zip(self.params, grads)):
if self.weight_decay > 0:
grad = grad + self.weight_decay * param
if self.momentum > 0:
if self.nesterov:
grad = grad + self.momentum * self.velocity[i]
else:
grad = grad + self.momentum * self.velocity[i]
self.velocity[i] = self.momentum * self.velocity[i] + (1 - self.dampening) * grad
param -= self.lr * self.velocity[i]
else:
param -= self.lr * grad
def zero_grad(self):
pass
class AdamOptimizer:
"""Adam 优化器"""
def __init__(self, params: List[np.ndarray], lr: float = 0.001,
beta1: float = 0.9, beta2: float = 0.999,
eps: float = 1e-8, weight_decay: float = 0.0):
self.params = params
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.eps = eps
self.weight_decay = weight_decay
self.m = [np.zeros_like(p) for p in params]
self.v = [np.zeros_like(p) for p in params]
self.t = 0
def step(self, grads: List[np.ndarray]):
self.t += 1
for i, (param, grad) in enumerate(zip(self.params, grads)):
if self.weight_decay > 0:
grad = grad + self.weight_decay * param
# 更新一阶矩估计
self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * grad
# 更新二阶矩估计
self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * (grad ** 2)
# 偏差校正
m_hat = self.m[i] / (1 - self.beta1 ** self.t)
v_hat = self.v[i] / (1 - self.beta2 ** self.t)
# 更新参数
param -= self.lr * m_hat / (np.sqrt(v_hat) + self.eps)
class RMSpropOptimizer:
"""RMSprop 优化器"""
def __init__(self, params: List[np.ndarray], lr: float = 0.01,
alpha: float = 0.99, eps: float = 1e-8,
weight_decay: float = 0.0, momentum: float = 0.0):
self.params = params
self.lr = lr
self.alpha = alpha
self.eps = eps
self.weight_decay = weight_decay
self.momentum = momentum
self.square_avg = [np.zeros_like(p) for p in params]
self.velocity = [np.zeros_like(p) for p in params] if momentum > 0 else None
def step(self, grads: List[np.ndarray]):
for i, (param, grad) in enumerate(zip(self.params, grads)):
if self.weight_decay > 0:
grad = grad + self.weight_decay * param
# 更新平方平均
self.square_avg[i] = self.alpha * self.square_avg[i] + (1 - self.alpha) * (grad ** 2)
if self.momentum > 0:
self.velocity[i] = self.momentum * self.velocity[i] + grad / (np.sqrt(self.square_avg[i]) + self.eps)
param -= self.lr * self.velocity[i]
else:
param -= self.lr * grad / (np.sqrt(self.square_avg[i]) + self.eps)
class AdamWOptimizer:
"""AdamW 优化器(带权重衰减的 Adam)"""
def __init__(self, params: List[np.ndarray], lr: float = 0.001,
beta1: float = 0.9, beta2: float = 0.999,
eps: float = 1e-8, weight_decay: float = 0.01):
self.params = params
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.eps = eps
self.weight_decay = weight_decay
self.m = [np.zeros_like(p) for p in params]
self.v = [np.zeros_like(p) for p in params]
self.t = 0
def step(self, grads: List[np.ndarray]):
self.t += 1
for i, (param, grad) in enumerate(zip(self.params, grads)):
# AdamW: 直接在参数上应用权重衰减
if self.weight_decay > 0:
param = param * (1 - self.lr * self.weight_decay)
# 更新一阶矩估计
self.m[i] = self.beta1 * self.m[i] + (1 - self.beta1) * grad
# 更新二阶矩估计
self.v[i] = self.beta2 * self.v[i] + (1 - self.beta2) * (grad ** 2)
# 偏差校正
m_hat = self.m[i] / (1 - self.beta1 ** self.t)
v_hat = self.v[i] / (1 - self.beta2 ** self.t)
# 更新参数
param -= self.lr * m_hat / (np.sqrt(v_hat) + self.eps)
self.params[i] = param
class LearningRateScheduler:
"""学习率调度器"""
def __init__(self, optimizer, scheduler_type: str = 'cosine',
initial_lr: float = 0.1, T_max: int = 100,
warmup_epochs: int = 5):
self.optimizer = optimizer
self.scheduler_type = scheduler_type
self.initial_lr = initial_lr
self.T_max = T_max
self.warmup_epochs = warmup_epochs
self.current_epoch = 0
def step(self):
self.current_epoch += 1
if self.scheduler_type == 'step':
# 阶梯衰减
lr = self.initial_lr * (0.1 ** (self.current_epoch // 30))
elif self.scheduler_type == 'cosine':
# 余弦退火
if self.current_epoch <= self.warmup_epochs:
lr = self.initial_lr * self.current_epoch / self.warmup_epochs
else:
lr = self.initial_lr * 0.5 * (1 + np.cos(np.pi *
(self.current_epoch - self.warmup_epochs) /
(self.T_max - self.warmup_epochs)))
elif self.scheduler_type == 'exponential':
lr = self.initial_lr * (0.95 ** self.current_epoch)
else:
lr = self.initial_lr
self.optimizer.lr = lr
return lr
def train_neural_network():
"""神经网络训练测试"""
print("=" * 60)
print("神经网络优化器比较")
print("=" * 60)
# 生成合成数据
np.random.seed(42)
n_samples = 1000
n_classes = 10
input_dim = 20
hidden_dim = 64
output_dim = n_classes
X = np.random.randn(n_samples, input_dim)
y = np.random.randint(0, n_classes, n_samples)
# 分割训练集和验证集
n_train = int(0.8 * n_samples)
X_train, X_val = X[:n_train], X[n_train:]
y_train, y_val = y[:n_train], y[n_train:]
# 优化器配置
optimizers_config = [
("SGD", SGDOptimizer, {'lr': 0.1, 'momentum': 0.9}),
("SGD + Nesterov", SGDOptimizer, {'lr': 0.1, 'momentum': 0.9, 'nesterov': True}),
("RMSprop", RMSpropOptimizer, {'lr': 0.01, 'alpha': 0.99}),
("Adam", AdamOptimizer, {'lr': 0.001}),
("AdamW", AdamWOptimizer, {'lr': 0.001, 'weight_decay': 0.01}),
]
n_epochs = 50
results = {}
for name, OptimizerClass, config in optimizers_config:
print(f"\n训练 {name}...")
# 初始化网络和优化器
np.random.seed(42)
model = SimpleNeuralNetwork(input_dim, hidden_dim, output_dim)
optimizer = OptimizerClass(model.params, **config)
train_losses = []
val_losses = []
val_accuracies = []
for epoch in range(n_epochs):
# 前向传播
y_pred_train = model.forward(X_train)
train_loss = model.cross_entropy_loss(y_pred_train, y_train)
# 反向传播
grads = model.backward(X_train, y_train, train_loss)
# 更新
optimizer.step(grads)
# 验证
y_pred_val = model.forward(X_val)
val_loss = model.cross_entropy_loss(y_pred_val, y_val)
val_acc = np.mean(np.argmax(y_pred_val, axis=1) == y_val)
train_losses.append(train_loss)
val_losses.append(val_loss)
val_accuracies.append(val_acc)
results[name] = {
'train_loss': train_losses,
'val_loss': val_losses,
'val_acc': val_accuracies,
'final_acc': val_accuracies[-1]
}
print(f" 最终训练损失: {train_losses[-1]:.4f}")
print(f" 最终验证损失: {val_losses[-1]:.4f}")
print(f" 最终验证准确率: {val_accuracies[-1]:.4f}")
# 打印总结
print("\n" + "=" * 60)
print("结果总结")
print("=" * 60)
print(f"{'优化器':<20} {'最终训练损失':<15} {'最终验证损失':<15} {'验证准确率':<10}")
print("-" * 60)
for name, result in results.items():
print(f"{name:<20} {result['train_loss'][-1]:<15.4f} "
f"{result['val_loss'][-1]:<15.4f} {result['final_acc']:<10.4f}")
if __name__ == "__main__":
train_neural_network()第十二部分:前沿研究方向
12.1 神经网络损失 Landscape 的几何
研究方向 12.1.1(Landscape 可视化):
- 使用低维投影(如 PCA、t-SNE)可视化高维 loss landscape
- 识别”碗状”区域和”尖锐”区域
- 量化局部最优的平坦度
研究方向 12.1.2(Mode Connectivity):
- 发现不同局部最优之间存在低损失路径
- 这些路径通常经过高损失区域,但路径上的中间点仍具有良好泛化能力
12.2 分布式与异步优化
研究方向 12.2.1(联邦学习): 其中 是第 个客户端的本地损失。
研究方向 12.2.2(通信压缩):
- Top- 稀疏化
- 随机量化(QSGD)
- 差分隐私梯度
12.3 自动微分与优化
研究方向 12.3.1(超参数优化): 使用二阶方法优化学习率、动量等超参数:
研究方向 12.3.2(Meta-Learning): 通过优化学习使模型能够快速适应新任务。
附录 A:优化算法速查表
A.1 一阶方法
| 算法 | 更新公式 | 特点 | 适用场景 |
|---|---|---|---|
| GD | 简单 | 小数据集 | |
| SGD | 同上, 是随机近似 | 高效 | 大数据集 |
| Momentum | 加速 | 一般场景 | |
| Nesterov | 更快的收敛 | 深度网络 | |
| AdaGrad | 自适应LR | 稀疏数据 | |
| RMSprop | 改进AdaGrad | RNN | |
| Adam | 双矩估计 | 综合最优 | 默认选择 |
| AdamW | Adam + 权重衰减 | 正则化 | Transformer |
A.2 二阶方法
| 算法 | 近似对象 | 计算复杂度 | 特点 |
|---|---|---|---|
| 牛顿法 | 二阶收敛 | ||
| BFGS | 低秩更新 | 无需显式Hessian | |
| L-BFGS | m个历史对 | 内存高效 | |
| 信赖域 | 二阶模型约束 | 依赖求解器 | 全局收敛 |
| 自然梯度 | 依赖F计算 | 参数空间感知 |
A.3 学习率调度
| 调度 | 公式 | 特点 |
|---|---|---|
| 固定 | 简单 | |
| 阶梯 | 跳跃下降 | |
| 指数 | 平滑下降 | |
| 余弦 | 周期性 | |
| Warmup | 前个epoch线性增长 | 稳定训练 |
附录 B:深度学习优化核心概念
B.1 损失 Landscape 关键概念
- 局部最优(Local Minimum):
- 鞍点(Saddle Point): 有正有负
- Plateau:梯度接近零但非最优的区域
- Sharp Minimum:曲率大的局部最优
- Flat Minimum:曲率小的局部最优,泛化能力好
B.2 梯度相关概念
- 梯度消失(Vanishing Gradient):深层网络反向传播时梯度变小
- 梯度爆炸(Exploding Gradient):梯度快速增大
- 梯度裁剪(Gradient Clipping):防止梯度爆炸
- 梯度噪声(Gradient Noise):有助于逃离鞍点
B.3 归一化技术
- Batch Normalization:批内归一化
- Layer Normalization:层内归一化
- Instance Normalization:样本内归一化
- Group Normalization:组内归一化
- Weight Normalization:权重参数化
- Weight Decay:权重正则化
附录 C:推荐阅读
C.1 经典教材
-
Nesterov, Y. - Introductory Lectures on Convex Optimization
- 最速下降法与动量法的理论基础
-
Boyd, S. & Vandenberghe, L. - Convex Optimization
- 凸优化的经典教材,部分内容适用于非凸
-
Bottou, L., Curtis, F., & Nocedal, J. - Optimization Methods for Large-Scale Machine Learning
- 大规模机器学习优化的全面综述
C.2 重要论文
- Kingma & Ba (2014) - Adam: A Method for Stochastic Optimization
- Reddi et al. (2018) - On the Convergence of Adam and Beyond
- Keskar et al. (2017) - On Large-Batch Training for Deep Learning
- Garipov et al. (2018) - Loss Surfaces, Mode Connectivity, and Fast Ensembling of DNNs
C.3 实践资源
- PyTorch Optimizers - 官方文档
- TensorFlow Optimizers - 官方文档
- AllenNLP Optimizer - 开源实现
附录 D:代码实践建议
D.1 调试技巧
- 梯度检查:使用数值梯度验证解析梯度
- 学习率范围测试:从 到 寻找最优
- 小批量测试:在完整训练前用小数据验证
- 损失监控:同时监控训练和验证损失
D.2 超参数选择建议
| 超参数 | 典型范围 | 建议 |
|---|---|---|
| 学习率 | - | 网格搜索 |
| 动量 | - | 通常 |
| Batch Size | - | 权衡效率与泛化 |
| Weight Decay | - | 常用 |
| Warmup Epochs | - | 常见 |
D.3 常见问题与解决方案
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 不收敛 | 损失振荡 | 降低学习率 |
| 梯度消失 | 训练停滞 | 检查初始化、使用归一化 |
| 梯度爆炸 | 损失 NaN | 梯度裁剪、降低学习率 |
| 泛化差 | 验证损失高 | 正则化、早停、数据增强 |
| 内存不足 | OOM 错误 | 减小 batch size、使用梯度累积 |
总结
非凸优化是深度学习的核心数学基础,其理论与实践在过去十年间经历了深刻变革。我们从经典的梯度下降法出发,系统探讨了:
- 理论基础:局部最优、鞍点、梯度 Lipschitz 条件
- 一阶方法:SGD、Momentum、Nesterov、AdaGrad、RMSprop、Adam
- 二阶方法:牛顿法、BFGS、信赖域方法
- 实践技巧:学习率调度、归一化、梯度裁剪
关键洞见包括:
- 高维空间中,鞍点比局部最优更常见
- 梯度噪声是逃离鞍点的天然帮手
- Flat minima 与良好泛化能力相关
- 适当的正则化和调度策略至关重要
继续深入学习,建议关注:
附录 E:高级优化理论专题
E.1 随机优化的深入分析
随机近似理论(Robbins-Monro 算法):
考虑求解方程 ,其中 :
定理 E.1.1(Robbins-Monro 收敛性): 如果步长序列 满足:
且函数满足:
则 以概率 1 收敛。
异步随机梯度下降:
在并行计算中,不同 worker 可能以不同速度执行:
其中 是延迟参数。
方差缩减技术:
SVRG(随机方差缩减梯度):
x̃ = x_t (每若干次迭代重置)
μ = ∇f(x̃)
for epoch:
for each sample i:
g = ∇F_i(x_t) - ∇F_i(x̃) + μ
x_{t+1} = x_t - ηg
x̃ = x_t
SAGA: 其中 是历史梯度的平均。
E.2 非光滑优化
次梯度定义:
定义 E.2.1(次梯度): 是函数 在 处的次梯度,如果:
次梯度的集合记为 。
例 E.2.1(范数的次梯度):
- 在 处的次梯度:
- 的次梯度:
次梯度方法:
收敛速率:
- 对于凸函数:
- 对于强凸函数:
近端梯度方法(Proximal Gradient):
对于复合问题: 其中 光滑, 可能非光滑(如 范数)。
近端算子:
迭代步骤:
例 E.2.2(软阈值): 对于 ,近端算子为: 这就是 ISTA(Iterative Shrinkage-Thresholding Algorithm)。
E.3 约束优化与投影
投影算子:
定义 E.3.1(投影): 到集合 的投影:
投影梯度方法:
投影的计算:
-
球约束:
-
箱约束:
-
概率单纯形: 使用高效算法如 Dykstra 或排序方法。
罚函数方法:
-
二次罚函数:
-
障碍函数:
-
增广拉格朗日方法:
E.4 分布式优化
集中式通信:
每个 worker 计算本地梯度,然后聚合:
通信开销:
通信时间 与计算时间 的比值是关键:
减少通信策略:
-
梯度压缩:
- 量化:使用低精度表示(如 8-bit)
- 稀疏化:只传输 Top-K 大小的梯度
- 错误反馈:将压缩误差累积
-
本地更新(Local SGD): 多个本地步骤后同步一次。
-
异步通信: 允许 worker 独立更新,主节点延迟聚合。
联邦学习:
在联邦学习中,数据分布在各个客户端:
其中 。
FedAvg 算法:
- 服务器广播模型
- 客户端计算
- 服务器聚合
附录 F:深度学习特定优化问题
F.1 训练不稳定性的诊断
症状 F.1.1:
| 症状 | 可能原因 | 诊断方法 |
|---|---|---|
| 损失爆炸 | 学习率过大 | 降低学习率、梯度裁剪 |
| 损失 NaN | 数值不稳定 | 检查 log 运算、数值精度 |
| 损失振荡 | 学习率不当 | 使用学习率调度 |
| 损失不变 | 梯度消失 | 检查激活函数、初始化 |
| 泛化恶化 | 过拟合 | 早停、正则化、数据增强 |
诊断工具:
# 梯度幅度监控
for name, param in model.named_parameters():
if param.grad is not None:
grad_norm = torch.norm(param.grad)
writer.add_scalar(f'grad/{name}', grad_norm, step)
# 权重幅度监控
for name, param in model.named_parameters():
weight_norm = torch.norm(param)
writer.add_scalar(f'weight/{name}', weight_norm, step)
# 激活值分布
for name, module in model.named_modules():
if hasattr(module, 'output'):
writer.add_histogram(f'activation/{name}', module.output, step)F.2 批量大小与泛化
大批量问题:
Keskar 等人的发现:
- 大批量 () 倾向于收敛到 sharp minima
- Sharp minima 具有更差的泛化能力
解决方案:
-
学习率缩放:
-
Warmup: 开始时使用小学习率,逐渐增加到目标值。
-
噪声注入: 增大梯度噪声或参数噪声。
-
Label Smoothing: 软化标签,减少过拟合。
F.3 残差连接与梯度流
残差连接的数学分析:
考虑带残差连接的块:
梯度流动:
这确保了即使 的梯度很小, 仍然可以有效传播。
DenseNet 的推广:
Highway Networks:
其中 是门控函数。
F.4 归一化层的深入分析
BatchNorm 的梯度流:
前向传播:
反向传播需要计算:
训练与测试的差异:
测试时使用移动平均:
为什么 BatchNorm 有效:
- 内部协变量转移减少
- 损失 landscape 平滑
- 权重缩放的不变性
- 正则化效应
Group Normalization:
将通道分成 组,每组内计算均值方差:
不依赖 batch size,适合小批量训练。
附录 G:前沿研究与未来方向
G.1 Transformer 训练的优化挑战
自注意力的计算:
优化难点:
- 注意力权重的数值稳定性: 可能很大或很小
- 内存消耗: 注意力矩阵
- 不稳定的梯度:尤其是深层 Transformer
解决方案:
- 缩放因子:保持方差稳定
- Flash Attention:IO-aware 的精确注意力实现
- Sparse Attention:稀疏化注意力模式
- 层次化注意力:多尺度处理
G.2 大模型训练的特殊考虑
混合精度训练:
使用 FP16 或 BF16 计算加速,FP32 累加防止精度损失:
# 前向传播
with autocast():
output = model(input)
loss = criterion(output, target)
# 反向传播
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()梯度检查点(Gradient Checkpointing):
用计算换内存,保存部分中间结果:
# 不保存所有中间结果
outputs = checkpoint(model, inputs)
# 反向传播时重新计算学习率调度:
大模型训练的标准调度:
- Linear Warmup:前几千步线性增加学习率
- Cosine Decay:余弦衰减到最小值
- 保持(Linear Hold):最后若干步保持最小学习率
G.3 神经网络架构搜索(NAS)
搜索空间:
- 操作类型:卷积、池化、跳连等
- 连接模式:有向无环图
- 超参数:通道数、卷积核大小等
搜索策略:
- 强化学习:使用 RNN 控制器生成架构
- 梯度方法:DARTS,可微搜索
- 进化算法:NSGA-Net
- 贝叶斯优化:基于代理模型
DARTS 的优化框架: 其中
G.4 元学习与超参数优化
MAML(Model-Agnostic Meta-Learning):
学习能够快速适应新任务的初始化。
自动学习率调整:
Hypergradient Descent:
学习如何学习(Learning to Learn):
使用循环优化器:
附录 H:代码调试与性能优化
H.1 PyTorch 性能分析
import torch
from torch.profiler import profile, ProfilerActivity
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
record_shapes=True,
profile_memory=True,
with_stack=True
) as prof:
output = model(input)
loss = criterion(output, target)
loss.backward()
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))H.2 常见性能瓶颈
| 瓶颈 | 症状 | 解决方案 |
|---|---|---|
| CPU-GPU 传输 | 大量时间在 H2D/D2H | 使用 pinned memory |
| 小批量 | GPU 利用率低 | 增大批量、梯度累积 |
| 同步原语 | 部分 GPU 等待 | 异步操作、欠运行 |
| 内存碎片 | 分配失败 | 预分配、内存池 |
H.3 混合精度优化
from torch.cuda.amp import GradScaler, autocast
scaler = GradScaler()
for input, target in dataloader:
with autocast():
output = model(input)
loss = criterion(output, target)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()H.4 分布式训练
import torch.distributed as dist
def setup(rank, world_size):
dist.init_process_group("nccl", rank=rank, world_size=world_size)
def cleanup():
dist.destroy_process_group()
# 每个进程
model = model.to(rank)
model = DDP(model, device_ids=[rank])
# 训练循环
for data in dataloader:
output = model(data)
loss = criterion(output, target)
loss.backward()
optimizer.step()附录 I:实践检查清单
I.1 训练前检查
- 数据预处理正确(归一化、增强)
- 损失函数选择合理
- 评估指标明确
- Baseline 模型可复现
- 随机种子固定
I.2 训练中监控
- 训练/验证损失趋势
- 梯度幅度
- 学习率设置
- 内存使用
- GPU 利用率
I.3 训练后分析
- 最终性能指标
- 过拟合程度
- 错误分析
- 预测可视化
- 模型公平性检查
本指南编写于 2026-04-19,供归愚知识库使用