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可以對一個文件夾下所有子文件夾進行標記分類,比如:

trainn├─catsn│ ├─cat.0.jpgn│ ├─cat.1.jpgn│ ├─cat.2.jpgn│ └─.......n└─dogsn ├─dog.0.jpgn ├─dog.1.jpgn ├─dog.2.jpgn └─.......n

只需要制定train文件夾,就可以直接自動將cats文件夾下的圖像記錄為標籤0dogs文件夾下的圖像記錄為標籤1,以此類推 。

我們來試一下:

from mxnet import gluonnbatch_size = 36ndataset = gluon.data.vision.ImageFolderDataset(../data/train/)ntrain_sets = gluon.data.DataLoader(dataset, batch_size, shuffle=True)n

這個時候的train_sets是一個iteration,可以直接用for函數調用,我們來試一下:

for imgs, labels in train_sets:n print(labels)n breakn"""nout:n[ 0. 0. 1. 1. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 0. 0. 1. 1.n 1. 1. 0. 0. 1. 1. 1. 0. 1. 1. 0. 1. 1. 1. 0. 1. 1. 0.]n<NDArray 36 @cpu(0)>n"""n

列印一下圖像:

_, figs = plt.subplots(6, 6, figsize=(6,6))nfor i in range(6):n for j in range(6):n x = nd.transpose(imgs[i*6+j], (1,2,0))n figs[i][j].imshow(x.asnumpy())n figs[i][j].axes.get_xaxis().set_visible(False)n figs[i][j].axes.get_yaxis().set_visible(False)n

我另一方面注意到,這個iteration 實際上是一個多線程載入過程,我們查看CPU使用情況:

真·黑科技~~~

我們現在所得出來的imgs就可以直接用於Gluon上的輸入數據,這一部分會在之後的內容中講到。

數據增強

圖片增強通過一系列的隨機變化生成大量「新」的樣本,從而減低過擬合的可能。現在在深度卷積神經網路訓練中,圖片增強是必不可少的一部分。

這裡我們直接使用使用李沐大佬的教程例子作為講解。我們還是讀取一張圖片作為輸入:

from mxnet import imagenimg = image.imdecode(open(../data/train/cats/cat.123.jpg,rb).read())nimshow(img.asnumpy())n

接下來我們定義一個輔助函數,給定輸入圖片img的增強方法aug,它會運行多次並畫出結果。

def apply(img, aug, n=3):n _, figs = plt.subplots(n, n, figsize=(8,8))n for i in range(n):n for j in range(n):n # 轉成float,一是因為aug需要float類型數據來方便做變化。n # 二是這裡會有一次copy操作,因為有些aug直接通過改寫輸入n #(而不是新建輸出)獲取性能的提升n x = img.astype(float32)n # 有些aug不保證輸入是合法值,所以做一次clipn y = aug(x).clip(0,254)n # 顯示浮點圖片時imshow要求輸入在[0,1]之間n figs[i][j].imshow(y.asnumpy()/255.0)n figs[i][j].axes.get_xaxis().set_visible(False)n figs[i][j].axes.get_yaxis().set_visible(False)n

1. 水平方向翻轉:

# 以.5的概率做翻轉nfrom mxnet import imagenaug = image.HorizontalFlipAug(.5)napply(img, aug)n

2. 隨機裁剪一塊:

# 隨機裁剪一個塊 150 x 150 的區域naug = image.RandomCropAug([150, 150])napply(img, aug)n

3. 隨機色調:

# 隨機色調變化naug = image.HueJitterAug(.5)napply(img, aug)n

還有許許多多可以添加的各類函數,都在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 ndnimport randomndef random_mask(ndimg, size, flag=0):n w, h = ndimg.shape[:2] # 獲取圖像的尺寸n w_ = random.randint(0, w-size) #確定起始坐標的位置範圍n h_ = random.randint(0, h-size)n if flag==0:n # 隨機遮蓋的形狀是一個正方形n ndimg[w_:w_+size, h_:h_+size, :] = nd.zeros((size, size, 3)) # 用黑色來遮蓋n return ndimgn elif flag==1:n # 隨機遮蓋的形狀是一個長方形n w_size = random.randint(0, size-1)n h_size = random.randint(0, size-1)n # 用隨機雜訊來遮蓋n ndimg[w_:w_+w_size, h_:h_+h_size, :] = mx.ndarray.random_uniform(low=0, high=255, shape=(w_size, h_size, 3))n return ndimgn

載入圖片:

from mxnet import imagenimg = image.imdecode(open(../data/train/cats/cat.123.jpg,rb).read())nimg = random_mask(img.astype(float32)/255., 150, flag=0)nimshow(img.asnumpy())n

我們換一種,換成長方形和隨機雜訊填充(使上面代碼中flag=1):

我們批量處理,寫一個完整數據增強的transform的函數 :

import randomndef apply_aug_list(img, augs):n for f in augs:n img = f(img)n return imgntrain_augs = [n image.ResizeAug(250), # 將短邊resize至250n image.HorizontalFlipAug(.5), # 0.5概率的水平翻轉變換n image.HueJitterAug(.6), # -0.6~0.6的隨機色調n image.BrightnessJitterAug(.5), # -0.5~0.5的隨機亮度n image.RandomCropAug((230,230)), # 隨機裁剪成(230,230)n]ndef get_transform(augs):n # 獲得transform函數n def transform(data, label):n data = data.astype(float32) # 部分數據增強接受`float32`n if augs is not None:n data = apply_aug_list(data, augs)n data = random_mask(data, 150, flag=1) # 執行random_mask, 隨機遮蓋n data = nd.transpose(data, (2,0,1))/255 # 改變維度順序為(c, w, h)n label = np.array(label) # 調整下label格式n return data, label.astype(float32)n return transformn

這樣我就可以將這個變換加入到數據載入中了:

batch_size = 36ndataset = gluon.data.vision.ImageFolderDataset(../data/train/, transform=get_transform(train_augs))ntrain_sets = gluon.data.DataLoader(dataset, batch_size, shuffle=True)nnfor imgs, labels in train_sets:n print(labels)n breakn

輸出標籤:

[ 1. 0. 0. 0. 0. 1. 1. 1. 0. 1. 1. 1. 0. 1. 1. 1. 1. 0.n 0. 0. 0. 0. 0. 1. 0. 0. 0. 1. 0. 1. 0. 0. 0. 0. 0. 1.]n<NDArray 36 @cpu(0)>n

我們看一下效果

_, figs = plt.subplots(6, 6, figsize=(6,6))nfor i in range(6):n for j in range(6):n x = nd.transpose(imgs[i*6+j], (1,2,0))n figs[i][j].imshow(x.asnumpy())n figs[i][j].axes.get_xaxis().set_visible(False)n figs[i][j].axes.get_yaxis().set_visible(False)n

# 看看第二張狗nsample = nd.transpose(imgs[1], (1,2,0)) nimshow(sample.asnumpy())nprint(label[1])n

[ 1.]n<NDArray 1 @cpu(0)>n

成功實現了[1708.04896] Random Erasing Data Augmentation中提到了Random Erasing,同時我也做了對比試驗,這個數據增強的辦法的確可以提升模型的泛化性。

這僅僅是個例子,MXNet和Gluon還可以做更多的事情,在講到後面進行模型訓練的過程時候,我會繼續給大家介紹一些,能夠讓大家覺得這個深度學習工具非常好用的黑科技。

敬請期待~~~


推薦閱讀:

mxnet中如何使用makeloss?
如何看待MXNet在CVPR2017上公布的gluon介面?

TAG:深度学习DeepLearning | mxnet | TensorFlow |