點擊上方「MLNLP」,選擇「星標」公眾號
重磅乾貨,第一時間送達
編輯:憶臻
https://www.zhihu.com/question/35339639
本文僅作為學術分享,如果侵權,會刪文處理
機器學習算法與自然語言處理報導
使用深度學習(CNN)算法進行圖像識別工作時,有哪些data augmentation 的奇技淫巧?作者:景略集智
https://www.zhihu.com/question/35339639/answer/406894510
估計很多搞深度學習的都遇到過這個情況:有不錯的想法,可以用深度學習模型實現。於是興致勃勃的上網找相關數據集,結果卻發現只有很少一部分圖像。
你想起來,很多常見的數據集都有成千上萬張井然有序的圖像,你還想起來不少大拿說過足夠的數據對於模型性能至關重要。望著眼前寒酸的數據,失望之餘不禁長嘆「只有這麼點數據,我這麼牛的模型能成嗎?」
答案是,能成!但是在我們揭曉為何能成之前,需要簡單回應一些基本的問題。
為什麼老說需要大量的數據?
常用的神經網絡的參數數量我們訓練機器學習模型,其實就是我們調參的過程,這樣模型能將具體的輸入(比如說圖像)映射為輸出(標籤)。我們的優化目標就是盡力讓模型的損失調低,當然需要往正確的方向調整模型參數才行。
成功的神經網絡擁有數以百萬計的參數!
自然而然,如果你有很多參數,就需要為模型展示大量的樣本,才能讓模型獲得良好的性能。同時,根據模型所執行任務的複雜程度,你也需要合適數量的模型參數。
如果數據不夠,我該怎麼獲得更多數據?
其實你不用在四處尋找新圖像添加到你的圖像數據集裡,為何?因為神經網絡剛開始時沒那麼聰明。比如,對於一個訓練不足的神經網絡來說,它會覺得下面這 3 個網球是 3 張不同的唯一圖像:
完全一樣的網球,神經網絡的解釋卻不同那麼,要想獲得更多數據,我們只需對現有數據集做些微小的調整。所謂微小的調整,包括翻轉、旋轉、平移等等。不管怎麼說,神經網絡會把這些略微調整後的圖像當做不同的圖像。
運動中的數據增強卷積神經網絡能夠魯棒地將物體分類,即便物體放置在不同的方向上,這也就是所說的不變性屬性。更具體的說,卷積神經網絡能夠穩定應對圖像平移、圖像視角、圖像大小或光線條件的變化。
這實際上就是圖像增強的邏輯前提。在現實工作中,我們可能得到一個數據集,其中的圖像是在部分情況下所拍,但目標應用卻是在多種情況下,比如不同的方向、位置、尺寸和亮度等等。我們會用額外修正過的數據訓練神經網絡,應對這些情況。我們對圖像稍作調整,補充數據不足這個短處的方法,就是圖像增強。
如果我手頭數據很多,圖像增強還有用嗎?
那必須,有用。圖像增強能增加你的數據集中的數據數量。這和神經網絡的學習方式有關。我們舉個例子:
假設數據集中的兩個類。左邊的代表 A 品牌(福特),右邊的代表 B 品牌(雪佛蘭)
想像你有個數據集,包含兩個品牌的汽車圖像,如上所示。我們假設所有 A 品牌的汽車都如上面左圖所示擺放(也就是所有汽車朝向左側),所有B品牌的汽車都如右圖所示擺放(朝向右側)。現在,我們把這個數據集輸入到搭建的神經網絡中,希望經過訓練後,模型能有不錯的性能。
假設完成了訓練,我們輸入上面一張圖像,比如 A 品牌的汽車,但模型卻認為是 B 品牌的汽車。這時我們估計就鬱悶了,明明模型在數據集上達到了 95% 的準確度啊?這種情況不是誇張,經常出現。
福特汽車(A品牌),但面向右側為什麼會出現這種情況?因為這就是機器學習算法的工作原理。算法會找到能將一個類和另一個類區分的最明顯的特徵。在這個例子中,特徵就是所有 A 品牌汽車朝向左側、所有 B 品牌汽車朝向右側。
你的神經網絡的質量和輸入的數據質量一樣。
我們該怎樣防止這種情況?我們必須減少數據集中不相關的特徵。對於我們上面的汽車分類模型來說,一個簡單的解決方法就是增加兩種品牌汽車的圖像數量,且面朝其它方向。如果你能將數據集中的圖像水平翻轉,讓它們朝向另一邊,那就更好了。用這樣處理後的新數據集訓練神經網絡,模型就會得到理想的性能了。
著手開始
在我們深入了解多種不同的圖像增強方法前,我們必須考慮幾個問題。
在我們的機器學習流水線的哪個部分增強數據?
答案似乎很明顯:在將數據輸入到模型之前增強數據啊。沒錯,但是這裡有兩個選擇。一個就是事先進行所有必需的圖像平移工作,基本上就是增加數據集大小;另一個選擇是將圖像輸入機器學習模型之前,以小批量進行圖像平移。
第一種選擇就是常說的離線增強。當數據集相對較小時,優先選擇這種方法,因為你最終會通過一個與執行的轉換數量相等的因子來增加數據集的大小。比如這裡的汽車圖像數據集,將所有圖像翻轉後,就能將數據集大小增加一倍。
第二種選擇就是常說的在線增強。這種方法比較適合數據集較大時的情況,因為我們很難應付數據集爆炸性變大。相反,我們可以小批量平移輸入到模型中的圖像。有些機器學習框架支持在線增強,使用GPU可以加快增強速度。
常見的圖像增強方法
在這一部分,我們會展示一些基本卻很強大的圖像增強方法,也是目前普遍應用的方法。
在介紹這些方法前,為了簡單起見,我們先做個假設。就是我們不需要考慮圖像的邊界之外的東西。對於超出圖像邊界之外的情況,我們需要插補一些信息。在介紹完圖像增強類型後,會詳細解釋這種情況。
對於下面的每種方法,我們也都定義了一個增強因子(augmentation factor),用以增強數據集(即數據增強因子)。
翻轉你可以水平或垂直翻轉圖像。有些框架沒有提供垂直翻轉的函數,不過可以變通一下:將圖像旋轉180度,然後水平翻轉。下面是一些翻轉後的圖像示例。
從左至右分別是:原始圖像,水平翻轉圖像,垂直翻轉圖像
你可以使用下面的命令行翻轉圖像。數據增強因子=2到4X
# NumPy.'img' = A single image.
flip_1 = np.fliplr(img)
# TensorFlow. 'x' = A placeholder for an image.
shape = [height, width, channels]
x = tf.placeholder(dtype = tf.float32, shape = shape)
flip_2 = tf.image.flip_up_down(x)
flip_3 = tf.image.flip_left_right(x)
flip_4 = tf.image.random_flip_up_down(x)
flip_5 = tf.image.random_flip_left_right(x)
關於旋轉操作,需要注意的一點是在旋轉後,可能無法保存圖像的維度。如果你的圖像是正方形,旋轉 90 度後可以保存圖像尺寸。如果圖像為矩形,旋轉 180 度後則可以保存圖像尺寸。以更小的角度旋轉圖像將改變圖像的最終尺寸。在下個部分,會講解如何處理這種問題。下面是正方形圖像旋轉 90 度的示例:
當我們從左到右移動時,圖像相對於前一個圖像順時針旋轉 90 度
用如下任一命令可以將圖像旋轉。數據增強因子 =2 到 4X
# Placeholders: 'x' = A single image, 'y' = A batch of images
# 'k' denotes the number of 90 degree anticlockwise rotations
shape = [height, width, channels]
x = tf.placeholder(dtype = tf.float32, shape = shape)
rot_90 = tf.image.rot90(img, k=1)
rot_180 = tf.image.rot90(img, k=2)
# To rotate in any angle. In the example below, 'angles' is in radians
shape = [batch, height, width, 3]
y = tf.placeholder(dtype = tf.float32, shape = shape)
rot_tf_180 = tf.contrib.image.rotate(y, angles=3.1415)
# Scikit-Image. 'angle' = Degrees. 'img' = Input Image
# For details about 'mode', checkout the interpolation section below.
rot = skimage.transform.rotate(img, angle=45, mode='reflect')
可以將圖像放大或縮小。在放大時,放大後的圖像尺寸要大於原始圖像。大部分圖像框架會根據原始圖像的尺寸對新圖像進行裁切。在下個部分我們會處理圖像縮小的問題,因為圖像縮小會縮小圖像的尺寸,這就迫使我們處理圖像邊界之外的信息。下面是圖像縮放的示例:
從左至右分別為:原始圖像,圖像向外縮放10%,圖像向外縮放20%
通過下面的命令執行圖像縮放。數據增強因子=任意。
# Scikit Image. 'img' = Input Image, 'scale' = Scale factor
# For details about 'mode', checkout the interpolation section below.
scale_out = skimage.transform.rescale(img, scale=2.0, mode='constant')
scale_in = skimage.transform.rescale(img, scale=0.5, mode='constant')
# Don't forget to crop the images back to the original size (for
# scale_out)
和縮放不同,裁切是從原始圖像中隨機採樣一部分。然後我們將採樣區域大小調整為原始圖像大小。這種方法就是常說的隨機裁剪。下面是隨機裁剪的圖像示例。如果你仔細看,就會注意到裁剪和縮放之間的區別。
從左至右:原始圖像,從左上角裁剪出一個正方形部分,然後從右下角裁剪出一個正方形部分。剪裁的部分被調整為原始圖像大小
在 TensorFlow 上,可以用以下任一命令行裁剪圖像。數據增強因子=抽象。
# TensorFlow. 'x' = A placeholder for an image.
original_size = [height, width, channels]
x = tf.placeholder(dtype = tf.float32, shape = original_size)
# Use the following commands to perform random crops
crop_size = [new_height, new_width, channels]
seed = np.random.randint(1234)
x = tf.random_crop(x, size = crop_size, seed = seed)
output = tf.images.resize_images(x, size = original_size)
平移包括將圖像沿著 X 或 Y 方向(或兩個方向)移動圖像。在下面的例子的,我們假定圖像邊界之外為黑色背景,並被恰當平移。這種增強方法非常有用,因為大部分對象幾乎能位於圖像中的任何位置。這能讓卷積神經網絡查看所有信息。
從左至右:原始圖像,圖像翻轉到右側,圖像向上翻轉
可以用如下命令平移圖像。數據增強因子=任意。
# pad_left, pad_right, pad_top, pad_bottom denote the pixel
# displacement. Set one of them to the desired value and rest to 0
shape = [batch, height, width, channels]
x = tf.placeholder(dtype = tf.float32, shape = shape)
# We use two functions to get our desired augmentation
x = tf.image.pad_to_bounding_box(x, pad_top, pad_left, height + pad_bottom + pad_top, width + pad_right + pad_left)
output = tf.image.crop_to_bounding_box(x, pad_bottom, pad_right, height, width)
當神經網絡試圖學習可能並無用處的高頻特徵時(即頻繁發生的無意義模式),常常會發生過擬合。具有零均值特徵的高斯噪聲本質上就是在所有頻率上都有數據點,能有效使得高頻特徵失真,減弱它對模型的影響。這也意味著低頻成分(通常也是我們關心的數據)也會失真,但神經網絡能夠通過學習忽略這部分影響。添加正確數量的噪聲就能增強神經網絡的學習能力。
一個相對弱化的版本就是椒鹽噪聲,它是以隨機的白色及黑色像素點鋪滿整個圖像。給圖像添加椒鹽噪聲的作用和添加高斯噪聲是一樣的,但產生的失真效果相對較弱。
從左至右:原始圖像,增加了高斯噪聲的圖像,添加了椒鹽噪聲的圖像
在 TensorFlow 上,可以用如下命令行為圖像添加高斯噪聲。數據增強因子 =2X
#TensorFlow. 'x' = A placeholder for an image.
shape = [height, width, channels]
x = tf.placeholder(dtype = tf.float32, shape = shape)
# Adding Gaussian noise
noise = tf.random_normal(shape=tf.shape(x), mean=0.0, stddev=1.0,
dtype=tf.float32)
output = tf.add(x, noise)
高級增強方法
在實際中,自然數據會存在於各種各樣的情況中,只用上面的簡單方法無法處理。例如,我們需要識別圖像中的風景。那麼風景包含很多情況:冰天雪地的荒原、綠油油的草原、茂密的森林等等。這聽起來不就是分類任務嗎?這麼判斷大體是對的,但有處例外。我們忽視了圖像的一個關鍵特徵——拍攝季節,而這將會影響模型的性能。
如果我們的神經網絡沒能理解某些風景可能存在於多種情況中(下雪、潮溼、明亮的環境等等),那麼它很可能錯誤地將冰凍的湖泊當做冰天雪地的荒原,或者把沼澤標為溼地。
緩解這種問題的一個方法就是添加更多的圖像,這樣我們就能應對所有的季節變化情況,但這是一項非常艱巨的任務。想像一下,擴展我們的數據增強概念,人為地產生不同季節的效果,會是多麼炫酷的事情!
條件式生成對抗網絡
不用了解背後的繁雜細節,反正條件式生成對抗網絡能夠將一個域的圖像轉換為另一個域的圖像。如果你覺得這聽著太籠統,不是的;它是一種非常強大的神經網絡!下面是條件式生成對抗網絡的將夏季場景轉換為冬季場景的示例。
用 CycleGAN 改變季節上面的方法雖然魯棒,但屬於計算密集型,需要大量的計算力。一種更為廉價的方法是神經風格遷移。它能抓住一張圖像的紋理、氣氛和外觀(即「風格」),然後將它們和另一張圖像的內容相融合。使用這種強大的方法,我們可以製作出和條件式生成對抗網絡相同的效果(實際上這種方法在條件式生成對抗網絡出現之前就提出來了!)。
這種方法的唯一缺點就是生成的圖像看起來太過藝術範兒,沒有真實感。不過,風格遷移仍然有不少優點,比如「深度圖像風格遷移」技術,能夠產生引人矚目的效果,如下所示:
我們這裡不再深入探討這兩種方法,因為它們的內部原理在這裡並不是重點。我們可以使用這兩種現有模型來增強圖像。
插值簡介
如果你想平移一張沒有黑色背景的圖像呢?如果想縮小圖像或以很小的角度旋轉圖像呢?
在我們進行這類變換操作之後,還需要保存原始圖像的尺寸。由於我們的圖像並不包含圖像邊界之外的信息,因此我們需要做些假設。通常,我們會假設圖像邊界之外的空白區域上每個像素點的值為常數 0。所以當你進行這些變換操作時,你會得到一片圖像沒有覆蓋的黑色區域。
從左至右:逆時針旋轉 45 度的圖像,右側翻轉的圖像和向內縮放的圖像
但是這種假設是正確的嗎?很遺憾,在實際工作中,大部分情況下不是。不過圖像預處理和機器學習框架有很多標準方法,藉助它們你可以決定如何填充未知區域。它們定義如下。
常數
最簡單的插值方法就是用一些常數值取填充未知區域。對於自然狀態下的圖像,這種方法可能不湊效,但是可以用於在單色背景下拍攝的圖像。
邊緣
可以將圖像的邊緣值擴展到圖像邊界以外。這種方法也適用於輕微的圖像平移。
反射
可以沿著圖像邊界反射圖像的像素值。這種方法對於包含樹林、山脈等連續或自然背景的圖像,非常有用。
對稱
這種方法和反射類似,除了一點:在反射邊界上拷貝邊緣像素。正常情況下,反射和對稱可以交替使用,但處理非常小的圖像或模式時,差異會非常明顯。
包裹
這種方法是在超出圖像邊界之外的地方重複填充圖像,仿佛在平鋪圖像。由於對很多場景並無意義,不如其它方法那麼常用。
除了這些方法以外,你也可以設計自己的方法處理未知區域,但通常這些方法對於大多數分類問題效果良好。
從左至右:常數,邊緣,反射,對稱和包裹模式
如果我使用以上全部方法,我的機器學習算法會有很好的魯棒性嗎?
如果以正確的方式用它們,那麼答案是 Yes。何為正確方式?有時並非所有的增強技術都適用於數據集。以我們開頭的汽車圖像為例,下面是可以修改圖像的一些方法。
從左至右:原始圖像,水平翻轉,旋轉180度,旋轉90度(順時針)
當然它們還是同一輛車的圖像,但是你的目標應用可能永遠不會看到在這些方向的汽車。
例如,你只是想隨機分類路上的汽車,那麼只有第二張圖像對數據集有意義。但是如果你有一家處理交通事故的保險公司,你也想識別車禍中被撞翻、撞壞的車,那麼第三張圖像對於數據集最有意義。而對於上面這兩種情況,第四張圖像都沒有意義。
重點是,在使用圖像增強方法的時候,我們必須保證不能增加無關的數據。
這一切值得嗎?
你可能期待看到些結果,好給你點動力做圖像增強。我們就舉個例子,證明圖像增強確實有用,你自己也可以去驗證一下。
我們首先創建兩個神經網絡,將數據分類到四個類別中的一個:貓,獅子,老虎和豹子。這兩個神經網絡一個使用圖像增強,一個不使用。從這裡(https://drive.google.com/drive/folders/1GpIpbqBQ_ak1Z_4yAj7t6YRqDDyyBbAq?usp=sharing)下載數據集。
如果你查看了數據集,會注意到每個類別只有50張圖像用於訓練和測試模型。前面說過,其中一個分類模型不能使用圖像增強技術,為了公平起見,我們使用遷移學習方法讓模型應對數據稀缺的問題。
對於沒有使用圖像增強的模型,我們使用 VGG19 網絡。該模型的 TensorFlow 實現地址:https://github.com/thatbrguy/VGG19-Transfer-Learn 。如果你將代碼庫克隆到本地,就能從這裡(https://drive.google.com/drive/folders/1GpIpbqBQ_ak1Z_4yAj7t6YRqDDyyBbAq?usp=sharing)得到數據集,從這裡(https://mega.nz/#!xZ8glS6J!MAnE91ND_WyfZ_8mvkuSa2YcA7q-1ehfSm-Q1fxOvvs)得到vgg19.py文件。現在你就可以運行模型驗證性能。
不過,為數據增強再另外編寫代碼確實有些麻煩。所以可以藉助一些平臺,比如 Nanonets,其內置了風格遷移個圖像增強技術。只需將數據上傳至平臺,等待他們的伺服器訓練即可(通常是30分鐘)。
完成訓練後,你可以對其 API 請求調用來計算測試的準確性。你可以在 GitHub repo 中找到對應的示例代碼片段 (不要忘記在代碼段中插入模型的 ID)。
結果如下:
VGG19(沒有使用圖像增強):最高測試準確率為76%
Nanonets(使用了圖像增強):最高測試準確率為94%
結果差距還是很明顯的,對吧?事實上,大多數模型如果數據更多,性能會更好。為了具體的證明,可仔細看看下面這張表。它顯示了 Cifar 10 (C10) 和 Cifar 100 (C100) 數據集上常用的神經網絡的錯誤率。C10+ 和 C100+ 列是進行數據增強後的錯誤率。
結語
本文我們分享了幾種圖像增強的方法,以及為何需要圖像增強。普通的圖像增強方法包括:翻轉、旋轉、平移、裁剪、縮放和高斯噪聲;高級版圖像增強方法還有常數填充、反射、邊緣延伸、對稱和包裹模式等。希望這些圖像增強方法對你在應用深度學習中緩解圖像不足的問題時有所幫助。
推薦閱讀:
就最近看的paper談談預訓練語言模型發展
如何評價Word2Vec作者提出的fastText算法?深度學習是否在文本分類等簡單任務上沒有優勢?
從Word2Vec到Bert,聊聊詞向量的前世今生(一)