0
在上一篇專(zhuān)欄中,我們利用卷積自編碼器對(duì) MNIST 數(shù)據(jù)進(jìn)行了實(shí)驗(yàn),這周我們來(lái)看一個(gè) Kaggle 上比較經(jīng)典的一個(gè)圖像分類(lèi)的比賽 CIFAR( CIFAR-10 - Object Recognition in Images ),這個(gè)比賽現(xiàn)在已經(jīng)關(guān)閉了,但不妨礙我們來(lái)去通過(guò)它學(xué)習(xí)一下卷積神經(jīng)網(wǎng)絡(luò)做圖像識(shí)別的代碼結(jié)構(gòu)。相信很多學(xué)過(guò)深度學(xué)習(xí)的同學(xué)都嘗試過(guò)這個(gè)比賽,如果對(duì)此比較熟悉的可以跳過(guò)本篇,如果沒(méi)有嘗試過(guò)的同學(xué)可以來(lái)學(xué)習(xí)一下哈。
整個(gè)代碼已經(jīng)放在了我的 GitHub 上,建議可以把代碼 pull 下來(lái),邊看文章邊看代碼。
GitHub 地址:NELSONZHAO/zhihu
如果覺(jué)得有幫助,麻煩點(diǎn)個(gè) star 啦~
文章主要分為兩個(gè)部分,第一部分我們將通過(guò)一個(gè)簡(jiǎn)單的 KNN 來(lái)實(shí)現(xiàn)圖像的分類(lèi),第二部分我們通過(guò)卷積神經(jīng)網(wǎng)絡(luò)提升整個(gè)圖像分類(lèi)的性能。
提到圖像分類(lèi),我們可能會(huì)想到傳統(tǒng)機(jī)器學(xué)習(xí)中 KNN 算法,通過(guò)找到當(dāng)前待分類(lèi)圖像的 K 個(gè)近鄰,以近鄰的類(lèi)別判斷當(dāng)前圖像的類(lèi)別。
由于我們的圖像實(shí)際上是由一個(gè)一個(gè)像素組成的,因此每一個(gè)圖像可以看做是一個(gè)向量,那么我們此時(shí)就可以來(lái)計(jì)算向量(圖片)之間的距離。比如,我們的圖片如果是 32x32 像素的,那么可以展開(kāi)成一個(gè) 1x1024 的向量,就可以計(jì)算這些向量間的 L1 或者 L2 距離,找到它們的近鄰,從而根據(jù)近鄰的類(lèi)別來(lái)判斷圖像的類(lèi)別。
以下例子中 K=5。
下面我們就來(lái)用 scikit-learn 實(shí)現(xiàn)以下 KNN 對(duì)圖像的分類(lèi)。
首先我們需要下載數(shù)據(jù)文件,網(wǎng)址為 https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 。我們數(shù)據(jù)包含了 60000 萬(wàn)圖片,每張圖片的維度為 32 x 32 x 3,這些圖片都有各自的標(biāo)注,一共分為了以下十類(lèi):
airplane
automobile
bird
cat
deer
dog
frog
horse
ship
truck
數(shù)據(jù)是被序列化以后存儲(chǔ)的,因此我們需要使用 Python 中的 pickle 包將它們讀進(jìn)來(lái)。整個(gè)壓縮包解壓以后,會(huì)有 5 個(gè) data_batch 和 1 個(gè) test_batch。我們首先把數(shù)據(jù)加載進(jìn)來(lái):
我們定義了一個(gè)函數(shù)來(lái)獲取 batch 中的 features 和 labels,通過(guò)上面的步驟,我們就可以獲得 train 數(shù)據(jù)與 test 數(shù)據(jù)。
我們的每個(gè)圖片的維度是 32 x 32 x 3,其中 3 代表 RGB。我們先來(lái)看一些這些圖片長(zhǎng)什么樣子。
每張圖片的像素其實(shí)很低,縮小以后我們可以看到圖片中有汽車(chē),馬,飛機(jī)等。
構(gòu)造好了我們的 x_train, y_train, x_test 以及 y_test 以后,我們就可以開(kāi)始建模過(guò)程。在將圖片扔進(jìn)模型之前,我們首先要對(duì)數(shù)據(jù)進(jìn)行預(yù)處理,包括重塑和歸一化兩步,首先將 32 x 32 x 3 轉(zhuǎn)化為一個(gè) 3072 維的向量,再對(duì)數(shù)據(jù)進(jìn)行歸一化,歸一化的目的在于計(jì)算距離時(shí)保證各個(gè)維度的量綱一致。
到此為止,我們已經(jīng)對(duì)數(shù)據(jù)進(jìn)行了預(yù)處理,下面就可以調(diào)用 KNN 來(lái)進(jìn)行訓(xùn)練,我分別采用了 K=1,3,5 來(lái)看模型的效果。
從 KNN 的分類(lèi)準(zhǔn)確率來(lái)看,是要比我們隨機(jī)猜測(cè)類(lèi)別提高了不少。我們隨機(jī)猜測(cè)圖片類(lèi)別時(shí),準(zhǔn)確率大概是 10%,KNN 方式的圖片分類(lèi)可以將準(zhǔn)確率提高到 35% 左右。當(dāng)然有興趣的小伙伴還可以去測(cè)試一下其他的 K 值,同時(shí)在上面的算法中,默認(rèn)距離衡量方式是歐式距離,還可以嘗試其他度量距離來(lái)進(jìn)行建模。
雖然 KNN 在 test 數(shù)據(jù)集上表現(xiàn)有所提升,但是這個(gè)準(zhǔn)確率還是太低了。除此之外,KNN 有一個(gè)缺點(diǎn),就是所有的計(jì)算時(shí)間都在 predict 階段,當(dāng)一個(gè)新的圖來(lái)的時(shí)候,涉及到大量的距離計(jì)算,這就意味著一旦我們要拿它來(lái)進(jìn)行圖像識(shí)別,那可能要等非常久才能拿到結(jié)果,而且還不是那么的準(zhǔn)。
在上一部分,我們用了非常簡(jiǎn)單的 KNN 思想實(shí)現(xiàn)了圖像分類(lèi)。在這個(gè)部分,我們將通過(guò)卷積神經(jīng)網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)一個(gè)更加準(zhǔn)確、高效的模型。
加載數(shù)據(jù)的過(guò)程與上一部分相同,不再贅述。當(dāng)我們將數(shù)據(jù)加載完畢后,首先要做以下三件事:
對(duì)輸入數(shù)據(jù)歸一化
對(duì)標(biāo)簽進(jìn)行 one-hot 編碼
構(gòu)造訓(xùn)練集,驗(yàn)證集和測(cè)試集
對(duì)輸入數(shù)據(jù)歸一化
在這里我們使用 sklearn 中的 minmax 歸一化。
首先將訓(xùn)練數(shù)據(jù)集重塑為 [50000, 3072] 的形狀,利用 minmax 來(lái)進(jìn)行歸一化。最后再將圖像重塑回原來(lái)的形狀。
對(duì)標(biāo)簽進(jìn)行 one-hot 編碼
同樣我們?cè)谶@里使用 sklearn 中的 LabelBinarizer 來(lái)進(jìn)行 one-hot 編碼。
構(gòu)造 train 和 val
目前我們已經(jīng)有了 train 和 test 數(shù)據(jù)集,接下來(lái)我們要將加載進(jìn)來(lái)的 train 分成訓(xùn)練集和驗(yàn)證集。從而在訓(xùn)練過(guò)程中觀(guān)察驗(yàn)證集的結(jié)果。
我們將訓(xùn)練數(shù)據(jù)集按照 8:2 分為 train 和 validation。
卷積網(wǎng)絡(luò)
完成了數(shù)據(jù)的預(yù)處理,我們接下來(lái)就要開(kāi)始進(jìn)行建模。
首先我們把一些重要的參數(shù)設(shè)置好,并且將輸入和標(biāo)簽 tensor 構(gòu)造好。
img_shape 是整個(gè)訓(xùn)練集的形狀,為 [40000, 32, 32, 3],同時(shí)我們的輸入形狀是 [batch_size, 32, 32, 3],由于前面我們已經(jīng)對(duì)標(biāo)簽進(jìn)行了 one-hot 編碼,因此標(biāo)簽是一個(gè) [batch_size, 10] 的 tensor。
接下來(lái)我們先來(lái)看一下整個(gè)卷積網(wǎng)絡(luò)的結(jié)構(gòu):
在這里我設(shè)置了兩層卷積 + 兩層全連接層的結(jié)構(gòu),大家也可以嘗試其他不同的結(jié)構(gòu)和參數(shù)。
conv2d 中我自己定義了初始化權(quán)重為 truncated_normal,事實(shí)證明權(quán)重初始化對(duì)于卷積結(jié)果有一定的影響。
在這里,我們來(lái)說(shuō)一下 conv2d 的參數(shù):
輸入 tensor:inputs_
濾波器的數(shù)量:64
濾波器的 size:height=2, width=2, depth 默認(rèn)與 inputs_的 depth 相同
strides:strides 默認(rèn)為 1x1,因此在這里我沒(méi)有重新設(shè)置 strides
padding:padding 我選了 same,在 strides 是 1 的情況下,經(jīng)過(guò)卷積以后 height 和 width 與原圖保持一致
kernel_initializer:濾波器的初始化權(quán)重
在這里講一下卷積函數(shù)中的兩種常見(jiàn) padding 方式,分別是 valid,same。假設(shè)我們輸入圖片長(zhǎng)和寬均為 h,filter 的 size 為 k x k,strides 為 s x s,padding 大小 = p。當(dāng) padding=valid 時(shí),經(jīng)過(guò)卷積以后的圖片新的長(zhǎng)(或?qū)挘?;當(dāng) padding=same 時(shí),經(jīng)過(guò)卷積以后
。但在 TensorFlow 中的實(shí)現(xiàn)與這里有所區(qū)別,在 TensorFlow 中,當(dāng) padding=valid 時(shí),
;當(dāng) padding=same 時(shí),
。
其余參數(shù)類(lèi)似,這里不再贅述,如果還不是很清楚的小伙伴可以去查看官方文檔。
在第一個(gè)全連接層中我加入了 dropout 正則化防止過(guò)擬合,同時(shí)加快訓(xùn)練速度。
訓(xùn)練模型
完成了模型的構(gòu)建,下面我們就來(lái)開(kāi)始訓(xùn)練整個(gè)模型。
在訓(xùn)練過(guò)程中,每 100 輪打印一次日志,顯示出當(dāng)前 train loss 和 validation 上的準(zhǔn)確率。
我們來(lái)看一下最終的訓(xùn)練結(jié)果:
上圖是我之前跑的一次結(jié)果,這次跑出來(lái)可能有所出入,但準(zhǔn)確率大概會(huì)在 65%-70% 之間。
最后在 validation 上的準(zhǔn)確率大約穩(wěn)定在了 70% 左右,我們接下來(lái)看一下在 test 數(shù)據(jù)上的準(zhǔn)確率。下面的代碼是在 test 測(cè)試準(zhǔn)確率的代碼。
我們把訓(xùn)練結(jié)果加載進(jìn)來(lái),設(shè)置 test 的 batchs_size 為 100,來(lái)測(cè)試我們的訓(xùn)練結(jié)果。最終我們的測(cè)試準(zhǔn)確率也基本在 70% 左右。
至此,我們實(shí)現(xiàn)了兩種圖像分類(lèi)的算法。第一種是 KNN,它的思想非常好理解,但缺點(diǎn)在于計(jì)算量都集中在測(cè)試階段,訓(xùn)練階段的計(jì)算量幾乎為 0,另外,它的準(zhǔn)確性也非常差。第二種我們利用 CNN 實(shí)現(xiàn)了分類(lèi),最終的測(cè)試結(jié)果大約在 70% 左右,相比 KNN 的 30% 準(zhǔn)確率,它的分類(lèi)效果表現(xiàn)的相當(dāng)好。當(dāng)然,如果想要繼續(xù)提升模型的準(zhǔn)確率,就需要采用其他的一些手段,如果感興趣的小伙伴可以去看一下相關(guān)鏈接(http://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html#43494641522d3130) 里的技巧,Kaggle 上的第一名準(zhǔn)確率已經(jīng)超過(guò)了 95%。
如果覺(jué)得有用,請(qǐng)記得給 GitHub 打一個(gè) Star,非常感謝!
雷峰網(wǎng)版權(quán)文章,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見(jiàn)轉(zhuǎn)載須知。