动手学深度学习-37

敖炜 Lv5

微调

应用迁移学习(transfer learning)将从源数据集学到的知识迁移到目标数据集。例如,尽管ImageNet数据集中的大多数图像与椅子无关,但在此数据集上训练的模型可能会提取更通用的图像特征,这有助于识别边缘、纹理、形状和对象组合。这些类似的特征也可能有效地识别椅子。

步骤

迁移学习中的常见技巧:微调(fine-tuning)。包括以下四个步骤:

  1. 在源数据集(例如ImageNet数据集)上预训练神经网络模型,即源模型
  2. 创建一个新的神经网络模型,即目标模型。这将复制源模型上的所有模型设计及其参数(输出层除外)。我们假定这些模型参数包含从源数据集中学到的知识,这些知识也将适用于目标数据集。我们还假设源模型的输出层与源数据集的标签密切相关;因此不在目标模型中使用该层。
  3. 向目标模型添加输出层,其输出数是目标数据集中的类别数。然后随机初始化该层的模型参数。
  4. 在目标数据集(如椅子数据集)上训练目标模型。输出层将从头开始进行训练,而所有其他层的参数将根据源模型的参数进行微调。

微调。

当目标数据集比源数据集小得多时,微调有助于提高模型的泛化能力

热狗识别

让我们通过具体案例演示微调:热狗识别。
我们将在一个小型数据集上微调ResNet模型。该模型已在ImageNet数据集上进行了预训练。
这个小型数据集包含数千张包含热狗和不包含热狗的图像,我们将使用微调模型来识别图像中是否包含热狗。

获取数据集

使用的热狗数据集来源于网络。该数据集包含1400张热狗的“正类”图像,以及包含尽可能多的其他食物的“负类”图像。含着两个类别的1000张图片用于训练,其余的则用于测试。

1
2
3
4
5
6
7
8
9
10
11
%matplotlib inline
import os
import torch
import torchvision
from torch import nn
from d2l import torch as d2l

#@save
d2l.DATA_HUB["hotdog"] = (d2l.DATA_URL + 'hotdog.zip', 'fba480ffa8aa7e0febbb511d181409f899b9baa5')

data_dir = d2l.download_extract('hotdog')

分别读取训练和测试数据集中的所有图像文件

1
2
3
train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, "train"))

test_imgs = torchvision.datasets.ImageFloder(os.path.join(data_dir, "test"))

数据增广

1
2
3
4
5
6
7
8
9
10
11
12
# 使用RGB通道的均值和标准差,以标准化每个通道(因为预训练模型在ImageNet上做了标准化)
normalize = torchvision.transforms.Normaliza([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])

定义和初始化模型

使用在ImageNet数据集上预训练的ResNet-18作为源模型。在这里,我们指定pretrained=True以自动下载预训练的模型参数。如果首次使用此模型,则需要连接互联网才能下载。

1
pretrained_net = torchvision.models.resnet18(pretrained=True)

预训练的源模型实例包含许多特征层和一个输出层fc。此划分的主要目的是促进对除输出层以外所有层的模型参数进行微调。

在ResNet的全局平均汇聚层后,全连接层转换为ImageNet数据集的1000个类输出。之后,我们构建一个新的神经网络作为目标模型。它的定义方式与预训练源模型的定义方式相同,只是最终层中的输出数量被设置为目标数据集中的类数(而不是1000个)。

在下面的代码中,目标模型finetune_net中成员变量features的参数被初始化为源模型相应层的模型参数。由于模型参数是在ImageNet数据集上预训练的,并且足够好,因此通常只需要较小的学习率即可微调这些参数。

成员变量output的参数是随机初始化的,通常需要更高的学习率才能从头开始训练。假设Trainer实例中的学习率为,我们将成员变量output中参数的学习率设置为

1
2
3
finetune_net = torchvision.models.resnet18(pretrained=True)
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2)
nn.init.xavier_uniform_(finetune_net.fc.weight);

微调模型

定义一个训练函数train_fine_tuning,该函数使用微调,因此可以多次调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 如果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_iter, "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(),
"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)

小结

  • 迁移学习将从源数据集中学到的知识迁移到目标数据集,微调是迁移学习的常见技巧。
  • 除输出层外,目标模型从源模型中复制所有模型设计及其参数,并根据目标数据集对这些参数进行微调。但是,目标模型的输出层需要从头开始训练。
  • 通常,微调参数使用较小的学习率,而从头开始训练输出层可以使用更大的学习率。
  • 标题: 动手学深度学习-37
  • 作者: 敖炜
  • 创建于 : 2023-09-03 17:08:03
  • 更新于 : 2024-04-19 09:29:13
  • 链接: https://ao-wei.github.io/2023/09/03/动手学深度学习-37/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论