迁移学习(transfer learning)将从源数据集学到的知识迁移到目标数据集。尽管两个数据集内容不尽相同,但在源数据集上训练的模型可能会提取更通用的图像特征,这有助于识别边缘、纹理、形状和对象组合。
当目标数据集比源数据集小得多时,迁移学习中的微调有助于提高模型的泛化能力。主要包括以下步骤:
-
在源数据集(例如ImageNet数据集)上预训练神经网络模型,即源模型。
-
创建一个新的神经网络模型,即目标模型。这将复制源模型上的所有模型设计及其参数(输出层除外)。假定这些模型参数包含从源数据集中学到的知识,这些知识也将适用于目标数据集。假设源模型的输出层与源数据集的标签密切相关,因此不在目标模型中使用该层。
-
向目标模型添加输出层,其输出数是目标数据集中的类别数。然后随机初始化该层的模型参数。
-
在目标数据集(如椅子数据集)上训练目标模型。输出层将从头开始进行训练,而所有其他层的参数将根据源模型的参数进行微调。
使用ResNet18在一个小数据集上进行微调,这个小型数据集包含数千张包含热狗和不包含热狗的图像,将使用微调模型来识别图像中是否包含热狗。
!pip install git+https://github.com/d2l-ai/d2l-zh@release # installing d2l
!pip install matplotlib_inline
!pip install matplotlib==3.0.0
%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'))
train_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)
#直接使用在ImageNet上的数据
normalize = torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
tran_augs = torchvision.transforms.Compose([
torchvision.transforms.RandomResizedCrop(224),
torchvision.transforms.RandomHorizontalFlip(),
torchvision.transforms.ToTensor(),
normalize])
test_augs = torchvision.transforms.Compose([
torchvision.transforms.Resize(256),
torchvision.transforms.CenterCrop(224),
torchvision.transforms.ToTensor(),
normalize])
#调用进行预训练过的resnet18
finetune_net = torchvision.models.resnet18(pretrained=True)
#修改最后一层输出层的参数 finetune_net.fc.in_features 即最后一层的输入个数不改变
finetune_net.fc = nn.Linear(finetune_net.fc.in_features,2)
# 如果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:#这里就是让最后一层的lr变得大一点 因为微调前面用的lr就比较小
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)
运行结果,在测试集上的精度达到了0.935,比训练精度还高是因为对训练数据进行了数据增强,对比下没有进行修改的测试数据更加的纯粹,所以精度会比测试的高。
#作为对比实验,这里直接使用初始的resnet模型来进行学习
net = torchvision.models.resnet18()
net.fc = nn.Linear(net.fc.in_features,2)
train_fine_tuning(net,0.001,param_group=False)
运行结果如下,可以看出还是微调效果更好,实质是因为它的初始参数值更有效。