微调是计算机视觉,特别是对深度学习来讲最重要的一个技术,就是你可以发现整个深度学习为什么能够work,这是因为有微调在。所谓的微调也叫transfer learning,就是迁移学习,这是改变了整个计算机视觉的方法。所以这个是最重要的一个技术,大家一定是要搞清楚。
目录
理论部分
实践部分
首先可以看一下 image net,它里面有120万张图片,类别数是1000,这是比较大的数据集;mnist是6万张数据,类别数是10类,很小,这个是80年代的代表数据。但是其实真正你一般要用的数据集不会像 image net那么大,你可能你自己的东西就是一个 5 万个样本,100类的样子。这个是你一般的你自己的书准备书籍的尺寸。但是有个问题,有问题是什么?我们知道数据集越大越好。通常来是你希望能在很大的数据集上训练好的东西,训练好的模型,能够帮助你提升你的数据集成的精度。但是有时真的希望对整个物体识别有一定的基础的情况下,不需要很多数据集就能学好。这其实就是一个核心的思想,这也是人工智能所追求的一个目标。就是说我教了一点点东西以后,你可以自己拓展到别的地方去。这也是人工智能最重要的一个基石。
下面看一下网络架构,最下的那一块是在做特征的提取。特征提取将原始的像素变换成最后能够进行线性分割的一些特征。最后一层有一个全链接层,再加一个 soft Max 做分类,你可以认为它这里就是一个很简单的线性分类器,也就是 soft Max regression——soft Max 回归。你几乎可以认为所有做分类的那神经网络都是长成这样子。可以认除了最后一块,剩下所有东西都在做特征提取。通过学习的手段,能够把原始的数据能够转换到一个可以线性分类的空间里面,一个语义空间里面,
微调的意思就是假设在image net 做语义的抽取,最后得到一个信息分类器。我可以认为这个模型无论对 image net也好,还是对我的图片数据集也好。应该都可以做比较好,是一个很不错的作为特征提取的模型。所以微调核心思想就是在一个原数据集上,可以把特征提取在我的目标数据拿来重新用一用。
假设我在元数据集上训练好了一个模型,确认训练好之后,在自己数据集上重新训练模型的时候,使用一个跟 pre train一样架构的模型。如果pretrain用的是 resnet 18,我也用 Resnet18。我做除了最后一层模型的初始化的时候,不再是随机的初始化,而是从训练好的模型的weight复制过来。等价于是把你的特征提取模块复制过来,作为我初始化的模型,使得我一开始就能做到还不错的一些特征的表达。但最后一层就不管了,所以最后一层可以随机初始化。由于特征提取部分是从pretrain复制过来的,因此可能训练结果从一开始就差不多,因此我只对特征提取部分进行微调即可。
使用了更强的正则化。为什么?是因为我们通常会使用更小的学习率,因为你的已经比较好了,已经跟我的最优解比较近了。我不需要特别强的学习率,而且我会减少我的数据迭代。
接下来是一些常用的数据:
一般来说,一个神经网络来讲,越下的层,学习的东西是一些底层的细节,越上面,它可能更加语义化一些。比如上图第一层,可能学了一些边角的东西,到后面可能是真的去识别那些狗什么样的。我们可以认为越到后面,跟标号越相关,但越到前面越是底层。所以底层的特征更加通用,高层的数据更加相关。
这里一个做法是,把底层的一些类给固定住,不要优化。他可以认为是一个更强的正则的效果。通常是假设你的数据真的很小的情况下,全部开始训练很可能会over fitting 的情况下,可以固定住一些底部的层层参数,不参与更新。
代码:
#微调
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)]#zheng类是热狗
not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]#负类是香蕉之类的
d2l.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4);
#数据增广
'''在训练期间,我们首先从图像中裁切随机大小和随机长宽比的区域,然后将该区域缩放为输入图像。
在测试过程中,我们将图像的高度和宽度都缩放到256像素,然后裁剪中央区域作为输入。
此外,对于RGB(红、绿和蓝)颜色通道,我们分别标准化每个通道。
具体而言,该通道的每个值减去该通道的平均值,然后将结果除以该通道的标准差。'''
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),
torchvision.transforms.CenterCrop(224),
torchvision.transforms.ToTensor(), normalize])
#定义和初始化模型
#把 pretrained设了这个东西之后,
# 就意味着不仅把模型的定义弄下来,还把在 Imagenet 上训练好的那些 parameter 给拿过来。
pretrained_net = torchvision.models.resnet18(pretrained=True)
print(pretrained_net.fc)
#只对最后一层的weight进行随机初始化,其他直接用pretrain的
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);
#微调模型
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")
'''果是等于true,这是一个参数等于true,我怎么办?我把,不是最后一层的所有的层都拿出来,
这一层用的是一个 learning rate,是一个默认的 learning rate。但是最后一层就是
f c parameter,它的 learning rate 用的是 10 倍的 n learning rate。什么意思?
就是我给你一个学习率,除了最后一层,因为是随机初始化的对吧,别的已经初始化比较好了。
我用的是一个比较小的学习率,最后一层用的是一个 10 倍更大的学习率,是因为我们最后一层是
随机初始化的,我们希望他能够学得更快,别的层我们不希望他改变太多,这是一个小trick。'''
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)
#另外一个就是你如果没有 enable 选项,按正常来,就跟之前一样。
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)
'''可以看到,基本上到了第二个 epoch 的精度就很好了,训练和测试都差不多,基本上我的 loss 没有增加,而且是有点点抖动,很平淡在这个地方。
而且精确度一开始比较平的一个地方,你其实这里有点抖动,但没关系'''
#为了进行比较, 所有模型参数初始化为随机值
scratch_net = torchvision.models.resnet18()
scratch_net.fc = nn.Linear(scratch_net.fc.in_features, 2)
train_fine_tuning(scratch_net, 5e-4, param_group=False)
for param in finetune_net.parameters():
param.requires_grad = False
weight = pretrained_net.fc.weight
hotdog_w = torch.split(weight.data, 1, dim=0)[713]
print(hotdog_w.shape)