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

上一部分我们讲了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还可以做更多的事情,在讲到后面进行模型训练的过程时候,我会继续给大家介绍一些,能够让大家觉得这个深度学习工具非常好用的黑科技。

敬请期待~~~

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏PPV课数据科学社区

强大的PyTorch:10分钟让你了解深度学习领域新流行的框架

摘要: 今年一月份开源的PyTorch,因为它强大的功能,它现在已经成为深度学习领域新流行框架,它的强大源于它内部有很多内置的库。本文就着重介绍了其中几种有特色...

3549
来自专栏奇点大数据

Pytorch神器(4)

上一次,我们用最简短的篇幅讲述了用Pytorch实现线性回归的过程。整个程序仅仅用了约60多行就完成了一个线性回归机器学习程序的全部内容。这次的文章,我们来对上...

733
来自专栏AI科技评论

开发 | TensorFlow中RNN实现的正确打开方式

上周写的文章《完全图解RNN、RNN变体、Seq2Seq、Attention机制》介绍了一下RNN的几种结构,今天就来聊一聊如何在TensorFlow中实现这些...

4115
来自专栏应兆康的专栏

OpenCV+TensorFlow 人工智能图像处理 (1)

OpenCV是一个开源的计算机视觉库,OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和M...

1682
来自专栏机器之心

教程 | 如何用PyTorch实现递归神经网络?

选自Nvidia.devblogs 作者:James Bradbury 参与:Jane W、吴攀 从 Siri 到谷歌翻译,深度神经网络已经在机器理解自然语言方...

27612
来自专栏企鹅号快讯

从零开始用Python构造决策树

来源:Python中文社区 作者:weapon 本文长度为700字,建议阅读5分钟 本文介绍如何不利用第三方库,仅用python自带的标准库来构造一个决策树。 ...

2097
来自专栏月色的自留地

《连连看》算法c语言演示(自动连连看)

1699
来自专栏游戏开发那些事

【小白学游戏常用算法】二、A*启发式搜索算法

  在上一篇博客中,我们一起学习了随机迷宫算法,在本篇博客中,我们将一起了解一下寻路算法中常用的A*算法。

992
来自专栏灯塔大数据

每周学点大数据 | No.39单词共现矩阵计

No.39期 单词共现矩阵计算 Mr. 王:这里还有一个很典型的例子——单词共现矩阵计算。 这个例子是计算文本集合中词的共现矩阵。我们设 M 是一个 N×N...

4285
来自专栏AI研习社

TensorFlow 中 RNN 实现的正确打开方式

上周写的文章《完全图解 RNN、RNN 变体、Seq2Seq、Attention 机制》介绍了一下 RNN 的几种结构,今天就来聊一聊如何在 TensorFlo...

3898

扫码关注云+社区