但很可惜的是,訓練資料和秘笈還有一個特點很相似,那就是可遇而不可求!也就是說很難獲取,除了那些公共資料集之外,如果使用者想基於自己的業務場景準備資料的話,不僅資料的生產和標註過程會比較複雜,而且一般需要的數量規模也會非常龐大,因為只有充足的資料,才能確保模型訓練的效果,這導致資料集的製作成本往往非常高。這個情況在計算機視覺領域尤甚,因為影像要一張一張拍攝與標註,要是搞個幾十萬圖片,想想都讓人“不寒而慄”!
8大資料增廣方法
首先我們們先來看看以ImageNet影像分類任務為代表的標準資料增廣方法,該方法的操作過程可以分為以下幾個步驟:
- 影像解碼,也就是將影像轉為Numpy格式的資料,簡寫為 ImageDecode。
- 影像隨機裁剪,隨機將影像的長寬均裁剪為 224 大小,簡寫為 RandCrop。
- 水平方向隨機翻轉,簡寫為 RandFlip。
- 影像資料的歸一化,簡寫為 Normalize。
- 影像資料的重排。影像的資料格式為[H, W, C](即高度、寬度和通道數),而神經網路使用的訓練資料的格式為[C, H, W],因此需要對影像資料重新排列,例如[224, 224, 3] 變為 [3, 224, 224],簡寫為 Transpose。
- 多幅影像資料組成 batch 資料,如 BatchSize 個 [3, 224, 224] 的影像資料拼組成 [batch-size, 3, 224, 224],簡寫為 Batch。
相比於上述標準的影像增廣方法,研究者也提出了很多改進的影像增廣策略,這些策略均是在標準增廣方法的不同階段插入一定的操作,基於這些策略操作所處的不同階段,大概分為三類:
- 影像變換類:對 RandCrop 後的 224 的影像進行一些變換,包括AutoAugment和RandAugment。
- 影像裁剪類:對Transpose 後的 224 的影像進行一些裁剪,包括CutOut、RandErasing、HideAndSeek和GridMask。
- 影像混疊:對 Batch 後的資料進行混合或疊加,包括Mixup和Cutmix。
PaddleClas中整合了上述所有的資料增廣策略,每種資料增廣策略的參考論文與參考開原始碼均在下面的介紹中列出。下文將介紹這些策略的原理與使用方法,並以下圖為例,對變換後的效果進行視覺化。
影像變換類
透過組合一些影像增廣的子策略對影像進行修改和跳轉,這些子策略包括亮度變換、對比度增強、銳化等。基於策略組合的規則不同,可以劃分為AutoAugment和RandAugment兩種方式。
01
AutoAugment
論文地址:
https://arxiv.org/abs/1805.09501v1
不同於常規的人工設計影像增廣方式,AutoAugment是在一系列影像增廣子策略的搜尋空間中透過搜尋演算法找到並組合成適合特定資料集的影像增廣方案。針對ImageNet資料集,最終搜尋出來的資料增廣方案包含 25 個子策略組合,每個子策略中都包含兩種變換,針對每幅影像都隨機的挑選一個子策略組合,然後以一定的機率來決定是否執行子策略中的每種變換。
PaddleClas中AutoAugment的使用方法如下所示。
from ppcls.data.imaug import DecodeImage from ppcls.data.imaug import ResizeImage from ppcls.data.imaug import ImageNetPolicy from ppcls.data.imaug import transform size = 224 # 影像解碼 decode_op = DecodeImage() # 影像隨機裁剪 resize_op = ResizeImage(size=(size, size)) # 使用AutoAugment影像增廣方法 autoaugment_op = ImageNetPolicy() ops = [decode_op, resize_op, autoaugment_op] # 影像路徑 imgs_dir = “/imgdir/xxx.jpg” fnames = os.listdir(imgs_dir) for f in fnames: data = open(os.path.join(imgs_dir, f)).read() img = transform(data, ops)
變換結果如下圖所示。
02
RandAugment
論文地址:
https://arxiv.org/pdf/1909.13719.pdf
AutoAugment 的搜尋方法比較暴力,直接在資料集上搜尋針對該資料集的最優策略,計算量會很大。在 RandAugment對應的論文中作者發現,針對越大的模型,越大的資料集,使用 AutoAugment 方式搜尋到的增廣方式產生的收益也就越小;而且這種搜尋出的最優策略是針對指定資料集的,遷移能力較差,並不太適合遷移到其他資料集上。
在 RandAugment 中,作者提出了一種隨機增廣的方式,不再像 AutoAugment 中那樣使用特定的機率確定是否使用某種子策略,而是所有的子策略都會以同樣的機率被選擇到,論文中的實驗也表明這種資料增廣方式即使在大模型的訓練中也具有很好的效果。
PaddleClas中RandAugment的使用方法如下所示。
from ppcls.data.imaug import DecodeImage from ppcls.data.imaug import ResizeImage from ppcls.data.imaug import RandAugment from ppcls.data.imaug import transform size = 224 # 影像解碼 decode_op = DecodeImage() # 影像隨機裁剪 resize_op = ResizeImage(size=(size, size)) # 使用RandAugment影像增廣方法 randaugment_op = RandAugment() ops = [decode_op, resize_op, randaugment_op] # 影像路徑 imgs_dir = “/imgdir/xxx.jpg” fnames = os.listdir(imgs_dir) for f in fnames: data = open(os.path.join(imgs_dir, f)).read() img = transform(data, ops)
變換結果如下圖所示。
影像裁剪類
影像裁剪類主要是對Transpose 後的 224 的影像進行一些裁剪,即裁剪掉部分影像,或者也可以理解為對部分影像做遮蓋,共有CutOut、RandErasing、HideAndSeek和GridMask四種方法。
03
Cutout
論文地址:
https://arxiv.org/abs/1708.04552
Cutout 可以理解為 Dropout 的一種擴充套件操作,不同的是 Dropout 是對影像經過網路後生成的特徵進行遮擋,而 Cutout 是直接對輸入的影像進行遮擋,相對於Dropout對噪聲的魯棒性更好。作者在論文中也進行了說明,這樣做法有以下兩點優勢:
- 透過 Cutout 可以模擬真實場景中主體被部分遮擋時的分類場景。
- 可以促進模型充分利用影像中更多的內容來進行分類,防止網路只關注顯著性的影像區域,從而發生過擬合。
PaddleClas中Cutout的使用方法如下所示。
from ppcls.data.imaug import DecodeImage from ppcls.data.imaug import ResizeImage from ppcls.data.imaug import Cutout from ppcls.data.imaug import transform size = 224 # 影像解碼 decode_op = DecodeImage() # 影像隨機裁剪 resize_op = ResizeImage(size=(size, size)) # 使用Cutout影像增廣方法 cutout_op = Cutout(n_holes=1, length=112) ops = [decode_op, resize_op, cutout_op] # 影像路徑 imgs_dir = “/imgdir/xxx.jpg” fnames = os.listdir(imgs_dir) for f in fnames: data = open(os.path.join(imgs_dir, f)).read() img = transform(data, ops)
裁剪結果如下圖所示:
04
RandomErasing
論文地址:
https://arxiv.org/pdf/1708.04896.pdf
RandomErasing 與 Cutout 方法類似,同樣是為了解決訓練出的模型在有遮擋資料上泛化能力較差的問題,作者在論文中也指出,隨機裁剪的方式與隨機水平翻轉具有一定的互補性。作者也在行人再識別(REID)上驗證了該方法的有效性。與Cutout不同的是,在RandomErasing中,圖片以一定的機率接受該種預處理方法,生成掩碼的尺寸大小與長寬比也是根據預設的超引數隨機生成。
PaddleClas中RandomErasing的使用方法如下所示。
from ppcls.data.imaug import DecodeImage from ppcls.data.imaug import ResizeImage from ppcls.data.imaug import ToCHWImage from ppcls.data.imaug import RandomErasing from ppcls.data.imaug import transform size = 224 # 影像解碼 decode_op = DecodeImage() # 影像隨機裁剪 resize_op = ResizeImage(size=(size, size)) # 使用RandomErasing影像增廣方法 randomerasing_op = RandomErasing() ops = [decode_op, resize_op, tochw_op, randomerasing_op] # 影像路徑 imgs_dir = “/imgdir/xxx.jpg” fnames = os.listdir(imgs_dir) for f in fnames: data = open(os.path.join(imgs_dir, f)).read() img = transform(data, ops) img = img.transpose((1, 2, 0))
裁剪結果如下圖所示。
05
HideAndSeek
論文地址:
https://arxiv.org/pdf/1811.02545.pdf
HideAndSeek方法將影像分為若干大小相同的區域塊(patch),對於每塊區域,都以一定的機率生成掩碼,如下圖所示,可能是完全遮擋、完全不遮擋或者遮擋部分。
PaddleClas中HideAndSeek的使用方法如下所示:
from ppcls.data.imaug import DecodeImage from ppcls.data.imaug import ResizeImage from ppcls.data.imaug import ToCHWImage from ppcls.data.imaug import HideAndSeek from ppcls.data.imaug import transform size = 224 # 影像解碼 decode_op = DecodeImage() # 影像隨機裁剪 resize_op = ResizeImage(size=(size, size)) # 使用HideAndSeek影像增廣方法 hide_and_seek_op = HideAndSeek() ops = [decode_op, resize_op, tochw_op, hide_and_seek_op] # 影像路徑 imgs_dir = “/imgdir/xxx.jpg” fnames = os.listdir(imgs_dir) for f in fnames: data = open(os.path.join(imgs_dir, f)).read() img = transform(data, ops) img = img.transpose((1, 2, 0))
裁剪結果如下圖所示。
06
GridMask
論文地址:
https://arxiv.org/abs/2001.04086
作者在論文中指出,之前的影像裁剪方法存在兩個問題,如下圖所示:
- 過度刪除區域可能造成目標主體大部分甚至全部被刪除,或者導致上下文資訊的丟失,導致增廣後的資料成為噪聲資料;
- 保留過多的區域,對目標主體及上下文基本產生不了什麼影響,失去增廣的意義。
因此如何避免過度刪除或過度保留成為需要解決的核心問題。GridMask是透過生成一個與原圖解析度相同的掩碼,並將掩碼進行隨機翻轉,與原圖相乘,從而得到增廣後的影像,透過超引數控制生成的掩碼網格的大小。
在訓練過程中,有兩種以下使用方法:
- 設定一個機率p,從訓練開始就對圖片以機率p使用GridMask進行增廣。
- 一開始設定增廣機率為0,隨著迭代輪數增加,對訓練圖片進行GridMask增廣的機率逐漸增大,最後變為p。
論文中表示,經過驗證後,上述第二種方法的訓練效果更好一些。
PaddleClas中GridMask的使用方法如下所示。
from data.imaug import DecodeImage from data.imaug import ResizeImage from data.imaug import ToCHWImage from data.imaug import GridMask from data.imaug import transform size = 224 # 影像解碼 decode_op = DecodeImage() # 影像隨機裁剪 resize_op = ResizeImage(size=(size, size)) # 影像資料的重排 tochw_op = ToCHWImage() # 使用GridMask影像增廣方法 gridmask_op = GridMask(d1=96, d2=224, rotate=1, ratio=0.6, mode=1, prob=0.8) ops = [decode_op, resize_op, tochw_op, gridmask_op] # 影像路徑 imgs_dir = “/imgdir/xxx.jpg” fnames = os.listdir(imgs_dir) for f in fnames: data = open(os.path.join(imgs_dir, f)).read() img = transform(data, ops) img = img.transpose((1, 2, 0))
結果如下圖所示:
影像混疊
前文所述的影像變換與影像裁剪都是針對單幅影像進行的操作,而影像混疊是對兩幅影像進行融合,生成一幅影像,Mixup和Cutmix兩種方法的主要區別為混疊的方式不太一樣。
07
Mixup
論文地址:
https://arxiv.org/pdf/1710.09412.pdf
Mixup是最先提出的影像混疊增廣方案,其原理就是直接對兩幅圖的畫素以一個隨機的比例進行相加,不僅簡單,而且方便實現,在影像分類和目標檢測領域上都取得了不錯的效果。為了便於實現,通常只對一個 batch 內的資料進行混疊,在Cutmix中也是如此。
如下是imaug中的實現,需要指出的是,下述實現會出現對同一幅進行相加的情況,也就是最終得到的圖和原圖一樣,隨著 batch-size 的增加這種情況出現的機率也會逐漸減小。
PaddleClas中Mixup的使用方法如下所示。
from ppcls.data.imaug import DecodeImage from ppcls.data.imaug import ResizeImage from ppcls.data.imaug import ToCHWImage from ppcls.data.imaug import transform from ppcls.data.imaug import MixupOperator size = 224 # 影像解碼 decode_op = DecodeImage() # 影像隨機裁剪 resize_op = ResizeImage(size=(size, size)) # 影像資料的重排 tochw_op = ToCHWImage() # 使用HideAndSeek影像增廣方法 hide_and_seek_op = HideAndSeek() # 使用Mixup影像增廣方法 mixup_op = MixupOperator() ops = [decode_op, resize_op, tochw_op] imgs_dir = “/imgdir/xxx.jpg” #影像路徑 batch = [] fnames = os.listdir(imgs_dir) for idx, f in enumerate(fnames): data = open(os.path.join(imgs_dir, f)).read() img = transform(data, ops) batch.append( (img, idx) ) # fake label new_batch = mixup_op(batch)
混疊結果如下圖所示。
08
Cutmix
論文地址:
https://arxiv.org/pdf/1905.04899v2.pdf
與 Mixup 直接對兩幅圖進行相加不一樣,Cutmix 是從另一幅圖中隨機裁剪出一個 ROI(region of interest, 感興趣區域),然後覆蓋當前影像中對應的區域,程式碼實現如下所示:
from ppcls.data.imaug import DecodeImage from ppcls.data.imaug import ResizeImage from ppcls.data.imaug import ToCHWImage from ppcls.data.imaug import transform from ppcls.data.imaug import CutmixOperator size = 224 # 影像解碼 decode_op = DecodeImage() # 影像隨機裁剪 resize_op = ResizeImage(size=(size, size)) # 影像資料的重排 tochw_op = ToCHWImage() # 使用HideAndSeek影像增廣方法 hide_and_seek_op = HideAndSeek() # 使用Cutmix影像增廣方法 cutmix_op = CutmixOperator() ops = [decode_op, resize_op, tochw_op] imgs_dir = “/imgdir/xxx.jpg” #影像路徑 batch = [] fnames = os.listdir(imgs_dir) for idx, f in enumerate(fnames): data = open(os.path.join(imgs_dir, f)).read() img = transform(data, ops) batch.append( (img, idx) ) # fake label new_batch = cutmix_op(batch)
混疊結果如下圖所示:
實驗
經過實驗驗證,在ImageNet1k資料集上基於PaddleClas使用不同資料增廣方式的分類精度如下所示,可見透過資料增廣方式可以有效提升模型的準確率。
注意:
在這裡的實驗中,為了便於對比,將l2 decay固定設定為1e-4,在實際使用中,更小的l2 decay一般效果會更好。結合資料增廣,將l2 decay由1e-4減小為7e-5均能帶來至少0.3~0.5%的精度提升。
PaddleClas資料增廣避坑
指南以及部分注意事項
最後再為大家介紹幾個PaddleClas資料增廣使用方面的小Trick:
- 在使用影像混疊類的資料處理時,需要將配置檔案中的use_mix設定為True,另外由於影像混疊時需對label進行混疊,無法計算訓練資料的準確率,所以在訓練過程中沒有列印訓練準確率。
- 在使用資料增廣後,由於訓練資料更難,所以訓練損失函式可能較大,訓練集的準確率相對較低,但其擁有更好的泛化能力,所以驗證集的準確率相對較高。
- 在使用資料增廣後,模型可能會趨於欠擬合狀態,建議可以適當的調小l2_decay的值來獲得更高的驗證集準確率。
- 幾乎每一類影像增廣均含有超引數,PaddleClas在這裡只提供了基於ImageNet-1k的超引數,其他資料集需要使用者自己除錯超引數,當然如果對於超引數的含義不太清楚的話,可以閱讀相關的論文,除錯方法也可以參考訓練技巧的章節(https://github.com/PaddlePaddle/PaddleClas/blob/master/docs/zh_CN/models/Tricks.md)。
如在使用過程中有問題,可加入飛槳官方QQ群進行交流:1108045677。
如果您想詳細瞭解更多飛槳的相關內容,請參閱以下文件。
·飛槳官網地址·
·飛槳開源框架專案地址·
GitHub:
https://github.com/PaddlePaddle/Paddle
Gitee:
https://gitee.com/paddlepaddle/Paddle
·飛槳PaddleClas專案地址·
GitHub:
https://github.com/PaddlePaddle/PaddleClas
Gitee: