动手深度学习——计算机视觉

一、数据增广

参考李沐《动手深度学习》哔哩哔哩视频

1.1 为何进行数据增广?

  1. CES真实案例:
    几年前,一家做无人售货的公司发现演示机器在现场的效果很差,因为现场在赌城拉斯维加斯,现场与之前的开发测试办公室:
  • 色温不同。赌城灯光很暗,偏黄
  • 测试demo时机器放在桌子上,桌子很亮,反射后图片取景有变化

解决:开场前一天现场测试有问题,马上现场采集数据发回国内,训练一个新的模型,并且买了一块桌布就没有反光了。

例如语音识别、CV等场景,训练模型时可以模拟部署场景的各种情况,是提高模型泛化性的一种手段。

  1. 数据增广的优点:
  • 数据增⼴可以⽣成相似但不同的训练样本,从⽽扩⼤了训练集的规模,增加数据多样性。
  • 此外,随机改变训练样本可以减少模型对某些属性的依赖,从⽽提⾼模型的泛化能⼒。例如,我们可以以不同的⽅式裁剪图像,使感兴趣的对象出现在不同的位置,减少模型对于对象出现位置的依赖。我们还可以调整亮度、颜⾊等因素来降低模型对颜⾊的敏感度
  • 增广方式:语音中加入不同背景音,图片可以改变颜色、亮度和形状等。

一般的做法是图片进行随机在线数据增广之后再进行训练,相当于一个正则项。

  1. 软件安装:
pip install torch==1.10.2
pip install torchvision==0.11.3
pip install d2l==0.17.4

1.2 常见图片增广方式

%matplotlib inline
import torch
import torchvision
from torch import nn
from d2l import torch as d2l

d2l.set_figsize()
img = d2l.Image.open('../img/cat1.jpg')
d2l.plt.imshow(img);


定义辅助函数apply。此函数在输⼊图像img上多次运⾏图像增⼴⽅法aug并显⽰所有结果:

def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
    Y = [aug(img) for _ in range(num_rows * num_cols)]#对num_rows * num_cols的图片进行增广存储到Y
    d2l.show_images(Y, num_rows, num_cols, scale=scale)#打印出增广后的图片

img:被增广图片
aug:增广办法
num_rows、num_cols:图片被增广成几行几列
scale:放大比例

1.2.1 翻转

  • 图片可以左右、上下翻转,但不一定总是可行。比如树叶可以上下翻转,但是建筑上下翻转就很奇怪,所以要根据样本来决定。
  • 使⽤transforms模块来创建RandomFlipLeftRight实例,这样就各有50%的⼏率使图像向左或向右翻转
apply(img,torchvision.transforms.RandomHorizontalFlip())#随机左右翻转

apply(img, torchvision.transforms.RandomVerticalFlip())#随机上下翻转

1.2.2 切割(裁剪)

从原图切割一块,再变形到固定形状(卷积网络固定输入)。切割方式可以是随机高宽比、随机大小、随机位置。

shape_aug = torchvision.transforms.RandomResizedCrop(
    (200, 200), scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)
  • (200, 200):裁剪后resize到200*200像素(卷积网络输入是固定的)
  • scale(0.1,1):随机裁剪⼀个⾯积为原始⾯积10%到100%的区域
  • ratio=(0.5, 2)):裁剪区域的宽⾼⽐从0.5到2之间随机取值

1.2.3 改变颜⾊

主要是改变色调(偏黄偏蓝等等)、饱和度(浓度)、亮度等等。创建一个RandomColorJitter实例,并设置如何同时[随机更改图像的亮度(brightness)、对比度(contrast)、饱和度(saturation)和色调(hue)]:

color_aug = torchvision.transforms.ColorJitter(
    brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
apply(img, color_aug)

#brightness=0.5表示明度在50%-150%之间随机变化,其它参数类同
#四个参数随机取值组合决定图片变化

1.2.4 综合使用

  • 在实践中,我们将结合多种图像增广方法。比如,我们可以通过使用一个Compose实例来综合上面定义的不同的图像增广方法,并将它们应用到每个图像:
shape_aug = torchvision.transforms.RandomResizedCrop(
    (200, 200), scale=(0.1, 1), ratio=(0.5, 2))
color_aug = torchvision.transforms.ColorJitter(
    brightness=0.2, contrast=0, saturation=0.2, hue=0.05)

augs = torchvision.transforms.Compose([
    torchvision.transforms.RandomHorizontalFlip(), color_aug, shape_aug])
apply(img, augs)

  • 还可以有其它方式,比如高斯模糊、锐化、图片中间去块等等。但不是每一种都有用。如果测试集或者部署环境中没有那些奇怪的图片,某些方法可以不用。所以是应该先考虑测试集、部署环境中图片会有哪些变化,再考虑图片增广应该使用哪些方法。

1.3 使⽤图像增⼴进⾏训练

我们使⽤CIFAR-10数据集进行图像增广之后再训练模型。CIFAR-10数据集中对象的颜⾊和⼤⼩差异更明显。CIFAR-10数据集中的前32个训练图像如下所⽰:

all_images = torchvision.datasets.CIFAR10(train=True, root="../data",
                                          download=True)
d2l.show_images([all_images[i][0] for i in range(32)], 4, 8, scale=0.8);
  • 为了在预测过程中得到确切的结果,我们通常对训练样本只进行图像增广,且在预测过程中不使用随机操作的图像增广。在这里,我们[只使用最简单的随机左右翻转]。(因为图片已经在最中间了,且比较小,大概32*32。且训练测试集亮度颜色差不多,不需要做太厉害的增广)
  • 此外,我们使用ToTensor实例将一批图像转换为4d的Tensor矩阵方便训练,即形状为(批量大小,通道数,高度,宽度)的32位浮点数,取值范围为0到1。
train_augs = torchvision.transforms.Compose([
     torchvision.transforms.RandomHorizontalFlip(),
     torchvision.transforms.ToTensor()])

test_augs = torchvision.transforms.Compose([
     torchvision.transforms.ToTensor()])
  1. 生成数据集
    接下来,我们[定义一个辅助函数,以便于读取图像和应用图像增广]。PyTorch数据集提供的transform函数应用图像增广来转化图像:
def load_cifar10(is_train, augs, batch_size):
    dataset = torchvision.datasets.CIFAR10(root="../data", train=is_train,
                                           transform=augs, download=True)
    dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
                    shuffle=is_train, num_workers=d2l.get_dataloader_workers())
    return dataloader

这里的d2l.get_dataloader_workers()=4,多开进程是因为图片随机增广的时候计算量很大,开多进程可以快一点。

  1. 定义模型
    我们在CIFAR-10数据集上训练一个多GPU的ResNet-18模型:
#@save
def train_batch_ch13(net, X, y, loss, trainer, devices):
    """用多GPU进行小批量训练"""
    if isinstance(X, list):
        # 微调BERT中所需(稍后讨论)
        X = [x.to(devices[0]) for x in X]#如果X是list就一个个copy到devices
    else:
        X = X.to(devices[0])
    y = y.to(devices[0])
    net.train()
    trainer.zero_grad()#梯度归零
    pred = net(X)
    l = loss(pred, y)
    l.sum().backward()#梯度回传
    trainer.step()
    train_loss_sum = l.sum()
    train_acc_sum = d2l.accuracy(pred, y)
    return train_loss_sum, train_acc_sum
#@save
def train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,
               devices=d2l.try_all_gpus()):
    """用多GPU进行模型训练"""
    timer, num_batches = d2l.Timer(), len(train_iter)
    animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0, 1],
                            legend=['train loss', 'train acc', 'test acc'])
    net = nn.DataParallel(net, device_ids=devices).to(devices[0])
    for epoch in range(num_epochs):
        # 4个维度:储存训练损失,训练准确度,实例数,特点数
        metric = d2l.Accumulator(4)
        for i, (features, labels) in enumerate(train_iter):
            timer.start()
            l, acc = train_batch_ch13(
                net, features, labels, loss, trainer, devices)
            metric.add(l, acc, labels.shape[0], labels.numel())
            timer.stop()
            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
                animator.add(epoch + (i + 1) / num_batches,
                             (metric[0] / metric[2], metric[1] / metric[3],
                              None))
        test_acc = d2l.evaluate_accuracy_gpu(net, test_iter)
        animator.add(epoch + 1, (None, None, test_acc))
    print(f'loss {metric[0] / metric[2]:.3f}, train acc '
          f'{metric[1] / metric[3]:.3f}, test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec on '
          f'{str(devices)}')
  1. 增广训练
    定义train_with_data_aug函数,使用图像增广来训练模型]。该函数获取所有的GPU,并使用Adam作为训练的优化算法,将图像增广应用于训练集,最后调用刚刚定义的用于训练和评估模型的train_ch13函数。
batch_size, devices, net = 256, d2l.try_all_gpus(), d2l.resnet18(10, 3)

def init_weights(m):
    if type(m) in [nn.Linear, nn.Conv2d]:
        nn.init.xavier_uniform_(m.weight)

net.apply(init_weights)

def train_with_data_aug(train_augs, test_augs, net, lr=0.001):#训练集是增广数据集train_augs
    train_iter = load_cifar10(True, train_augs, batch_size)
    test_iter = load_cifar10(False, test_augs, batch_size)
    loss = nn.CrossEntropyLoss(reduction="none")
    trainer = torch.optim.Adam(net.parameters(), lr=lr)
    train_ch13(net, train_iter, test_iter, loss, trainer, 10, devices)

使用基于随机左右翻转的图像增广来训练模型:

train_with_data_aug(train_augs, test_augs, net)

loss 0.167, train acc 0.943, test acc 0.843
5486.0 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]


不用图像增广,效果:

loss 0.072, train acc 0.975, test acc 0.824
5560.0 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1)]

对比可以看到,简单翻转图片可以有效降低过拟合程度(训练测试集精度差异更小,overfiting更小)。有些情况下测试集精度高于训练集精度,是由于训练集图片增广太狠,出现很多奇怪的图片,精度下降。而测试集中图片不会那么奇怪,效果反而更好。

1.4 小结

  • 图像增广基于现有的训练数据生成随机图像,来提高模型的泛化能力。
  • 为了在预测过程中得到确切的结果,我们通常对训练样本只进行图像增广,而在预测过程中不使用带随机操作的图像增广。
  • 深度学习框架提供了许多不同的图像增广方法,这些方法可以被同时应用。

二、微调

2.1 为啥要微调

  • 实际工作中,我们训练的模型数据集大小通常在Fashion-MNIST(6万张)训练数据集和ImageNet(120万张)数据集之间。假如我们需要训练一个识别车的模型,适合ImageNet的复杂模型可能会在这个汽车数据集上过拟合(汽车数据集多样性比ImageNet小)。
  • 此外,由于训练样本数量有限,训练模型的准确性可能无法满足实际要求。

    解决方案有两种:
  1. 收集更多的数据。 但是,收集和标记数据可能需要大量的时间和金钱。 例如,为了收集ImageNet数据集,研究人员花费了数百万美元的研究资金。 尽管目前的数据收集成本已大幅降低,但这一成本仍不能忽视。

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

2.2 微调(fine-tuning)步骤

当目标数据集比源数据集小得多时,微调有助于提高模型的泛化能力。(其实相当于用源模型的参数作为目标模型的参数初始化,这样比目标模型随机初始化效果好。而输出层是随机初始化来训练)

  1. 在源数据集(例如ImageNet数据集)上预训练神经网络模型,即源模型
  2. 创建一个新的神经网络模型,即目标模型。这将复制源模型上的所有模型设计及其参数(输出层除外)。我们假定这些模型参数包含从源数据集中学到的知识,这些知识也将适用于目标数据集。我们还假设源模型的输出层与源数据集的标签密切相关;因此不在目标模型中使用该层。
  3. 向目标模型添加输出层,其输出数是目标数据集中的类别数。然后随机初始化该层的模型参数。
  4. 在目标数据集(如汽车数据集)上训练目标模型。输出层将从头开始进行训练,而所有其他层的参数将根据源模型的参数进行微调。
  • 源数据集可能包含目标数据集中的某些类别,这些样本可以加入目标数据集一起初始化。(在初始化期间使用来自预训练模型的相应权重向量)
  • 在已有神经网络上微调目标数据集,但具有强大的正则化
    • 使用较小的学习率
    • 使用较少的迭代周期
  • 如果源数据集比目标数据集更复杂,则微调通常会得到更高质量的模型

  • 神经网络学习分层特征表示
    • 低层特征是通用的(底层权重可以固定,模型复杂度变低,数据集小的时候相当于正则)
    • 高层特征与数据集中的对象更相关
  • 在微调期间修复底层的参数
    • 另一个强有力的正则

2.3 总结

  • 微调通过使用在大数据集上训练好的模型,来初始化目标模型权重以得到更好的精度
  • 预训练模型质量很重要
  • 微调通常速度更快,精度更高
  • 以后学术界或者大公司才会从头训练大的深度学习模型,慢慢的几乎所有基于深度学习的应用都会基于微调。
  • 如果目标数据集和源数据集差异非常大,那么从头训练目标数据集效果可能更好。或者医学的就找医学的预训练模型。

2.4 代码举例

参考《13.2. 微调》

简介:热狗数据集有正负两类,预下载在ImageNet上训练好的resnet-18模型,然后在热狗数据集上微调。微调时,最后一个fc层学习率为10*lr,其它层学习率为lr,最后效果很好。(基本2个epoch效果就很好了,lr=5e-5比较小)

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上做了RGB三通道标准化,所以热狗数据集也要做这一步。(如果网络中有BN就不需要这么做了)
  • ImageNet输入是224*224,所以热狗数据集图片也要resize到224
  • 数据增强只用简单的水平翻转

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2022年5月18日
下一篇 2022年5月18日

相关推荐