Lazy loaded image
机器学习常用优化器及其选型
Words 2794Read Time 7 min
2026-3-6
2026-3-13
type
Post
status
Published
date
Mar 6, 2026
slug
summary
tags
DL
开发
思考
精选
category
知行合一
icon
password
😀
这里写文章的前言: 一个简单的开头,简述这篇文章讨论的问题、目标、人物、背景是什么?并简述你给出的答案。
可以说说你的故事:阻碍、努力、结果成果,意外与转折。
 

目录

  • GD 梯度下降 (Gradient Descent)
    • BGD:每批次用全部数据计算一次梯度更新参数,最基础的GD。
    • SGD:每次随机选用一个样本计算梯度,减少计算量。
    • MBGD:小批次梯度下降,是BGD和SGD的折中。
  • 动量优化(决定了梯度更新的方式)
    • Momentum:加速收敛,减少震荡。
    • NAG:提前看
  • 自适应
    • AdaGrad:无需手动学习率。
    • RMSProp:避免AdaGrad学习率下降过快。
    • Adadelta:RMSProp变种
  • 流行
    • Adam:Momentum + RMSProp加速收敛,减少震荡。
    • AdamW:优化版Adam,泛化更好。
  • 稀疏优化器
    • FTRL:L1 + L2 + AdaGrad
    • AdaGrad:设计稀疏特征学习率更大
 
 

梯度计算

梯度是 loss 对模型参数的偏导数,通过反向传播计算,表示 loss 增长最快的方向
当然,我们的目的是让loss更小,所以我们沿梯度反方向更新参数。
对于应用工程师,我们在这里不深究梯度计算的原理,网上有很多优质的定义,大家可以自行参阅。
核心是利用:反向传播(Backpropagation) + 链式法则
 

梯度更新

这才是不同优化器的作用所在。

最基本的梯度更新:Gradient Descent

假设我们已经计算出了梯度:
最简单的更新方式:
其中:
  • = 参数
  • = 梯度
  • = 学习率
💡
很好理解,梯度 = 上坡最快方向,更新 = 往反方向走
所以:
 

为什么简单梯度更新不好?

SGD 有三个问题:

1 震荡问题

如果 loss surface 是平底峡谷,来回震荡。

2 收敛慢

在平缓区域:梯度很小,更新非常慢。

3 学习率难调

太大 → 发散
notion image
太小 → 收敛慢
notion image

 

第一类改进:Momentum动量(梯度优化)

核心思想:
不要只看当前梯度,要结合历史梯度
notion image
更新方式:
解释:
  • = 当前速度(动量), = 上次迭代速度(动量)
  • = 当前梯度
  • = 动念衰减系数,比如指定 90%历史方向 + 10%当前梯度
  • = 第 次迭代时的模型参数
  • = 学习率
💡
替代了原有的 优化的是「梯度方向」
方向 = 当前梯度 + 历史惯性
效果:当在峡谷时:减速。当在正确方向时:加速。

 

第二类改进:AdaGrad(学习率自适应)

SGD中,所有参数学习率一样但现实中有些参数更新频繁,有些参数很少更新
AdaGrad记录历史梯度平方:
累积梯度平方:
参数更新:
解释:
  • = 历史梯度平方累积
  • = 当前梯度gt=∇L(θt)
  • = 第 次迭代时的模型参数
  • = 初始学习率
  • = 防止除零的小常数(例如
  • = 当前迭代步数
💡
替代了 优化的是「学习率」
频繁更新的参数,历史梯度累计更大(分母更大),降低其学习率。低频参数则加速。

 

第三类改进:RMSProp(学习率自适应优化)

AdaGrad存在梯度一直累加问题,会导致学习率越来越小。
RMSProp通过「只记录最近梯度」进行改进:
梯度平方的指数移动平均:
参数更新:
解释:
  • = 梯度平方的指数移动平均
  • = 当前梯度gt=∇L(θt)
  • = 衰减系数(通常 0.9)
  • = 当前模型参数
  • = 学习率
  • = 防止除零的小常数
  • = 当前迭代步数
💡
替代了 优化的是「学习率」
速度为最近梯度平方的平均值,通过滑动窗口实现
 

第三类改进:Adam(同时优化梯度和学习率)

Adam是目前最流行的优化器,结合了 Momentum + RMSProp 的核心思想。
维护两个量:
一阶矩(动量),即Momentum,负责优化梯度
二阶矩(梯度平方),即RMSProp,负责优化学习率
更新:
解释:
  • = 第 $t$ 次迭代时模型参数
  • = 当前梯度
  • = 梯度一阶矩估计(梯度的指数移动平均)
  • = 梯度二阶矩估计(梯度平方的指数移动平均)
  • = 纠偏一阶矩 注意看右下角有一个t方、因为刚开始没有历史,而β又偏大。
  • = 纠偏二阶矩 所以需要放大一下,否则太小。
  • = 一阶矩衰减系数(通常 0.9)
  • = 二阶矩衰减系数(通常 0.999)
  • = 学习率
  • = 防止除零的小常数(例如 $10^{-8}$)
  • = 当前迭代步数
💡
就是结合了上面两个。注意纠偏
  • 代替
  • 替代
 

进一步改进

NAG加速梯度(Nesterov Accelerated Gradient)

核心思想:
先沿动量方向预测未来位置,再在那个位置计算梯度。
Momentum 动量可能带着参数往错误方向冲太远,NAG 通过提前看一步的方式改进
下一步预计位置(通过动量算):
计算梯度:
更新动量:
更新参数:
解释:
  • = 当前动量
  • = 上一步动量
  • = 在预测位置计算的梯度
  • = 动量衰减系数
  • = 学习率
  • = 预测参数位置
💡
Momentum:
  • 用「历史动量 + 当前梯度」更新当前动量。
  • 再用当前动量更新参数,到下一个位置。
NAG:
  • 先用「历史动量」计算「预测位置的梯度」
  • 然后根据「历史动量 + 预测位置的梯度」计算当前动量
    • (如果下一步梯度方向发生变化,此时可以抵消或者修正)
  • 再用当前动量更新参数,到下一个位置。
用预计 共同确定

AdamW 正则化防止过拟合

Adam 原始更新:
AdamW 梯度更新:
为什么要多一项 呢?问题出在这里:
给定模型当前参数损失函数:
其中
  • :数据损失
  • :L2 正则化
梯度变成
更新:
可以看到右上角多了一项,λθ 也被除以 √v
也就是说正则化强度会被 自适应缩放。此时不同参数的 weight decay 强度不同,这就破坏了 L2 的本意。
💡
所以AdamW很简单,就是不要把 L2 写进 Loss 的梯度里,避免被学习率适应影响。
而是在后面更新 的时候再减掉
 
AdamW 梯度更新也可以写成:
💡
其实就是一组权重
其中 是一个小于1的数,乘以这个数可以实现decay,避免权重太大。

解释

  • = 当前参数
  • = 纠偏一阶矩(Momentum)
  • = 纠偏二阶矩(RMSProp)
  • = 学习率
  • = weight decay 系数
  • = 防止除零

 

其他改进

FTRL(Follow-The-Regularized-Leader)

人如其名:跟随过去表现最好的参数
核心思想:
选择一个参数,使得过去所有损失 + 正则化 最小。
  • 结合 L1 正则 + L2 正则 + AdaGrad 的思想,专门为大规模稀疏特征设计。
  • 我们不只看当前梯度,而是看 所有历史梯度的累积
典型应用:
Google 广告系统大量使用 FTRL。

更新公式

累计梯度:
参数更新:
实际实现形式:
如果
否则

解释

  • = 当前梯度
  • = 历史累计梯度
  • = 第 i 个参数梯度平方累积
  • = 累计梯度变量
  • = 学习率
  • = 平滑参数
  • = L1 正则
  • = L2 正则
 

推导

首先我们知道AdaGrad
把符号改一下,防止除0的我们先不关心
其中
其实可以看出原本的 变成了 即「有效学习率」
 
 
转化为优化问题:
解释:
  • = 损失函数的一阶近似
  • 是不让参数跳太远
求导:
得到:
就是 SGD。
 
 

FTRL 的核心优势:
因此非常适合:

 

最后总结

优化器的演进其实是解决不同问题:
优化器
解决问题
Momentum
震荡
NAG
「动量」方向误差
AdaGrad
参数「学习率」不同
RMSProp
AdaGrad 「学习率」衰减
Adadelta
自动「学习率」
Adam
Momentum + RMSProp
AdamW
Adam 正则问题
FTRL
稀疏特征 + 在线学习
所有优化器其实都是:
区别只是:
优化器
update
SGD
g
Momentum
v
AdaGrad
g / √G
RMSProp
g / √v
Adam
m / √v
 
 
 

扫盲:现代深度学习训练代码用的基本都是MBGD

这是一段很经典的深度学习训练代码:
我们训练时候的Batch(大小由batch_size指定)与BGD(Batch Gradient Descent)名字里面的Batch不一样,而BGD名字里的Batch指的是整个数据集“批量“处理。而常见代码中的Batch就是Mini Batch。
 
为什么要用MBGD而不是BGD?
Batch Gradient Descent 每次用全部训练数据计算一次梯度,然后更新参数。
  1. 成本太高:如果有1个亿的样本,需要完整扫描计算,且一个epoch只更新一次,收敛慢。
  1. 必须等待:必须等所有数据到了,才能进行一次迭代。
  1. 硬件压力:每次显存需要载入全量数据、成本极高。
 
为什么要用MBGD而不是SGD?
  1. 梯度变化大:每个样本千奇百怪、梯度方差大、震荡、难以收敛。
  1. 计算效率低:GPU可以进行并行计算、而SGD浪费了这个能力。
  1. 梯度噪声大:单样本无法代表整体样本。
 
 

什么是稀疏模型?

稀疏模型到底是什么意思?模型还有稀疏的吗,不应该是数据稀疏吗?
这是一个非常好的问题。很多人第一次听到“稀疏模型”都会困惑,因为更常见的是“稀疏数据”。其实 稀疏模型和稀疏数据是两个不同概念
稀疏数据
稀疏数据指的是:输入「特征」大部分是 0
例如One-Hot编码
维度可能达到 10^6 甚至 10^9,但每个样本只有几十个非零
所以叫:sparse data(稀疏数据)
稀疏模型
稀疏模型指的是:模型「参数」大部分是 0
例如一个线性模型:
如果训练结果是:
只有少数权重非零。
这就叫:sparse model(稀疏模型)
为什么我们希望模型稀疏?原因有三个:
  1. 自动特征选择:如果w_i = 0,这个特征可以直接丢掉了
  1. 计算更快:只有非0位置要计算
  1. 防止过拟合:模型容易记住噪声
如何让模型稀疏?最经典的是:
  • L1 正则:
  • FTRL: 因为更新时有一个条件: 很多 feature 权重直接变 0。
 
 

扩展思考

为什么 L1 正则会产生稀疏模型,而 L2 不会?
核心原因:L1 惩罚对小权重有“固定拉力”,而 L2 的拉力会越来越小。
L1 正则:( )
梯度:(大小基本是常数)
所以权重更新像:
💡
不管 w 的数值大小,都有固定力度往 0 拉,一旦跨过 0 就会直接变成 0 → 产生稀疏。
L2 正则:( )
梯度:
更新:
💡
当 w 数值很小时,梯度也很小,所以只是无限接近 0,但几乎不会变成 0
💡
根本原因:因为 L1 的梯度(导数)是常数,不随 w 变小而变小。
 
上一篇
机器学习常用损失函数及其选型
下一篇
营销算法-增益模型(Uplift Model)