TensorFlow 优化器怎么选?Adam vs SGD 实战对比和选择指南
优化器决定了模型参数更新的方向和步长——选错了,再好的模型架构也训不出好结果。TensorFlow 提供了十几种优化器,但 90% 的场景你只需要在 Adam 和 SGD 之间做选择。这篇文章不罗列 API,而是讲清楚每个优化器的原理差异、什么时候用哪个、以及一些实战中容易踩的坑。
先搞懂优化器在做什么
优化器的核心工作就一件事:根据梯度更新参数。区别在于"怎么用梯度"——
- SGD:梯度指向哪,就往那走一步,步长固定
- Adam:记住历史梯度的方向和大小,自适应调整步长
- 其他优化器:在这两个思路之间做各种变体
理解了这个本质,选优化器就不是背表格了。
SGD —— 简单但被低估
pythonfrom tensorflow.keras.optimizers import SGD # 基本 SGD optimizer = SGD(learning_rate=0.01) # 带动量——实际使用时的标准配置 optimizer = SGD(learning_rate=0.01, momentum=0.9) # Nesterov 动量——更激进的变体 optimizer = SGD(learning_rate=0.01, momentum=0.9, nesterov=True)
为什么 SGD 值得重视
纯 SGD(无动量)确实慢,但加上 momentum 之后完全不同。动量的效果是:梯度方向一致时加速(积累动量),方向变化时减速(动量抵消),帮助逃出局部最优和鞍点。
SGD 最大的优势是泛化性能。大量研究表明,虽然 Adam 收敛更快,但 SGD(+momentum)最终往往能达到更好的泛化结果。原因在于 SGD 的更新路径更"曲折",更容易跳出尖锐的局部最优,找到更平坦的最优解——平坦的最优解泛化性更好。
什么时候用 SGD
- 追求最终精度(打比赛、生产部署)
- 数据量大(>100K 样本),有足够时间训练
- 你愿意花时间调学习率
学习率调参是 SGD 的主要成本
SGD 需要手动设置学习率,而且不同阶段需要不同的学习率。典型做法是配合学习率衰减:
pythonfrom tensorflow.keras.optimizers.schedules import CosineDecay lr_schedule = CosineDecay(initial_learning_rate=0.1, decay_steps=50000) optimizer = SGD(learning_rate=lr_schedule, momentum=0.9)
0.1 是 SGD 的经典初始学习率(配合 momentum),比 Adam 的 0.001 大很多——因为 SGD 没有自适应机制,需要更大的步长来补偿。
Adam —— 不用动脑的默认选择
pythonfrom tensorflow.keras.optimizers import Adam optimizer = Adam(learning_rate=0.001)
Adam 为什么好用
Adam 维护了两个移动平均:一阶矩(梯度的指数平均,即方向)和二阶矩(梯度平方的指数平均,即大小)。然后用一阶矩除以二阶矩的平方根来更新参数——效果是梯度大时步长自动缩小,梯度小时步长自动放大。
这带来两个实际好处:
- 几乎不需要调学习率:0.001 对大多数任务都工作
- 每个参数有独立的学习率:稀疏特征也能得到合理的更新
Adam 的坑
- 权重衰减实现有 bug:标准 Adam 把 L2 正则化加到了梯度里,而不是直接惩罚权重。这导致正则化效果被自适应学习率削弱。解决方案是用 AdamW:
pythonfrom tensorflow.keras.optimizers import AdamW optimizer = AdamW(learning_rate=0.001, weight_decay=0.01)
AdamW 在 Transformer 类模型(BERT、ViT 等)中几乎是标配。
- 有时泛化不如 SGD:Adam 收敛快,但可能收敛到尖锐的最优解,测试集表现反而不如 SGD。
Adam vs SGD:到底选哪个
这是最常见的问题,直接给结论:
| 维度 | Adam | SGD + Momentum |
|---|---|---|
| 收敛速度 | 快(通常快 2-5 倍) | 慢 |
| 调参难度 | 低(lr=0.001 开箱即用) | 高(需调 lr + schedule) |
| 最终精度 | 一般 | 通常更高 |
| 泛化性能 | 稍差 | 更好 |
| 稀疏数据 | 好 | 差 |
| 显存占用 | 高(额外存储一阶/二阶矩) | 低 |
实战建议
- 项目初期 / 快速验证:用 Adam,快速跑出基线结果
- 追求最佳精度:先 Adam 预训练,再切换 SGD 精调
- 数据稀疏(NLP、推荐):Adam 或 Adagrad
- 显存紧张:SGD
Adam 预训练 + SGD 精调的混合策略
这是竞赛和工业界常用的套路:
python# 阶段 1:Adam 快速收敛 model.compile(optimizer=Adam(learning_rate=0.001), loss="...") model.fit(x_train, y_train, epochs=20) # 阶段 2:切换 SGD 精调 model.compile(optimizer=SGD(learning_rate=0.001, momentum=0.9), loss="...") model.fit(x_train, y_train, epochs=30)
切换时学习率通常设为 Adam 最终学习率的 1/10 到 1/100,让 SGD 在 Adam 找到的最优解附近精细搜索。
其他优化器:什么时候才需要
RMSprop —— RNN 的老搭档
pythonfrom tensorflow.keras.optimizers import RMSprop optimizer = RMSprop(learning_rate=0.001)
RMSprop 是 Adam 的前身之一,只维护二阶矩(不做一阶矩的指数平均)。在 RNN/LSTM 训练中曾有不错的效果,但现在基本被 Adam 替代了。如果你没有特别理由,不需要选 RMSprop。
Adagrad —— 稀疏特征的经典选择
pythonfrom tensorflow.keras.optimizers import Adagrad optimizer = Adagrad(learning_rate=0.01)
Adagrad 对频繁出现的特征用小学习率,对罕见特征用大学习率。适合处理极度稀疏的数据(比如广告点击率预测,特征空间几百万维但每条样本只激活几十个)。
缺点是学习率只减不增,训练后期可能过早衰减到接近 0。Adadelta 是 Adagrad 的改进版,限制了累积历史的影响,但实际效果不如 Adam。
Nadam —— Adam 的 Nesterov 版本
pythonfrom tensorflow.keras.optimizers import Nadam optimizer = Nadam(learning_rate=0.001)
Nadam 把 Nesterov 动量的思路融入 Adam——在计算梯度时先用当前动量"往前看一步"。理论上收敛更快,但实际差异很小。如果你对 Adam 的收敛速度不满意,Nadam 可以试一下,但别期望质变。
Ftrl —— 大规模稀疏场景专用
pythonfrom tensorflow.keras.optimizers import Ftrl optimizer = Ftrl(learning_rate=0.01, l1_regularization_strength=0.01)
Ftrl(Follow-the-Regularized-Leader)是 Google 为点击率预测设计的优化器,天生支持 L1/L2 正则化,适合在线学习场景。只在推荐系统/广告的工业级部署中才会用到。
学习率调参实战
优化器选对了,学习率没调好还是白搭。几个实用经验:
学习率太大 vs 太小的信号
- 太大:Loss 震荡不下降,或直接 NaN
- 太小:Loss 下降极慢,几百个 epoch 还在慢慢爬
- 刚好:Loss 在前几个 epoch 快速下降,然后稳定收敛
学习率预热(Warmup)
大模型训练的标准操作——前 N 步用很小的学习率,线性增加到目标值:
pythonwarmup_steps = 1000 total_steps = 50000 def warmup_cosine_schedule(step): lr = 0.001 if step < warmup_steps: return lr * (step / warmup_steps) # 之后余弦衰减 progress = (step - warmup_steps) / (total_steps - warmup_steps) return lr * 0.5 * (1 + tf.cos(3.14159 * progress)) optimizer = Adam(learning_rate=warmup_cosine_schedule)
预热避免训练初期参数还很随机时,大梯度导致的不稳定更新。Transformer 类模型几乎必用。
快速决策参考
| 你的情况 | 推荐优化器 | 学习率 |
|---|---|---|
| 刚开始,不确定 | Adam | 0.001 |
| 追求最高精度 | SGD + Momentum | 0.1 + 余弦衰减 |
| 大模型微调 | AdamW | 0.001, weight_decay=0.01 |
| NLP/稀疏特征 | Adam | 0.001 |
| 推荐系统/广告 | Ftrl | 0.01 |
| 显存不够 | SGD + Momentum | 0.1 |
| 想两全其美 | Adam → SGD | Adam: 0.001, SGD: 0.001 |
别在优化器上纠结太久——先选 Adam 跑出结果,再根据需要切换。大部分性能提升来自数据和模型架构,不是优化器。