1.微调在深度学习中计算机视觉最重要的技术,微调也是迁移学习
2.标注一个数据集很贵
①ImageNet标注了1000多万张图片,实际使用120万张图片,类别是1000,大型数据集
②Fashion-MNIST一共有6万张图片,类别是10,小型数据集
③通常的数据集是两者之间,5万图片左右。类别是100,每一类物体有500张图片
训练样本有限,训练模型的准确性可能无法满足实际的要求。
解决方案:
Ⅰ更多的数据集,但耗时间和金钱
Ⅱ应用迁移学习(transfer leanring):将源数据集上学到的知识(提取更通用的特征,有助于识别边缘,纹理,形状和对象组合)迁移到目标数据集上,提升数据集上的精度 。思想:某个模型有一定的识别物体基础,不需要自己提供太大的数据集就能够获得更好的精度。
3.网络架构
①一个神经网络一般可以分为两块:Ⅰ特征提取将原始像素变成容易线性分割的特征
Ⅱ 线性分类器来做分类
4.微调
①在源数据集上(大型数据集)训练好了一个模型pre-train。模型中特征提取的部分对目标数据集有效的,优于随机生成特征提取。在目标数据集上也会根据数据重新学习,训练次数不会太多。
②分类是不能直接使用的,因为标号变化了,难以重用。
思想:在一个大型数据集上训练好了模型用于特征提取部分,在目标数据集上提取特征进行重用
5.微调中的权重初始化
①源数据集上源模型pre-train。创建一个新的神经网络模型,目标模型。新模型重用源模型的模型设计和参数(输出层除外)
②向目标模型添加输出层,输出数是目标数据集的类别数。随机初始化该层的模型参数(标号不同)
③在目标数据集上训练目标模型。输出层将从头开始进行训练,而所有其他层的参数将根据源模型的参数进行微调
注意:因为损失 Loss 是从后往前进行传递的,所以最后的分类部分训练比较快,进行随机初始化也不会有太大的影响;而前面的特征提取的部分本身已经具备很好的特征提取效果,只是根据源数据集和目标数据集的差异进行微调,可能在最开始训练的时候就已经比较接近最终的结果,所以不用做过多的训练和变动。
6.训练
①一个目标数据集上的正常的训练任务,但使用更强的正则化:
Ⅰ使用更小的学习率(模型比较好) Ⅱ使用更少的数据迭代
②源数据集远复杂与目标数据,通常微调效果更好。
7.重用分类器权重
①源数据集可能有目标数据中的部分标号。比如源数据集有“车”,你的目标数据集也有车
②使用预训练好模型分类器对应标号 对应的向量来做初始化
8.固定一些层
①神经网络通常学习有层次的特征表示
Ⅰ低层次的特征更加通用
Ⅱ高层次的特征跟数据集相关
Ⅲ高层次对标号的关联度很大,低层次的特征更加通用。
②可以固定底部一些层的参数,不参与更新(不做优化,不改变底层的权重。模型复杂度变低):更强的正则(底部参数不更新,不容易过拟合)
【总结】
①微调通过使用在大数据上得到的预训练好的模型来初始化模型权重完成提升精度。(白嫖)
②预训练模型质量很重要
③微调通常速度更快,精度更高。
【代码实现】
1.获取热狗数据集 小数据集上使用微调ResNet模型,该模型在ImageNet预训练
import os
import torch
import torchvision
from torch import nn
from d2l import torch as d2l
# 热狗识别 小数据集上使用微调ResNet模型,该模型在ImageNet预训练
# 获取数据集
d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL + 'hotdog.zip',
'fba480ffa8aa7e0febbb511d181409f899b9baa5')
data_dir = d2l.download_extract('hotdog')
2.读取训练和测试数据集
train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'))
test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'))
3.数据增广
normalize = torchvision.transforms.Normalize(
[0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
# RGB(红、绿和蓝)颜色通道,我们分别标准化每个通道。 具体而言,该通道的每个值减去该通道的平均值,然后将结果除以该通道的标准差
# 在Image模型已经做了这个
)
train_augs = torchvision.transforms.Compose([
torchvision.transforms.RandomResizedCrop(224), # 缩放224*224
torchvision.transforms.RandomHorizontalFlip(), # 水平翻转
torchvision.transforms.ToTensor(),
normalize
])
test_augs = torchvision.transforms.Compose([
torchvision.transforms.Resize([256, 256]), # 缩放256
torchvision.transforms.CenterCrop(224), # 剪裁中央224*224区域输入
torchvision.transforms.ToTensor(),
normalize
])
4.定义和初始化模型 pretrained=True自动下载预训练的模型参数
# 定义和初始化模型 pretrained=True自动下载预训练的模型参数
pretrained_net = torchvision.models.resnet18(pretrained=True)
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) #输出层w随机初始化
5. 模型微调 训练函数
# 如果param_group=True,输出层中的模型参数将使用十倍的学习率 param_group模型初始值
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: # 体征提取层学习率n低,随机初始化的输出层w学习率是10n
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)
6.使用较小的学习率
train_fine_tuning(finetune_net, 5e-5)
loss 0.552, train acc 0.870, test acc 0.951
7.对比 不使用模型初始化 aram_group=False
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)
loss 0.348, train acc 0.843, test acc 0.863