想象你是一家甜品店的面包师。你每天烤一炉新蛋糕,都要在小本子上记一笔“甜度波动”的账:新蛋糕的读数只占一成印象,九成保留过去所有蛋糕的旧印象。可本子买回来是空白的——前几页记下的数字里,掺满了没写过字的空白页,账面上的甜度被空白拉得很低。而你恰恰是按这个偏低的小本子放糖的,糖就会不知不觉放多,甜度失控。Adam 优化器里就躺着这样一个“小本子”:它用同样的记账方式估计梯度的强度(二阶矩),也是从零开始记,早期账面系统性偏低,学习步长被高估,训练就更容易跑偏。偏差校正,就是给这本账乘一个修正系数,把空白页的污染洗掉,让账面如实反映真实水平。这篇文章,我们就用这本“甜度小本子”讲透:偏差从哪来、校正公式怎么推、去掉校正会怎样。
理解偏差校正,能帮你快速定位训练初期 loss 尖峰与发散问题,写出与 PyTorch 行为一致的自定义优化器,是排查优化异常的必备内功。
目录
可视化演示
展示Adam优化器中EMA初始化偏差来源与校正效果,对比有无偏差校正的步长和Loss曲线,交互调节β值查看校正强度变化。
本节配有交互动效(进入视口后自动加载,约 3KB)。
EMA初始化偏差来源 零初始化:m₀=0, v₀=0导致初期估计偏小 β^t衰减:随步数t增加偏差逐渐消失 m̂_t = m_t / (1 - β^t) 偏差来源于EMA零初始化,β越大初期偏差越严重,需除以(1-β^t)校正
零初始化:小本子上的空白页
面包师小李的小本子买来是空的。第一天记账,他记的是“新品蛋糕甜度波动 0.8”,但按记账规则,新读数只占一成、旧账占九成,而旧账什么也没有,只能按 0 记。于是账面不是 0.8,而是 0.08。本子越新,空白页的“零”混得越多,账面就越失真。这就是偏差的源头:不是因为蛋糕不甜,而是因为账本从零开始。
Adam 优化器里的二阶矩估计 v_t,就是这样一个“甜度小本子”。它用指数加权平均估计梯度平方 g_t² 的期望,递推式是 v_t = β₂v_{t-1} + (1-β₂)g_t²,初始值 v₀=0。从零初始化几乎成为所有优化器的默认选择——它实现简洁、数值干净,而且在训练后期完全无碍,因为初始值的影响会随 t 指数衰减。但在训练最初的几步,v₀=0 这个“空白页”的污染非常明显。
正因为 v₀=0,前几步的 v_t 里混着大量“应该存在却还没写进去”的零。账面被拉低,而 Adam 是用 √v_t 去除步长的——分母偏小,步长就偏大。空白页越多,蛋糕越容易被加糖加到齁。
- 当前强度估计 v_t本步更新后账面上的数值,代表“最近梯度有多强”,由旧记忆与新读数加权混合而成。
- 旧记忆的留存 β₂v_{t-1}上一轮的估计被保留 β₂ 的比例,β₂ 越接近 1,记忆越长,对新梯度越不敏感。
- 新读数的注入 (1-β₂)g_t²当前梯度的平方以 1-β₂ 的权重写入账本,这是唯一真正来自这一刻数据的信息。
指数加权平均:一成新、九成旧
小李记账有个死规矩:新蛋糕的甜度最多只占一成印象,九成留给过去所有蛋糕的综合记忆。这规矩的好处是,一炉蛋糕烤坏了不会毁掉整本账;坏处是,想摆脱本子开头的空白页也很慢——空白页的“零”会赖在账里很久很久。
在数学上,这个“九成旧、一成新”的规矩就是指数加权平均(EMA)。把 v_t 反复展开,历史链条清晰可见:每一步旧账都乘一个 β₂,越久远的读数权重越小,呈指数衰减。g_t² 的权重是 (1-β₂),g_{t-1}² 的权重是 (1-β₂)β₂,g_1² 的权重是 (1-β₂)β₂^{t-1}。所有项一加,就是一个等比数列。
权重和小于 1,意味着 v_t 是“打了折扣”的平均值。β₂ 越大,折扣持续的时间越长——默认 β₂=0.999 时,空白页的污染要上千步才能基本排干净。所以偏差不是“前两步忍忍就过去”的小事。
- 历史链条展开不断用 v_t 的定义代回 v_{t-1},最终每一项梯度平方都带上一个指数衰减权重。
- 权重之和等比数列求和,t 项权重相加得到 1-β₂^t,这是一个严格小于 1 的数。
- 缺失的权重完整账本该有的权重总和是 1,差额 β₂^t 恰好等于“空白页”在整个记忆里占的比例。
校正公式:除以(1-βᵗ)
小李终于发现问题:账面甜度比实际低,是因为空白页占了一部分记忆配额。既然空白页占比是 β₂^t,真实账目只占 1-β₂^t,那把账面整体放大 1/(1-β₂^t) 倍即可。比如 β₂=0.9、t=1 时,账面应放大 10 倍;t=10 时放大到约 1.54 倍——空白页越少,补的越少。
要严格推导这个“放大倍数”,只需问一个问题:v_t 的期望到底是多少?假设梯度平方在训练初期大致平稳,即每个时刻 E[g_i²] 都等于同一个常数 E[g²],那么对展开式两边取期望,指数权重可以从 E[g²] 中提出,剩下的恰好是等比数列之和 1-β₂^t:
于是 Adam 的完整记账流程是:先写账(更新 m、v),再按当前步数 t 洗一遍空白页(除以 1-β^t),最后才用洗干净的账决定步子。注意,一阶矩和二阶矩都要校正,只校正其中一个都会让步长偏离设计值。
- 平稳性假设假设短期内每个梯度平方的期望相同,都记作 E[g²],这是推导“无偏校正”的前提。
- 对展开式取期望把展开式代入并取期望,指数衰减权重可以作用于常数的期望,得到折扣后的结果。
- 解出真实值等式两边同时除以 (1-β₂^t),得到的二阶矩估计期望恰好等于真实值,称为无偏估计。
- 一阶矩同样处理梯度的一阶矩 m_t 也是从零起步、同样被空白页拉低,用各自的 β₁ 做同样的校正。
校正的作用:步长恢复理性
洗掉空白页之后,账面终于如实反映蛋糕真实甜度。小李发现,之前按偏低账面放糖,其实一直在超量放糖;校正后按真实甜度来,甜度刚刚好。Adam 的步长公式里,m̂ 提供方向、√v̂ 提供尺度,两者一除,量级大致回到“按学习率 α 规划好的大小”。
Adam 的参数更新式是 θ_t = θ_{t-1} - α·m̂_t/(√v̂_t+ε)。当梯度方向比较一致时,m̂_t/(√v̂_t+ε) 的绝对值接近 1,因此每一步实际移动的距离约等于 α——这是设计者想要的行为:学习率 α 是“每步走多远”的直接旋钮。
没有校正时,m 被低估导致分子偏小,但 v 被低估得更狠,分母缩水更多。净效果是步长被系统性放大——下一节立刻用数字说话。校正这个小动作,把 Adam 从“看账面冲动放糖”拉回“按真实甜度精准放糖”。
- 校正后的一阶矩 m̂_t把方向记忆按 1-β₁^t 放大,消除从零起步带来的方向低估。
- 校正后的二阶矩开方 √v̂_t把强度记忆按 1-β₂^t 放大再开方,作为步长分母,防止梯度大时步子过大。
- 比值决定方向与量级同号且大小接近的 m̂ 与 √v̂ 相除后约等于 ±1,步长量级由 α 决定。
- ε 兜底一个极小的常数,避免分母为零,保证数值稳定。
去掉校正:前几步炸掉
假设小李偷懒,不洗空白页直接做蛋糕。账面甜度偏低,他按账面放糖,等于把糖量放大了好几倍。第一批蛋糕齁得没法吃——训着训着 loss 突然尖峰、甚至直接 NaN,参数一步跳出好区域,再也回不来。
用数字看:默认 β₁=0.9、β₂=0.999,未校正步长与校正步长之比是 (1-β₁^t)/√(1-β₂^t)。第 1 步,分子是 0.1,分母是 √0.001≈0.0316,比值约 3.16——第一步就走出 3 倍多的路。更糟的是第 10~30 步,分子已涨到 0.65~0.96,分母才爬到 0.1~0.17,比值冲到 5~6.5 倍。直到上千步后,比值才慢慢回落到接近 1。
训练初期参数离最优点最远、损失曲面最陡,恰恰是步长放大的重灾区。3~6 倍的步长足以跳过窄谷、跨出凸域,甚至引发数值溢出。所以“去掉校正训练会炸”不是修辞——它是默认参数下的数学事实,而这一课恰好发生在你最承受不起损失的早期阶段。
- 放大倍数 γ_t把有校正和无校正的步长相除,分子分母各自的偏差在校正中的因子相抵消,只剩这个干净的比例。
- 第 1 步代入分子 1-0.9=0.1,分母 √(1-0.999)=√0.001≈0.0316,比值≈3.16。
- 峰值区间前 10~30 步,分子迅速接近 1 而分母仍很小,γ_t 冲到 5~6.5 的最大值,随后指数回落。
- 长期衰减t→∞ 时分子、分母都趋向 1,γ_t→1,但头一千步的放大足以让早期训练面目全非。
β 的权衡:记忆长度与校正力度
小本子买哪一款,小李纠结了很久:记性太差(β 小),账目跟着每炉蛋糕剧烈抖动;记性太好(β 大),空白页赖着不走,早期账面长期偏低。这是个典型的偏差-方差权衡,而偏差校正解决的是“记性太好”带来的偏,代价是校正力度要跟着变大。
β 的数学含义是“旧记忆每步留存的比例”。连续 k 步之后,一项旧读数的权重只剩 β^k;要让权重衰减到一半,大约需要 ln0.5/lnβ 步。业界默认 β₁=0.9(约 7 步半衰)、β₂=0.999(约 693 步半衰):方向账记性短、反应快,强度账记性长、求稳。β₂ 越接近 1,1-β₂^t 在前期越小,校正因子 1/(1-β₂^t) 就越大——越是依赖长期记忆,越离不开偏差校正。
所以千万不要为了“减少偏差”而把 β₂ 调小到 0.9——那会牺牲强度估计的平滑性,让自适应学习率变得毛躁。正确做法是:保留大 β₂ 求稳,同时老老实实做偏差校正,让两者各司其职。
- k 步后的剩余权重旧信息每步乘以 β,k 步后只剩 β^k。
- 半衰期求解令 β^k=0.5,两边取对数解出 k=ln0.5/lnβ。
- 有效窗口近似常用近似 1/(1-β) 表示记忆的有效长度,β=0.999 时约 1000 步。
工程实现:数对步数算准校正
小李把校正公式贴在本子扉页,却发现一个更隐蔽的坑:放大倍数依赖“这是第几页”。如果页数数错了,放大倍数就是错的。比如从第 0 页开始数,第一步就变成除以 1-β⁰=0,直接除零崩溃;重启新本子后不重新数页,也会把旧页数带进新账。
工程实现有四个关键点。第一,迭代计数 t 从 1 开始,每做一次参数更新就加 1,校正时用当前 t 计算 β^t。第二,学习率调度、warmup、梯度累积都要与 t 解耦——t 只反映“记账步数”。第三,分母里的 ε=1e-8 不能省,它防止 √v̂ 过小时除零。第四,二阶矩 v 的数值范围跨度极大,早期可能小到 1e-6 以下,半精度下做除法易下溢,累积 v 建议用 float32。
好消息是,PyTorch 的 torch.optim.Adam 已经内置了 bias_correction1 和 bias_correction2,默认开启;TensorFlow 的 Adam 和 Keras 也帮你算好了。真正容易踩坑的是手写优化器、把别的框架的 Adam 移植到新项目,或者魔改 AdamW、LAMB 的底层算子——这些场景下,漏掉一个 √(1-β₂^t) 就会让训练初期静悄悄地跑偏。数对页数,账才实。
- 步数递增每次参数更新前 t 加 1,从 t=1 开始,保证校正因子有意义。
- 当前步校正用最新的 t 计算 1-β₂^t,洗掉这一时刻的空白页污染。
- 数值兜底除以 1-β₂^t 前先确认其不为 0;加上 ε 防止 √v̂ 除零。
AI 应用场景
大模型微调:训练初期 loss 尖峰排查
阶段:模型训练 / 优化器调试
解决的问题:微调大语言模型时,前几百步 loss 突然尖峰甚至 NaN,工程师第一反应是调学习率或 warmup,却常常忽略是自研或移植的 Adam 内核漏掉了偏差校正。没有校正时,步长被放大 3~6.5 倍,一步跨出平坦区域,损失直接起飞。
具体案例:用 DeepSpeed 自定义 fuse 优化器微调 7B 模型,前 50 步 loss 飙到初始值的 10 倍;逐项对比 PyTorch 原生 Adam,发现 denominator 少了 √(1-β₂ᵗ) 校正因子,补上后 loss 曲线恢复平滑。
强化学习:critic 网络前几步发散
阶段:RL 训练稳定化
解决的问题:PPO 等强化学习算法的价值网络用 Adam 更新,早期二阶矩被低估导致 critic 步长过大,价值估计来回震荡,advantage 计算失真,最终 policy 也被带偏。偏差校正让 critic 前几百步平稳落座。
具体案例:自研 RL 框架复现 PPO 时,前 200 步 critic loss 不降反升;用 torch.optim.Adam 默认实现后稳定收敛,定位到是手写优化器漏了 bias correction。
自定义优化器开发:AdamW/LAMB 魔改
阶段:底层算子 / 训练框架开发
解决的问题:在 Triton 或 CUDA 里实现 fused AdamW 时,如果 kernel 里漏掉偏差校正,即使权重衰减和 warmup 都做对了,学习率调度曲线仍与预期不符——前 1000 步实际步长比设计值高 1~6 倍,模型表现系统性变差。
具体案例:某推理训练框架把 Adam 移植到自定义 kernel,步长曲线与 PyTorch 对照偏差明显,排查发现是忘记在除法前乘 √(1-β₂ᵗ),修复后两条曲线完全重合。
Python 演示
import math def train_adam(correct=True, steps=200, lr=0.1, beta1=0.9, beta2=0.999, eps=1e-8): x = 0.0 m = 0.0 v = 0.0 max_step = 0.0 for t in range(1, steps + 1): g = math.exp(x) # 目标函数 f(x)=exp(x), 梯度等于函数值 m = beta1 * m + (1 - beta1) * g v = beta2 * v + (1 - beta2) * g * g if correct: m_hat = m / (1 - beta1 ** t) v_hat = v / (1 - beta2 ** t) else: m_hat = m v_hat = v step = lr * m_hat / (math.sqrt(v_hat) + eps) x -= step max_step = max(max_step, abs(step)) if t <= 3 or t % 50 == 0: print(' t=%3d x=%9.4f step=%.4f' % (t, x, step)) print(' final x=%.3f max_step=%.3f' % (x, max_step)) return x print('Adam with bias correction:')x_corrected = train_adam(correct=True)print()print('Adam without bias correction:')x_uncorrected = train_adam(correct=False)print()print('Travel distance ratio: %.2f' % (abs(x_uncorrected) / abs(x_corrected)))
for t in range(1, steps + 1):迭代计数 t 从 1 开始,这是偏差校正公式的前提v = beta2 * v + (1 - beta2) * g * g二阶矩(强度账本)的指数加权更新m_hat = m / (1 - beta1 ** t)一阶矩偏差校正,洗掉方向账的空白页v_hat = v / (1 - beta2 ** t)二阶矩偏差校正,洗掉强度账的空白页step = lr * m_hat / (math.sqrt(v_hat) + eps)用校正后的账目计算步长max_step = max(max_step, abs(step))记录整个训练过程中最大的步长if t <= 3 or t % 50 == 0:只打印前 3 步和每 50 步,方便观察早期差异
常见误区
误区反正多走几步 v 就会逼近真实值,前几步的小偏差无所谓,不校正也能收敛,何必多此一举。
正解默认 β₂=0.999 时,步长放大比例在第 100 步仍约 3.2 倍,第 1000 步仍约 1.26 倍。训练的头几百步参数刚从初始点出发、对步长最敏感,3~6 倍的步长足以跨出良好区域甚至发散。小本子前 1000 页都残留空白页,不是“前几页”的小事。
为何常见:直觉以为指数加权很快遗忘初始值,实际上 β 接近 1 时遗忘极慢,偏差的“寿命”远比想象中长。
误区偏差主要是 v 引起的,m 只是方向,偏一点无所谓,所以只对二阶矩做校正就够了。
正解m 和 v 都从零起步、都被空白页拉低。校正 m 会让步长变大,校正 v 会让步长变小,两者组合才是设计步长。只校 v 不校 m,步长偏小、收敛变慢;两者都不校,步长偏大、早期跑偏。方向账和强度账是两本账,只改一本都记不实。
为何常见:因为二阶矩偏差的直接影响(分母变 6 倍)比一阶矩(分子变 10 倍以内)更显眼,容易让人误以为一阶矩不需要管。
误区偏差导致步长过大,那训练初期把学习率调小(warmup),等效于变相校正,就不需要专门做偏差校正了。
正解warmup 是额外的工程手段,解决的是大梯度、大曲率下的极端更新;偏差校正解决的是估计量的系统性低估,二者解决的问题不同、效果相互叠加。去掉校正只靠 warmup,warmup 结束后步长仍然偏大 3 倍以上,问题依旧。
为何常见:两者都在训练早期起作用,又都能缓解 loss 尖峰,容易被误认为同一件事。
误区t 从 0 还是 1 开始,反正只是幂次,差别不大,顺手写成 range(steps) 就行。
正解t 从 0 开始,第一步的校正因子是 1-β⁰=0,分母为零直接崩溃;即使特判跳过,后续每一步的步数也整体错位,校正力度偏大。必须从 t=1 开始,且每步递增。小本子必须从第一页数起,数错页数整本账都错。
为何常见:程序员习惯下标从 0 开始,写循环时顺手就把迭代计数也写成从 0 开始了。
面试题
Q1. Adam 中二阶矩估计 v_t 在训练初期偏小的根本原因是?
- A.学习率被设置得过大
- B.v 的初值被初始化为零← 正确答案
- C.梯度本身的值就偏小
- D.β 系数被设置得偏小
答案:B。v₀=0 意味着前 t 步的指数加权和中,缺失的历史项全部按 0 记账,账面被空白页系统性拉低。学习率、梯度大小只影响绝对值,不改变“估计相对真实值偏低”这一结构性问题。
Q2. 推导偏差校正公式 v̂_t = v_t/(1-β₂^t) 时,需要以下哪个假设?
- A.每步梯度方向都保持不变
- B.梯度平方的期望基本平稳← 正确答案
- C.损失函数在整个定义域凸
- D.学习率必须按余弦方式衰减
答案:B。推导中把 E[g_i²] 视作同一常数 E[g²] 并提出求和号,这是平稳性假设。它不要求方向稳定、凸性,也不依赖学习率调度,属于对随机梯度统计特性的近似。
Q3. 默认参数 β₁=0.9、β₂=0.999 下,第 1 步未校正步长约为校正步长的多少倍?
- A.约 1.1 倍
- B.约 3.2 倍← 正确答案
- C.约 6.5 倍
- D.约 31.6 倍
答案:B。放大倍数 γ₁=(1-β₁)/√(1-β₂)=0.1/√0.001≈3.16。6.5 倍是第 10~30 步的峰值;31.6 倍是只考虑 v 单边缩水时的值,完整比例还得算上 m 的分子。
Q4. 训练进行到 t 很大时,校正因子 1/(1-β^t) 趋近于?
- A.趋近于无穷
- B.趋近于 0
- C.趋近于 1← 正确答案
- D.趋近于 β
答案:C。t→∞ 时 β^t→0,1-β^t→1,校正因子趋向“乘 1”,即后期校正几乎不起作用。这正是“偏差在校正帮助下很快消失,而不校正则残留上千步”的数学原因。
Q5. 关于 m 和 v 的偏差校正,下列说法正确的是?
- A.只有二阶矩需要做校正
- B.一阶矩和二阶矩都需要校正← 正确答案
- C.只校正一阶矩就完全足够
- D.二者实际上都不需要校正
答案:B。m 与 v 都从零初始化、都被空白页拉低,Adam 原论文对两者分别用 1-β₁^t 与 1-β₂^t 校正。分子分母的校正因子比例失调,都会让实际步长偏离设计值。
Q6. 把 β₂ 从 0.9 调到 0.999,训练初期的估计偏差会?
- A.偏差明显变大← 正确答案
- B.偏差明显变小
- C.偏差保持不变
- D.偏差先变大后变小
答案:A。β₂ 越大,1-β₂^t 在前期越小,v_t 相对 E[g²] 的折扣越狠,偏差越大,相应地校正因子 1/(1-β₂^t) 也越大。所以长记忆带来平滑,也带来更强的前期偏差。
Q7. 偏差校正与学习率 warmup 的关系是?
- A.两者作用相同且可互相替代
- B.warmup 是校正的另一种实现
- C.分别解决估计偏差与极端步长← 正确答案
- D.二者只影响训练的后期阶段
答案:C。偏差校正解决估计量的系统性低估,warmup 解决大梯度下极端步长的冲击,机制不同、效果互补。去掉校正只靠 warmup,warmup 结束后步长仍偏大数倍,问题照旧。
Q8. 去掉偏差校正后,训练初期可能出现哪些现象?
- A.实际步长比设计步长明显更大← 正确答案
- B.训练初期损失出现尖峰← 正确答案
- C.参数每次更新幅度变小
- D.收敛曲线变得更加平缓
答案:AB。未校正时步长被放大 3~6.5 倍,参数更新幅度显著变大,容易造成损失尖峰甚至 NaN。C、D 描述的“幅度变小、曲线平缓”与实际相反。
Q9. 关于二阶矩偏差校正,下列说法正确的有?
- A.校正后估计更接近真实梯度平方均值← 正确答案
- B.β₂ 越大则初期校正力度越强← 正确答案
- C.迭代步数 t 需要逐次递增参与计算← 正确答案
- D.校正只在凸优化问题上有效
答案:ABC。A 是校正的“无偏性”目标;B 中校正因子 1/(1-β₂^t) 随 β₂ 增大而变大;C 中 t 每步加 1 才能算准 β^t。D 错误,非凸问题同样适用,校正与凸性无关。
Q10. 实现含偏差校正的 Adam 时,工程上需要注意哪些点?
- A.每步参数更新后 t 要递增← 正确答案
- B.重启训练时迭代计数归零← 正确答案
- C.分母 ε 可以省略来加快速度
- D.二阶矩累积可直接用半精度
答案:AB。t 从 1 开始每步递增,重启训练要重新计数,否则校正因子用错。ε=1e-8 防止除零不可省略;v 早期极小,半精度下易下溢,建议用 float32 累积。