1. 从物理直觉到代码:理解动量的“惯性”本质
想象一下,你正在推一个沉重的箱子。一开始,你需要用很大的力气才能让它动起来,但一旦它开始滑动,你只需要用很小的力就能让它保持前进,甚至在你停止推它之后,它还会因为惯性继续滑行一段距离。这个“惯性”,就是动量优化器(Momentum)最核心的灵感来源。
在深度学习的训练世界里,我们常常把模型参数的优化过程,想象成一个小球在山谷(损失函数曲面)中滚动,寻找最低点(最优解)。最基础的优化方法,比如随机梯度下降(SGD),就像是一个“健忘”的登山者。他每走一步,只看脚下最陡峭的方向,然后迈出一步,接着就完全忘记刚才的方向,重新判断。在平坦的山坡上,他走得很慢;在陡峭的峡谷里,他又容易在两侧来回弹跳,走出一条“之”字形的低效路径。
动量优化器给这个小球加上了“记忆”和“惯性”。它不再只看当前这一步的梯度(山坡的陡峭程度),还会记住之前几步的移动方向。具体来说,它会维护一个叫做“速度”(velocity)的变量,这个变量是过去所有梯度的加权平均。每次更新时,参数不仅会沿着当前梯度的方向移动,更会沿着这个“速度”的方向继续前进。这就好比推箱子,你现在的推力(当前梯度)会叠加在箱子已有的速度上,共同决定箱子下一步的运动。
这种设计带来了几个立竿见影的好处。首先,加速收敛:在梯度方向一致的平坦区域,历史速度会不断累积,让参数更新得越来越快,迅速穿越“平原”。其次,抑制震荡:在峡谷地形中,当梯度方向频繁正负交替时,动量带来的惯性会起到“平滑”作用,抵消掉一部分反向的力,让优化路径更加稳定,减少无谓的来回摆动。最后,逃离局部极小值:强大的惯性有时能帮助参数冲过一些较浅的局部最低点,有更大的机会找到更优的解。
我第一次在训练一个图像分类网络时,把SGD换成带动量的SGD,训练曲线立刻就变得平滑了许多,原来那种锯齿状的、上上下下的波动明显减少了,而且达到相同精度所需的训练轮数(epoch)也缩短了将近三分之一。这种“丝滑”的体验,让我瞬间理解了动量为什么是几乎所有现代优化器(如Adam、RMSProp)的基石组件。
2. 深入PyTorch源码:揭秘“无归一化”的工程实现
理解了物理概念,我们得看看代码是怎么写的。很多朋友在初次使用PyTorch的动量优化器时,可能会感到困惑:为什么我按照教科书上的公式设置参数,效果却不对?问题的关键,就藏在框架的实现细节里。
我们打开PyTorch中torch.optim.SGD的源码(以常见版本为例),找到核心的更新部分。为了让你看得更清楚,我把关键逻辑摘出来并做了简化注释:
# 简化版的PyTorch SGD with Momentum 核心更新逻辑
for param in parameters:
if param.grad is None:
continue
d_p = param.grad # 当前梯度 g_t
if momentum != 0:
# 获取或初始化动量缓冲区(即速度v)
buf = state['momentum_buffer']
# 关键行:更新动量缓冲区
# 这就是 v_t = β * v_{t-1} + g_t
buf.mul_(momentum).add_(d_p)
# 使用更新后的速度来更新参数
# 这就是 θ_t = θ_{t-1} - lr * v_t
d_p = buf
# 参数更新
param.add_(d_p, alpha=-lr)
看明白了吗?重点就在buf.mul_(momentum).add_(d_p)这一行。它直接执行了 v = β * v + g。这里没有我们可能在理论教材里看到的那个(1 - β)因子!


183

被折叠的 条评论
为什么被折叠?



