迁移学习
1.基本概念
假设我们想从图像中识别出不同种类的椅子,然后将购买链接推荐给用户。一种可能的方法是先找出100种常见的椅子,为每种椅子拍摄1,000张不同角度的图像,然后在收集到的图像数据集上训练一个分类模型。这个椅子数据集虽然可能比Fashion-MNIST数据集要庞大,但样本数仍然不及ImageNet数据集中样本数的十分之一。这可能会导致适用于ImageNet数据集的复杂模型在这个椅子数据集上过拟合。同时,因为数据量有限,最终训练得到的模型的精度也可能达不到实用的要求。
为了应对上述问题,一个显而易见的解决办法是收集更多的数据。然而,收集和标注数据会花费大量的时间和资金。例如,为了收集ImageNet数据集,研究人员花费了数百万美元的研究经费。虽然目前的数据采集成本已降低了不少,但其成本仍然不可忽略。
另外一种解决办法是应用迁移学习(transfer learning),将从源数据集学到的知识迁移到目标数据集上。例如,虽然ImageNet数据集的图像大多跟椅子无关,但在该数据集上训练的模型可以抽取较通用的图像特征,从而能够帮助识别边缘、纹理、形状和物体组成等。这些类似的特征对于识别椅子也可能同样有效。
2.Fine-tuning
- 第一步:在新数据集上,替换预训练ConvNet顶层的分类器并retrain该分类器;
- 第二步:以较小的学习率继续反向传播来微调预训练网络的权重;
- 两种做法:微调ConvNet的所有层,或者保持some earlier layers fixed (due to overfitting concerns) ,只微调some higher-level portion of the network;
from torchvision import models
pretrained_net = models.resnet18(pretrained=True)
# 打印最后两层网络
print([*pretrained_net.named_children()][-2:])
# [('avgpool', AdaptiveAvgPool2d(output_size=(1, 1))),
# ('fc', Linear(in_features=512, out_features=1000, bias=True))]
# 改变全连接层
pretrained_net.fc = nn.Linear(512, 2)
print(pretrained_net.fc)
# Linear(in_features=512, out_features=2, bias=True)
此时,pretrained_net
的fc
层就被随机初始化了,但是其他层依然保存着预训练得到的参数。由于是在很大的ImageNet数据集上预训练的,所以参数已经足够好,因此一般只需使用较小的学习率来微调这些参数,而fc
中的随机初始化参数一般需要更大的学习率从头训练。PyTorch可以方便的对模型的不同部分设置不同的学习参数,在下面代码中将fc
的学习率设为已经预训练过的部分的10倍。
output_params = list(map(id, pretrained_net.fc.parameters()))
feature_params = filter(lambda p: id(p) not in output_params, pretrained_net.parameters())
lr = 0.01
optimizer = optim.SGD([{'params': feature_params},
{'params': pretrained_net.fc.parameters(), 'lr': lr * 10}],
lr=lr, weight_decay=0.001)
3.Fixed
利用在大数据集(如ImageNet)上预训练过的ConvNet(如AlexNet,VGGNet),移除最后几层(一般是最后分类器),将剩下的ConvNet作为应用于新数据集的固定不变的特征提取器,输出特征称为CNN codes,提取出所有CNN codes之后,再基于新数据集训练一个线性分类器。
# 将模型参数设置为不进行梯度更新
for name, layer in pretrained_net.named_children():
requires_grad = False
if name == 'fc':
requires_grad = True
for param in layer.parameters():
param.requires_grad = requires_grad
# 查看设置结果
for name, layer in pretrained_net.named_children():
for param in layer.parameters():
print(f'{name} requires grad = {param.requires_grad}' )
break
#conv1 requires grad = False
#bn1 requires grad = False
#layer1 requires grad = False
#layer2 requires grad = False
#layer3 requires grad = False
#layer4 requires grad = False
#fc requires grad = True
lr = 0.01
optimizer = optim.SGD(pretrained_net.fc.parameters(), lr=lr, weight_decay=0.001)