迁移学习(微调)
如果我们想要设计一个模型来做某个任务,而已经有类似的现有模型了,这时候我们可以对现有模型进行微调来完成新的任务
微调 Fine-tuning
我们可以将一个神经网络模型的分为两个部分,输出层用于对物品进行分类,其他层用来提取特征
那么对于一个类似的任务,我们可以将已经训练好的模型的前N层拿过来,然后对最后的输出层进行修改,然后利用我们收集的数据进行微调,就能很好的完成新的任务。
微调的步骤如下:
- 在源数据集(例如ImageNet数据集)上预训练神经网络模型,即源模型
- 创建一个新的神经网络模型,即目标模型。这将复制源模型上的所有模型设计及其参数(输出层除外)
我们假定这些模型参数包含从源数据集中学到的知识,这些知识也将适用于目标数据集。我们还假设
源模型的输出层与源数据集的标签密切相关;因此不在目标模型中使用该层
- 向目标模型添加输出层,其输出数是目标数据集中的类别数。然后随机初始化该层的模型参数
- 在目标数据集(如椅子数据集)上训练目标模型。输出层将从头开始进行训练,而所有其他层的参数将根据源模型的参数进行微调
训练:
在微调中,使用了目标数据集进行正常训练,但是使用更强的正则化
- 使用更小的学习率
- 使用更少的数据迭代
因为我们任务源模型的效果已经非常不错了,不需要进行大量训练
源数据集远复杂于目标数据,通常微调效果会更好
如果源数据中也有目标数据中的标号,则可以使用预训练好的模型分类器对应标号对应的向量来对输出层进行初始化
最下面的层通常更加通用,高层次的特征则跟数据集相关,我们也可以将底层的参数固定,不进行训练,达到更强正则的效果。
总结
- 微调通过使用在大数据上得到的预训练好的模型来初始化模型权重来完成提升精度
- 预训练的模型质量很重要
- 微调通常速度更快,精度更高
2 代码实现
- 导入数据集
%matplotlib inline
import os
import torch
import torchvision
from torch import nn
from d2l import torch as d2l
d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL + 'hotdog.zip',
'fba480ffa8aa7e0febbb511d181409f899b9baa5')
data_dir = d2l.download_extract('hotdog') # 一个热狗数据集
train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'))
test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'))
- 显示图片
hotdogs = [train_imgs[i][0] for i in range(8)]
not_hotdogs = [train_imgs[-i- 1][0] for i in range(8)]
d2l.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4);
- 数据增广
# 使用RGB通道的均值和标准差,以标准化每个通道
normalize = torchvision.transforms.Normalize(
[0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
# 数据增广,做normalize的原因是ImageNet上做了这个事情
train_augs = torchvision.transforms.Compose([
torchvision.transforms.RandomResizedCrop(224), # ImageNet是224,我们用了它上面训练好的模型做微调
torchvision.transforms.RandomHorizontalFlip(),
torchvision.transforms.ToTensor(),
normalize])
# 测试数据的增广
test_augs = torchvision.transforms.Compose([
torchvision.transforms.Resize([256, 256]),
torchvision.transforms.CenterCrop(224),
torchvision.transforms.ToTensor(),
normalize])
- 定义模型
# 定义和初始化模型
#pretrained_net = torchvision.models.resnet18(pretrained=True) # 拿到ResNet18的模型,并且拿到训练好的参数
pretrained_net = torchvision.models.resnet18(weights=torchvision.models.ResNet18_Weights.DEFAULT)
pretrained_net.fc # 最后一层输出层
finetune_net = torchvision.models.resnet18(weights=torchvision.models.ResNet18_Weights.DEFAULT)
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2) # 将输出层修改为自己需要的层
nn.init.xavier_uniform_(finetune_net.fc.weight); # 对最后一层的权重进行初始化
- 定义训练函数(前N层学习率低,最后输出层学习率高)
# 如果param_group=True,输出层中的模型参数将使用十倍的学习率
def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5,
param_group=True):
train_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(
os.path.join(data_dir, 'train'), transform=train_augs),
batch_size=batch_size, shuffle=True)
test_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(
os.path.join(data_dir, 'test'), transform=test_augs),
batch_size=batch_size)
devices = d2l.try_all_gpus()
loss = nn.CrossEntropyLoss(reduction="none")
if param_group:
params_1x = [param for name, param in net.named_parameters()
if name not in ["fc.weight", "fc.bias"]]
trainer = torch.optim.SGD([{'params': params_1x},
{'params': net.fc.parameters(), # 输出层中的模型使用10倍的学习率
'lr': learning_rate * 10}],
lr=learning_rate, weight_decay=0.001)
else:
trainer = torch.optim.SGD(net.parameters(), lr=learning_rate,
weight_decay=0.001)
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,
devices)
- 开始训练,设置一个很小的学习率
train_fine_tuning(finetune_net, 5e-5) # 给一个很小的学习率
结果:
loss 0.188, train acc 0.933, test acc 0.935
43.9 examples/sec on [device(type='cuda', index=0)]