Skip to content

微调

一、什么是微调

微调(fine-tuning)是迁移学习的一种常见技巧,就是把在一个大数据集(比如 ImageNet)上训练好的模型(源模型),拿来在一个小的目标数据集上进行调整,让模型能适应新的任务。

比如我们想识别图片里的椅子,但是我们只有几千张椅子的图片,直接训练模型很容易过拟合,而且准确率不高。这时候我们可以用在 ImageNet 上训练好的 ResNet 模型,这个模型已经学会了很多通用的图像特征,比如边缘、纹理、形状这些,这些特征对识别椅子也有用。我们只需要把这个模型的输出层改一下,变成识别椅子的类别,然后用我们的椅子数据集稍微调整一下模型的参数,就能得到一个准确率不错的模型了。

二、微调的步骤

微调的步骤很简单,一共四步:

  1. 先在源数据集(比如 ImageNet)上训练好一个模型,这个模型就是源模型。

  2. 创建一个新的目标模型,复制源模型的所有层和参数,但是不复制输出层,因为源模型的输出层是针对源数据集的类别,和我们的目标任务不一样。

  3. 给目标模型加一个新的输出层,输出层的类别数是目标数据集的类别数,然后随机初始化这个输出层的参数。

  4. 用目标数据集训练目标模型,输出层从头开始训练,其他层的参数用小的学习率进行微调。

alt text

三、实战:热狗识别

我们用热狗识别的例子来看看怎么用 PyTorch 做微调。

1. 获取数据集

我们用的热狗数据集有 1400 张热狗的图片,还有 1400 张其他食物的图片,其中 1000 张用来训练,400 张用来测试。

首先下载数据集:

python
import torch
import torchvision
from d2l import torch as d2l

d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL + 'hotdog.zip',
'fba480ffa8aa7e0febbb511d181409f899b9baa5')
data_dir = d2l.download_extract('hotdog')

然后读取训练和测试数据集:

python
train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'))
test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'))

alt text

2. 数据预处理

训练的时候,我们需要对图片做一些增强,比如随机裁剪、随机翻转,然后把图片转换成模型需要的格式,再做标准化:

python
# 标准化的均值和标准差,用的是ImageNet的统计值
normalize = torchvision.transforms.Normalize(
[0.485, 0.456, 0.406], [0.229, 0.224, 0.225])

# 训练集的预处理
train_augs = torchvision.transforms.Compose([
torchvision.transforms.RandomResizedCrop(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])

3. 定义模型

我们用在 ImageNet 上预训练好的 ResNet-18 作为源模型,然后把输出层改成 2 类(热狗和不是热狗):

python
# 加载预训练的ResNet-18
pretrained_net = torchvision.models.resnet18(pretrained=True)

# 查看源模型的输出层
print(pretrained_net.fc)  # Linear(in_features=512, out_features=1000, bias=True)

# 创建目标模型,把输出层改成2类
finetune_net = torchvision.models.resnet18(pretrained=True)
finetune_net.fc = torch.nn.Linear(finetune_net.fc.in_features, 2)
# 初始化输出层的参数
torch.nn.init.xavier_uniform_(finetune_net.fc.weight)

4. 训练模型

我们定义一个训练函数,训练的时候,输出层用大一点的学习率,其他层用小一点的学习率:

python
def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5):
    # 加载数据
    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)
    
    # 获取所有GPU
    devices = d2l.try_all_gpus()
    # 把模型放到GPU上
    net = torch.nn.DataParallel(net, device_ids=devices)
    # 损失函数
    loss = torch.nn.CrossEntropyLoss(reduction="none")
    # 优化器,输出层的学习率是其他层的10倍
    params_1x = [param for name, param in net.named_parameters()
                 if name not in ["module.fc.weight", "module.fc.bias"]]
    trainer = torch.optim.SGD([
        {'params': params_1x},
        {'params': net.module.fc.parameters(), 'lr': learning_rate * 10}
    ], 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)

训练完成后,我们可以看到模型的测试准确率大概在 93% 左右,比从头训练的模型准确率高很多。

5. 对比:从头训练模型

我们也可以训练一个从头初始化的模型,看看对比效果:

python
# 从头初始化模型
scratch_net = torchvision.models.resnet18()
scratch_net.fc = torch.nn.Linear(scratch_net.fc.in_features, 2)
# 训练,用大一点的学习率
train_fine_tuning(scratch_net, 5e-4, param_group=False)

从头训练的模型测试准确率大概在 84% 左右,比微调的模型低很多,说明微调确实能提升模型的性能。

四、小结

  1. 微调是迁移学习的一种方法,把在大数据集上训练好的模型拿来在小数据集上调整,能提升模型的泛化能力,避免过拟合

  2. 微调的时候,我们复制源模型的所有层和参数,只替换输出层,然后用小的学习率微调其他层的参数,输出层用大一点的学习率从头训练。

  3. 微调的模型比从头训练的模型准确率高很多,因为源模型已经学会了很多通用的特征,这些特征对目标任务也有用。

(注:文档部分内容可能由 AI 生成) 源地址

京ICP备2024093538号-1