随着深度学习的进步和硬件的更新迭代,计算机视觉技术也得到了更大的提升,在计算机视觉领域,经常要训练深度学习的模型,而训练模型的最终目的是为了更好的应用到实际当中去,那就要解决一个精度问题和泛化能力,对于泛化能力的提升,前面也做了很多的工作,这篇主要是讲解图像的增广,减小对训练数据集的某些属性的依赖,从而测试的时候可以得到更好的泛化能力。
图像增广(image augmentation)技术是通过对训练图像做一系列的改变,产生一些相似但不相同的训练样本,比如随机裁剪,使得目标出现在不同位置,减小模型对其位置的依赖;调整亮度、色彩等因素,减小模型对色彩的敏感度等,这些操作最终都是为了提高泛化能力。
我准备了一张宽500,高400像素的猫的图片,在画布中显示看下效果,注意坐标的表示:
import d2lzh as d2l
import mxnet as mx
from mxnet import autograd,gluon,image,init,nd
from mxnet.gluon import data as gdata,loss as gloss,utils as gutils
import sys
import time
d2l.set_figsize()
img=image.imread('cat.jpg')
d2l.plt.imshow(img.asnumpy()) #img.shape:(400,500,3)
# 如果使用Jupter Notebook可以不写下面这个show
# 将会显示 和一张画布图片,注意坐标的起始位置
d2l.plt.show()
为了后续便于观察这张猫的图像,做了增广之后的效果,我们在画布上定义一个显示多行多列的图片的函数:
d2l.set_figsize()
img=image.imread('cat.jpg')
#d2lzh包中已有
def show_images(imgs,num_rows,num_cols,scale=2):
figsize=(num_cols*scale,num_rows*scale)
_,axes=d2l.plt.subplots(num_rows,num_cols,figsize=figsize)
for i in range(num_rows):
for j in range(num_cols):
axes[i][j].imshow(imgs[i*num_cols+j].asnumpy())
#隐藏X,Y轴
axes[i][j].get_xaxis().set_visible(False)
axes[i][j].get_yaxis().set_visible(False)
return axes
#将每个图片做增广然后显示
def apply(img,aug,num_rows=2,num_cols=4,scale=1.5):
Y=[aug(img) for _ in range(num_cols*num_rows)]
show_images(Y,num_rows,num_cols,scale)
然后分别看下不同随机增广后的效果:
左右翻转
apply(img,gdata.vision.transforms.RandomFlipLeftRight())
上下翻转
apply(img,gdata.vision.transforms.RandomFlipTopBottom())
随机位置裁剪(原面积的10%~100%,高宽比05~2),然后将宽高再缩放到200像素
shape_aug=gdata.vision.transforms.RandomResizedCrop((200,200),scale=(0.1,1),ratio=(0.5,2))
apply(img,shape_aug)
亮度变化(1-0.5到1+0.5)
apply(img,gdata.vision.transforms.RandomBrightness(0.5))
色调变化
apply(img,gdata.vision.transforms.RandomHue(0.5))
创建RndomColorJitter实例同时设置亮度、对比度、饱和度、色调的变化
color_aug=gdata.vision.transforms.RandomColorJitter(brightness=0.5,contrast=0.5,saturation=0.5,hue=0.5)
apply(img,color_aug)
Compose组合叠加多个图像增广方法
augs=gdata.vision.transforms.Compose([gdata.vision.transforms.RandomFlipLeftRight(),color_aug,shape_aug])
apply(img,augs)
d2l.plt.show()
训练CIFAR-10数据集
CIFAR-10也是一个常用的数据集,没有的情况,CIFAR10方法将会自动下载。我们来看下这个数据集的大小,以及显示前面32张图,这个训练数据集是由50000张,高宽是32,通道是3的图像组成,测试集是10000张,里面的类别还是蛮多的,动物、车、船、飞机等
show_images(gdata.vision.CIFAR10(train=True)[0:32][0], 4, 8, scale=0.8)
print(gdata.vision.CIFAR10(train=True)._data.shape)#(50000, 32, 32, 3)
一般都是在训练的时候进行图像的增广,而在预测时不应用。
# 训练数据集做增广
# ToTensor转换成MXNet需要的格式(批处理大小,通道,高,宽)(N,C,H,W)
flip_aug=gdata.vision.transforms.Compose([gdata.vision.transforms.RandomFlipLeftRight(),gdata.vision.transforms.ToTensor()])
# 预测时不做增广
no_aug=gdata.vision.transforms.Compose([gdata.vision.transforms.ToTensor()])
# 加载数据集并应用图像增广
num_works = 0 if sys.platform.startswith('win32') else 4
def load_cifar10(is_train, augs, batch_size):
return gdata.DataLoader(gdata.vision.CIFAR10(train=is_train).transform_first(augs), batch_size=batch_size, shuffle=is_train, num_workers=num_works)
使用ResNet-18模型与Adam优化算法来训练
def train_with_data_aug(train_augs, test_augs, lr=0.001):
batch_size, ctx, net = 256, d2l.try_all_gpus(), d2l.resnet18(10)
net.initialize(ctx=ctx, init=init.Xavier())
trainer = gluon.Trainer(net.collect_params(),
'adam', {'learning_rate': lr})
loss = gloss.SoftmaxCrossEntropyLoss()
train_iter = load_cifar10(True, train_augs, batch_size)
test_iter = load_cifar10(False, test_augs, batch_size)
d2l.train(train_iter, test_iter, net, loss, trainer, ctx, num_epochs=10)
train_with_data_aug(flip_aug, no_aug)
本人电脑配置低了,训练时很容易内存溢出:
mxnet.base.MXNetError: [10:20:10] c:\jenkins\workspace\mxnet-tag\mxnet\src\storage\./pooled_storage_manager.h:157: cudaMalloc failed: out of memory
于是将批量大小从256修改成64,然后就可以训练了,也只有一个gpu的情况,效果还是可以的,如下:
training on [gpu(0)]
epoch 1, loss 1.3235, train acc 0.526, test acc 0.659, time 124.6 sec
epoch 2, loss 0.7716, train acc 0.729, test acc 0.738, time 122.7 sec
epoch 3, loss 0.5864, train acc 0.797, test acc 0.801, time 122.8 sec
epoch 4, loss 0.4848, train acc 0.833, test acc 0.782, time 123.5 sec
epoch 5, loss 0.4053, train acc 0.861, test acc 0.808, time 123.7 sec
epoch 6, loss 0.3367, train acc 0.883, test acc 0.820, time 122.7 sec
epoch 7, loss 0.2883, train acc 0.900, test acc 0.828, time 124.3 sec
epoch 8, loss 0.2420, train acc 0.917, test acc 0.850, time 124.3 sec
epoch 9, loss 0.2026, train acc 0.930, test acc 0.840, time 124.9 sec
epoch 10, loss 0.1671, train acc 0.943, test acc 0.846, time 125.1 sec
其实对于组合叠加增广,在前面的WGAN(Wasserstein生成对抗网络)源码的讲解 已有使用,如下:
import torchvision.transforms as transforms
import torchvision.datasets as dset
dataset = dset.CIFAR10(root=opt.dataroot, download=True,
transform=transforms.Compose([
transforms.Resize(opt.imageSize),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
)
可以看出PyTorch框架的写法与MXNet框架的方法差不多,一般也是一些语法细节的区别。PyTorch是来自import torchvision.transforms as transforms这个模块,而MXNet是来自from mxnet.gluon import data as gdata里面的gdata.vision.transforms这个模块。
关于ToTensor()转换形状的函数,做个示例:
transformer = gdata.vision.transforms.ToTensor()
imgs = nd.random.uniform(0, 255, (4, 2, 3)).astype(dtype=np.uint8)
print(transformer(imgs))#会将(4,2,3)形状转换成(3,4,2)的[0,1)的浮点数