PyTorch 的优化器模块 torch.optim,是神经网络训练中负责更新模型参数的核心模块。它根据自动求导得到的梯度,按照一定的优化算法调整权重和偏置,使模型的损失函数逐步下降。

简单地说,torch.optim 模块回答的是:模型参数应该朝哪个方向更新、每次更新多少,以及如何让训练过程更稳定、更高效。

如果说 torch.nn 模块负责构建模型,torch.autograd 模块负责计算梯度,那么 torch.optim 模块就负责把梯度真正转化为参数更新。没有优化器,模型即使能够计算损失和梯度,也无法通过训练不断改进。

一、认识 torch.optim 模块

torch.optim 是 PyTorch 中专门用于优化模型参数的模块。它提供了多种常见优化算法,如 SGD、Adam、AdamW、RMSprop 等。

打开网易新闻 查看精彩图片

图 1:优化器模块在 PyTorch 训练流程中的位置

在一个典型训练过程中,优化器通常位于反向传播之后、下一轮前向传播之前:

→ 进入下一轮训练

一个最简单的优化器使用方式如下:

这里的核心是:

• model.parameters() 提供需要更新的模型参数

• optim.Adam(...) 指定使用 Adam 优化算法

• lr=0.001 指定学习率

优化器本身并不定义模型结构,也不负责计算梯度。它只根据参数已有的 .grad 信息执行更新。

二、优化器在训练闭环中的作用

神经网络训练的目标,是让损失函数尽可能小。优化器的作用,就是根据梯度信息调整参数,使模型向更小的损失方向移动。

打开网易新闻 查看精彩图片

图 2:从梯度到参数更新的基本过程

一个简化的参数更新过程可以理解为:

其中:

• θ 表示模型参数

• L 表示损失函数

• ∇θL 表示损失函数对参数 θ 的梯度

• η 表示学习率

• ← 表示用新值替换旧值

这条公式体现了梯度下降的基本思想:如果梯度指出损失上升最快的方向,那么参数更新就朝相反方向移动,从而尽量降低损失。

在 PyTorch 中,这个过程通常由三行代码完成:

optimizer.step()

它们分别表示:

• 清空旧梯度

• 反向传播计算新梯度

• 根据梯度更新参数

这三步是理解 PyTorch 优化器的关键。

三、创建优化器:把参数交给优化算法

创建优化器时,通常需要把模型参数传入优化器:

这里的 model.parameters() 会返回模型中所有需要训练的参数,包括各层的权重和偏置。

可以查看这些参数:

    print(name, param.shape, param.requires_grad)

输出结果类似:

2.bias torch.Size([3]) True

优化器只会更新传入给它的参数。如果某个参数没有被传入优化器,即使它有梯度,也不会被这个优化器更新。

这也是为什么自定义模型时,网络层通常要定义在 __init__() 中,并正确注册为 nn.Module 的子模块。只有被模型管理的参数,才能通过 model.parameters() 被优化器找到。

四、zero_grad():清空上一轮梯度

在 PyTorch 中,梯度默认会累积,而不是每次自动覆盖。因此,每次反向传播前通常需要清空旧梯度。

例如:

optimizer.zero_grad()

如果不清空梯度,当前批次计算出的梯度会与上一轮梯度相加,导致参数更新不符合预期。

一个简单例子如下:

第二次打印的 x.grad 并不是只包含 y2 对 x 的梯度,而是前后两次梯度的累加。

在普通训练循环中,通常应写成:

optimizer.step()

这个顺序很重要:

• 先清空旧梯度

• 再计算当前梯度

• 最后更新参数

也可以写成:

optimizer.zero_grad(set_to_none=True)

这种写法会把梯度设为 None,在某些场景下可以减少内存占用。但初学阶段可以先使用默认写法,理解梯度清零的作用更重要。

五、backward() 与 step() 的关系

loss.backward() 和 optimizer.step() 经常连在一起出现,但它们的作用完全不同。

loss.backward()

表示:根据当前计算图,计算损失函数对模型参数的梯度。

optimizer.step()

表示:根据参数中的 .grad,按照优化算法更新参数。

例如:

这里可以理解为:

• pred = model(x) 构建前向计算图

• loss = criterion(pred, target) 得到损失

• loss.backward() 计算梯度

• optimizer.step() 使用梯度更新参数

需要注意的是,optimizer.step() 不会自动执行反向传播。如果没有先调用 loss.backward(),参数通常没有可用梯度,优化器也就无法完成有效更新。

六、学习率:控制每次参数更新的步长

学习率(Learning Rate)是优化器中最重要的超参数之一。它决定每次参数沿梯度方向更新的幅度。

学习率过大,可能导致训练震荡,甚至损失无法下降;学习率过小,训练会非常缓慢,甚至长时间停留在效果较差的位置。

例如:

optimizer = optim.SGD(model.parameters(), lr=0.01)

其中 lr=0.01 就是学习率。

可以把学习率理解为参数更新的“步长”:

其中 η 就是学习率。

常见经验是:

• SGD 常使用相对较大的学习率,例如 0.1、0.01、0.001

• Adam 常使用较小的学习率,例如 0.001、0.0001

• 学习率没有固定最优值,需要结合任务、模型和数据调整

在实际训练中,如果损失剧烈震荡,可以尝试降低学习率;如果损失下降很慢,可以尝试适当增大学习率。

七、SGD:最基础的梯度下降优化器

SGD 是随机梯度下降(Stochastic Gradient Descent)的缩写,是最基础、也最重要的优化算法之一。

基本写法如下:

SGD 的基本思想是:根据当前批次数据计算出的梯度,更新模型参数。

加入动量(momentum)后,SGD 可以在一定程度上减少震荡,并加快沿稳定方向的更新:

动量可以理解为给参数更新加入“惯性”。如果多个批次的梯度方向比较一致,参数会沿这个方向更稳定地前进;如果梯度方向频繁变化,动量可以缓解短期波动。

SGD 的优点是:

• 原理清晰

• 行为可控

• 在许多任务中泛化表现较好

SGD 的不足是:

• 对学习率较敏感

• 训练速度可能较慢

• 通常需要更仔细地调参

因此,在教学和理解优化原理时,SGD 非常重要;在实际工程中,Adam 和 AdamW 也非常常用。

八、Adam:自适应学习率优化器

Adam 是深度学习中非常常用的优化器。它会根据梯度的一阶矩和二阶矩估计,为不同参数自适应地调整更新幅度。

常见写法如下:

optimizer = optim.Adam(model.parameters(), lr=0.001)

Adam 的优势是:

• 对学习率相对不那么敏感

• 收敛通常较快

• 适合许多神经网络任务

• 初学和实验阶段使用方便

一个完整示例如下:

这里要注意:Adam 只是参数更新算法,不改变模型结构,也不改变损失函数的定义。

在很多入门实验中,Adam 是一个稳妥的默认选择;但在一些任务中,SGD、AdamW 或带学习率调度的方案可能表现更好。

九、AdamW:带解耦权重衰减的 Adam

AdamW 是 Adam 的常用改进版本,尤其在 Transformer、视觉模型和大规模预训练模型中非常常见。

常见写法如下:

AdamW 中的 weight_decay 通常用于控制模型参数不要过大,从而起到一定的正则化作用。

需要注意的是,weight_decay 并不等同于学习率。它控制的是参数衰减强度,而学习率控制的是梯度更新步长。

可以简单理解为:

• lr 决定每次更新走多远

• weight_decay 限制参数不要无限变大

• AdamW 把权重衰减与 Adam 的梯度更新机制更清晰地分离

在现代深度学习实践中,如果模型比较复杂,尤其是使用 Transformer 类结构,AdamW 往往比普通 Adam 更常见。

十、RMSprop 与其他优化器

除了 SGD、Adam 和 AdamW,PyTorch 还提供了其他优化器,例如:

• optim.RMSprop

• optim.Adadelta

• optim.Adagrad

• optim.NAdam

• optim.LBFGS

其中,RMSprop 也是一种自适应学习率优化器,曾在循环神经网络等任务中较常见。

示例:

不同优化器的差异,主要体现在如何利用梯度历史信息、如何调整不同参数的更新幅度,以及是否引入动量、权重衰减等机制。

初学阶段不必一次掌握所有优化器。更重要的是先理解:

• 优化器接收模型参数

• 反向传播得到梯度

• 优化器根据梯度更新参数

• 不同优化器只是更新策略不同

十一、参数组:为不同参数设置不同学习率

有时,一个模型中不同部分需要使用不同的学习率。例如,在迁移学习中,预训练主干网络可以使用较小学习率,新加的分类头可以使用较大学习率。

这时可以使用参数组:

完整示例如下:

这里可以理解为:

• feature 部分学习率较小,避免大幅改变已有特征表示

• classifier 部分学习率较大,使新任务头更快适应当前任务

参数组还可以为不同部分设置不同的 weight_decay、momentum 等选项。

十二、冻结参数与优化器

在迁移学习中,经常需要冻结部分参数,只训练某些层。

例如:

    

冻结后,最好只把仍然需要训练的参数交给优化器:

这样做可以让训练目标更清晰,也避免优化器持有不需要更新的参数。

完整示例:

    

需要注意的是,设置 requires_grad=False 会阻止梯度计算,但如果优化器早已创建,仍可能持有旧参数引用。因此,冻结参数后,通常应重新创建优化器或确认优化器中的参数组是否正确。

十三、学习率调度器:动态调整学习率

训练过程中,学习率不一定始终固定。有时训练前期需要较大学习率快速下降,后期需要较小学习率精细调整。

PyTorch 提供了学习率调度器,例如:

• StepLR

• MultiStepLR

• ExponentialLR

• CosineAnnealingLR

• ReduceLROnPlateau

一个简单示例:

在训练循环中通常写成:

    

这里的含义是:每经过 10 个 epoch,学习率乘以 0.1。

学习率调度器的作用不是直接更新模型参数,而是调整优化器中的学习率,从而影响后续参数更新幅度。

需要注意的是,不同调度器的调用时机可能不同。有些按 epoch 调整,有些按 batch 调整,有些根据验证集指标调整。实际使用时应根据调度器设计选择合适的位置。

十四、保存与恢复优化器状态

训练模型时,有时不仅要保存模型参数,还要保存优化器状态。因为 Adam、AdamW、RMSprop 等优化器内部会维护梯度历史信息。

保存方式如下:

恢复训练时:

如果只是推理,通常只需要加载模型参数;如果要从中断处继续训练,最好同时恢复优化器状态。

原因是:优化器状态会影响后续参数更新。如果只恢复模型参数而不恢复优化器状态,训练虽然可以继续,但优化过程可能与中断前不完全一致。

十五、一个完整示例:用优化器训练简单分类模型

打开网易新闻 查看精彩图片

图 3:优化器驱动的神经网络训练闭环

下面用一个完整示例,把模型、损失函数、自动求导和优化器串起来:

    

这个训练闭环可以概括为:

前向传播 → 计算损失 → 清空梯度 → 反向传播 → 参数更新

其中:

• torch.nn 负责模型结构和损失函数

• torch.autograd 负责自动计算梯度

• torch.optim 负责根据梯度更新参数

优化器并不是单独工作的,它必须与模型、损失函数和自动求导机制配合,才能完成真正的训练过程。

十六、使用优化器时应注意的问题

1、不要忘记 zero_grad()

训练循环中通常要写:

optimizer.step()

如果忘记 zero_grad(),梯度会不断累积,导致参数更新异常。

2、不要把 step() 放在 backward() 前面

错误写法:

loss.backward()

正确写法:

optimizer.step()

因为优化器需要根据已经计算好的梯度更新参数。

3、优化器只会更新传入的参数

如果某些参数没有传给优化器,它们不会被更新。

例如:

optimizer = optim.Adam(model.classifier.parameters(), lr=0.001)

这表示只更新 classifier 部分,而不是整个模型。

4、冻结参数后要检查优化器

如果先创建优化器,再冻结参数,优化器中可能仍然保存了原来的参数组。更稳妥的做法是:冻结参数后重新创建优化器。

5、学习率过大或过小都会影响训练

学习率过大,损失可能震荡或发散;学习率过小,训练可能非常缓慢。遇到训练不稳定时,学习率通常是最先需要检查的超参数之一。

6、Adam 不等于一定更好

Adam 使用方便,收敛速度通常较快,但并不意味着在所有任务中都优于 SGD。不同优化器适合不同任务,实际效果需要通过实验验证。

7、权重衰减不是学习率

lr 控制参数更新步长,weight_decay 控制参数衰减强度。二者作用不同,不应混淆。

8、继续训练时应保存优化器状态

如果要从中断处继续训练,建议同时保存:

optimizer.state_dict()

否则模型参数虽然恢复了,但优化器内部状态没有恢复,后续训练轨迹可能发生变化。

小结

torch.optim 模块负责根据梯度更新模型参数,是神经网络训练闭环中的关键环节。学习优化器,重点是理解 zero_grad() 清空旧梯度、backward() 计算新梯度、step() 执行参数更新,以及学习率、权重衰减、参数组和学习率调度器如何影响训练过程。

点赞有美意,赞赏是鼓励