PyTorch 微调怎么冻结和解冻参数?检查梯度与优化器是否生效

用普通 PyTorch 分层模型演示冻结、清理旧梯度与分阶段解冻,核对更新前后的参数,并把新解冻参数加入现有优化器。

PyTorch 微调中,冻结参数通常用 requires_grad_(False),解冻用 requires_grad_(True)。但只改这个标志还不够:解冻后的参数必须出现在优化器里;已经存在的旧梯度也要清理。验证时应同时检查梯度和一次更新前后的参数,而不只是打印“可训练参数数量”。

PyTorch 自动求导说明介绍了通过 requires_grad 控制哪些参数参与梯度计算,并说明 eval() 不等于关闭梯度。下面演示普通参数冻结与分阶段解冻,不注入 LoRA 适配器。

PyTorch 微调怎么冻结和解冻参数?检查梯度与优化器是否生效

把三个开关分清楚

操作 解决什么 不能替代什么
requires_grad_(False) 不为所选叶子参数累积新梯度 不能代替清理已有 grad 或选择模块运行模式
model.eval() 切换 Dropout、BatchNorm 等模块的运行行为 不能代替冻结参数
优化器参数组 决定哪些参数由该优化器更新 不能代替开启参数梯度

本例由一个特征层、Tanh 和一个输出层组成。先固定特征层,只更新输出层;随后把特征层解冻并加入现有优化器,再确认它能更新。小模型从随机参数开始,只验证更新机制,不是有真实预训练能力的微调案例。

完整代码:冻结、验证、解冻、再验证

以下完整脚本在 Windows、Python 3.11.15、PyTorch 2.8.0+cpu、NumPy 2.3.3 环境实际运行通过,只验证这个 CPU 玩具任务。它不下载模型权重,也不代表真实模型的精度、速度或显存表现。在已启用的独立 Python 环境中可安装相同依赖:

python -m pip install torch==2.8.0 --index-url https://download.pytorch.org/whl/cpu
python -m pip install numpy==2.3.3

保存为 freeze_demo.py,运行 python freeze_demo.py。所有输入和目标是人为构造的小数组,没有下载或训练真实大模型。

import torch

torch.set_num_threads(1)
torch.manual_seed(7)
model = torch.nn.Sequential(
    torch.nn.Linear(2, 3),
    torch.nn.Tanh(),
    torch.nn.Linear(3, 1),
)
x = torch.tensor([[0., 1.], [1., 0.], [1., 1.], [-1., 0.]])
y = x[:, :1] - x[:, 1:]
loss_fn = torch.nn.MSELoss()

# This small model has no BatchNorm or Dropout.
model[0].requires_grad_(False)
for p in model.parameters():
    p.grad = None
optimizer = torch.optim.SGD(
    [p for p in model.parameters() if p.requires_grad], lr=0.1)
feature_before = model[0].weight.detach().clone()
head_before = model[2].weight.detach().clone()
optimizer.zero_grad(set_to_none=True)
loss_fn(model(x), y).backward()
print("frozen_grad_is_none:", model[0].weight.grad is None)
optimizer.step()
print("feature_unchanged:", torch.equal(feature_before, model[0].weight))
print("head_changed:", not torch.equal(head_before, model[2].weight))
assert torch.equal(feature_before, model[0].weight)
assert not torch.equal(head_before, model[2].weight)

# Add the newly unfrozen parameters to the existing optimizer.
model[0].requires_grad_(True)
optimizer.add_param_group({"params": list(model[0].parameters()), "lr": 0.01})
feature_before = model[0].weight.detach().clone()
optimizer.zero_grad(set_to_none=True)
loss_fn(model(x), y).backward()
optimizer.step()
print("unfrozen_feature_changed:", not torch.equal(feature_before, model[0].weight))
assert not torch.equal(feature_before, model[0].weight)

输出怎样才算操作生效

本次运行四项结果都为 True:

frozen_grad_is_none: True
feature_unchanged: True
head_changed: True
unfrozen_feature_changed: True

冻结时特征层 grad 为 None,更新后特征层权重不变、输出层权重变化;解冻并补入参数组后,特征层权重开始变化。读者应保存更新前的参数副本,对同一小批次做更新,再核对冻结层不变、目标训练层发生变化。

Optimizer.add_param_group 官方说明明确列出了分阶段微调时,把解冻层加入优化器的用途。例子先把特征层排除在优化器外,解冻时才添加,因此不会重复注册;若原优化器已经包含全部参数,不要再次添加同一参数。

换成自己的模型,最常见的三个检查点

  1. 根据 model.named_parameters() 和模型结构选定需要冻结的层。不要假设所有模型第一层都写作 model[0]。
  2. 冻结已有训练层时,清除历史 p.grad,并检查优化器参数组。requires_grad=False 不会自动抹掉上一步已经存在的梯度。
  3. 解冻时确认参数确实加入优化器,核对新参数组学习率,再用小批次检查有限损失、有效梯度和更新差值。

示例对新解冻层设 lr=0.01,输出层 lr=0.1,目的是展示参数组可以有不同学习率,不是经过调优的推荐比例。若重新创建优化器,原来的动量等历史状态通常会丢失;本文采用给原优化器追加新组的方式。

常见问题:冻结了,BatchNorm 统计为什么还变?

requires_grad 管的是参数求导,BatchNorm 运行均值等是缓冲状态,训练模式下仍可能变化。若要固定这一行为,需按任务设置相应模块为 eval(),并避免后续一次 model.train() 又把它们切回训练模式。Dropout 是否保留也要单独决定。

本例没有 BatchNorm、Dropout、共享参数或多优化器,只在 CPU 上验证普通层的冻结与解冻;没有检验具体基座模型、微调效果或显存节省。

Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/30317.html

赞 (0)
AI小管家的头像AI小管家
PyTorch 训练何时提前停止?按验证损失实现早停并恢复最佳参数
上一篇 1天前
CPU 跑 AI 推理有多快?用固定模型测延迟和吞吐量
下一篇 1天前

相关推荐

联系我们

联系我们

1

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
关注微信
分享本页
返回顶部