迁移学习(微调)

如果我们想要设计一个模型来做某个任务,而已经有类似的现有模型了,这时候我们可以对现有模型进行微调来完成新的任务

微调 Fine-tuning

我们可以将一个神经网络模型的分为两个部分,输出层用于对物品进行分类,其他层用来提取特征

那么对于一个类似的任务,我们可以将已经训练好的模型的前N层拿过来,然后对最后的输出层进行修改,然后利用我们收集的数据进行微调,就能很好的完成新的任务。

微调的步骤如下:

我们假定这些模型参数包含从源数据集中学到的知识,这些知识也将适用于目标数据集。我们还假设
源模型的输出层与源数据集的标签密切相关;因此不在目标模型中使用该层

Image

训练:
在微调中,使用了目标数据集进行正常训练,但是使用更强的正则化

因为我们任务源模型的效果已经非常不错了,不需要进行大量训练
源数据集远复杂于目标数据,通常微调效果会更好

如果源数据中也有目标数据中的标号,则可以使用预训练好的模型分类器对应标号对应的向量来对输出层进行初始化

最下面的层通常更加通用,高层次的特征则跟数据集相关,我们也可以将底层的参数固定,不进行训练,达到更强正则的效果。

总结

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);

Image

# 使用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);  # 对最后一层的权重进行初始化
# 如果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)]

Image

代码链接