機器學習原來這麼有趣!第三章:圖像識別【鳥or飛機】?深度學習與卷積神經網路

作者:Adam Geitgey

原文:medium.com/@ageitgey/ma

譯者:巡洋艦科技——趙95

校對:離線Offline——林沁

轉載請聯繫譯者。

你是不是看煩了各種各樣有關深度學習的報道,卻仍不知其所云?讓我們一起來改變這個現狀吧!

這一次,我們將應用深度學習技術,來寫一個識別圖像中物體的程序。換句話說,我們會解釋 Google 相冊搜索圖片時所用到的「黑科技」:

Google 現在可以讓你在你自己的圖片庫裡面,根據你的描述搜索圖片,即使這些圖片根本沒有被標註任何標籤!這是怎麼做到的??

和第一、二章一樣,這篇指南是為那些對機器學習感興趣,但又不知從哪裡開始的人而寫的。這意味著文中有大量的概括。但是那又如何呢?只要能讓讀者對機器學習更感興趣,這篇文章的任務也就完成了。

用深度學習識別物體

xkcd#1425(出自http://xkcd.com,漢化來自http://xkcd.tw

你可能曾經看過這個著名的 xkcd 的漫畫。

一個 3 歲的小孩可以識別出鳥類的照片,然而最頂尖的計算機科學家們已經花了 50 年時間來研究如何讓電腦識別出不同的物體。漫畫里的靈感就是這麼來的。

在最近的幾年裡,我們終於找到了一種通過深度卷積神經網路(deepconvolutional neural networks)來進行物體識別的好方法。這些個詞聽起來就像是威廉·吉布森科幻小說里的生造詞,但是如果你把這個想法逐步分解,你絕對可以理解它。

讓我們開始吧——讓我們一起來寫一個識別鳥類的程序!

由淺入深

在我們在識別鳥類之前,讓我們先做個更簡單的任務——識別手寫的數字「8」。

在第二章中,我們已經了解到神經網路是如何通過連接無數神經元來解決複雜問題的。我們創造了一個小型神經網路,然後根據各種因素(房屋面積、格局、地段等)來估計房屋的價格:

在第一章中我們提到了,機器學習,就是關於重複使用同樣的泛型演算法,來處理不同的數據,解決不同的問題的一種概念。所以這次,我們稍微修改一下同樣的神經網路,試著用它來識別手寫文字。但是為了更加簡便,我們只會嘗試去識別數字「8」。

機器學習只有在你擁有數據(最好是大量數據)的情況下,才能有效。所以,我們需要有大量的手寫「8」來開始我們的嘗試。幸運的是,恰好有研究人員建立了MNIST 手寫數字資料庫,它能助我們一臂之力。MNIST提供了 60,000 張手寫數字的圖片,每張圖片解析度為 18×18。下列是資料庫中的一些例子:

MNIST資料庫中的數字8

萬物皆「數」

在第二章中我們創造的那個神經網路,只能接受三個數字輸入(卧室數、面積、地段)。但是現在,我們需要用神經網路來處理圖像。所以到底怎樣才能把圖片,而不是數字,輸入到神經網路里呢?

結論其實極其簡單。神經網路會把數字當成輸入,而對於電腦來說,圖片其實恰好就是一連串代表著每個像素顏色的數字:

我們把一幅 18×18 像素的圖片當成一串含有 324 個數字的數組,就可以把它輸入到我們的神經網路裡面了:

為了更好地操控我們的輸入數據,我們把神經網路的輸入節點擴大到 324 個:

請注意,我們的神經網路現在有了兩個輸出(而不僅僅是一個房子的價格)。第一個輸出會預測圖片是「8」的概率,而第二個則輸出不是「8」的概率。概括地說,我們就可以依靠多種不同的輸出,利用神經網路把要識別的物品進行分組。

雖然我們的神經網路要比上次大得多(這次有 324 個輸入,上次只有 3 個!),但是現在的計算機一眨眼的功夫就能夠對這幾百個節點進行運算。當然,你的手機也可以做到。

現在唯一要做的就是用各種「8」和非「8」的圖片來訓練我們的神經網路了。當我們餵給神經網路一個「8」的時候,我們會告訴它是「8」的概率是 100% ,而不是「8」的概率是 0%,反之亦然。

下面是一些訓練數據:

……訓練數據好好吃

在現代的筆記本電腦上,訓練這種神經網路幾分鐘就能完成。完成之後,我們就可以得到一個能比較準確識別字跡「8」的神經網路。歡迎來到(上世紀八十年代末的)圖像識別的世界!

短淺的目光

僅僅把像素輸入到神經網路里,就可以識別圖像,這很棒!機器學習就像魔法一樣!……對不對?

呵呵,當然,不會,這麼,簡單。

首先,好消息是,當我們的數字就在圖片的正中間的時候,我們的識別器幹得還不錯。

壞消息是:

當數字並不是正好在圖片中央的時候,我們的識別器就完全不工作了。一點點的位移我們的識別器就掀桌子不幹了(╯‵□′)╯︵┻━┻。

這是因為我們的網路只學習到了正中央的「8」。它並不知道那些偏離中心的「8」長什麼樣子。它僅僅知道中間是「8」的圖片規律。

在真實世界中,這種識別器好像並沒什麼卵用。真實世界的問題永遠不會如此輕鬆簡單。所以,我們需要知道,當「8」不在圖片正中時,怎麼才能讓我們的神經網路識別它。

暴力方法 #1:滑框搜索

我們已經創造出了一個能夠很好地識別圖片正中間「8」的程序。如果我們乾脆把整個圖片分成一個個小部分,並挨個都識別一遍,直到我們找到「8」,這樣能不能行呢?

這個叫做滑動窗口演算法(sliding window),是暴力演算法之一。在有限的情況下,它能夠識別得很好。但實際上它並不怎麼有效率,你必須在同一張圖片裡面一遍一遍地識別不同大小的物體。實際上,我們可以做得更好!

暴力方法 #2:更多的數據與一個深度神經網

剛剛我們提到,經過訓練之後,我們只能找出在中間的「8」。如果我們用更多的數據來訓練,數據中包括各種不同位置和大小的「8」,會怎樣呢?

我們並不需要收集更多的訓練數據。實際上,我們可以寫一個小腳本來生成各種各樣不同位置「8」的新圖片:

通過組合不同版本的訓練圖片,我們創造出了合成訓練數據Synthetic Training Data)。這是一種非常實用的技巧!

使用這種方法,我們能夠輕易地創造出無限量的訓練數據。

數據越多,這個問題對神經網路來說就越複雜。但是通過擴大神經網路,它就能尋找到更複雜的規律了。

要擴大我們的網路,我們首先要把把節點一層一層的堆積起來:

因為它比傳統的神經網路層數更多,所以我們把它稱作「深度神經網路」(deep neural network)。

這個想法在二十世紀六十年代末就出現了,但直至今日,訓練這樣一個大型神經網路也是一件緩慢到不切實際的事情。然而,一旦我們知道了如何使用 3D 顯卡(最開始是用來進行快速矩陣乘法運算)來替代普通的電腦處理器,使用大型神經網路的想法就立刻變得可行了。實際上,你用來玩守望先鋒的 NVIDIAGeForce GTX1080 這款顯卡,就可以極快速的訓練我們的神經網路。

但是儘管我們能把我們的神經網路擴張得特別大,並使用 3D 顯卡快速訓練它,這依然不能讓我們一次性得到結論。我們需要更智能地將圖片處理後,放入到神經網路里。

仔細想想,如果把圖片最上方和最下方的「8」當成兩個不同的對象來處理,並寫兩個不同的網路來識別它們,這件事實在是說不通。

應該有某種方法,使得我們的神經網路,在沒有額外的訓練數據的基礎上,能夠非常智能的識別出圖片上任何位置的「8」,都是一樣是「8」。幸運的是……這就是!

卷積性的解決辦法

作為人類,你能夠直觀地感知到圖片中存在某種層級(hierarchy)或者是概念結構(conceptual structure)。參考下面的這個圖片:

作為人類,你立刻就能識別出這個圖片的層級:

· 地面是由草和水泥組成的

· 圖中有一個小孩

· 小孩在騎彈簧木馬

· 彈簧木馬在草地上

最重要的是,我們識別出了小孩兒,無論這個小孩所處的環境是怎樣的。當每一次出現不同的環境時,我們人類不需要重新學習小孩兒這個概念。

但是現在,我們的神經網路做不到這些。它認為「8」出現在圖片的不同位置,就是不一樣的東西。它不能理解「物體出現在圖片的不同位置還是同一個物體」這個概念。這意味著在每種可能出現的位置上,它必須重新學習識別各種物體。這弱爆了。

我們需要讓我們的神經網路理解平移不變性(translationinvariance)這個概念——也就是說,無論「8」出現在圖片的哪裡,它都是「8」。

我們會通過一個叫做卷積(Convolution)的方法來達成這個目標。卷積的靈感是由計算機科學和生物學共同激發的。(有一些瘋狂的生物學家,它們用奇怪的針頭去戳貓的大腦,來觀察貓是怎樣處理圖像的 >_<)。

卷積是如何工作的

之前我們提到過,我們可以把一整張圖片當做一串數字輸入到神經網路裡面。不同的是,這次我們會利用「位移物相同」[1]的概念來把這件事做得更智能。

下面就是,它怎樣工作的分步解釋——

第一步:把圖片分解成部分重合的小圖塊

和上述的滑框搜索類似的,讓我們把滑框滑過整個圖片,並存儲下每一個框裡面的小圖塊:

這麼做之後,我們把圖片分解成了 77 塊同樣大小的小圖塊。

第二步:把每個小圖塊輸入到小型神經網路中

之前,我們做的事是把單張圖片輸入到神經網路中,來判斷它是否一為「8」。這一次我們還做同樣的事情,只不過我們輸入的是一個個小圖塊:

重複這個步驟 77 次,每次判斷一張小圖塊。

然而,有一個非常重要的不同:對於每個小圖塊,我們會使用同樣的神經網路權重。換一句話來說,我們平等對待每一個小圖塊。如果哪個小圖塊有任何異常出現,我們就認為這個圖塊是「異常」(interesting)的

第三步:把每一個小圖塊的結果都保存到一個新的數組當中

我們不想並不想打亂小圖塊的順序。所以,我們把每個小圖塊按照圖片上的順序輸入並保存結果,就像這樣:

換一句話來說,我們從一整張圖片開始,最後得到一個稍小一點的數組,裡面存儲著我們圖片中的哪一部分有異常。

第四步:縮減像素採樣

第三步的結果是一個數組,這個數組對應著原始圖片中最異常的部分。但是這個數組依然很大:

為了減小這個數組的大小,我們利用一種叫做最大池化(max pooling)的函數來降採樣(downsample)。它聽起來很棒,但這仍舊不夠!

讓我們先來看每個 2×2 的方陣數組,並且留下最大的數:

這裡,一旦我們找到組成 2×2 方陣的 4 個輸入中任何異常的部分,我們就只保留這一個數。這樣一來我們的數組大小就縮減了,同時最重要的部分也保留住了。

最後一步:作出預測

到現在為止,我們已經把一個很大的圖片縮減到了一個相對較小的數組。

你猜怎麼著?數組就是一串數字而已,所以我們我們可以把這個數組輸入到另外一個神經網路里面去。最後的這個神經網路會決定這個圖片是否匹配。為了區分它和卷積的不同,我們把它稱作「全連接」網路(「Fully Connected」 Network)。

所以從開始到結束,我們的五步就像管道一樣被連接了起來:

添加更多步驟

我們的圖片處理管道是一系列的步驟:卷積、最大池化,還有最後的「全連接」網路。

你可以把這些步驟任意組合、堆疊多次,來解決真實世界中的問題!你可以有兩層、三層甚至十層卷積層。當你想要縮小你的數據大小時,你也隨時可以調用最大池化函數。

我們解決問題的基本方法,就是從一整個圖片開始,一步一步逐漸地分解它,直到你找到了一個單一的結論。你的卷積層越多,你的網路就越能識別出複雜的特徵。

比如說,第一個卷積的步驟可能就是嘗試去識別尖銳的東西,而第二個卷積步驟則是通過找到的尖銳物體來找鳥類的喙,最後一步是通過鳥喙來識別整隻鳥,以此類推。

下面是一個更實際的深層卷積網路的樣子(就是你們能在研究報告裡面找到的那種例子一樣):

這裡,他們從一個 224×224 像素的圖片開始,先使用了兩次卷積和最大池化,再使用三次卷積,一次最大池化;最後再使用兩個全連接層。最終的結果是這個圖片能被分類到 1000 種不同類別當中的某一種!

建造正確的網路

所以,你是怎麼知道我們需要結合哪些步驟來讓我們的圖片分類器工作呢?

說句良心話,你必須做許多的實驗和檢測才能回答這個問題。在為你要解決的問題找到完美的結構和參數之前,你可能需要訓練 100 個網路。機器學習需要反覆試驗!

建立我們的鳥類分類器

現在我們已經做夠了準備,我們已經可以寫一個小程序來判定一個圖中的物體是不是一隻鳥了。

誠然,我們需要數據來開始。CIFAR10 資料庫免費提供了 6000 張鳥類的圖片和 52000張非鳥類的圖片。但是為了獲取更多的數據,我們仍需添加 Caltech-UCSD Birds-200-2011 資料庫,這裡面包括了另外的12000 張鳥類的圖片。

這是我們整合後的資料庫裡面的一些鳥類的圖片:

這是資料庫里一些非鳥類圖片:

這些數據對於我們這篇文章解釋說明的目的來說已經夠了,但是對於真實世界的問題來說,72000 張低解析度的圖片還是太少了。如果你想要達到 Google 這種等級的表現的話,你需要上百萬張高清無碼大圖。在機器學習這個領域中,有更多的數據永遠比一個更好的演算法更重要!現在你知道為什麼谷歌總是樂於給你提供無限量免費圖片存儲了吧? 他們,需要,你的,數據!

為了建立我們的分類器,我們將會使用 TFLearn。TFLearn 是 Google TensorFlow 的一個封裝,Google TensorFlow 包含了一個擁有簡單 API的深度學習庫。用它來建立卷積網路的過程,和寫幾行代碼定義我們網路的層級一樣簡單。

下面是定義並訓練我們網路的代碼:

# -*- coding: utf-8 -*-"""基於這個 tflearn 樣例代碼:https://github.com/tflearn/tflearn/blob/master/examples/images/convnet_cifar10.py"""from __future__ import division, print_function,absolute_import# 導入 tflearn 和一些輔助文件import tflearnfrom tflearn.data_utils import shufflefrom tflearn.layers.core import input_data,dropout, fully_connectedfrom tflearn.layers.conv import conv_2d,max_pool_2dfrom tflearn.layers.estimator import regressionfrom tflearn.data_preprocessing importImagePreprocessingfrom tflearn.data_augmentation importImageAugmentationimport pickle# 載入數據集X, Y, X_test, Y_test =pickle.load(open("full_dataset.pkl", "rb"))# 打亂數據X, Y = shuffle(X, Y)# 確定數據是規範的img_prep = ImagePreprocessing()img_prep.add_featurewise_zero_center()img_prep.add_featurewise_stdnorm()# 翻轉、旋轉和模糊效果數據集中的圖片,# 來創造一些合成訓練數據.img_aug = ImageAugmentation()img_aug.add_random_flip_leftright()img_aug.add_random_rotation(max_angle=25.)img_aug.add_random_blur(sigma_max=3.)# 定義我們的網路架構:# 輸入內容是一張 32x32 大小, 3 個顏色通道(紅、綠、藍)的圖片network = input_data(shape=[None, 32, 32, 3], data_preprocessing=img_prep, data_augmentation=img_aug)# 第一步: 卷積network = conv_2d(network, 32, 3,activation=relu)# 第二步: 最大池化network = max_pool_2d(network, 2)# 第三步: 再卷積network = conv_2d(network, 64, 3,activation=relu)# 第四步: 再再卷積network = conv_2d(network, 64, 3,activation=relu)# 第五步: 再最大池化network = max_pool_2d(network, 2)# 第六步: 擁有 512 個節點的全連接神經網路network = fully_connected(network, 512,activation=relu)# 第七步: Dropout - 在訓練過程中隨機丟掉一些數據來防止過擬合network = dropout(network, 0.5)# 第八步: 擁有兩個輸出 (0=不是鳥, 1=是鳥) 的全連接神經網路,yong l做出最終預測network = fully_connected(network, 2,activation=softmax)# 告訴 tflearn 我們想如何訓練神經網路network = regression(network, optimizer=adam, loss=categorical_crossentropy, learning_rate=0.001)# 把網路打包為一個模型對象 model = tflearn.DNN(network,tensorboard_verbose=0, checkpoint_path=bird-classifier.tfl.ckpt)# 開始訓練!我們將進行 100 次訓練, 並實時監視它.model.fit(X, Y, n_epoch=100, shuffle=True,validation_set=(X_test, Y_test), show_metric=True, batch_size=96, snapshot_epoch=True, run_id=bird-classifier)# 當訓練結束時保存模型model.save("bird-classifier.tfl")print("Network trained and saved asbird-classifier.tfl!")

如果你的遊戲顯卡足夠好,顯存足夠大(比如說 Nvidia GeForceGTX980 Ti),一個小時以內訓練就能結束,但是如果你用一般的 CPU 來訓練的話,需要的時間可能更長。

隨著訓練的進行,準確度也會增加。在第一遍訓練之後,它的準確率是75.4%。10 次訓練之後,準確率就上升到了91.7%。當訓練了大約 50 次的時候,它的準確率達到了 95.5%。繼續訓練並沒有增加它的準確度,所以我停止了。

恭喜!我們的程序現在能識別鳥類的圖片了!

測試我們的網路

現在我們擁有了一個訓練過的神經網路,我們可以開始使用它了!這兒有一個簡單的腳本,能夠接收一個圖片並預測圖中是否是鳥類。

但是為了真正檢測我們的神經網路的效果,我們需要大量的圖片來測試。我創造的那個資料庫裡面有 15000 張用來驗證的圖片。當我把這15000 張圖片放到程序里運行的時候,它的預測準確率達到了 95%。

看起來還不錯,對吧?呃……這事兒吧還得辯證地看……

95% 是有多準確?

我們的網路聲稱有 95% 的準確率。但是細節決定成敗,這個數字的表示的意義可能有很多。

比如說,如果我們的訓練圖片是由 5% 的鳥類圖片和 95% 的非鳥類圖片組成的呢?一個程序即使每次都猜「不是鳥」也能達到 95% 的準確率!這也就意味著這個程序並沒有什麼作用。

相比於準確率,我們必須更多地關注在數字本身。為了判別一個分類系統有多好,我們需要知道它是怎樣出錯誤的,而不是僅僅關注它錯了多少次。

與其只考慮我們預測的對與錯,不如把我們的程序分解成四個不同的類別——

· 首先,對於那些被我們的網路正確辨認為鳥類而且確實是鳥類的,我們叫他們真正類(True Positives):

哇哦!我們的網路能夠識別那麼多種不同的鳥類!

· 第二,被辨認為非鳥類,而且確實是非鳥類的,我們稱為真負類(True Negatives):

我們才不會認錯馬和卡車呢!

· 第三,被辨認為鳥類,但實際上卻是非鳥類的,我們叫假正類(False Positives):

好多飛機被誤認成了鳥!情有可原。

· 第四,被辨認為非鳥類,但卻是鳥類的,我們叫假負類(False Negatives):

這些鳥騙過我們!蠢蠢的鴕鳥!它們能算是鳥類嗎?

那 15000 張驗證圖片中,每種類別的數量如下圖所示:

為什麼我們要把結果做上述分類呢?因為每一個錯誤產生的幾率並不都是一樣的。

設想如果我們寫一個通過 MRI 圖像來探測癌症的程序。如果我們探測到了癌症,我們更希望它是假正類而不是假負類。因為假負類是最可怕的情況——那就是你的程序告訴你,你絕對沒有病,但實際上你已經病入膏肓了。

我們需要計算準確率和召回率(Precisionand Recall metrics)而並不僅僅關注總體的準確度。準確率和召回率給了我們程序表現的一個清晰的反饋:

這告訴我們,在我們判定是「鳥類」的樣本中,有 97% 的樣本的確是鳥類!但是這同時也告訴我們說,我們只找出了整個數據集中 90% 的真實鳥類。換句話說,我們可能不會找到每一隻鳥,但是當我們找到一隻鳥的時候,我們很確定它就是一隻鳥!

路還在遠方……

現在既然你知道了一些關於深度卷積網路的基本概念,你可以用 TFLearn 嘗試一下各種結構神經網路的例子。它包括了自帶的數據集,你甚至不用自己去收集圖片。

你同時也知道了如何開始創造分支或是學習機器學習的其他領域。接下來為什麼不嘗試著去學習如何用演算法來訓練我們的電腦玩雅達利的遊戲呢?

1. 譯者註:也就是平移不變性。


推薦閱讀:

視覺計算-理論還是實踐?
深扒人工智慧——歷史篇
法律中的遞歸現象
英國:臨床人工智慧數字革命一觸即發?
阿里 A.I. Labs 三項重磅發布:開放語音後又開放 AR,還發了款路由器

TAG:人工智能 | 机器学习 | 图像识别 |