鍍金池/ 教程/ 人工智能/ 圖像分類
識(shí)別數(shù)字
線性回歸
情感分析
詞向量
個(gè)性化推薦
圖像分類
語(yǔ)義角色標(biāo)注
機(jī)器翻譯

圖像分類

本教程源代碼目錄在book/image_classification, 初次使用請(qǐng)參考PaddlePaddle安裝教程。

背景介紹

圖像相比文字能夠提供更加生動(dòng)、容易理解及更具藝術(shù)感的信息,是人們轉(zhuǎn)遞與交換信息的重要來(lái)源。在本教程中,我們專注于圖像識(shí)別領(lǐng)域的一個(gè)重要問題,即圖像分類。

圖像分類是根據(jù)圖像的語(yǔ)義信息將不同類別圖像區(qū)分開來(lái),是計(jì)算機(jī)視覺中重要的基本問題,也是圖像檢測(cè)、圖像分割、物體跟蹤、行為分析等其他高層視覺任務(wù)的基礎(chǔ)。圖像分類在很多領(lǐng)域有廣泛應(yīng)用,包括安防領(lǐng)域的人臉識(shí)別和智能視頻分析等,交通領(lǐng)域的交通場(chǎng)景識(shí)別,互聯(lián)網(wǎng)領(lǐng)域基于內(nèi)容的圖像檢索和相冊(cè)自動(dòng)歸類,醫(yī)學(xué)領(lǐng)域的圖像識(shí)別等。

一般來(lái)說(shuō),圖像分類通過(guò)手工特征或特征學(xué)習(xí)方法對(duì)整個(gè)圖像進(jìn)行全部描述,然后使用分類器判別物體類別,因此如何提取圖像的特征至關(guān)重要。在深度學(xué)習(xí)算法之前使用較多的是基于詞袋(Bag of Words)模型的物體分類方法。詞袋方法從自然語(yǔ)言處理中引入,即一句話可以用一個(gè)裝了詞的袋子表示其特征,袋子中的詞為句子中的單詞、短語(yǔ)或字。對(duì)于圖像而言,詞袋方法需要構(gòu)建字典。最簡(jiǎn)單的詞袋模型框架可以設(shè)計(jì)為底層特征抽取、特征編碼、分類器設(shè)計(jì)三個(gè)過(guò)程。

而基于深度學(xué)習(xí)的圖像分類方法,可以通過(guò)有監(jiān)督或無(wú)監(jiān)督的方式學(xué)習(xí)層次化的特征描述,從而取代了手工設(shè)計(jì)或選擇圖像特征的工作。深度學(xué)習(xí)模型中的卷積神經(jīng)網(wǎng)絡(luò)(Convolution Neural Network, CNN)近年來(lái)在圖像領(lǐng)域取得了驚人的成績(jī),CNN直接利用圖像像素信息作為輸入,最大程度上保留了輸入圖像的所有信息,通過(guò)卷積操作進(jìn)行特征的提取和高層抽象,模型輸出直接是圖像識(shí)別的結(jié)果。這種基于"輸入-輸出"直接端到端的學(xué)習(xí)方法取得了非常好的效果,得到了廣泛的應(yīng)用。

本教程主要介紹圖像分類的深度學(xué)習(xí)模型,以及如何使用PaddlePaddle訓(xùn)練CNN模型。

效果展示

圖像分類包括通用圖像分類、細(xì)粒度圖像分類等。圖1展示了通用圖像分類效果,即模型可以正確識(shí)別圖像上的主要物體。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-01.png" alt="png" />
圖1. 通用圖像分類展示

圖2展示了細(xì)粒度圖像分類-花卉識(shí)別的效果,要求模型可以正確識(shí)別花的類別。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-02.png" alt="png" />
圖2. 細(xì)粒度圖像分類展示

一個(gè)好的模型既要對(duì)不同類別識(shí)別正確,同時(shí)也應(yīng)該能夠?qū)Σ煌暯?、光照、背景、變形或部分遮擋的圖像正確識(shí)別(這里我們統(tǒng)一稱作圖像擾動(dòng))。圖3展示了一些圖像的擾動(dòng),較好的模型會(huì)像聰明的人類一樣能夠正確識(shí)別。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-03.png" alt="png" />
圖3. 擾動(dòng)圖片展示[22]

模型概覽

圖像識(shí)別領(lǐng)域大量的研究成果都是建立在PASCAL VOC、ImageNet等公開的數(shù)據(jù)集上,很多圖像識(shí)別算法通常在這些數(shù)據(jù)集上進(jìn)行測(cè)試和比較。PASCAL VOC是2005年發(fā)起的一個(gè)視覺挑戰(zhàn)賽,ImageNet是2010年發(fā)起的大規(guī)模視覺識(shí)別競(jìng)賽(ILSVRC)的數(shù)據(jù)集,在本章中我們基于這些競(jìng)賽的一些論文介紹圖像分類模型。

在2012年之前的傳統(tǒng)圖像分類方法可以用背景描述中提到的三步完成,但通常完整建立圖像識(shí)別模型一般包括底層特征學(xué)習(xí)、特征編碼、空間約束、分類器設(shè)計(jì)、模型融合等幾個(gè)階段。
  1). 底層特征提取: 通常從圖像中按照固定步長(zhǎng)、尺度提取大量局部特征描述。常用的局部特征包括SIFT(Scale-Invariant Feature Transform, 尺度不變特征轉(zhuǎn)換) [1]、HOG(Histogram of Oriented Gradient, 方向梯度直方圖) [2]、LBP(Local Bianray Pattern, 局部二值模式) [3] 等,一般也采用多種特征描述子,防止丟失過(guò)多的有用信息。
  2). 特征編碼: 底層特征中包含了大量冗余與噪聲,為了提高特征表達(dá)的魯棒性,需要使用一種特征變換算法對(duì)底層特征進(jìn)行編碼,稱作特征編碼。常用的特征編碼包括向量量化編碼 [4]、稀疏編碼 [5]、局部線性約束編碼 [6]、Fisher向量編碼 [7] 等。
  3). 空間特征約束: 特征編碼之后一般會(huì)經(jīng)過(guò)空間特征約束,也稱作特征匯聚。特征匯聚是指在一個(gè)空間范圍內(nèi),對(duì)每一維特征取最大值或者平均值,可以獲得一定特征不變形的特征表達(dá)。金字塔特征匹配是一種常用的特征聚會(huì)方法,這種方法提出將圖像均勻分塊,在分塊內(nèi)做特征匯聚。
  4). 通過(guò)分類器分類: 經(jīng)過(guò)前面步驟之后一張圖像可以用一個(gè)固定維度的向量進(jìn)行描述,接下來(lái)就是經(jīng)過(guò)分類器對(duì)圖像進(jìn)行分類。通常使用的分類器包括SVM(Support Vector Machine, 支持向量機(jī))、隨機(jī)森林等。而使用核方法的SVM是最為廣泛的分類器,在傳統(tǒng)圖像分類任務(wù)上性能很好。

這種方法在PASCAL VOC競(jìng)賽中的圖像分類算法中被廣泛使用 [18]。NEC實(shí)驗(yàn)室在ILSVRC2010中采用SIFT和LBP特征,兩個(gè)非線性編碼器以及SVM分類器獲得圖像分類的冠軍 [8]。

Alex Krizhevsky在2012年ILSVRC提出的CNN模型 [9] 取得了歷史性的突破,效果大幅度超越傳統(tǒng)方法,獲得了ILSVRC2012冠軍,該模型被稱作AlexNet。這也是首次將深度學(xué)習(xí)用于大規(guī)模圖像分類中。從AlexNet之后,涌現(xiàn)了一系列CNN模型,不斷地在ImageNet上刷新成績(jī),如圖4展示。隨著模型變得越來(lái)越深以及精妙的結(jié)構(gòu)設(shè)計(jì),Top-5的錯(cuò)誤率也越來(lái)越低,降到了3.5%附近。而在同樣的ImageNet數(shù)據(jù)集上,人眼的辨識(shí)錯(cuò)誤率大概在5.1%,也就是目前的深度學(xué)習(xí)模型的識(shí)別能力已經(jīng)超過(guò)了人眼。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-04.png" alt="png" />
圖4. ILSVRC圖像分類Top-5錯(cuò)誤率

CNN

傳統(tǒng)CNN包含卷積層、全連接層等組件,并采用softmax多類別分類器和多類交叉熵?fù)p失函數(shù),一個(gè)典型的卷積神經(jīng)網(wǎng)絡(luò)如圖5所示,我們先介紹用來(lái)構(gòu)造CNN的常見組件。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-05.png" alt="png" />
圖5. CNN網(wǎng)絡(luò)示例[20]

  卷積層(convolution layer): 執(zhí)行卷積操作提取底層到高層的特征,發(fā)掘出圖片局部關(guān)聯(lián)性質(zhì)和空間不變性質(zhì)。
  池化層(pooling layer): 執(zhí)行降采樣操作。通過(guò)取卷積輸出特征圖中局部區(qū)塊的最大值(max-pooling)或者均值(avg-pooling)。降采樣也是圖像處理中常見的一種操作,可以過(guò)濾掉一些不重要的高頻信息。
  全連接層(fully-connected layer,或者fc layer): 輸入層到隱藏層的神經(jīng)元是全部連接的。
  非線性變化: 卷積層、全連接層后面一般都會(huì)接非線性變化層,例如Sigmoid、Tanh、ReLu等來(lái)增強(qiáng)網(wǎng)絡(luò)的表達(dá)能力,在CNN里最常使用的為ReLu激活函數(shù)。
  Dropout [10] : 在模型訓(xùn)練階段隨機(jī)讓一些隱層節(jié)點(diǎn)權(quán)重不工作,提高網(wǎng)絡(luò)的泛化能力,一定程度上防止過(guò)擬合。

另外,在訓(xùn)練過(guò)程中由于每層參數(shù)不斷更新,會(huì)導(dǎo)致下一次輸入分布發(fā)生變化,這樣導(dǎo)致訓(xùn)練過(guò)程需要精心設(shè)計(jì)超參數(shù)。如2015年Sergey Ioffe和Christian Szegedy提出了Batch Normalization (BN)算法 [14] 中,每個(gè)batch對(duì)網(wǎng)絡(luò)中的每一層特征都做歸一化,使得每層分布相對(duì)穩(wěn)定。BN算法不僅起到一定的正則作用,而且弱化了一些超參數(shù)的設(shè)計(jì)。經(jīng)過(guò)實(shí)驗(yàn)證明,BN算法加速了模型收斂過(guò)程,在后來(lái)較深的模型中被廣泛使用。

接下來(lái)我們主要介紹VGG,GoogleNet和ResNet網(wǎng)絡(luò)結(jié)構(gòu)。

VGG

牛津大學(xué)VGG(Visual Geometry Group)組在2014年ILSVRC提出的模型被稱作VGG模型 [11] 。該模型相比以往模型進(jìn)一步加寬和加深了網(wǎng)絡(luò)結(jié)構(gòu),它的核心是五組卷積操作,每?jī)山M之間做Max-Pooling空間降維。同一組內(nèi)采用多次連續(xù)的3X3卷積,卷積核的數(shù)目由較淺組的64增多到最深組的512,同一組內(nèi)的卷積核數(shù)目是一樣的。卷積之后接兩層全連接層,之后是分類層。由于每組內(nèi)卷積層的不同,有11、13、16、19層這幾種模型,下圖展示一個(gè)16層的網(wǎng)絡(luò)結(jié)構(gòu)。VGG模型結(jié)構(gòu)相對(duì)簡(jiǎn)潔,提出之后也有很多文章基于此模型進(jìn)行研究,如在ImageNet上首次公開超過(guò)人眼識(shí)別的模型[19]就是借鑒VGG模型的結(jié)構(gòu)。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-06.png" alt="png" />
圖6. 基于ImageNet的VGG16模型

GoogleNet

GoogleNet [12] 在2014年ILSVRC的獲得了冠軍,在介紹該模型之前我們先來(lái)了解NIN(Network in Network)模型 [13] 和Inception模塊,因?yàn)镚oogleNet模型由多組Inception模塊組成,模型設(shè)計(jì)借鑒了NIN的一些思想。

NIN模型主要有兩個(gè)特點(diǎn):1) 引入了多層感知卷積網(wǎng)絡(luò)(Multi-Layer Perceptron Convolution, MLPconv)代替一層線性卷積網(wǎng)絡(luò)。MLPconv是一個(gè)微小的多層卷積網(wǎng)絡(luò),即在線性卷積后面增加若干層1x1的卷積,這樣可以提取出高度非線性特征。2) 傳統(tǒng)的CNN最后幾層一般都是全連接層,參數(shù)較多。而NIN模型設(shè)計(jì)最后一層卷積層包含類別維度大小的特征圖,然后采用全局均值池化(Avg-Pooling)替代全連接層,得到類別維度大小的向量,再進(jìn)行分類。這種替代全連接層的方式有利于減少參數(shù)。

Inception模塊如下圖7所示,圖(a)是最簡(jiǎn)單的設(shè)計(jì),輸出是3個(gè)卷積層和一個(gè)池化層的特征拼接。這種設(shè)計(jì)的缺點(diǎn)是池化層不會(huì)改變特征通道數(shù),拼接后會(huì)導(dǎo)致特征的通道數(shù)較大,經(jīng)過(guò)幾層這樣的模塊堆積后,通道數(shù)會(huì)越來(lái)越大,導(dǎo)致參數(shù)和計(jì)算量也隨之增大。為了改善這個(gè)缺點(diǎn),圖(b)引入3個(gè)1x1卷積層進(jìn)行降維,所謂的降維就是減少通道數(shù),同時(shí)如NIN模型中提到的1x1卷積也可以修正線性特征。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-07.png" alt="png" />
圖7. Inception模塊

GoogleNet由多組Inception模塊堆積而成。另外,在網(wǎng)絡(luò)最后也沒有采用傳統(tǒng)的多層全連接層,而是像NIN網(wǎng)絡(luò)一樣采用了均值池化層;但與NIN不同的是,池化層后面接了一層到類別數(shù)映射的全連接層。除了這兩個(gè)特點(diǎn)之外,由于網(wǎng)絡(luò)中間層特征也很有判別性,GoogleNet在中間層添加了兩個(gè)輔助分類器,在后向傳播中增強(qiáng)梯度并且增強(qiáng)正則化,而整個(gè)網(wǎng)絡(luò)的損失函數(shù)是這個(gè)三個(gè)分類器的損失加權(quán)求和。

GoogleNet整體網(wǎng)絡(luò)結(jié)構(gòu)如圖8所示,總共22層網(wǎng)絡(luò):開始由3層普通的卷積組成;接下來(lái)由三組子網(wǎng)絡(luò)組成,第一組子網(wǎng)絡(luò)包含2個(gè)Inception模塊,第二組包含5個(gè)Inception模塊,第三組包含2個(gè)Inception模塊;然后接均值池化層、全連接層。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-08.jpeg" alt="jpeg" />
圖8. GoogleNet[12]

上面介紹的是GoogleNet第一版模型(稱作GoogleNet-v1)。GoogleNet-v2 [14] 引入BN層;GoogleNet-v3 [16] 對(duì)一些卷積層做了分解,進(jìn)一步提高網(wǎng)絡(luò)非線性能力和加深網(wǎng)絡(luò);GoogleNet-v4 [17] 引入下面要講的ResNet設(shè)計(jì)思路。從v1到v4每一版的改進(jìn)都會(huì)帶來(lái)準(zhǔn)確度的提升,介于篇幅,這里不再詳細(xì)介紹v2到v4的結(jié)構(gòu)。

ResNet

ResNet(Residual Network) [15] 是2015年ImageNet圖像分類、圖像物體定位和圖像物體檢測(cè)比賽的冠軍。針對(duì)訓(xùn)練卷積神經(jīng)網(wǎng)絡(luò)時(shí)加深網(wǎng)絡(luò)導(dǎo)致準(zhǔn)確度下降的問題,ResNet提出了采用殘差學(xué)習(xí)。在已有設(shè)計(jì)思路(BN, 小卷積核,全卷積網(wǎng)絡(luò))的基礎(chǔ)上,引入了殘差模塊。每個(gè)殘差模塊包含兩條路徑,其中一條路徑是輸入特征的直連通路,另一條路徑對(duì)該特征做兩到三次卷積操作得到該特征的殘差,最后再將兩條路徑上的特征相加。

殘差模塊如圖9所示,左邊是基本模塊連接方式,由兩個(gè)輸出通道數(shù)相同的3x3卷積組成。右邊是瓶頸模塊(Bottleneck)連接方式,之所以稱為瓶頸,是因?yàn)樯厦娴?x1卷積用來(lái)降維(圖示例即256->64),下面的1x1卷積用來(lái)升維(圖示例即64->256),這樣中間3x3卷積的輸入和輸出通道數(shù)都較小(圖示例即64->64)。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-09.png" alt="png" />
圖9. 殘差模塊

圖10展示了50、101、152層網(wǎng)絡(luò)連接示意圖,使用的是瓶頸模塊。這三個(gè)模型的區(qū)別在于每組中殘差模塊的重復(fù)次數(shù)不同(見圖右上角)。ResNet訓(xùn)練收斂較快,成功的訓(xùn)練了上百乃至近千層的卷積神經(jīng)網(wǎng)絡(luò)。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-10.png" alt="png" />
圖10. 基于ImageNet的ResNet模型

數(shù)據(jù)準(zhǔn)備

通用圖像分類公開的標(biāo)準(zhǔn)數(shù)據(jù)集常用的有CIFARImageNet、COCO等,常用的細(xì)粒度圖像分類數(shù)據(jù)集包括CUB-200-2011、Stanford Dog、Oxford-flowers等。其中ImageNet數(shù)據(jù)集規(guī)模相對(duì)較大,如模型概覽一章所講,大量研究成果基于ImageNet。ImageNet數(shù)據(jù)從2010年來(lái)稍有變化,常用的是ImageNet-2012數(shù)據(jù)集,該數(shù)據(jù)集包含1000個(gè)類別:訓(xùn)練集包含1,281,167張圖片,每個(gè)類別數(shù)據(jù)732至1300張不等,驗(yàn)證集包含50,000張圖片,平均每個(gè)類別50張圖片。

由于ImageNet數(shù)據(jù)集較大,下載和訓(xùn)練較慢,為了方便大家學(xué)習(xí),我們使用CIFAR10數(shù)據(jù)集。CIFAR10數(shù)據(jù)集包含60,000張32x32的彩色圖片,10個(gè)類別,每個(gè)類包含6,000張。其中50,000張圖片作為訓(xùn)練集,10000張作為測(cè)試集。圖11從每個(gè)類別中隨機(jī)抽取了10張圖片,展示了所有的類別。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-11.png" alt="png" />
圖11. CIFAR10數(shù)據(jù)集[21]

Paddle API提供了自動(dòng)加載cifar數(shù)據(jù)集模塊 paddle.dataset.cifar。

通過(guò)輸入python train.py,就可以開始訓(xùn)練模型了,以下小節(jié)將詳細(xì)介紹train.py的相關(guān)內(nèi)容。

模型結(jié)構(gòu)

Paddle 初始化

通過(guò) paddle.init,初始化Paddle是否使用GPU,trainer的數(shù)目等等。

import sys
import gzip
import paddle.v2 as paddle
from vgg import vgg_bn_drop
from resnet import resnet_cifar10

# PaddlePaddle init
paddle.init(use_gpu=False, trainer_count=1)

本教程中我們提供了VGG和ResNet兩個(gè)模型的配置。

VGG

首先介紹VGG模型結(jié)構(gòu),由于CIFAR10圖片大小和數(shù)量相比ImageNet數(shù)據(jù)小很多,因此這里的模型針對(duì)CIFAR10數(shù)據(jù)做了一定的適配。卷積部分引入了BN和Dropout操作。

  1. 定義數(shù)據(jù)輸入及其維度

    網(wǎng)絡(luò)輸入定義為 data_layer (數(shù)據(jù)層),在圖像分類中即為圖像像素信息。CIFRAR10是RGB 3通道32x32大小的彩色圖,因此輸入數(shù)據(jù)大小為3072(3x32x32),類別大小為10,即10分類。

      datadim = 3 * 32 * 32
      classdim = 10
    
      image = paddle.layer.data(
          name="image", type=paddle.data_type.dense_vector(datadim))
  2. 定義VGG網(wǎng)絡(luò)核心模塊

      net = vgg_bn_drop(image)

    VGG核心模塊的輸入是數(shù)據(jù)層,vgg_bn_drop 定義了16層VGG結(jié)構(gòu),每層卷積后面引入BN層和Dropout層,詳細(xì)的定義如下:

      def vgg_bn_drop(input):
          def conv_block(ipt, num_filter, groups, dropouts, num_channels=None):
              return paddle.networks.img_conv_group(
                  input=ipt,
                  num_channels=num_channels,
                  pool_size=2,
                  pool_stride=2,
                  conv_num_filter=[num_filter] * groups,
                  conv_filter_size=3,
                  conv_act=paddle.activation.Relu(),
                  conv_with_batchnorm=True,
                  conv_batchnorm_drop_rate=dropouts,
                  pool_type=paddle.pooling.Max())
    
          conv1 = conv_block(input, 64, 2, [0.3, 0], 3)
          conv2 = conv_block(conv1, 128, 2, [0.4, 0])
          conv3 = conv_block(conv2, 256, 3, [0.4, 0.4, 0])
          conv4 = conv_block(conv3, 512, 3, [0.4, 0.4, 0])
          conv5 = conv_block(conv4, 512, 3, [0.4, 0.4, 0])
    
          drop = paddle.layer.dropout(input=conv5, dropout_rate=0.5)
          fc1 = paddle.layer.fc(input=drop, size=512, act=paddle.activation.Linear())
          bn = paddle.layer.batch_norm(
              input=fc1,
              act=paddle.activation.Relu(),
              layer_attr=paddle.attr.Extra(drop_rate=0.5))
          fc2 = paddle.layer.fc(input=bn, size=512, act=paddle.activation.Linear())
          return fc2

    2.1. 首先定義了一組卷積網(wǎng)絡(luò),即conv_block。卷積核大小為3x3,池化窗口大小為2x2,窗口滑動(dòng)大小為2,groups決定每組VGG模塊是幾次連續(xù)的卷積操作,dropouts指定Dropout操作的概率。所使用的img_conv_group是在paddle.networks中預(yù)定義的模塊,由若干組 Conv->BN->ReLu->Dropout 和 一組 Pooling 組成。

    2.2. 五組卷積操作,即 5個(gè)conv_block。 第一、二組采用兩次連續(xù)的卷積操作。第三、四、五組采用三次連續(xù)的卷積操作。每組最后一個(gè)卷積后面Dropout概率為0,即不使用Dropout操作。

    2.3. 最后接兩層512維的全連接。

  3. 定義分類器

    通過(guò)上面VGG網(wǎng)絡(luò)提取高層特征,然后經(jīng)過(guò)全連接層映射到類別維度大小的向量,再通過(guò)Softmax歸一化得到每個(gè)類別的概率,也可稱作分類器。

      out = paddle.layer.fc(input=net,
                            size=classdim,
                            act=paddle.activation.Softmax())
  4. 定義損失函數(shù)和網(wǎng)絡(luò)輸出

    在有監(jiān)督訓(xùn)練中需要輸入圖像對(duì)應(yīng)的類別信息,同樣通過(guò)paddle.layer.data來(lái)定義。訓(xùn)練中采用多類交叉熵作為損失函數(shù),并作為網(wǎng)絡(luò)的輸出,預(yù)測(cè)階段定義網(wǎng)絡(luò)的輸出為分類器得到的概率信息。

      lbl = paddle.layer.data(
          name="label", type=paddle.data_type.integer_value(classdim))
      cost = paddle.layer.classification_cost(input=out, label=lbl)

ResNet

ResNet模型的第1、3、4步和VGG模型相同,這里不再介紹。主要介紹第2步即CIFAR10數(shù)據(jù)集上ResNet核心模塊。

net = resnet_cifar10(image, depth=56)

先介紹resnet_cifar10中的一些基本函數(shù),再介紹網(wǎng)絡(luò)連接過(guò)程。

  conv_bn_layer : 帶BN的卷積層。
  shortcut : 殘差模塊的"直連"路徑,"直連"實(shí)際分兩種形式:殘差模塊輸入和輸出特征通道數(shù)不等時(shí),采用1x1卷積的升維操作;殘差模塊輸入和輸出通道相等時(shí),采用直連操作。
  basicblock : 一個(gè)基礎(chǔ)殘差模塊,即圖9左邊所示,由兩組3x3卷積組成的路徑和一條"直連"路徑組成。
  bottleneck : 一個(gè)瓶頸殘差模塊,即圖9右邊所示,由上下1x1卷積和中間3x3卷積組成的路徑和一條"直連"路徑組成。
  layer_warp : 一組殘差模塊,由若干個(gè)殘差模塊堆積而成。每組中第一個(gè)殘差模塊滑動(dòng)窗口大小與其他可以不同,以用來(lái)減少特征圖在垂直和水平方向的大小。

def conv_bn_layer(input,
                  ch_out,
                  filter_size,
                  stride,
                  padding,
                  active_type=paddle.activation.Relu(),
                  ch_in=None):
    tmp = paddle.layer.img_conv(
        input=input,
        filter_size=filter_size,
        num_channels=ch_in,
        num_filters=ch_out,
        stride=stride,
        padding=padding,
        act=paddle.activation.Linear(),
        bias_attr=False)
    return paddle.layer.batch_norm(input=tmp, act=active_type)

def shortcut(ipt, n_in, n_out, stride):
    if n_in != n_out:
        return conv_bn_layer(ipt, n_out, 1, stride, 0,
                             paddle.activation.Linear())
    else:
        return ipt

def basicblock(ipt, ch_out, stride):
    ch_in = ch_out * 2
    tmp = conv_bn_layer(ipt, ch_out, 3, stride, 1)
    tmp = conv_bn_layer(tmp, ch_out, 3, 1, 1, paddle.activation.Linear())
    short = shortcut(ipt, ch_in, ch_out, stride)
    return paddle.layer.addto(input=[tmp, short], act=paddle.activation.Relu())

def layer_warp(block_func, ipt, features, count, stride):
    tmp = block_func(ipt, features, stride)
    for i in range(1, count):
        tmp = block_func(tmp, features, 1)
    return tmp

resnet_cifar10 的連接結(jié)構(gòu)主要有以下幾個(gè)過(guò)程。

  底層輸入連接一層 conv_bn_layer,即帶BN的卷積層。
  然后連接3組殘差模塊即下面配置3組 layer_warp ,每組采用圖 10 左邊殘差模塊組成。
  最后對(duì)網(wǎng)絡(luò)做均值池化并返回該層。

注意:除過(guò)第一層卷積層和最后一層全連接層之外,要求三組 layer_warp 總的含參層數(shù)能夠被6整除,即 resnet_cifar10 的 depth 要滿足 $(depth - 2) % 6 == 0$ 。

def resnet_cifar10(ipt, depth=32):
    # depth should be one of 20, 32, 44, 56, 110, 1202
    assert (depth - 2) % 6 == 0
    n = (depth - 2) / 6
    nStages = {16, 64, 128}
    conv1 = conv_bn_layer(
        ipt, ch_in=3, ch_out=16, filter_size=3, stride=1, padding=1)
    res1 = layer_warp(basicblock, conv1, 16, n, 1)
    res2 = layer_warp(basicblock, res1, 32, n, 2)
    res3 = layer_warp(basicblock, res2, 64, n, 2)
    pool = paddle.layer.img_pool(
        input=res3, pool_size=8, stride=1, pool_type=paddle.pooling.Avg())
    return pool

訓(xùn)練模型

定義參數(shù)

首先依據(jù)模型配置的cost定義模型參數(shù)。

# Create parameters
parameters = paddle.parameters.create(cost)

可以打印參數(shù)名字,如果在網(wǎng)絡(luò)配置中沒有指定名字,則默認(rèn)生成。

print parameters.keys()

構(gòu)造訓(xùn)練(Trainer)

根據(jù)網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)和模型參數(shù)來(lái)構(gòu)造出trainer用來(lái)訓(xùn)練,在構(gòu)造時(shí)還需指定優(yōu)化方法,這里使用最基本的Momentum方法,同時(shí)設(shè)定了學(xué)習(xí)率、正則等。

# Create optimizer
momentum_optimizer = paddle.optimizer.Momentum(
    momentum=0.9,
    regularization=paddle.optimizer.L2Regularization(rate=0.0002 * 128),
    learning_rate=0.1 / 128.0,
    learning_rate_decay_a=0.1,
    learning_rate_decay_b=50000 * 100,
    learning_rate_schedule='discexp')

# Create trainer
trainer = paddle.trainer.SGD(cost=cost,
                             parameters=parameters,
                             update_equation=momentum_optimizer)

通過(guò) learning_rate_decay_a (簡(jiǎn)寫$a$) 、learning_rate_decay_b (簡(jiǎn)寫$b$) 和 learning_rate_schedule 指定學(xué)習(xí)率調(diào)整策略,這里采用離散指數(shù)的方式調(diào)節(jié)學(xué)習(xí)率,計(jì)算公式如下, $n$ 代表已經(jīng)處理過(guò)的累計(jì)總樣本數(shù),$lr_{0}$ 即為 settings 里設(shè)置的 learning_rate。

$$ lr = lr_{0} * a^ {\lfloor \frac{n}{ b}\rfloor} $$

訓(xùn)練

cifar.train10()每次產(chǎn)生一條樣本,在完成shuffle和batch之后,作為訓(xùn)練的輸入。

reader=paddle.batch(
    paddle.reader.shuffle(
        paddle.dataset.cifar.train10(), buf_size=50000),
        batch_size=128)

通過(guò)feeding來(lái)指定每一個(gè)數(shù)據(jù)和paddle.layer.data的對(duì)應(yīng)關(guān)系。例如: cifar.train10()產(chǎn)生數(shù)據(jù)的第0列對(duì)應(yīng)image層的特征。

feeding={'image': 0,
         'label': 1}

可以使用event_handler回調(diào)函數(shù)來(lái)觀察訓(xùn)練過(guò)程,或進(jìn)行測(cè)試等, 該回調(diào)函數(shù)是trainer.train函數(shù)里設(shè)定。

event_handler_plot可以用來(lái)利用回調(diào)數(shù)據(jù)來(lái)打點(diǎn)畫圖:

http://wiki.jikexueyuan.com/project/deep-learning/images/03-12.png" alt="png" />

from paddle.v2.plot import Ploter

train_title = "Train cost"
test_title = "Test cost"
cost_ploter = Ploter(train_title, test_title)

step = 0
def event_handler_plot(event):
    global step
    if isinstance(event, paddle.event.EndIteration):
        if step % 1 == 0:
            cost_ploter.append(train_title, step, event.cost)
            cost_ploter.plot()
        step += 1
    if isinstance(event, paddle.event.EndPass):

        result = trainer.test(
            reader=paddle.batch(
                paddle.dataset.cifar.test10(), batch_size=128),
            feeding=feeding)
        cost_ploter.append(test_title, step, result.cost)

event_handler 用來(lái)在訓(xùn)練過(guò)程中輸出文本日志

# End batch and end pass event handler
def event_handler(event):
    if isinstance(event, paddle.event.EndIteration):
        if event.batch_id % 100 == 0:
            print "\nPass %d, Batch %d, Cost %f, %s" % (
                event.pass_id, event.batch_id, event.cost, event.metrics)
        else:
            sys.stdout.write('.')
            sys.stdout.flush()
    if isinstance(event, paddle.event.EndPass):
        # save parameters
        with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f:
            parameters.to_tar(f)

        result = trainer.test(
            reader=paddle.batch(
                paddle.dataset.cifar.test10(), batch_size=128),
            feeding=feeding)
        print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)

通過(guò)trainer.train函數(shù)訓(xùn)練:

trainer.train(
    reader=reader,
    num_passes=200,
    event_handler=event_handler_plot,
    feeding=feeding)

一輪訓(xùn)練log示例如下所示,經(jīng)過(guò)1個(gè)pass, 訓(xùn)練集上平均error為0.6875 ,測(cè)試集上平均error為0.8852 。

Pass 0, Batch 0, Cost 2.473182, {'classification_error_evaluator': 0.9140625}
...................................................................................................
Pass 0, Batch 100, Cost 1.913076, {'classification_error_evaluator': 0.78125}
...................................................................................................
Pass 0, Batch 200, Cost 1.783041, {'classification_error_evaluator': 0.7421875}
...................................................................................................
Pass 0, Batch 300, Cost 1.668833, {'classification_error_evaluator': 0.6875}
..........................................................................................
Test with Pass 0, {'classification_error_evaluator': 0.885200023651123}

圖12是訓(xùn)練的分類錯(cuò)誤率曲線圖,運(yùn)行到第200個(gè)pass后基本收斂,最終得到測(cè)試集上分類錯(cuò)誤率為8.54%。

http://wiki.jikexueyuan.com/project/deep-learning/images/03-13.png" alt="png" />
圖12. CIFAR10數(shù)據(jù)集上VGG模型的分類錯(cuò)誤率

應(yīng)用模型

可以使用訓(xùn)練好的模型對(duì)圖片進(jìn)行分類,下面程序展示了如何使用paddle.infer接口進(jìn)行推斷,可以打開注釋,更改加載的模型。

from PIL import Image
import numpy as np
import os
def load_image(file):
    im = Image.open(file)
    im = im.resize((32, 32), Image.ANTIALIAS)
    im = np.array(im).astype(np.float32)
    # PIL打開圖片存儲(chǔ)順序?yàn)镠(高度),W(寬度),C(通道)。
    # PaddlePaddle要求數(shù)據(jù)順序?yàn)镃HW,所以需要轉(zhuǎn)換順序。
    im = im.transpose((2, 0, 1)) # CHW
    # CIFAR訓(xùn)練圖片通道順序?yàn)锽(藍(lán)),G(綠),R(紅),
    # 而PIL打開圖片默認(rèn)通道順序?yàn)镽GB,因?yàn)樾枰粨Q通道。
    im = im[(2, 1, 0),:,:] # BGR
    im = im.flatten()
    im = im / 255.0
    return im

test_data = []
cur_dir = os.path.dirname(os.path.realpath(__file__))
test_data.append((load_image(cur_dir + '/image/dog.png'),)

# with gzip.open('params_pass_50.tar.gz', 'r') as f:
#    parameters = paddle.parameters.Parameters.from_tar(f)

probs = paddle.infer(
    output_layer=out, parameters=parameters, input=test_data)
lab = np.argsort(-probs) # probs and lab are the results of one batch data
print "Label of image/dog.png is: %d" % lab[0][0]

總結(jié)

傳統(tǒng)圖像分類方法由多個(gè)階段構(gòu)成,框架較為復(fù)雜,而端到端的CNN模型結(jié)構(gòu)可一步到位,而且大幅度提升了分類準(zhǔn)確率。本文我們首先介紹VGG、GoogleNet、ResNet三個(gè)經(jīng)典的模型;然后基于CIFAR10數(shù)據(jù)集,介紹如何使用PaddlePaddle配置和訓(xùn)練CNN模型,尤其是VGG和ResNet模型;最后介紹如何使用PaddlePaddle的API接口對(duì)圖片進(jìn)行預(yù)測(cè)和特征提取。對(duì)于其他數(shù)據(jù)集比如ImageNet,配置和訓(xùn)練流程是同樣的,大家可以自行進(jìn)行實(shí)驗(yàn)。

參考文獻(xiàn)

[1] D. G. Lowe, Distinctive image features from scale-invariant keypoints. IJCV, 60(2):91-110, 2004.

[2] N. Dalal, B. Triggs, Histograms of Oriented Gradients for Human Detection, Proc. IEEE Conf. Computer Vision and Pattern Recognition, 2005.

[3] Ahonen, T., Hadid, A., and Pietikinen, M. (2006). Face description with local binary patterns: Application to face recognition. PAMI, 28.

[4] J. Sivic, A. Zisserman, Video Google: A Text Retrieval Approach to Object Matching in Videos, Proc. Ninth Int'l Conf. Computer Vision, pp. 1470-1478, 2003.

[5] B. Olshausen, D. Field, Sparse Coding with an Overcomplete Basis Set: A Strategy Employed by V1?, Vision Research, vol. 37, pp. 3311-3325, 1997.

[6] Wang, J., Yang, J., Yu, K., Lv, F., Huang, T., and Gong, Y. (2010). Locality-constrained Linear Coding for image classification. In CVPR.

[7] Perronnin, F., Sánchez, J., & Mensink, T. (2010). Improving the fisher kernel for large-scale image classification. In ECCV (4).

[8] Lin, Y., Lv, F., Cao, L., Zhu, S., Yang, M., Cour, T., Yu, K., and Huang, T. (2011). Large-scale image clas- sification: Fast feature extraction and SVM training. In CVPR.

[9] Krizhevsky, A., Sutskever, I., and Hinton, G. (2012). ImageNet classification with deep convolutional neu- ral networks. In NIPS.

[10] G.E. Hinton, N. Srivastava, A. Krizhevsky, I. Sutskever, and R.R. Salakhutdinov. Improving neural networks by preventing co-adaptation of feature detectors. arXiv preprint arXiv:1207.0580, 2012.

[11] K. Chatfield, K. Simonyan, A. Vedaldi, A. Zisserman. Return of the Devil in the Details: Delving Deep into Convolutional Nets. BMVC, 2014。

[12] Szegedy, C., Liu, W., Jia, Y., Sermanet, P., Reed, S., Anguelov, D., Erhan, D., Vanhoucke, V., Rabinovich, A., Going deeper with convolutions. In: CVPR. (2015)

[13] Lin, M., Chen, Q., and Yan, S. Network in network. In Proc. ICLR, 2014.

[14] S. Ioffe and C. Szegedy. Batch normalization: Accelerating deep network training by reducing internal covariate shift. In ICML, 2015.

[15] K. He, X. Zhang, S. Ren, J. Sun. Deep Residual Learning for Image Recognition. CVPR 2016.

[16] Szegedy, C., Vanhoucke, V., Ioffe, S., Shlens, J., Wojna, Z. Rethinking the incep-tion architecture for computer vision. In: CVPR. (2016).

[17] Szegedy, C., Ioffe, S., Vanhoucke, V. Inception-v4, inception-resnet and the impact of residual connections on learning. arXiv:1602.07261 (2016).

[18] Everingham, M., Eslami, S. M. A., Van Gool, L., Williams, C. K. I., Winn, J. and Zisserman, A. The Pascal Visual Object Classes Challenge: A Retrospective. International Journal of Computer Vision, 111(1), 98-136, 2015.

[19] He, K., Zhang, X., Ren, S., and Sun, J. Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification. ArXiv e-prints, February 2015.

[20] http://deeplearning.net/tutorial/lenet.html

[21] https://www.cs.toronto.edu/~kriz/cifar.html

[22] http://cs231n.github.io/classification/


知識(shí)共享許可協(xié)議
本教程PaddlePaddle 創(chuàng)作,采用 知識(shí)共享 署名-相同方式共享 4.0 國(guó)際 許可協(xié)議進(jìn)行許可。