在深度学习中我们经常会使用到预训练进行微调,提高模型的泛化能力,加快收敛速度,节约训练时间。
一般来讲,预训练多种方式,常见的有对预训练层设置更小的学习率,或者固定预训练层,不进行权重更新,这里讨论的是后一种。
现在假设有模型:
class Net(nn.Module):def __init__(self):super(Net, self).__init__()self.layer1=conv_base # conv_base是由conv、BN等组成的卷积基self.fc1=nn.Linear(512,100) # 分类器self.fc2=nn.Linear(100, 10) # 分类器def forward(self,x):feat=self.layers(x)out=self.fc1(feat)out=self.fc2(out)return out
其中layer1是预训练的特征提取,而fc1和fc2是我们需要训练的,所以我们希望固定layer1的参数,仅训练fc1和fc2的参数,实现的方法有两种(本质是一样的):
# 首先将固定层layer1的requires_grad属性置False
for name, p in model.layer1.named_parameters(): # 固定feat权重p.requires_grad = False# 在优化器中过滤到这些requires_grad参数
para=filter(lambda x:x.requires_grad is not False,model.parameters())
optimizer = torch.optim.SGD(para, lr=0.1)
para=[{"params":model.fc1.parameters()},{"params":model.fc2.parameters()}]
optimizer = torch.optim.SGD(para, lr=0.1)
以上两种方式,通过优化器指定仅更新参数的方法,训练特定参数。
通过以上方式训练后,layer1的参数是不变的,仅fc1和fc2的参数变化,可是当我用layer1的参数在数据集上验证时,发现指标变化了,如果layer1的参数是不变的,指标也应该和训练之前一样。
实际上问题在于train()和eval()模式上,我们知道BatchNorm是需要根据数据集样本的均值和方差进行运算的,在训练是BatchNorm会计算本次数据集样本的均值和方差进行运算,同时会保存本次均值和方差。验证和测试时会数据量比较小(极端为1张),此时的均值和方差就用训练时保存的均值和方差计算。
所以虽然我们固定了layer1参数训练,但是处于model.train()模式下的layer1,其包含的BatchNorm中的参数是变化的,当我们再次用layer1在数据集上测试时使用的就是更新后的mean和std了,造成指标发生了变化。
综上就是,虽然layer1非BatchNorm层的参数(比如conv等)没有变化,但是BatchNorm的参数变化了。
解决办法也很简单:
# 训练时:先将模型全设为train模型,再将固定曾layer1设为eval模式,这样保证了参数不更新,且BatchNorm层参数也不更新
model.train()
model.layer1.eval()# 测试时:
model.eval()