微调解释

前面介绍和使用的Fashion-MNIST训练数据集只有6万张图像,我们在这个数据集上训练了模型。另外当前学术界使用最广泛的大规模图像数据集为ImageNet,它有超过1000万的图像和1000类的物体。

假设我们要完成在视频中检测凳子的类别,再把这个凳子类别的购买链接推荐给用户。一种方法是找出数百张凳子,为这些凳子从不同角度拍摄来构建一个数据集,然后在这个数据集上训练一个模型。但这时问题就出现了,数据集单一会导致模型过拟合,且会使最终训练得到的模型精度达不到实用的要求

为了解决上述问题的方法是应用迁移学习。虽然ImageNet数据集图像大多跟椅子无关,但在该数据集上训练的模型可以抽取较通用的图像特征,从而能帮助识别边缘、纹理、形状和物体组成等。这些类似的特征对于识别椅子也可能有效。

因此这部分介绍了迁移学习中的一种常用技术:微调。该技术由以下4步构成:

  1. 获取预训练模型
  2. 创建新的神经网络模型。他复制了原模型除输出层外的所有结构和参数。输出层结构由目标数据集确定。
  3. 在目标数据集上训练目标模型。我们从头训练输出层,而其余参数基于元模型的参数微调得到。

热狗识别

获取数据集

我们使用的热狗数据集(点击下载)是从网上抓取的,它含有1400张包含热狗的正类图像,和同样多包含其他食品的负类图像。各类的1000张图像被用于训练,其余则用于测试。

我们首先将压缩后的数据集下载到路径 data_dir之下,然后在该路径将下载好的数据集解压,得到两个文件夹 hotdog/trainhotdog/test。这两个文件夹下面均有 hotdognot-hotdog两个类别文件夹,每个类别文件夹里面是图像文件。

data_dir = r'E:\z2024\studying\article\deep_learning\Dive-into-DL-PyTorch\code'
os.listdir(os.path.join(data_dir, "hotdog"))

我们创建两个 ImageFolder实例来分别读取训练数据集和测试数据集中的所有图像文件

train_imgs = ImageFolder(os.path.join(data_dir, 'hotdog/train'))
test_imgs = ImageFolder(os.path.join(data_dir, 'hotdog/test'))

下面画出前8张正类图像和最后8张负类图像。可以看到,它们的大小和高宽比各不相同。

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)

在这里插入图片描述

定义和初始化模型

我们使用在ImageNet数据集上预训练的ResNet-18作为源模型。这里指定 weights='DEFAULT来自动下载并加载预训练的模型参数。在第一次使用时需要联网下载模型参数。

pretrained_net = models.resnet18(weights='DEFAULT')

下面打印源模型的成员变量 fc。作为一个全连接层,它将ResNet最终的全局平均池化层输出变换成ImageNet数据集上1000类的输出。

print(pretrained_net.fc)
pretrained_net.fc = nn.Linear(512, 2)

此时,pretrained_netfc层就被随机初始化了,但是其他层依然保存着预训练得到的参数。由于是在很大的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)

微调模型

我们先定义一个使用微调的训练函数 train_fine_tuning以便多次调用

def train_fine_tuning(net, optimizer, batch_size=128, num_epochs=5):
    train_iter = DataLoader(ImageFolder(os.path.join(data_dir, 'hotdog/train'), transform=train_augs),
                            batch_size, shuffle=True)
    test_iter = DataLoader(ImageFolder(os.path.join(data_dir, 'hotdog/test'), transform=test_augs),
                           batch_size)
    loss = torch.nn.CrossEntropyLoss()
    d2l.train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)

根据前面的设置,我们将以10倍的学习率从头训练目标模型的输出层参数。

train_fine_tuning(pretrained_net, optimizer)

输出:
在这里插入图片描述
作为对比,我们定义一个相同的模型,但将它的所有模型参数都初始化为随机值。由于整个模型都需要从头训练,我们可以使用较大的学习率。

scratch_net = models.resnet18(weights=None, num_classes=2)
lr = 0.1
optimizer = optim.SGD(scratch_net.parameters(), lr=lr, weight_decay=0.001)
train_fine_tuning(scratch_net, optimizer)

输出:
在这里插入图片描述
总结

  • 迁移学习中微调方法可以在数据集不足情况下获取在目标数据集上效果比较好的模型。
  • 更熟悉torch中数据获取方法ImageFolder和加载方法DataLoader。
  • 学到了python内置方法map()、list()和filter()
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐