非凸优化深度指南

引言:为什么非凸优化在人工智能时代至关重要

优化理论是现代数学与计算科学的交叉领域,其中非凸优化(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(经典的非凸函数)

  1. Rastrigin 函数(高维多模态函数): 这是一个经典的多模态测试函数,具有许多局部最优。

  2. Himmelblau 函数(双峰函数): 这个函数有四个局部最优和一个鞍点。

  3. Rosenbrock 函数(“香蕉函数”): 这个函数有一个弯曲的全局最优谷。

1.3 必要条件与充分条件

定理 1.3.1(一阶必要性):如果 是局部最优且 处可微,则:

定理 1.3.2(二阶必要性):如果 是局部最优且 处二阶可微,则:

定理 1.3.3(二阶充分性):如果 满足: 是严格局部最优解。

定义 1.3.1(驻点):满足 的点 称为驻点(critical point)。驻点包括局部最优、鞍点和平台区中心。

1.4 神经网络损失函数的特殊性质

现代神经网络,如CNNRNNTransformer 等,其损失函数具有以下特殊性质:

性质 1.4.1(规模化性质):许多神经网络的损失函数在某些参数方向上具有尺度不变性。例如,ReLU 网络的参数缩放可能不改变输出:

性质 1.4.2(过度参数化):现代大模型通常具有远多于数据点数量的参数。这种过度参数化可能导致:

  • 每个局部最优实际上也是全局最优
  • 所有局部最优具有相似的损失值
  • 鞍点可以被容易地逃离

性质 1.4.3(景观对称性):神经网络存在大量对称性,例如交换同一层中不同神经元的位置不改变函数。这导致:

  • 大量等价的局部最优
  • 景观的高度简并性

第二部分:鞍点问题与逃离策略

2.1 鞍点的分类与性质

定义 2.1.1(鞍点的严格定义):设 二阶可微, 满足 是鞍点当且仅当 既有正特征值也有负特征值。

定理 2.1.1(鞍点的拓扑分类)

  • 严格鞍点(Strict Saddle): 在某个方向上负曲率(),在其他方向上正曲率
  • 退化鞍点(Degenerate Saddle): 包含零特征值

在深度学习中的意义:大量研究表明,神经网络训练早期遇到的” plateau “主要是由鞍点而非局部最优引起的。这可以从以下角度理解:

  1. 高维空间中,局部最优是稀有事件
  2. 随机矩阵理论预测高维随机 Hessian 在原点附近几乎肯定是鞍点
  3. 梯度噪声有助于逃离鞍点

2.2 逃离鞍点的算法策略

策略 2.2.1(梯度噪声):随机梯度 可以分解为: 其中 是采样噪声。噪声的协方差与负曲率方向对齐时,有助于逃离鞍点。

定理 2.2.1(噪声驱动的逃离):考虑带噪声的梯度下降: 其中 是均值为零、协方差为 的噪声。在适当条件下,逃离鞍点的速率正比于

策略 2.2.2(Hessian 信息):利用二阶信息逃离鞍点:

  1. 信赖域方法(Trust Region Methods):

  2. 共轭梯度法:通过 Hessian-vector 乘积快速求解二阶近似

  3. 负曲率方向下降(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,如果 的一个邻域内变化缓慢,即 较小。

度量方法

  1. Sharpness

  2. Hessian 范数

  3. 有效平坦度(Effective Flatness):通过扰动实验测量

定理 3.3.1(Keshar 猜想/实证):Flat minima 与良好的泛化能力相关。这一观察催生了以下研究方向:

  1. Sharpness-Aware Minimization (SAM)

  2. Entropy-SGD:在损失函数中加入局部熵项以寻找更平坦的区域

3.4 局部最优的多样性

定理 3.4.1(Loss Landscape 的复杂性):神经网络损失函数景观具有以下特点:

  1. 碗状谷:存在从输入到输出的单调路径
  2. Funnel 结构:存在漏斗状结构便于收敛
  3. 大量次优盆:存在许多具有相似损失值的不同盆

在实践中的意义:这意味着我们不需要找到”最好”的局部最优——任何”足够好”的局部最优都能提供良好的性能。


第四部分:梯度下降法的理论基础

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(局部收敛性):如果 是严格局部最优且 ,则牛顿法局部二阶收敛。

优点:收敛速度快 缺点

  1. 需要计算 Hessian( 存储)
  2. 需要求解线性系统 计算)
  3. 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 梯度范数作为收敛准则

替代准则:由于高维非凸问题中 可能表示鞍点,使用以下准则:

  1. Hessian 正定性检测
  2. 方向导数检测
  3. 噪声尺度检测:当 接近噪声水平时停止

第九部分:神经网络训练的优化视角

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)

作用:防止梯度爆炸,稳定训练过程,特别是在 RNNTransformer 训练中非常重要。

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改进AdaGradRNN
Adam 双矩估计综合最优默认选择
AdamWAdam + 权重衰减正则化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 经典教材

  1. Nesterov, Y. - Introductory Lectures on Convex Optimization

    • 最速下降法与动量法的理论基础
  2. Boyd, S. & Vandenberghe, L. - Convex Optimization

    • 凸优化的经典教材,部分内容适用于非凸
  3. Bottou, L., Curtis, F., & Nocedal, J. - Optimization Methods for Large-Scale Machine Learning

    • 大规模机器学习优化的全面综述

C.2 重要论文

  1. Kingma & Ba (2014) - Adam: A Method for Stochastic Optimization
  2. Reddi et al. (2018) - On the Convergence of Adam and Beyond
  3. Keskar et al. (2017) - On Large-Batch Training for Deep Learning
  4. Garipov et al. (2018) - Loss Surfaces, Mode Connectivity, and Fast Ensembling of DNNs

C.3 实践资源

  1. PyTorch Optimizers - 官方文档
  2. TensorFlow Optimizers - 官方文档
  3. AllenNLP Optimizer - 开源实现

附录 D:代码实践建议

D.1 调试技巧

  1. 梯度检查:使用数值梯度验证解析梯度
  2. 学习率范围测试:从 寻找最优
  3. 小批量测试:在完整训练前用小数据验证
  4. 损失监控:同时监控训练和验证损失

D.2 超参数选择建议

超参数典型范围建议
学习率 - 网格搜索
动量 - 通常
Batch Size - 权衡效率与泛化
Weight Decay - 常用
Warmup Epochs - 常见

D.3 常见问题与解决方案

问题症状解决方案
不收敛损失振荡降低学习率
梯度消失训练停滞检查初始化、使用归一化
梯度爆炸损失 NaN梯度裁剪、降低学习率
泛化差验证损失高正则化、早停、数据增强
内存不足OOM 错误减小 batch size、使用梯度累积

总结

非凸优化是深度学习的核心数学基础,其理论与实践在过去十年间经历了深刻变革。我们从经典的梯度下降法出发,系统探讨了:

  1. 理论基础:局部最优、鞍点、梯度 Lipschitz 条件
  2. 一阶方法:SGD、Momentum、Nesterov、AdaGrad、RMSprop、Adam
  3. 二阶方法:牛顿法、BFGS、信赖域方法
  4. 实践技巧:学习率调度、归一化、梯度裁剪

关键洞见包括:

  • 高维空间中,鞍点比局部最优更常见
  • 梯度噪声是逃离鞍点的天然帮手
  • 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(范数的次梯度)

  1. 处的次梯度:
  2. 的次梯度:

次梯度方法

收敛速率

  • 对于凸函数:
  • 对于强凸函数:

近端梯度方法(Proximal Gradient)

对于复合问题: 其中 光滑, 可能非光滑(如 范数)。

近端算子

迭代步骤

例 E.2.2(软阈值): 对于 ,近端算子为: 这就是 ISTA(Iterative Shrinkage-Thresholding Algorithm)。

E.3 约束优化与投影

投影算子

定义 E.3.1(投影) 到集合 的投影:

投影梯度方法

投影的计算

  1. 球约束

  2. 箱约束

  3. 概率单纯形: 使用高效算法如 Dykstra 或排序方法。

罚函数方法

  1. 二次罚函数

  2. 障碍函数

  3. 增广拉格朗日方法

E.4 分布式优化

集中式通信

每个 worker 计算本地梯度,然后聚合:

通信开销

通信时间 与计算时间 的比值是关键:

减少通信策略

  1. 梯度压缩

    • 量化:使用低精度表示(如 8-bit)
    • 稀疏化:只传输 Top-K 大小的梯度
    • 错误反馈:将压缩误差累积
  2. 本地更新(Local SGD) 多个本地步骤后同步一次。

  3. 异步通信: 允许 worker 独立更新,主节点延迟聚合。

联邦学习

在联邦学习中,数据分布在各个客户端:

其中

FedAvg 算法

  1. 服务器广播模型
  2. 客户端计算
  3. 服务器聚合

附录 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 具有更差的泛化能力

解决方案

  1. 学习率缩放

  2. Warmup: 开始时使用小学习率,逐渐增加到目标值。

  3. 噪声注入: 增大梯度噪声或参数噪声。

  4. Label Smoothing: 软化标签,减少过拟合。

F.3 残差连接与梯度流

残差连接的数学分析

考虑带残差连接的块:

梯度流动:

这确保了即使 的梯度很小, 仍然可以有效传播。

DenseNet 的推广

Highway Networks

其中 是门控函数。

F.4 归一化层的深入分析

BatchNorm 的梯度流

前向传播:

反向传播需要计算:

训练与测试的差异

测试时使用移动平均:

为什么 BatchNorm 有效

  1. 内部协变量转移减少
  2. 损失 landscape 平滑
  3. 权重缩放的不变性
  4. 正则化效应

Group Normalization

将通道分成 组,每组内计算均值方差:

不依赖 batch size,适合小批量训练。


附录 G:前沿研究与未来方向

G.1 Transformer 训练的优化挑战

自注意力的计算

优化难点

  1. 注意力权重的数值稳定性 可能很大或很小
  2. 内存消耗 注意力矩阵
  3. 不稳定的梯度:尤其是深层 Transformer

解决方案

  1. 缩放因子:保持方差稳定
  2. Flash Attention:IO-aware 的精确注意力实现
  3. Sparse Attention:稀疏化注意力模式
  4. 层次化注意力:多尺度处理

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)
 
# 反向传播时重新计算

学习率调度

大模型训练的标准调度:

  1. Linear Warmup:前几千步线性增加学习率
  2. Cosine Decay:余弦衰减到最小值
  3. 保持(Linear Hold):最后若干步保持最小学习率

G.3 神经网络架构搜索(NAS)

搜索空间

  • 操作类型:卷积、池化、跳连等
  • 连接模式:有向无环图
  • 超参数:通道数、卷积核大小等

搜索策略

  1. 强化学习:使用 RNN 控制器生成架构
  2. 梯度方法:DARTS,可微搜索
  3. 进化算法:NSGA-Net
  4. 贝叶斯优化:基于代理模型

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,供归愚知识库使用