前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >1.试水:可定制的数据预处理与如此简单的数据增强(下)

1.试水:可定制的数据预处理与如此简单的数据增强(下)

原创
作者头像
SCP-173
发布2018-06-08 21:45:37
1.3K0
发布2018-06-08 21:45:37
举报

上一部分我们讲了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文件夹下的图像记录为标签0dogs文件夹下的图像记录为标签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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据加载
  • 数据增强
  • 自定义数据增强
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档