① 动机和意义。首先要搞清楚使用为什么要冻结某些层,以及那些层能够被冻结。
冻结网络参数的一些动机:
-
避免过拟合:当训练数据较少时,神经网络容易过拟合,即在训练集上表现很好,但在测试集上表现差。冻结一些参数可以减少网络的自由度,避免过拟合。
-
加速训练:当冻结某些参数时,这些参数在训练过程中不需要更新,因此可以减少训练时间。
-
迁移学习:在迁移学习中,我们通常使用预训练好的模型来初始化目标任务的模型。冻结预训练模型的一部分参数可以保持这些参数的特征提取能力不变,只训练目标任务的部分参数。
-
稳定网络:冻结网络的一部分参数可以使网络更加稳定,例如在生成对抗网络中,冻结判别器的参数可以保证生成器更容易生成真实样本。
哪些层会参与梯度更新:
一般模型参数的冻结都是针对全连接层和卷积层而言的,因为这些层通常具有大量的可学习参数,而且在训练过程中容易过拟合。通过冻结这些层的参数,可以减少模型中需要学习的参数数量,从而降低过拟合的风险,并提高模型的泛化能力。
另外,池化层、循环层和归一化层(Batch Normalization(BN层)详解)等层通常具有较少的可学习参数,因此它们的参数很少被冻结。但是,有时也会对这些层的某些参数进行冻结,例如,在使用预训练模型进行微调时,通常会冻结预训练模型中的所有层,并只对新添加的全连接层进行训练,以充分利用预训练模型的特征提取能力,并避免破坏预训练模型已经学习到的特征。
② 实例和代码。在PyTorch中,可以通过设置某些层的 requires_grad 属性为 False 来冻结这些层。
以下是一些冻结层的示例代码:
# 冻结第一层的参数(两种方法)
model[0].requires_grad = False
model[0].requires_grad_(False)
# 冻结前两层的参数
for param in model[:2].parameters():
param.requires_grad = False
# 指定名字的层进行冻结(所有fc层)
for name, param in model.named_parameters():
if 'fc' in name:
param.requires_grad = False
# 不进行梯度更新(评估模型时使用)
model.eval()
with torch.no_grad():
test(model)
同时optimizer部分也要进行相应的修改,应加上filter()函数只更新需要梯度的层:
# original
optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)
# add filter
optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=args.lr)
还有一篇文章pytorch 两种冻结层的方式 - 知乎 (zhihu.com)在定义神经网络的forward()函数的时候,使用with torch.no_grad():来冻结with torch.no_grad()缩进里面的部分层,这种方式会存在一定的问题,no_grad()会使得其上下文部分所有变量梯度均为None,可能导致无法反向传播。