最近几天在参加AI研习社的一个美食识别比赛,比赛方提供了6140张图片的训练集,856张图片的测试集。其中测试集没有标签,只用来生成预测数据进行提交。
任务难度不是很高,但是在做的过程中还是遇到了一些问题,有一些经验值得总结,这里主要记录一下在模型fine-tune中的一些经验教训。
1.模型选择
由简单到复杂,先后选择了resnet50、resnet101、resnext50_32x4d、resnext101_32x8d。
这些模型中,前两个在验证集上的acc在到达94%后就基本上不去了(也可能是我超参不合适没有到最佳性能),resnext50_32x4d的acc能够到达95%,而resnext101_32x8d比较轻松的就到达了96%(还没有训练完,可能会更高)。
另外还没有测试google出品的SEnet,有结果来再来补充。
2. 全连接层的设置
通用方法
按照pytorch官方的fine-tune教程以及网上搜到的大多数教程,fine-tine的方法如下:
model_ft = models.resnet50(pretrained=True) # 这里自动下载官方的预训练模型,并且将所有的参数层进行冻结
num_fc_ftr = model_ft.fc.in_features #获取到fc层的输入
model_ft.fc = nn.Linear(num_fc_ftr, n_classes) # 定义一个新的FC层
替换掉原网络的FC层,然后自己接一个新的,输出由自己任务的分类数决定。
存在的问题:
原本以为这样足够了,但是发现这样构成的网络,在验证集上的acc最多只有93%,而且训练集的loss最终也只是收敛到了一个比较高的数值,可以确定是欠拟合了。分析网络结构以后,发现在最后一个卷积层后接了一个池化层,然后就是我们添加的FC层,因此后续改进时我主要提高FC层的复杂度。
我采用的解决方法
这样参考了Fastai中对模型fine-tune时默认添加的head模块,即将原模型的FC层替换为如下所示的模块:
num_fc_ftr = model.fc.in_features #获取到fc层的输入
add_block = nn.Sequential(nn.Flatten(),
nn.BatchNorm1d(num_fc_ftr, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True),
nn.Dropout(p=0.25, inplace=False),
nn.Linear(in_features=num_fc_ftr, out_features=2048, bias=True),
nn.ReLU(inplace=True),
nn.BatchNorm1d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True),
nn.Dropout(p=0.5, inplace=False),
nn.Linear(in_features=2048, out_features=n_classes, bias=True))
model.fc = add_block
如上所示,一个block包含(Flatten、BatchNormal1d、Dropout、Linear、ReLU)几个部分,可以添加多个block,其中Linear层的输出个数是超参。需要注意的是最后一个block的激活函数可以不要,或者改为log_softmax。
经过这样改造的FC层,能够显著提高模型在验证集上的性能。
3. 训练中出现的问题
fine-tune时需要固定FC层之前的所有参数,只训练FC层的参数。在这里一开始我应该是犯了一些错误。
最初我是这么实现的:
model_ft = models.resnet50(pretrained=True) # 这里自动下载官方的预训练模型,并且
# 将所有的参数层进行冻结
for param in model_ft.parameters():
param.requires_grad = False
# 这里打印下全连接层的信息
num_fc_ftr = model_ft.fc.in_features #获取到fc层的输入
一个for循环把网络的所有参数求梯度的flag都设为了False,这里面也包含了FC层的。后来发现训练不太正常,loss下降的特别慢。可能就是这里FC层requires_grad = False的原因。
后来改为下面的实现方式:
model = models.resnext101_32x8d(pretrained=True)
for name, child in model.named_children():
if 'fc' not in name:
for param in child.parameters():
param.requires_grad = False
else:
for param in child.parameters():
param.requires_grad = True
num_fc_ftr = model.fc.in_features #获取到fc层的输入
实现略为繁琐,但是效果是达到了,训练也变得正常了。
未完待续。