上一部分我们讲了MXNet中NDArray模块实际上有很多可以继续玩的地方,不限于卷积,包括循环神经网络RNN、线性上采样、池化操作等,都可以直接用NDArray调用,进行计算。
在大概很久之前,我一直认为写个数据预处理或者数据增强需要单独写一个模块,比如我要做图像翻转,我肯定会把所有图片读取后,按照numpy方式进行操作,然后存起来,看到文件夹中的图片数量扩充成原来的三四倍,心意满满的写网络模型。但实际上,在MXNet中只需要写好你需要的`transform`就可以了,其他的问题MXNet会用会自动帮你解决。本篇文章,我分两个模块去讲,一个是数据加载问题,另一个是数据增强问题。
MXNet的数据加载被单独写成了一个函数模块Data Loading API,基本我们所熟知的各种数据加载方式都可以在里面直接调用(我暂且没有发现比较好的segmentation任务的数据加载方式,依然用的例子中的fcn-xs,这个之后会研究一下)。
MXNet主要数据加载形式分为两种,一种是经典加载方式,在gluon没有出来之前大家用的非常普遍的方式;另一种就是专为Gluon设计的加载方式。两种方法还是为了适应不同的前端表达接口,我会着重讲解一下第二种的使用方法,暂且把目标任务定为分类Classification。
在函数模块Data Loading API中提供了直接调用二进制数据rec方式接口,同样提供了根据img_lst去索引图像,并进行加载的方式。具体查看Data Loading API中内容接口,包括就文档中有很多介绍方法。
在新端口Gluon中提供了更为简便的数据接口(不得不说,这个接口的形式和Pytorch太像了。。),都在Gluon Package - mxnet documentation中,它也同样支持rec读取方式、和文件夹直接读取,我们以猫狗大赛dogs_vs_cats做例子,直接读数据进行读取。
MXNet可以对一个文件夹下所有子文件夹进行标记分类,比如:
train
├─cats
│ ├─cat.0.jpg
│ ├─cat.1.jpg
│ ├─cat.2.jpg
│ └─.......
└─dogs
├─dog.0.jpg
├─dog.1.jpg
├─dog.2.jpg
└─.......
只需要制定train
文件夹,就可以直接自动将cats
文件夹下的图像记录为标签0
,dogs
文件夹下的图像记录为标签1
,以此类推 。
我们来试一下:
from mxnet import gluon
batch_size = 36
dataset = gluon.data.vision.ImageFolderDataset('../data/train/')
train_sets = gluon.data.DataLoader(dataset, batch_size, shuffle=True)
这个时候的train_sets
是一个iteration
,可以直接用for
函数调用,我们来试一下:
for imgs, labels in train_sets:
print(labels)
break
"""
out:
[ 0. 0. 1. 1. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 0. 0. 1. 1.
1. 1. 0. 0. 1. 1. 1. 0. 1. 1. 0. 1. 1. 1. 0. 1. 1. 0.]
<NDArray 36 @cpu(0)>
"""
打印一下图像:
_, figs = plt.subplots(6, 6, figsize=(6,6))
for i in range(6):
for j in range(6):
x = nd.transpose(imgs[i*6+j], (1,2,0))
figs[i][j].imshow(x.asnumpy())
figs[i][j].axes.get_xaxis().set_visible(False)
figs[i][j].axes.get_yaxis().set_visible(False)
我另一方面注意到,这个iteration
实际上是一个多线程加载过程,我们查看CPU使用情况:
真·黑科技~~~
我们现在所得出来的imgs就可以直接用于Gluon上的输入数据,这一部分会在之后的内容中讲到。
图片增强通过一系列的随机变化生成大量“新”的样本,从而减低过拟合的可能。现在在深度卷积神经网络训练中,图片增强是必不可少的一部分。
这里我们直接使用使用李沐大佬的教程例子作为讲解。我们还是读取一张图片作为输入:
from mxnet import image
img = image.imdecode(open('../data/train/cats/cat.123.jpg','rb').read())
imshow(img.asnumpy())
接下来我们定义一个辅助函数,给定输入图片img
的增强方法aug
,它会运行多次并画出结果。
def apply(img, aug, n=3):
_, figs = plt.subplots(n, n, figsize=(8,8))
for i in range(n):
for j in range(n):
# 转成float,一是因为aug需要float类型数据来方便做变化。
# 二是这里会有一次copy操作,因为有些aug直接通过改写输入
#(而不是新建输出)获取性能的提升
x = img.astype('float32')
# 有些aug不保证输入是合法值,所以做一次clip
y = aug(x).clip(0,254)
# 显示浮点图片时imshow要求输入在[0,1]之间
figs[i][j].imshow(y.asnumpy()/255.0)
figs[i][j].axes.get_xaxis().set_visible(False)
figs[i][j].axes.get_yaxis().set_visible(False)
1. 水平方向翻转:
# 以.5的概率做翻转
from mxnet import image
aug = image.HorizontalFlipAug(.5)
apply(img, aug)
2. 随机裁剪一块:
# 随机裁剪一个块 150 x 150 的区域
aug = image.RandomCropAug([150, 150])
apply(img, aug)
3. 随机色调:
# 随机色调变化
aug = image.HueJitterAug(.5)
apply(img, aug)
还有许许多多可以添加的各类函数,都在Image API - mxnet documentation,并且里面已经内置了很多的数据增强的函数:
接下来是一个重点内容。。
前段时间我看到了一篇论文[1708.04896] Random Erasing Data Augmentation,它介绍了一种数据增强的方法,就是随机遮掉图像中部分区域,用空白或噪声代替,从而实现能够对图像的全局信息特征进行学习,增强鲁棒性。
这数据增强的方法在图像分类、目标识别、person re-ID等方面,取得了不错的效果,我们尝试直接用MXNet中的操作,写个变换。
在MXNet的新接口Gluon中,在上面我们用到了一个数据加载的函数,就是可以直接从文件夹中读取图像的gluon.data.vision.ImageFolderDataset
,里面提供了一个transform
参量,这个transform接受一个调用函数。
这里面我们需要注意,各种变换尽可能采用mxnet.nd里的函数。我们来试试:
from mxnet import nd
import random
def random_mask(ndimg, size, flag=0):
w, h = ndimg.shape[:2] # 获取图像的尺寸
w_ = random.randint(0, w-size) #确定起始坐标的位置范围
h_ = random.randint(0, h-size)
if flag==0:
# 随机遮盖的形状是一个正方形
ndimg[w_:w_+size, h_:h_+size, :] = nd.zeros((size, size, 3)) # 用黑色来遮盖
return ndimg
elif flag==1:
# 随机遮盖的形状是一个长方形
w_size = random.randint(0, size-1)
h_size = random.randint(0, size-1)
# 用随机噪声来遮盖
ndimg[w_:w_+w_size, h_:h_+h_size, :] = mx.ndarray.random_uniform(low=0, high=255, shape=(w_size, h_size, 3))
return ndimg
加载图片:
from mxnet import image
img = image.imdecode(open('../data/train/cats/cat.123.jpg','rb').read())
img = random_mask(img.astype('float32')/255., 150, flag=0)
imshow(img.asnumpy())
我们换一种,换成长方形和随机噪声填充(使上面代码中flag=1
):
我们批量处理,写一个完整数据增强的transform
的函数 :
import random
def apply_aug_list(img, augs):
for f in augs:
img = f(img)
return img
train_augs = [
image.ResizeAug(250), # 将短边resize至250
image.HorizontalFlipAug(.5), # 0.5概率的水平翻转变换
image.HueJitterAug(.6), # -0.6~0.6的随机色调
image.BrightnessJitterAug(.5), # -0.5~0.5的随机亮度
image.RandomCropAug((230,230)), # 随机裁剪成(230,230)
]
def get_transform(augs):
# 获得transform函数
def transform(data, label):
data = data.astype('float32') # 部分数据增强接受`float32`
if augs is not None:
data = apply_aug_list(data, augs)
data = random_mask(data, 150, flag=1) # 执行random_mask, 随机遮盖
data = nd.transpose(data, (2,0,1))/255 # 改变维度顺序为(c, w, h)
label = np.array(label) # 调整下label格式
return data, label.astype('float32')
return transform
这样我就可以将这个变换加入到数据加载中了:
batch_size = 36
dataset = gluon.data.vision.ImageFolderDataset('../data/train/', transform=get_transform(train_augs))
train_sets = gluon.data.DataLoader(dataset, batch_size, shuffle=True)
for imgs, labels in train_sets:
print(labels)
break
输出标签:
[ 1. 0. 0. 0. 0. 1. 1. 1. 0. 1. 1. 1. 0. 1. 1. 1. 1. 0.
0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 1. 0. 0. 0. 0. 0. 1.]
<NDArray 36 @cpu(0)>
我们看一下效果
_, figs = plt.subplots(6, 6, figsize=(6,6))
for i in range(6):
for j in range(6):
x = nd.transpose(imgs[i*6+j], (1,2,0))
figs[i][j].imshow(x.asnumpy())
figs[i][j].axes.get_xaxis().set_visible(False)
figs[i][j].axes.get_yaxis().set_visible(False)
# 看看第二张狗
sample = nd.transpose(imgs[1], (1,2,0))
imshow(sample.asnumpy())
print(label[1])
[ 1.]
<NDArray 1 @cpu(0)>
成功实现了[1708.04896] Random Erasing Data Augmentation中提到了Random Erasing,同时我也做了对比试验,这个数据增强的办法的确可以提升模型的泛化性。
这仅仅是个例子,MXNet和Gluon还可以做更多的事情,在讲到后面进行模型训练的过程时候,我会继续给大家介绍一些,能够让大家觉得这个深度学习工具非常好用的黑科技。
敬请期待~~~
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。