雷鋒網按:本文原作者天雨粟,原文載於作者的知乎專欄——機器不學習,雷鋒網(公眾號:雷鋒網) 經授權發布。 前言 在上一篇專欄中,我們利用卷積自編碼器對 MNIST 數據進行了實驗,這周我們來看一個 Kaggle 上比較經典的一個圖像分類的比賽 CIFAR( CIFAR-10 - Object Recognition in Images ),這個比賽現在已經關閉了,但不妨礙我們來去通過它學習一下卷積神經網絡做圖像識別的代碼結構。相信很多學過深度學習的同學都嘗試過這個比賽,如果對此比較熟悉的可以跳過本篇,如果沒有嘗試過的同學可以來學習一下哈。
整個代碼已經放在了我的 GitHub 上,建議可以把代碼 pull 下來,邊看文章邊看代碼。
GitHub 地址:NELSONZHAO/zhihu
如果覺得有幫助,麻煩點個 star 啦~
介紹 文章主要分為兩個部分,第一部分我們將通過一個簡單的 KNN 來實現圖像的分類,第二部分我們通過卷積神經網絡提升整個圖像分類的性能。
第一部分 提到圖像分類,我們可能會想到傳統機器學習中 KNN 算法,通過找到當前待分類圖像的 K 個近鄰,以近鄰的類別判斷當前圖像的類別。
由於我們的圖像實際上是由一個一個像素組成的,因此每一個圖像可以看做是一個向量,那麼我們此時就可以來計算向量(圖片)之間的距離。比如,我們的圖片如果是 32x32 像素的,那麼可以展開成一個 1x1024 的向量,就可以計算這些向量間的 L1 或者 L2 距離,找到它們的近鄰,從而根據近鄰的類別來判斷圖像的類別。
以下例子中 K=5。
下面我們就來用 scikit-learn 實現以下 KNN 對圖像的分類。
首先我們需要下載數據文件,網址為 https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 。我們數據包含了 60000 萬圖片,每張圖片的維度為 32 x 32 x 3,這些圖片都有各自的標註,一共分為了以下十類:
airplane
automobile
bird
cat
deer
dog
frog
horse
ship
truck
數據是被序列化以後存儲的,因此我們需要使用 Python 中的 pickle 包將它們讀進來。整個壓縮包解壓以後,會有 5 個 data_batch 和 1 個 test_batch。我們首先把數據加載進來:
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/c98ef9283de44e4e1b2c2e1c5c042042.png" data-rawwidth="2044" data-rawheight="1132" width="2044" data-original="https://pic4.zhimg.com/v2-1d896f8ee3014c7c01f7861d20993b0f_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/c98ef9283de44e4e1b2c2e1c5c042042.png"/>
我們定義了一個函數來獲取 batch 中的 features 和 labels,通過上面的步驟,我們就可以獲得 train 數據與 test 數據。
我們的每個圖片的維度是 32 x 32 x 3,其中 3 代表 RGB。我們先來看一些這些圖片長什麼樣子。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/51c40d32b40260282aceee887dc698e6.png" data-rawwidth="2050" data-rawheight="762" width="2050" data-original="https://pic4.zhimg.com/v2-31ad24ca78b94c7948708da076f381fb_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/51c40d32b40260282aceee887dc698e6.png"/>
每張圖片的像素其實很低,縮小以後我們可以看到圖片中有汽車,馬,飛機等。
構造好了我們的 x_train, y_train, x_test 以及 y_test 以後,我們就可以開始建模過程。在將圖片扔進模型之前,我們首先要對數據進行預處理,包括重塑和歸一化兩步,首先將 32 x 32 x 3 轉化為一個 3072 維的向量,再對數據進行歸一化,歸一化的目的在於計算距離時保證各個維度的量綱一致。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/9f5da05fdeae41ff4057b75a756960c5.png" data-rawwidth="1336" data-rawheight="384" width="1336" data-original="https://pic4.zhimg.com/v2-871ed7507ca8f7803eaca9f2d8097083_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/9f5da05fdeae41ff4057b75a756960c5.png"/>
到此為止,我們已經對數據進行了預處理,下面就可以調用 KNN 來進行訓練,我分別採用了 K=1,3,5 來看模型的效果。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/79772ffd63fdfdd175ef53f8147ffa23.png" data-rawwidth="1478" data-rawheight="510" width="1478" data-original="https://pic1.zhimg.com/v2-97cf6c5dcbb165ca6990c84af86fb790_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/79772ffd63fdfdd175ef53f8147ffa23.png"/>
從 KNN 的分類準確率來看,是要比我們隨機猜測類別提高了不少。我們隨機猜測圖片類別時,準確率大概是 10%,KNN 方式的圖片分類可以將準確率提高到 35% 左右。當然有興趣的小夥伴還可以去測試一下其他的 K 值,同時在上面的算法中,默認距離衡量方式是歐式距離,還可以嘗試其他度量距離來進行建模。
雖然 KNN 在 test 數據集上表現有所提升,但是這個準確率還是太低了。除此之外,KNN 有一個缺點,就是所有的計算時間都在 predict 階段,當一個新的圖來的時候,涉及到大量的距離計算,這就意味著一旦我們要拿它來進行圖像識別,那可能要等非常久才能拿到結果,而且還不是那麼的準。
第二部分 在上一部分,我們用了非常簡單的 KNN 思想實現了圖像分類。在這個部分,我們將通過卷積神經網絡來實現一個更加準確、高效的模型。
加載數據的過程與上一部分相同,不再贅述。當我們將數據加載完畢後,首先要做以下三件事:
對輸入數據歸一化
對標籤進行 one-hot 編碼
構造訓練集,驗證集和測試集
對輸入數據歸一化
在這裡我們使用 sklearn 中的 minmax 歸一化。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/78d1823f276f7d04e732cb6d70738ce5.png" data-rawwidth="1306" data-rawheight="548" width="1306" data-original="https://pic1.zhimg.com/v2-43e9740ab0e58b8b695350aed811dc08_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/78d1823f276f7d04e732cb6d70738ce5.png"/>
首先將訓練數據集重塑為 [50000, 3072] 的形狀,利用 minmax 來進行歸一化。最後再將圖像重塑回原來的形狀。
對標籤進行 one-hot 編碼
同樣我們在這裡使用 sklearn 中的 LabelBinarizer 來進行 one-hot 編碼。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/f1b335ada96d1949be662879bfd49155.png" data-rawwidth="1292" data-rawheight="250" width="1292" data-original="https://pic1.zhimg.com/v2-f4c39653f7d335f366da58f4cb6e74a8_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/f1b335ada96d1949be662879bfd49155.png"/>
構造 train 和 val
目前我們已經有了 train 和 test 數據集,接下來我們要將加載進來的 train 分成訓練集和驗證集。從而在訓練過程中觀察驗證集的結果。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/32417729e853b0619f2a8d8b847057c3.png" data-rawwidth="2048" data-rawheight="302" width="2048" data-original="https://pic2.zhimg.com/v2-2aa0395610803231edf0b9c47f74f3dd_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/32417729e853b0619f2a8d8b847057c3.png"/>
我們將訓練數據集按照 8:2 分為 train 和 validation。
卷積網絡
完成了數據的預處理,我們接下來就要開始進行建模。
首先我們把一些重要的參數設置好,並且將輸入和標籤 tensor 構造好。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/178b8afc1ce07dd49d277bfd42a5b79e.png" data-rawwidth="2044" data-rawheight="416" width="2044" data-original="https://pic4.zhimg.com/v2-8466ab59ca955bb107ea2aaf24efccd3_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/178b8afc1ce07dd49d277bfd42a5b79e.png"/>
img_shape 是整個訓練集的形狀,為 [40000, 32, 32, 3],同時我們的輸入形狀是 [batch_size, 32, 32, 3],由於前面我們已經對標籤進行了 one-hot 編碼,因此標籤是一個 [batch_size, 10] 的 tensor。
接下來我們先來看一下整個卷積網絡的結構:
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/fdec6309465f52c763d9be51850b9a77.png" data-rawwidth="1100" data-rawheight="1392" width="1100" data-original="https://pic2.zhimg.com/v2-af78c0a5cb504f3488dba582b33aaa49_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/fdec6309465f52c763d9be51850b9a77.png"/>
在這裡我設置了兩層卷積 + 兩層全連接層的結構,大家也可以嘗試其他不同的結構和參數。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/43cce2adf623325ffee293b1b6612cb2.png" data-rawwidth="2046" data-rawheight="1376" width="2046" data-original="https://pic1.zhimg.com/v2-466e9cb9813d69cdd5c624c5b8b588d0_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/43cce2adf623325ffee293b1b6612cb2.png"/>
conv2d 中我自己定義了初始化權重為 truncated_normal,事實證明權重初始化對於卷積結果有一定的影響。 在這裡,我們來說一下 conv2d 的參數:
輸入 tensor:inputs_
濾波器的數量:64
濾波器的 size:height=2, width=2, depth 默認與 inputs_的 depth 相同
strides:strides 默認為 1x1,因此在這裡我沒有重新設置 strides
padding:padding 我選了 same,在 strides 是 1 的情況下,經過卷積以後 height 和 width 與原圖保持一致
kernel_initializer:濾波器的初始化權重
在這裡講一下卷積函數中的兩種常見 padding 方式,分別是 valid,same。假設我們輸入圖片長和寬均為 h,filter 的 size 為 k x k,strides 為 s x s,padding 大小 = p。當 padding=valid 時,經過卷積以後的圖片新的長(或寬)為 ;當 padding=same 時,經過卷積以後 。但在 TensorFlow 中的實現與這裡有所區別,在 TensorFlow 中,當 padding=valid 時, ;當 padding=same 時, 。 其餘參數類似,這裡不再贅述,如果還不是很清楚的小夥伴可以去查看官方文檔。
在第一個全連接層中我加入了 dropout 正則化防止過擬合,同時加快訓練速度。
訓練模型
完成了模型的構建,下面我們就來開始訓練整個模型。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/b4e6b1c4555d6812e6e6c34db1320e5d.png" data-rawwidth="2030" data-rawheight="988" width="2030" data-original="https://pic1.zhimg.com/v2-6f480dd69a0e758039aa3cf2d7d51ca8_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/b4e6b1c4555d6812e6e6c34db1320e5d.png"/>
在訓練過程中,每 100 輪列印一次日誌,顯示出當前 train loss 和 validation 上的準確率。
我們來看一下最終的訓練結果:
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/f4450ff456f93c684f444d155407ece0.png" data-rawwidth="1530" data-rawheight="320" width="1530" data-original="https://pic3.zhimg.com/v2-5abd4144be03579779168f6aef07aa32_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/f4450ff456f93c684f444d155407ece0.png"/>
上圖是我之前跑的一次結果,這次跑出來可能有所出入,但準確率大概會在 65%-70% 之間。 最後在 validation 上的準確率大約穩定在了 70% 左右,我們接下來看一下在 test 數據上的準確率。下面的代碼是在 test 測試準確率的代碼。
<img src="https://static.leiphone.com/uploads/new/article/pic/201707/aaf274d2ffcbaa830aea7216ff37348f.png" data-rawwidth="2034" data-rawheight="950" width="2034" data-original="https://pic4.zhimg.com/v2-2ddbcdc29e59eaf41c92c41af537093b_r.png" _src="https://static.leiphone.com/uploads/new/article/pic/201707/aaf274d2ffcbaa830aea7216ff37348f.png"/>
我們把訓練結果加載進來,設置 test 的 batchs_size 為 100,來測試我們的訓練結果。最終我們的測試準確率也基本在 70% 左右。
總結 至此,我們實現了兩種圖像分類的算法。第一種是 KNN,它的思想非常好理解,但缺點在於計算量都集中在測試階段,訓練階段的計算量幾乎為 0,另外,它的準確性也非常差。第二種我們利用 CNN 實現了分類,最終的測試結果大約在 70% 左右,相比 KNN 的 30% 準確率,它的分類效果表現的相當好。當然,如果想要繼續提升模型的準確率,就需要採用其他的一些手段,如果感興趣的小夥伴可以去看一下相關連結(http://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html#43494641522d3130) 裡的技巧,Kaggle 上的第一名準確率已經超過了 95%。
如果覺得有用,請記得給 GitHub 打一個 Star,非常感謝!
雷鋒網版權文章,未經授權禁止轉載。詳情見轉載須知。