深度学习中的优化器
培训课件 · 面向深度学习入门与进阶
配套案例:MNIST 手写数字识别(Flatten → Dense(128) → Dense(10))
1. 开场:为什么要谈「优化器」?
想象你在一座大雾弥漫的山谷里找最低点:
- 损失函数告诉你:现在站的地方有多「高」(误差有多大);
- 梯度告诉你:脚下往哪个方向是下坡;
- 优化器决定:你迈多大步、要不要参考走过的路、每条路上的步长要不要不一样。
训练神经网络,本质上就是在参数空间 里找一组权重 W,bW, bW,b,让预测误差尽量小。
网络结构决定了「山谷的形状」,而优化器决定了你怎么下山。
同一套 MNIST 网络,换成 SGD 或 Adam,收敛速度、最终精度、显存占用都会不一样------这正是优化器值得单独学一课的原因。
本课件将从概念讲到公式,再落到你熟悉的 MNIST 案例,并对照五种常用优化器的参数量、优缺点与面试考点。
2. 通俗讲解:优化器是什么?
2.1 一句话定义
优化器(Optimizer):根据损失函数对参数的梯度,按某种规则更新网络权重的算法。
一次典型的训练步骤:
前向传播 → 算损失 Loss → 反向传播得到梯度 g → 优化器根据 g 更新参数 w
没有优化器,你就算得出梯度,也不知道「参数该怎么改」。
2.2 三个核心角色
| 角色 | 通俗比喻 | 符号 |
|---|---|---|
| 参数 www | 你在山谷中的位置 | 网络里的权重、偏置 |
| 梯度 g=∇wLg=\nabla_w Lg=∇wL | 当前位置最陡的下坡方向 | 反向传播算出来 |
| 学习率 η\etaη | 这一步打算迈多大 | learning_rate |
最朴素的更新只有一句话:
w←w−η⋅g w \leftarrow w - \eta \cdot g w←w−η⋅g
「沿着梯度的反方向走一小步。」------这就是 SGD 的核心思想。
后面更高级的优化器,都是在这句话上加「记忆」和「自适应步长」。
2.3 为什么不只用「减梯度」这么简单?
真实损失曲面往往像崎岖山地,而不是光滑碗底:
- 有的方向陡、有的方向平:固定步长容易在陡处震荡、在平处爬不动;
- 噪声大:每个 batch 的梯度都在抖,直走容易走偏;
- 鞍点多:梯度接近 0,但不一定到了谷底。
于是人们给优化器加上两类「外挂」:
- 一阶历史(动量):记住刚才大概往哪走,减少左右摇摆;
- 二阶历史(自适应学习率):哪个参数经常被大力更新,就给它把步长调小。
2.4 五种优化器的「进化故事」(先建立直觉)
SGD → 只会看当前这一步的梯度
↓
SGDM → 加上「惯性」(动量),走得更稳更快
↓
Adagrad → 给每个参数单独记账:常更新的自动减小步长
↓
RMSProp → 账本改成「近期加权平均」,避免 Adagrad 后期学不动
↓
Adam → 动量 + 自适应步长 两者都要(目前最常用的默认选择)
记住这条进化线,后面的公式就不会显得突兀。
2.5 公式速览(对照记忆)
下面统一约定:www 为参数,ggg 为当前梯度,η\etaη 为学习率,ε\varepsilonε 为防止除零的小数。
(1)SGD
w←w−η g w \leftarrow w - \eta\, g w←w−ηg
无额外状态,最简单。
(2)SGDM(SGD with Momentum)
引入速度 vvv(动量),μ\muμ 一般为 0.9:
v←μ v−η g,w←w+v v \leftarrow \mu\, v - \eta\, g,\quad w \leftarrow w + v v←μv−ηg,w←w+v
像滚下山的球:不只看脚下坡度,还带着惯往前冲。
(3)Adagrad
为每个参数累加历史平方梯度 sss:
s←s+g2,w←w−ηs+ε g s \leftarrow s + g^2,\quad w \leftarrow w - \frac{\eta}{\sqrt{s}+\varepsilon}\, g s←s+g2,w←w−s +εηg
常更新的参数,分母变大 → 有效学习率变小。
(4)RMSProp
把「永久累加」改成指数滑动平均 Eg2Eg\^2Eg2,ρ\rhoρ 一般为 0.9:
Eg2←ρ Eg2+(1−ρ) g2,w←w−ηEg2+ε g Eg\^2 \leftarrow \rho\, Eg\^2 + (1-\rho)\, g^2,\quad w \leftarrow w - \frac{\eta}{\sqrt{Eg\^2}+\varepsilon}\, g Eg2←ρEg2+(1−ρ)g2,w←w−Eg2 +εηg
解决 Adagrad 后期步长过小的问题。
(5)Adam
同时维护一阶矩 mmm 与二阶矩 vvv(这里的 vvv 是平方梯度均值,不是 SGDM 的速度):
m←β1m+(1−β1) gv←β2v+(1−β2) g2m^=m1−β1t,v^=v1−β2tw←w−ηm^v^+ε \begin{aligned} m &\leftarrow \beta_1 m + (1-\beta_1)\, g \\ v &\leftarrow \beta_2 v + (1-\beta_2)\, g^2 \\ \hat{m} &= \frac{m}{1-\beta_1^t},\quad \hat{v} = \frac{v}{1-\beta_2^t} \\ w &\leftarrow w - \eta \frac{\hat{m}}{\sqrt{\hat{v}}+\varepsilon} \end{aligned} mvm^w←β1m+(1−β1)g←β2v+(1−β2)g2=1−β1tm,v^=1−β2tv←w−ηv^ +εm^
默认常用:β1=0.9, β2=0.999\beta_1=0.9,\ \beta_2=0.999β1=0.9, β2=0.999。
直觉上:Adam ≈ 动量方向(像 SGDM)+ 自适应步长(像 RMSProp)。
2.6 自然过渡:从概念到你的代码
理论再清楚,也要落到一行配置上。在 Keras 里,换优化器往往只改 compile 里的一个参数:
python
model.compile(
optimizer='adam', # 也可换成 SGD / RMSprop / Adagrad 等
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['sparse_categorical_accuracy']
)
接下来用你已经跑通过的 MNIST Sequential 网络,把「优化器到底多占了多少参数」这件事算清楚------这是理解优化器成本最直观的案例。
3. 应用案例:MNIST 网络里的优化器参数
3.1 案例网络长什么样?
text
输入 (batch, 28, 28)
→ Flatten → (batch, 784) 参数: 0
→ Dense(128, relu) → (batch, 128) 参数: 784×128 + 128 = 100,480
→ Dense(10, softmax)→ (batch, 10) 参数: 128×10 + 10 = 1,290
可训练参数总量:
P=100,480+1,290=101,770 P = 100{,}480 + 1{,}290 = 101{,}770 P=100,480+1,290=101,770
对应 model.summary() 中的 Trainable params: 101,770 。
这些是网络真正「认识数字」的权重。
说明:
Dense(128)里的 128 是隐藏层宽度(超参数,可改);输出层
Dense(10)的 10 对应 0~9 十类,一般不要乱改。
3.2 你可能看到过这样的 summary
text
Total params: 305,312 (1.16 MB)
Trainable params: 101,770 (397.54 KB)
Non-trainable params: 0 (0.00 B)
Optimizer params: 203,542 (795.09 KB)
很多人第一次会懵:网络明明只有约 10 万参数,为什么 Total 变成了 30 万?
答案是:
Total params=Trainable+Optimizer=101,770+203,542=305,312 \text{Total params} = \text{Trainable} + \text{Optimizer} = 101{,}770 + 203{,}542 = 305{,}312 Total params=Trainable+Optimizer=101,770+203,542=305,312
Optimizer params 不是网络结构的一部分 ,而是优化器在训练时额外保存的「内部状态」(如动量、平方梯度均值等)。
推理部署时通常只加载 Trainable;断点续训才需要连同优化器状态一起保存。
3.3 统一公式(结合本案例)
在当前 Keras 实现中,优化器几乎都会额外挂 2 个标量:
iteration:更新了多少步learning_rate:当前学习率
再设每个可训练参数额外保存 kkk 份同形状状态,则:
Optimizer params≈k⋅P+2 \text{Optimizer params} \approx k \cdot P + 2 Optimizer params≈k⋅P+2
对本案例 P=101,770P=101{,}770P=101,770,实测如下:
| 优化器 | kkk | 额外状态含义 | Optimizer params | 计算过程 |
|---|---|---|---|---|
| SGD | 0 | 无 per-param 状态 | 2 | 0⋅P+2=20\cdot P+2=20⋅P+2=2 |
| SGDM | 1 | 动量 vvv | 101,772 | 1⋅P+21\cdot P+21⋅P+2 |
| Adagrad | 1 | 平方累加 sss | 101,772 | 1⋅P+21\cdot P+21⋅P+2 |
| RMSProp | 1 | 平方滑动平均 | 101,772 | 1⋅P+21\cdot P+21⋅P+2 |
| Adam | 2 | 一阶矩 mmm + 二阶矩 vvv | 203,542 | 2⋅P+22\cdot P+22⋅P+2 |
以 Adam 为例拆开看:
| 内容 | 数量 |
|---|---|
| 全部权重的 momentum mmm | 101,770 |
| 全部权重的 velocity vvv | 101,770 |
| iteration + learning_rate | 2 |
| 合计 | 203,542 |
这也解释了你在 Adam 下看到的那行:
Optimizer params: 203,542 (795.09 KB)
3.4 内存直觉
按 float32(4 字节)粗算优化器额外占用:
| kkk | 代表 | 约额外内存 |
|---|---|---|
| 0 | SGD | 可忽略 |
| 1 | SGDM / Adagrad / RMSProp | ≈ 397 KB |
| 2 | Adam | ≈ 795 KB |
MNIST 小网络上差别不大;一旦 PPP 上亿(大模型),Adam 比 SGD 多占约两倍优化器状态,就会成为显存选型的重要因素。
3.5 案例代码片段(训练入口)
python
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(
optimizer='adam', # 对比实验时可改为 SGD(momentum=0.9) 等
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['sparse_categorical_accuracy']
)
model.fit(x_train, y_train, batch_size=32, epochs=2,
validation_data=(x_test, y_test), validation_freq=1)
model.summary() # 重点看 Trainable / Optimizer / Total
建议课堂演示: 只改 optimizer,其余不动,观察 Optimizer params 与收敛曲线如何变化。
4. 生产应用场景:优化器怎么选?
| 场景 | 常见选择 | 原因(通俗说) |
|---|---|---|
| 图像分类 / 通用深度学习起步 | Adam | 省心、收敛快,超参相对好用 |
| CNN 大规模训练、追求极致精度 | SGD + Momentum(常配合学习率衰减) | 调好后泛化往往更扎实 |
| 循环网络 / 序列模型(部分任务) | RMSProp 或 Adam | 对非平稳目标更稳 |
| 稀疏特征(推荐、广告、部分 NLP 稀疏输入) | Adagrad / Adam | 低频特征也能得到合适更新幅度 |
| 显存极度紧张、超大模型 | SGD 或融合优化器 / 低精度状态 | 优化器状态少,省显存 |
| 微调预训练模型 | AdamW 等(Adam + 解耦权重衰减) | 工业界微调常用变体 |
| 教学演示、算法对比实验 | 五种都跑一遍 | 便于理解 k=0/1/2k=0/1/2k=0/1/2 与曲线差异 |
落地经验一句话:
- 先保证能收敛 → 优先 Adam;
- 要冲榜/要泛化 → 再认真调 SGDM + 学习率策略;
- 要省显存 → 看 kkk,能降则降。
5. 五种优化器对照:优缺点与参数量
5.1 总表(结合 MNIST 案例 P=101,770P=101{,}770P=101,770)
| 优化器 | 核心思想 | 优点 | 缺点 | Optimizer params | Total(约) |
|---|---|---|---|---|---|
| SGD | 当前梯度直接走 | 实现简单、显存最省、调好后泛化潜力大 | 慢、易震荡、对学习率敏感 | 2 | 101,772 |
| SGDM | 加惯性平滑方向 | 加速收敛、减轻震荡 | 仍需仔细调学习率;无自适应步长 | P+2=101,772P+2=101{,}772P+2=101,772 | 203,542 |
| Adagrad | 按历史强度缩小步长 | 适合稀疏特征 | 学习率可能衰减过快,后期「学不动」 | P+2=101,772P+2=101{,}772P+2=101,772 | 203,542 |
| RMSProp | 滑动平均版自适应 | 修正 Adagrad 后期过小问题 | 超参仍需经验;默认无动量 | P+2=101,772P+2=101{,}772P+2=101,772 | 203,542 |
| Adam | 动量 + 自适应 | 默认常用、收敛快、省调参时间 | 状态约为 2P2P2P;部分任务泛化略逊精心调过的 SGDM | 2P+2=203,5422P+2=203{,}5422P+2=203,542 | 305,312 |
补充:若
RMSProp(momentum=0.9),会再多存一份动量,Optimizer params 升至约 2P+22P+22P+2,与 Adam 同量级。
5.2 优缺点「人话版」
SGD
像谨慎的徒步者,每一步只看脚下。路对了能到山顶,但容易走冤枉路。
SGDM
像带着惯性的滑板:下坡冲得快,过坑少抖一下。但仍是「全场统一步长」。
Adagrad
像给每个参数发「疲劳值」:经常大力更新的参数越走越慢。稀疏场景友好,但可能越来越不敢迈步。
RMSProp
疲劳值改成「只看最近一段时间」,不会无限累积,长期训练更合理。
Adam
既看惯性方向,又按参数自动调步长,像有经验的向导------大多数时候你交给他就行;但背包更重(状态更多)。
5.3 和网络层参数的区别(务必分清)
| 类型 | 例子 | 何时存在 | 推理时需要吗? |
|---|---|---|---|
| 网络可训练参数 | Dense 的 W,bW,bW,b | 建网后就有 | 需要 |
| 优化器状态参数 | Adam 的 m,vm,vm,v | 开始训练后由优化器创建 | 通常不需要(续训才需要) |
面试或写报告时,不要把 Optimizer params 说成「模型有 30 万参数」------准确说法是:模型约 10.2 万,Adam 训练时额外约 20.4 万状态。
6. 面试题(含参考思路)
基础概念
Q1. 优化器和损失函数有什么区别?
- 损失函数:衡量「现在有多差」;
- 优化器:根据梯度决定「参数怎么改」。
Q2. 学习率太大/太小会怎样?
- 太大:损失震荡甚至发散;
- 太小:收敛极慢,易停在较差区域。
Q3. 为什么需要动量(Momentum)?
用历史梯度方向平滑当前更新,加速同向、抑制震荡。
算法对比
Q4. Adagrad 和 RMSProp 的核心差别?
Adagrad 累加全部历史 g2g^2g2;RMSProp 用指数滑动平均,避免长期学习率过小。
Q5. Adam 为什么常作为默认优化器?有什么代价?
结合动量与自适应学习率,收敛快、好调;代价是约 2P2P2P 的额外状态,部分任务泛化可能不如调优后的 SGDM。
Q6. 训练一个 P=1亿P=1\text{亿}P=1亿 参数的模型,Adam 大约比 SGD 多占多少优化器状态(float32)?
粗算:SGD 几乎无 per-param 状态;Adam 约 2P2P2P 个 float → 2×108×4B≈800MB2\times 10^8 \times 4\text{B} \approx 800\text{MB}2×108×4B≈800MB 量级(未计框架开销)。
结合本案例
Q7. 下列 summary 如何解释?
Trainable=101770,Optimizer=203542,Total=305312,优化器是 Adam。
答:203542=2×101770+2203542 = 2\times 101770 + 2203542=2×101770+2(mmm 与 vvv 各一份,另加 iteration 与 learning_rate)。
Q8. 同一网络,SGD 与 Adam 的 Optimizer params 分别是多少?
SGD:2;Adam:203,542。(以本案例 PPP 与当前 Keras 计数方式为准。)
Q9. Dense(128) 的 128 能改吗?和优化器有关吗?
128 是隐藏层宽度,可改,会影响 PPP,从而影响所有 k⋅Pk\cdot Pk⋅P 的优化器状态规模;与「选哪种优化器」是两件独立的事。
开放题
Q10. 线上要微调大模型且显存紧张,如何从优化器角度省资源?
可选 SGD/SGDM、减小状态精度、使用融合/分页优化器、梯度检查点等;并说明与吞吐、收敛的权衡。
7. 总结
- 优化器 = 训练时的「下山策略」:在梯度指引下更新权重。
- 进化主线:SGD → SGDM(加动量)→ Adagrad/RMSProp(自适应步长)→ Adam(两者结合)。
- 状态份数决定成本 :Optimizer params ≈kP+2\approx kP+2≈kP+2,SGD 的 k=0k=0k=0,SGDM/Adagrad/RMSProp 的 k=1k=1k=1,Adam 的 k=2k=2k=2。
- MNIST 案例锚点 :P=101,770P=101{,}770P=101,770;Adam 下 Optimizer=203,542,Total=305,312。
- 选型口诀 :先求稳用 Adam;要极致泛化打磨 SGDM;要省显存看 kkk。
- 分清两本账:Trainable 是模型本体;Optimizer 是训练时的工作记忆。
8. 课后作业
作业 A:概念巩固(书面)
- 用自己的话解释:梯度、学习率、优化器三者关系(不超过 200 字)。
- 画出五种优化器的「进化关系图」,并在每种旁边标注 k=0/1/2k=0/1/2k=0/1/2。
- 证明(算术):当 P=101,770P=101{,}770P=101,770 时,Adam 的 Optimizer params 为何是 203,542。
作业 B:动手实验(代码)
基于 class3/p14_mnist_sequential.py(或等价 MNIST 网络):
- 固定网络与
epochs、batch_size,分别使用:
SGD、SGD(momentum=0.9)、Adagrad、RMSprop、Adam; - 记录每次的:
- Trainable params
- Optimizer params
- Total params
- 最终
val_sparse_categorical_accuracy
- 写成一张对比表,并回答:
- 哪种 Optimizer params 最大?为什么?
- 准确率最高的是否一定是 Adam?若不是,可能原因是什么?
作业 C:思考拓展
- 若把隐藏层从 128 改为 256,PPP 变成多少?Adam 的 Optimizer params 大约变成多少?
- 推理服务保存模型时,需要保存 Adam 的 m,vm,vm,v 吗?什么场景下需要?
- (选做)查阅 AdamW 与 Adam 的差别,用三句话说明它在微调中的价值。
提交建议
- 作业 A:Markdown / 笔记;
- 作业 B:代码 + 结果表 + 不少于 5 行的结论;
- 作业 C:简答即可。
附录:速查卡片
text
更新通式: w ← w - η · (某种对 g 的加工)
SGD: 直接用 g 状态 k=0
SGDM: 用动量平滑 g 状态 k=1
Adagrad: 用累计 g² 缩小步长 状态 k=1
RMSProp: 用滑动平均 g² 缩小步长 状态 k=1
Adam: 动量 + 滑动平均 g² 状态 k=2
本课锚点: P=101,770
Adam: Optimizer = 2P+2 = 203,542
Total: P + Optimizer = 305,312
课件完 · 建议结合 p14_mnist_sequential.py 现场改优化器演示一节课