图像增强技术通过对训练图像进行一系列随机更改以生成相似但不同的训练示例来扩展训练数据集的规模。并且随机更改一些示例可以有效的减小模型对某些属性的依赖,提高模型的泛化能力:
通过不同的方式裁剪图像,可以使刚兴趣的对象出现在不同位置,减小模型对位置的依赖
通过对图片颜色的调节,降低模型对颜色的敏感度。
from d2l import mxnet as d2l
from mxnet import autograd, init, np, npx, gluon, image
from mxnet.gluon import nn
from plotly import express as px, graph_objs as go
from IPython.display import Image
import plotly.io as pio
pio.kaleido.scope.default_format = "svg"
npx.set_np()
使用形状为400×500的猫咪图片作为例子。
img = image.imread('img/cat1.jpg')
fig = px.imshow(img.asnumpy())
img_bytes = fig.to_image(format="png", engine="kaleido")
Image(img_bytes)
多数图像增强的方法具有一定的随机性,编写一个方法用于展示,一个功能对图片处理多次的变化情况
from plotly.subplots import make_subplots
def show_imgs(imgs, num_rows=2, num_cols=4, scale=0.8) :
fig = make_subplots(num_rows, num_cols)
for i in range(num_rows):
for j in range(num_cols):
fig.add_trace(go.Image(z=imgs[num_rows*i+j].asnumpy()),i+1,j+1)
fig.update_xaxes(visible=False, row=i+1, col=j+1)
fig.update_yaxes(visible=False, row=i+1, col=j+1)
img_bytes = fig.to_image(format="png", scale=scale, engine="kaleido")
return Image(img_bytes)
def apply(img, aug, num_rows=2, num_cols=4, scale=0.8) :
Y = [aug(img) for _ in range(num_rows * num_cols)]
return show_imgs(Y, num_rows, num_cols, scale=scale)
1. 翻转和剪切
左右翻转图像通常不会更改对象的类别。这是最早且使用最广泛的图像增强方法之一。通过transforms模块的RandomFlipLeftRight方法以50%的概率将图片左右翻转。
apply(img, gluon.data.vision.transforms.RandomFlipLeftRight())
使用RandomFlipTopBottom方法对图像进行上下翻转,同样时50%的概率。
apply(img, gluon.data.vision.transforms.RandomFlipTopBottom())
通过随机剪切图像可以使对象以不同的比例出现在图像的不同位置。可以降低模型对目标对象位置的依赖,从而提高泛化能力。下面使用RandomResizedCrop方法,获取图片某一区域:
size: 剪切之后图像大小
scale:随机裁剪一个面积为原图面积的范围,默认为 ( 0.08 , 1.0 ) (0.08 ,1.0)(0.08,1.0)
ratio:随机的宽高比范围,默认$( 3/4 , 4/3)
shape_aug = gluon.data.vision.transforms.RandomResizedCrop(size=(200, 200), scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)
2. 改变图片颜色
另一中增强方式是通过颜色增强。可以通过一下四个方面更改图片的颜色:
亮度
对比度
饱和度
色相
通过RandomBrightness随机更改图片的亮度,这里的brightness参数决定亮度的随机范围:[ m a x ( 0 , 1 − b r i g h t n e s s ) , 1 + b r i g h t n e s s ] [max(0, 1 - brightness), 1 + brightness][max(0,1−brightness),1+brightness]
apply(img, gluon.data.vision.transforms.RandomBrightness(0.5))
可以通过RandomHue随机更改色调。
apply(img, gluon.data.vision.transforms.RandomHue(0.5))
可以通过RandomColorJitter,同时更改亮度,对比度,饱和度和色调。
color_aug = gluon.data.vision.transforms.RandomColorJitter(
brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
apply(img, color_aug)
3. 多种增强方式同时使用
在实践中,可以将多种的图像增强方式叠加使用。可以通过Compose将这些方法叠加在一起。
augs = gluon.data.vision.transforms.Compose([
gluon.data.vision.transforms.RandomFlipLeftRight(), color_aug, shape_aug])
apply(img, augs)
4. 使用图像增强训练模型
这里,我们使用CIFAR-10数据集,与Fashion-MNIST数据集相比,CIFAR-10数据集中的对象的颜色和大小的差异更加明显。展示数据集的前32个图像。
show_imgs(gluon.data.vision.CIFAR10(train=True)[0:32][0], 4, 8, scale=0.8)
为了在预测期间获得确定的结果,我们通常使用图片增强用于训练过程,在测试期间不使用。对于这个数据集,通过最简单的左右翻转方法处理,以及使用ToTensor转化为MXnet使用的格式,float32,其形状为(批量大小,通道数,高度,宽度),值的范围为0到1之间。通过DataLoader将数据根据batch_size包装成为迭代器。
train_augs = gluon.data.vision.transforms.Compose([
gluon.data.vision.transforms.RandomFlipLeftRight(),
gluon.data.vision.transforms.ToTensor()])
# 测试集不需要进行图片增强
test_augs = gluon.data.vision.transforms.Compose([
gluon.data.vision.transforms.ToTensor()])
def get_workers(num):
# windows系统不能使用多线程转换
return 0 if __import__('sys').platform.startswith('win') else num
def load_cifar10(is_train, augs, batch_size):
return gluon.data.DataLoader(gluon.data.vision.CIFAR10(train=is_train).transform_first(augs),
batch_size=batch_size, shuffle=is_train, num_workers=get_workers(6))
5.训练模型
使用ResNet-18模型训练,并使用GPU进行训练。首先定义每个batch的训练方法:
def accuracy(y_hat, y):
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.astype(y.dtype) == y
return float(cmp.sum())
def train_batch(net, features, labels, loss, trainer, devices, split_f=d2l.split_batch):
X_shards, y_shards = split_f(features, labels, devices)
with autograd.record():
pred_shards = [net(X_shard) for X_shard in X_shards]
ls = [loss(pred_shard, y_shard) for pred_shard, y_shard
in zip(pred_shards, y_shards)]
for l in ls:
l.backward()
# ignore_stale_grad代表可以使用就得梯度参数
trainer.step(labels.shape[0], ignore_stale_grad=True)
train_loss_sum = sum([float(l.sum()) for l in ls])
train_acc_sum = sum(accuracy(pred_shard, y_shard)
for pred_shard, y_shard in zip(pred_shards, y_shards))
return train_loss_sum, train_acc_sum
def train(net, train_iter, test_iter, loss, trainer, num_epochs,
devices=d2l.try_all_gpus(), split_f=d2l.split_batch):
num_batches, timer = len(train_iter), d2l.Timer()
epochs_lst, loss_lst, train_acc_lst, test_acc_lst = [],[],[],[]
for epoch in range(num_epochs):
# Store training_loss, training_accuracy, num_examples, num_features
metric = d2l.Accumulator(4)
for i, (features, labels) in enumerate(train_iter):
timer.start()
l, acc = train_batch(
net, features, labels, loss, trainer, devices, split_f)
metric.add(l, acc, labels.shape[0], labels.size)
timer.stop()
if (i + 1) % (num_batches // 5) == 0:
epochs_lst.append(epoch + i / num_batches)
loss_lst.append(metric[0] / metric[2])
train_acc_lst.append(metric[1] / metric[3])
test_acc_lst.append(d2l.evaluate_accuracy_gpus(net, test_iter, split_f))
print(f"[epock {epoch+1}] train loss: {metric[0] / metric[2]:.3f} train acc: {metric[1] / metric[3]:.3f}",
f" test_loss: {test_acc_lst[-1]:.3f}")
print(f'loss {metric[0] / metric[2]:.3f}, train acc '
f'{metric[1] / metric[3]:.3f}, test acc {test_acc_lst[-1]:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec on '
f'{str(devices)}')
fig = go.Figure()
fig.add_trace(go.Scatter(x=epochs_lst, y=loss_lst, name='train loss'))
fig.add_trace(go.Scatter(x=epochs_lst, y=train_acc_lst, name='train acc'))
fig.add_trace(go.Scatter(x=list(range(1,len(test_acc_lst)+1)), y=test_acc_lst, name='test acc'))
fig.update_layout(width=800, height=480, xaxis_title='epoch', yaxis_range=[0, 1])
fig.show()
定义一个函数来实现训练模型。并使用Adam用作优化函数进行训练。
batch_size, devices, net = 128, d2l.try_all_gpus(), d2l.resnet18(10)
net.initialize(init=init.Xavier(), ctx=devices)
def train_with_data_aug(train_augs, test_augs, net, lr=0.001):
train_iter = load_cifar10(True, train_augs, batch_size)
test_iter = load_cifar10(False, test_augs, batch_size)
loss = gluon.loss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': lr})
train(net, train_iter, test_iter, loss, trainer, 10, devices)
train_with_data_aug(train_augs, test_augs, net)
6. 参考
https://d2l.ai/chapter_computer-vision/image-augmentation.html
7.代码