图像增广 讨论区


#1

http://zh.diveintodeeplearning.org/chapter_computer-vision/image-augmentation.html


#2

#3

在好几个讨论区下面都有关于DataLoaderissue 7593about data preprocess 数据预处理导致整体耗时的问题,但好像没看到好的解决方案。

一个简单的对比实验:
pytorch版本

import torchvision.datasets as dsets
import torchvision.transforms as transforms
import torch
import time

transform = transforms.Compose([
    transforms.Scale(40),
    transforms.RandomHorizontalFlip(),
    transforms.RandomCrop(32),
    transforms.ToTensor()])
train_dataset = dsets.CIFAR10(root='../data/', train=True, transform=transform,download=True)
test_dataset = dsets.CIFAR10(root='../data/', train=False, transform=transforms.ToTensor())
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=100, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=100, shuffle=False)
start = time.time()
for i, (images, labels) in enumerate(train_loader):
    pass
print("total time:", time.time() - start)

total time: 6.187443256378174
而且cpu1~cpu4(我的是四核)只有一个达到100%

gluon版本

from mxnet.gluon import data
from mxnet import image
import time

train_augs = [image.ResizeAug(40), image.HorizontalFlipAug(.5), image.RandomCropAug([32, 32])]
def get_transform(augs):
    def transform(img, label):
        img = img.astype('float32')
        if augs is not None:
            for f in augs:
                img = f(img)
        return img.transpose((2, 0, 1)) / 255, label
    return transform
train_dataset = data.vision.CIFAR10(train=True, transform=get_transform(train_augs))
test_dataset = data.vision.CIFAR10(train=False, transform=get_transform(None))
train_loader = data.DataLoader(train_dataset, batch_size=100, shuffle=True)
test_loader = data.DataLoader(test_dataset, batch_size=100, shuffle=False)

start = time.time()
for i, (imgs, labels) in enumerate(train_loader):
    pass
print("total time:", time.time() - start)

total time: 19.250367879867554
而且cpu1~cpu4(我的是四核)都达到100%

这主要是什么原因呢? 谢谢 :wink:


下述我进一步做了实验,发现问题其实是主要处在image里面的函数导致的,而不是dataset或者dataloader函数:(这也是两者源码基本一样,不至于差别这么大)
注:下述实验均将train_dataset里面的transform去掉了

  1. 对比dataset的时间
start = time.time()
for i in range(len(train_dataset)):
    data, label = train_dataset[i]
print("total time:", time.time() - start)

pytorch:total time: 1.8148322105407715
gluon:total time: 0.3120298385620117
这主要是gluon这里默认采用多线程

  1. 在“外面”加transform操作
# gluon
ff = [image.ResizeAug(40), image.HorizontalFlipAug(.5)]
start = time.time()
for i in range(len(train_dataset)):
    data, label = train_dataset[i]
    for f in ff:
        data = f(data)
print("total time:", time.time() - start)
# pytorch
trans = [transforms.Scale(40), transforms.RandomHorizontalFlip()]
start = time.time()
for i in range(len(train_dataset)):
    data, label = train_dataset[i]
    for f in trans:
        data = f(data)
print("total time:", time.time() - start)

pytorch:total time: 3.694923162460327
gluon:total time: 4.735454082489014

说明:当我们进一步增加这些数据增强操作时,时间差别会进一步变大 — 这主要是来自于image操作上的时间消耗(个人觉得)。一个建议,可以自己重新写一个utils来进行数据增强:可以参考官方风格迁移示例。

  1. 采用opencv执行上述(缩放+翻转+裁剪)操作,时间上面能够明显降下来
    从原本的 total time: 19.250367879867554 可以降到 total time: 2.8034250736236572 (这是没有转化回nd数据类型,如果直接在transform中转,时间会上升到 11.007773160934448, 建议将其放到每轮迭代出来时按 batch进行转换,能更高效)

上述解释如有错误,欢迎指出。


#4

就是图像增强这块儿耗时特别高,去掉以后0.1s一个batch,


#5

大佬们以后会改用opencv改写image那部分代码吗?


#6

现在版本好像就是内嵌opencv的<不确定 :sweat_smile:>,但是不高效的原因不太清楚(初入mxnet,不太清楚这些实现)。但之后开发者or贡献者肯定会提高这些操作的执行速度的。— 不仅仅只是opencv,用PIL.Image也能明显提高速度。目前可以自己写下这部分的代码来跑实验:grinning:

非常非常抱歉,我看了下gluon高级,上述的总结其实是存在问题的。我们可以直接“显示”地“避免延迟”(给nd对象加上wait_to_read())能够解决速度慢的问题。

我建议在图像增强这一章节里面的代码可以稍微修改下,指出这个问题。


#7

但是,这种还是满足不了GPU的速度,我先前就是由于GPU处理太快了,然后数据加载太慢,导致GPU的占用一直在0~90%之间波动,后来发现主要由于transform这块儿耗时最高,去掉transform以后可以0.1S加载一个batch(50张图),加上以后就需要1S,不知道你有没有好的解决办法


#8

用第三方的库进行图像处理呢?
顺便问个问题,我要对图片进行灰度处理(输出通道变成1该怎么办?)
看了下api里面的createAugmenter()并不支持这种操作诶


#9

你可以将输入先转为numpy类型,之后采用类似opencv的库,比如cv.resize()等等就可以进行处理。


#10

感觉只能这样了
(想要在一个库中实现所有的功能果然是痴心妄想)


#11

可以用mx.io.ImageRecordIter试试,换成这个函数以后数据加载的速度大幅度提升,因为所有操作直接用着c++的底层引擎


#12

好的~谢谢 :wink: , 恰好我看到官方给出的0.12版本中提到了对mx.io.ImageRecordIter进一步加速的说明。


#13

在“图像增广”部分, transform函数的示例代码(如下)有两处有问题:

  1. 第8和9行应该修改为
 data = apply_aug_list(data, augs)
  1. 第10行应该修改为
data = data.transpose((2,0,1) / 255

(原始示例代码如下)

1  def get_transform(augs):
2     def transform(data, label):
3         # data: sample x height x width x channel
4         # label: sample
5         data = data.astype('float32')
6         if augs is not None:
7             # apply to each sample one-by-one and then stack
8            data = nd.stack(*[
9                 apply_aug_list(d, augs) for d in data])
10        data = nd.transpose(data, (0,3,1,2))
11        return data, label.astype('float32')
12    return transform

#14

有个问题不明白,,图片增强后的结果是用变换后的图像替换原图像,还是将替换后的图像加入到训练集


#15

感觉像是把原图用曾广图替换了。有没有不用替换的,一起放进去训练的?


#16

这个数据增强是,用变换后的图片替换原图像


#17

怎么将都到NDArray形式的RGB图像进行旋转?


#18

@zhreshold 似乎现在image下没有rotate?


#19

确实没有,rotation可以用affine transform来模拟,也就是

cos(q)  −sin(q) 0
sin(q)  cos(q)  0
 0         0    1

的affine matrix。
其实可以考虑在image里面包opencv的函数,这样方便一点


#20

我现在是用的PIL的rotate