大模型必备数学_最优化理论基础

文章目录
摘要:有了损失函数和梯度下降,我们已经知道模型如何“朝着 loss 变小的方向更新参数”。但真实的大模型训练远比手写梯度下降复杂:loss 曲面不平坦,学习率会影响稳定性,梯度可能爆炸,优化器不同会导致收敛速度和泛化效果差异很大。本文从开发者熟悉的调参和训练日志出发,解释最优化在大模型训练中的作用,并用 NumPy 对比 SGD、Momentum、Adam 和 AdamW 的直觉。
前置知识:第 3 篇梯度下降,第 4 篇矩阵运算
阅读时间:约35分钟
代码环境:Python 3.10+,numpy >= 1.24,matplotlib >= 3.7
入门导读:先抓住主线
如果你是第一次读“最优化理论”,先不要把它理解成高深数学。把它放到训练工程里理解:我们有一个 loss,要在海量参数空间里找到一组足够好的参数;优化器决定每一步怎么走,学习率决定步子多大,稳定性技巧决定训练能不能走到最后。本文的重点是理解“为什么训练不是简单地一直梯度下降”。
读完先达到这个程度就够了:
- 能解释优化器和普通梯度下降的区别;
- 能理解学习率、动量、Adam、AdamW 分别解决什么问题;
- 能知道梯度裁剪和 warmup 为什么常见;
- 能从训练日志里判断 loss 发散、震荡或过拟合的基本信号。
带着这 3 个问题读:
- 为什么同一个模型和数据,换优化器可能结果差很多?
- 为什么大模型训练通常不用固定学习率从头跑到尾?
- 训练不稳定时,应该先怀疑哪些优化相关因素?
一、优化到底在优化什么
训练模型时,我们最终想找到一组参数 θ \theta θ,让损失函数 L ( θ ) L(\theta) L(θ) 尽可能小。
写成数学形式:
θ ∗ = arg min θ L ( θ ) \theta^*=\arg\min_{\theta} L(\theta) θ∗=argθminL(θ)
这行公式读作:找到一组最优参数 θ ∗ \theta^* θ∗,使 loss 最小。
但在深度学习里,“最小”通常不是严格的全局最优。大模型参数太多,loss 曲面太复杂,我们真正追求的是:在有限算力、有限时间和有限数据下,找到一组泛化效果足够好的参数。
从工程角度看,优化不是一件孤立的数学事,它会直接影响:
- loss 是否稳定下降;
- 是否出现 loss spike;
- 训练速度快不快;
- 最终模型是否过拟合;
- 同样预算下模型能力能到什么水平。
二、loss 曲面:为什么不是一路下山那么简单
在第 3 篇里,我们用 f ( x ) = x 2 f(x)=x^2 f(x)=x2 理解梯度下降。那是一个非常简单的碗形曲面,只有一个最低点。但神经网络的 loss 曲面复杂得多。
它可能包含:
- 局部低点:附近都更高,但不一定是全局最低;
- 鞍点:某些方向像低点,另一些方向还能继续下降;
- 平坦区域:梯度很小,模型学得很慢;
- 陡峭区域:梯度很大,参数更新容易不稳定。
所以真实训练不是“沿着梯度走就完了”。优化器要处理噪声、曲率、尺度差异和稳定性问题。
三、SGD:最基础的更新规则
SGD(Stochastic Gradient Descent,随机梯度下降)是最基础的优化方法。它每次用一个 batch 的数据估计梯度,然后更新参数:
θ t + 1 = θ t − η g t \theta_{t+1}=\theta_t-\eta g_t θt+1=θt−ηgt
其中:
- θ t \theta_t θt 是当前参数;
- g t g_t gt 是当前 batch 计算出的梯度;
- η \eta η 是学习率;
- 更新方向是梯度反方向。
SGD 简单、直观,但问题也明显:batch 梯度有噪声,更新方向可能抖动;不同参数的梯度尺度可能差很多;学习率不好选。
import numpy as np
# 一个简单函数:f(x) = (x - 3)^2,最低点在 x=3
def loss_fn(x):
return (x - 3) ** 2
def grad_fn(x):
return 2 * (x - 3)
x = -5.0
lr = 0.1
for step in range(10):
grad = grad_fn(x)
x -= lr * grad
print(f"step={step}, x={x:.4f}, loss={loss_fn(x):.4f}")
这个例子很顺利,因为函数太简单。真实大模型训练的梯度会有噪声,loss 曲面也不会这么规整。
四、Momentum:让更新带一点惯性
动量(Momentum)解决的是 SGD 更新方向抖动的问题。它会累计过去的梯度方向,让参数更新更像“带惯性的运动”。
公式可以写成:
v t = β v t − 1 + g t v_t=\beta v_{t-1}+g_t vt=βvt−1+gt
θ t + 1 = θ t − η v t \theta_{t+1}=\theta_t-\eta v_t θt+1=θt−ηvt
其中:
- v t v_t vt 是累计的速度;
- β \beta β 控制保留多少历史方向;
- g t g_t gt 是当前梯度。
直觉上,如果很多步都指向相似方向,Momentum 会加速;如果梯度来回抖动,动量会抵消一部分噪声。
def optimize_momentum(x0=-5.0, lr=0.1, beta=0.9, steps=10):
x = x0
v = 0.0
for step in range(steps):
g = grad_fn(x)
v = beta * v + g
x -= lr * v
print(f"step={step}, x={x:.4f}, loss={loss_fn(x):.4f}")
optimize_momentum()
Momentum 在很多传统深度学习任务里非常有效。后来的 Adam 也继承了“动量”的思想。
五、Adam:给每个参数自适应学习率
Adam(Adaptive Moment Estimation)是深度学习中最常见的优化器之一。它同时维护两个量:
- 一阶矩估计:类似 Momentum,记录梯度方向的滑动平均;
- 二阶矩估计:记录梯度平方的滑动平均,用来估计梯度尺度。
简化理解:Adam 会让每个参数拥有自己的自适应学习率。梯度经常很大的参数,步子会被压小;梯度经常很小的参数,步子相对不会太小。
这对大模型很重要,因为不同层、不同矩阵、不同参数的梯度尺度差异非常大。如果所有参数共用同样的更新尺度,训练可能很不稳定。
下面是一个简化版 Adam,用来理解核心思想:
def optimize_adam(x0=-5.0, lr=0.1, beta1=0.9, beta2=0.999, steps=10):
x = x0
m = 0.0
v = 0.0
eps = 1e-8
for t in range(1, steps + 1):
g = grad_fn(x)
m = beta1 * m + (1 - beta1) * g
v = beta2 * v + (1 - beta2) * (g ** 2)
# bias correction:修正初期估计偏小的问题
m_hat = m / (1 - beta1 ** t)
v_hat = v / (1 - beta2 ** t)
x -= lr * m_hat / (np.sqrt(v_hat) + eps)
print(f"step={t}, x={x:.4f}, loss={loss_fn(x):.4f}")
optimize_adam()
第一遍不需要记住 Adam 的完整公式。你只要知道:Adam 比 SGD 多记录了梯度历史和梯度尺度,因此在复杂神经网络里通常更容易稳定训练https://weibo.com/ttarticle/p/show?id=2309405363587312123943
六、AdamW:为什么大模型常用它
Adam 有一个常见搭档:权重衰减(weight decay)。权重衰减用于防止参数无限变大,帮助泛化。
传统做法会把 L2 正则项混进梯度里,但在 Adam 这种自适应优化器里,这种耦合方式可能不理想。AdamW 的核心改动是:把权重衰减从梯度更新里解耦出来。
简化写法:
θ t + 1 = θ t − η ⋅ A d a m U p d a t e − η λ θ t \theta_{t+1}=\theta_t-\eta \cdot AdamUpdate-\eta\lambda\theta_t θt+1=θt−η⋅AdamUpdate−ηλθt
这里 λ \lambda λ 是 weight decay 系数。最后一项会让参数按比例变小。
用大白话说:AdamW 一边按 Adam 的方式处理梯度,一边单独做权重衰减,不把两件事搅在一起。现代 Transformer 训练和微调里,AdamW 是非常常见的默认选择。
七、学习率调度:为什么不能固定一个 lr 跑到底
学习率是训练中最敏感的超参数之一。大模型训练通常不会从头到尾使用固定学习率,而是使用调度策略。
常见策略是 warmup + cosine decay:
- 训练初期,学习率从很小逐渐升高,避免模型刚开始就被大步更新冲坏;
- 到达峰值后,学习率按余弦曲线逐渐下降,让训练后期更细致地收敛。
import math
def lr_schedule(step, total_steps=100, warmup_steps=10, max_lr=1e-3):
if step < warmup_steps:
return max_lr * step / warmup_steps
progress = (step - warmup_steps) / (total_steps - warmup_steps)
return max_lr * 0.5 * (1 + math.cos(math.pi * progress))
for step in [0, 3, 10, 30, 60, 99]:
print(f"step={step:02d}, lr={lr_schedule(step):.6f}")
warmup 尤其适合大模型训练,因为初始阶段参数和梯度都还不稳定。直接使用较大学习率,容易导致 loss spike 或训练崩掉。
八、梯度裁剪:防止一步走飞
训练过程中,有时某个 batch 会产生特别大的梯度。如果直接更新,参数可能发生巨大变化,loss 突然飙升。这就是训练日志里常见的 loss spike。
梯度裁剪(gradient clipping)会限制梯度范数不超过某个阈值。完整的分段公式是:
g c l i p p e d = { g , if ∥ g ∥ ≤ m a x _ n o r m g ⋅ m a x _ n o r m ∥ g ∥ , if ∥ g ∥ > m a x _ n o r m g_{clipped}=\begin{cases} g, & \text{if } \|g\|\le max\_norm \\ g\cdot\dfrac{max\_norm}{\|g\|}, & \text{if } \|g\|> max\_norm \end{cases} gclipped=⎩ ⎨ ⎧g,g⋅∥g∥max_norm,if ∥g∥≤max_normif ∥g∥>max_norm
也就是说,只有当梯度范数超出阈值时,才把整个梯度向量等比例缩放到阈值大小;否则原样保留。这样做只改变梯度的模,不改变梯度的方向。
def clip_grad(g, max_norm=1.0):
norm = np.linalg.norm(g)
if norm > max_norm:
return g * (max_norm / norm)
return g
grad = np.array([10.0, 5.0, -3.0])
clipped = clip_grad(grad, max_norm=1.0)
print("原梯度范数:", np.linalg.norm(grad))
print("裁剪后范数:", np.linalg.norm(clipped))
print("裁剪后梯度:", clipped)
梯度裁剪不会让训练一定变好,但它能降低极端 batch 把训练带崩的风险。大模型训练和微调中非常常见。
九、训练日志该怎么看
优化理论最终要落到训练日志。下面是一些常见信号:
| 现象 | 可能原因 | 优先排查 |
|---|---|---|
| loss 持续下降 | 正常学习 | 继续观察验证集 |
| loss 震荡很大 | 学习率过高、batch 太小、数据噪声大 | 降 lr、增 batch、查数据 |
| loss 突然 spike | 坏 batch、梯度爆炸、混合精度溢出 | 梯度裁剪、查样本、查 AMP |
| 训练 loss 降,验证效果差 | 过拟合 | 数据增强、正则、早停 |
| loss 不动 | 学习率太低、模型容量不足、数据/标签错误 | 查 lr、查数据、查代码 |
如果你后面做微调,最重要的不是背优化器公式,而是能根据训练曲线判断问题方向。
十、一个实用默认配置
对很多大模型微调任务,一个常见起点是:
config = {
"optimizer": "AdamW",
"learning_rate": 2e-5,
"weight_decay": 0.01,
"warmup_ratio": 0.03,
"lr_scheduler": "cosine",
"max_grad_norm": 1.0,
}
这不是万能最优,只是一个比较稳的起点。实际项目里还要根据 base model、数据规模、任务类型、batch size 和 LoRA/full fine-tuning 方式调整。
常见误区
误区 1:优化器越新越好。
不是。优化器要看任务、模型和训练稳定性。AdamW 之所以常见,是因为它稳、成熟、生态支持好。
误区 2:训练不收敛就盲目加 epoch。
如果学习率、数据或 loss mask 有问题,训练更久只会浪费算力。
误区 3:loss 越低模型越好。
训练 loss 低不代表真实任务好。要看验证集、人工评估、事实性、安全性和成本。
误区 4:优化只是训练团队关心的事。
做应用和微调也必须懂优化,否则很难判断训练曲线是否健康。
总结
最优化理论回答的是:如何在复杂参数空间里找到足够好的模型参数。SGD 是基础更新规则,Momentum 缓解方向抖动,Adam 自适应调整每个参数的步长,AdamW 解耦权重衰减,warmup 和 cosine decay 控制训练节奏,梯度裁剪降低训练崩掉的风险。
第一遍记住主线即可:梯度告诉方向,优化器决定怎么走,学习率决定走多大一步,稳定性技巧保证别走飞。
大模型视角
后面讲预训练、SFT、LoRA、QLoRA、训练稳定性时,AdamW、warmup、cosine、gradient clipping 会反复出现。理解这些概念后,你看到训练配置不再只是复制参数,而能判断它们各自负责什么风险。
下一篇
机器学习三大范式:监督/无监督/强化学习概览 —— 数学基础到这里告一段落。下一篇开始进入机器学习范式,理解预训练、SFT、RLHF 分别属于什么学习方式.
更多推荐
所有评论(0)