参考李沐《动手深度学习》、哔哩哔哩视频
解决:开场前一天现场测试有问题,马上现场采集数据发回国内,训练一个新的模型,并且买了一块桌布就没有反光了。
例如语音识别、CV等场景,训练模型时可以模拟部署场景的各种情况,是提高模型泛化性的一种手段。
一般的做法是图片进行随机在线数据增广之后再进行训练,相当于一个正则项。
pip install torch==1.10.2
pip install torchvision==0.11.3
pip install d2l==0.17.4
%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:放大比例
apply(img,torchvision.transforms.RandomHorizontalFlip())#随机左右翻转
apply(img, torchvision.transforms.RandomVerticalFlip())#随机上下翻转
从原图切割一块,再变形到固定形状(卷积网络固定输入)。切割方式可以是随机高宽比、随机大小、随机位置。
shape_aug = torchvision.transforms.RandomResizedCrop(
(200, 200), scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)
主要是改变色调(偏黄偏蓝等等)、饱和度(浓度)、亮度等等。创建一个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%之间随机变化,其它参数类同
#四个参数随机取值组合决定图片变化
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)
我们使⽤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);
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()])
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,多开进程是因为图片随机增广的时候计算量很大,开多进程可以快一点。
#@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)}')
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更小)。有些情况下测试集精度高于训练集精度,是由于训练集图片增广太狠,出现很多奇怪的图片,精度下降。而测试集中图片不会那么奇怪,效果反而更好。
收集更多的数据。 但是,收集和标记数据可能需要大量的时间和金钱。 例如,为了收集ImageNet数据集,研究人员花费了数百万美元的研究资金。 尽管目前的数据收集成本已大幅降低,但这一成本仍不能忽视。
应用迁移学习(transfer learning)将从源数据集学到的知识迁移到目标数据集。 例如,尽管ImageNet数据集中的大多数图像与椅子无关,但在此数据集上训练的模型可能会提取更通用的图像特征,这有助于识别边缘、纹理、形状和对象组合。 这些类似的特征也可能有效地识别椅子。
当目标数据集比源数据集小得多时,微调有助于提高模型的泛化能力。(其实相当于用源模型的参数作为目标模型的参数初始化,这样比目标模型随机初始化效果好。而输出层是随机初始化来训练)
参考《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);