Skip to content

WARNING

🧪 Beta公测版本提示:教程主体已完成,正在优化细节,欢迎大家提Issue反馈问题或建议。

s08 优化器:从SGD到Adam — exercise.py 练习指南

Download exercise.py

练习目标

亲手实现 Momentum、RMSProp、NAG(Nesterov 加速梯度)三种优化器的更新规则,理解每种优化器"从 SGD 出发,一步步解决了什么问题"的设计演进脉络。

预备知识

建议先阅读 index.md 并运行 demo.py,确保理解:

概念数学定义
SGDθt+1=θtαgt
指数滑动平均(EMA)mt=βmt1+(1β)gt
Momentummt 定方向,θt+1=θtαmt
RMSPropvt 缩步长,θt+1=θtαgt/vt+ϵ
狭长峡谷损失地形某些方向陡峭、某些平缓,条件数 κ1

任务清单

任务1:实现 Momentum 更新规则

描述:补全 MomentumOptimizerExercise.step() 方法的三个 TODO——初始化速度向量、更新速度、更新参数。

公式回顾

mt=βmt1+(1β)gtθt+1=θtαmt

提示

  • 首次调用时 self.m is None,需要初始化为 np.zeros_like(theta)
  • self.m = self.beta * self.m + (1 - self.beta) * grad 更新速度
  • return theta - self.lr * self.m 更新参数
  • 注意 (1 - self.beta) 不能省略——如果不乘,就变成了对梯度做纯滑动平均而没有衰减

期望行为

  • (3.0,2.5) 出发,经过 5 步后动量向量应指向原点附近
  • 每一步的动量 mt 是历史梯度的加权平均,不会像 SGD 那样每步方向剧烈变化

为什么 Momentum 比 SGD 好? 在狭长峡谷中,SGD 在陡峭方向上来回震荡(一步正向一步反向),而 Momentum 通过平滑历史方向,抵消了这种震荡——正负梯度互相抵消,让 mt 在震荡方向上接近 0。


任务2:实现 RMSProp 自适应步长

描述:补全 RMSPropOptimizerExercise.step() 方法的三个 TODO——初始化 v、更新梯度平方的 EMA、自适应步长更新。

公式回顾

vt=βvt1+(1β)gtgtθt+1=θtαgtvt+ϵ

提示

  • grad ** 2 是逐元素平方,对应公式中的 gtgt
  • np.sqrt(self.v) 计算 vt
  • self.eps(通常 108)加到分母上防止除零
  • 有效学习率 =α/(vt+ϵ) —— 可以打印出来观察

期望行为

  • 陡峭方向(θ1,系数 a=20)的有效学习率应该小于平缓方向(θ2,系数 b=1
  • 这是因为 θ1 方向梯度大 → vt 增长快 → 分母大 → 有效步长自动变小
  • 最终输出应显示 θ₁ < θ₂: True

为什么 RMSProp 解决了步长不统一问题? SGD 对所有参数用同一个学习率,但不同参数梯度的尺度可能差几个数量级。RMSProp 让每个参数拥有自己的"自适应学习率"——梯度历史大的参数步长自动变小,历史小的步长自动放大。


任务3:实现 Nesterov 加速梯度 (NAG)

描述:补全 NAGOptimizer.step() 方法的五个 TODO。NAG 是 Momentum 的进阶版——它"先沿动量方向看一步,再在那个位置计算梯度"。

与普通 Momentum 的区别

  • Momentum:在当前点 θt 计算梯度,然后沿 mt 方向更新
  • NAG:先沿 mt1 方向走一步到"前瞻位置",在那个位置计算梯度,再更新

公式

前瞻位置:

θlookahead=θtαβmt1

在"前瞻"位置计算梯度并更新动量:

mt=βmt1+(1β)L(θlookahead)

参数更新:

θt+1=θtαmt

提示

  • 注意:NAG 的 step() 接收的是 grad_fn(一个函数),而不是 grad(一个值)!因为需要在 lookahead 位置重新计算梯度
  • theta_lookahead = theta - self.lr * self.beta * self.m
  • grad_lookahead = grad_fn(theta_lookahead) —— 在"前瞻"位置调梯度函数
  • 动量更新与 Momentum 相同,只是用的是 grad_lookahead 而非当前位置的梯度

直觉理解:NAG 像是一个"会思考的滚球"。普通 Momentum 是"盲人下山"——沿着惯性方向走,走到哪算哪。NAG 是"睁眼下山"——先往前看一眼,发现前面是悬崖(梯度变大),就可以提前调整方向。在数学上这对应更紧的收敛界。

期望行为:从 (3.0,2.5) 出发,经过 10 步后,NAG 的损失通常小于普通 Momentum——因为它更"聪明"地选择了更新方向。


优化器演进总览

优化器改进点要记住的关键行
SGD基线theta - lr * grad
Momentum加惯性,平滑方向m = beta*m + (1-beta)*grad
RMSProp自适应步长v = beta*v + (1-beta)*(grad**2)
Adam方向 + 步长 + 修正m + v + 偏差修正
NAG"会思考的"Momentum先在 lookahead 处算梯度

源码位置

clone 后打开(相对仓库根目录):

docs/nn-decision/dl/optimizers/code/exercise.py