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

圖像分類

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

背景介紹

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

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

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

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

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

效果展示

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

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

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

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

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

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

模型概覽

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

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

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

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

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

CNN

傳統(tǒng)CNN包含卷積層、全連接層等組件,并采用softmax多類別分類器和多類交叉熵損失函數(shù),一個典型的卷積神經(jīng)網(wǎng)絡(luò)如圖5所示,我們先介紹用來構(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í)行降采樣操作。通過取卷積輸出特征圖中局部區(qū)塊的最大值(max-pooling)或者均值(avg-pooling)。降采樣也是圖像處理中常見的一種操作,可以過濾掉一些不重要的高頻信息。
  全連接層(fully-connected layer,或者fc layer): 輸入層到隱藏層的神經(jīng)元是全部連接的。
  非線性變化: 卷積層、全連接層后面一般都會接非線性變化層,例如Sigmoid、Tanh、ReLu等來增強網(wǎng)絡(luò)的表達能力,在CNN里最常使用的為ReLu激活函數(shù)。
  Dropout [10] : 在模型訓(xùn)練階段隨機讓一些隱層節(jié)點權(quán)重不工作,提高網(wǎng)絡(luò)的泛化能力,一定程度上防止過擬合。

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

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

VGG

牛津大學(xué)VGG(Visual Geometry Group)組在2014年ILSVRC提出的模型被稱作VGG模型 [11] 。該模型相比以往模型進一步加寬和加深了網(wǎng)絡(luò)結(jié)構(gòu),它的核心是五組卷積操作,每兩組之間做Max-Pooling空間降維。同一組內(nèi)采用多次連續(xù)的3X3卷積,卷積核的數(shù)目由較淺組的64增多到最深組的512,同一組內(nèi)的卷積核數(shù)目是一樣的。卷積之后接兩層全連接層,之后是分類層。由于每組內(nèi)卷積層的不同,有11、13、16、19層這幾種模型,下圖展示一個16層的網(wǎng)絡(luò)結(jié)構(gòu)。VGG模型結(jié)構(gòu)相對簡潔,提出之后也有很多文章基于此模型進行研究,如在ImageNet上首次公開超過人眼識別的模型[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的獲得了冠軍,在介紹該模型之前我們先來了解NIN(Network in Network)模型 [13] 和Inception模塊,因為GoogleNet模型由多組Inception模塊組成,模型設(shè)計借鑒了NIN的一些思想。

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

Inception模塊如下圖7所示,圖(a)是最簡單的設(shè)計,輸出是3個卷積層和一個池化層的特征拼接。這種設(shè)計的缺點是池化層不會改變特征通道數(shù),拼接后會導(dǎo)致特征的通道數(shù)較大,經(jīng)過幾層這樣的模塊堆積后,通道數(shù)會越來越大,導(dǎo)致參數(shù)和計算量也隨之增大。為了改善這個缺點,圖(b)引入3個1x1卷積層進行降維,所謂的降維就是減少通道數(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ù)映射的全連接層。除了這兩個特點之外,由于網(wǎng)絡(luò)中間層特征也很有判別性,GoogleNet在中間層添加了兩個輔助分類器,在后向傳播中增強梯度并且增強正則化,而整個網(wǎng)絡(luò)的損失函數(shù)是這個三個分類器的損失加權(quán)求和。

GoogleNet整體網(wǎng)絡(luò)結(jié)構(gòu)如圖8所示,總共22層網(wǎng)絡(luò):開始由3層普通的卷積組成;接下來由三組子網(wǎng)絡(luò)組成,第一組子網(wǎng)絡(luò)包含2個Inception模塊,第二組包含5個Inception模塊,第三組包含2個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] 對一些卷積層做了分解,進一步提高網(wǎng)絡(luò)非線性能力和加深網(wǎng)絡(luò);GoogleNet-v4 [17] 引入下面要講的ResNet設(shè)計思路。從v1到v4每一版的改進都會帶來準(zhǔn)確度的提升,介于篇幅,這里不再詳細介紹v2到v4的結(jié)構(gòu)。

ResNet

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

殘差模塊如圖9所示,左邊是基本模塊連接方式,由兩個輸出通道數(shù)相同的3x3卷積組成。右邊是瓶頸模塊(Bottleneck)連接方式,之所以稱為瓶頸,是因為上面的1x1卷積用來降維(圖示例即256->64),下面的1x1卷積用來升維(圖示例即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ò)連接示意圖,使用的是瓶頸模塊。這三個模型的區(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ù)集常用的有CIFAR、ImageNet、COCO等,常用的細粒度圖像分類數(shù)據(jù)集包括CUB-200-2011Stanford Dog、Oxford-flowers等。其中ImageNet數(shù)據(jù)集規(guī)模相對較大,如模型概覽一章所講,大量研究成果基于ImageNet。ImageNet數(shù)據(jù)從2010年來稍有變化,常用的是ImageNet-2012數(shù)據(jù)集,該數(shù)據(jù)集包含1000個類別:訓(xùn)練集包含1,281,167張圖片,每個類別數(shù)據(jù)732至1300張不等,驗證集包含50,000張圖片,平均每個類別50張圖片。

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

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

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

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

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

Paddle 初始化

通過 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兩個模型的配置。

VGG

首先介紹VGG模型結(jié)構(gòu),由于CIFAR10圖片大小和數(shù)量相比ImageNet數(shù)據(jù)小很多,因此這里的模型針對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層,詳細的定義如下:

      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,窗口滑動大小為2,groups決定每組VGG模塊是幾次連續(xù)的卷積操作,dropouts指定Dropout操作的概率。所使用的img_conv_group是在paddle.networks中預(yù)定義的模塊,由若干組 Conv->BN->ReLu->Dropout 和 一組 Pooling 組成。

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

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

  3. 定義分類器

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

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

    在有監(jiān)督訓(xùn)練中需要輸入圖像對應(yīng)的類別信息,同樣通過paddle.layer.data來定義。訓(xùn)練中采用多類交叉熵作為損失函數(shù),并作為網(wǎng)絡(luò)的輸出,預(yù)測階段定義網(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ò)連接過程。

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

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)主要有以下幾個過程。

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

注意:除過第一層卷積層和最后一層全連接層之外,要求三組 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ò)拓撲結(jié)構(gòu)和模型參數(shù)來構(gòu)造出trainer用來訓(xùn)練,在構(gòu)造時還需指定優(yōu)化方法,這里使用最基本的Momentum方法,同時設(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)

通過 learning_rate_decay_a (簡寫$a$) 、learning_rate_decay_b (簡寫$b$) 和 learning_rate_schedule 指定學(xué)習(xí)率調(diào)整策略,這里采用離散指數(shù)的方式調(diào)節(jié)學(xué)習(xí)率,計算公式如下, $n$ 代表已經(jīng)處理過的累計總樣本數(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)

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

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

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

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

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 用來在訓(xùn)練過程中輸出文本日志

# 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)

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

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

一輪訓(xùn)練log示例如下所示,經(jīng)過1個pass, 訓(xùn)練集上平均error為0.6875 ,測試集上平均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)練的分類錯誤率曲線圖,運行到第200個pass后基本收斂,最終得到測試集上分類錯誤率為8.54%。

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

應(yīng)用模型

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

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打開圖片存儲順序為H(高度),W(寬度),C(通道)。
    # PaddlePaddle要求數(shù)據(jù)順序為CHW,所以需要轉(zhuǎn)換順序。
    im = im.transpose((2, 0, 1)) # CHW
    # CIFAR訓(xùn)練圖片通道順序為B(藍),G(綠),R(紅),
    # 而PIL打開圖片默認(rèn)通道順序為RGB,因為需要交換通道。
    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òu)成,框架較為復(fù)雜,而端到端的CNN模型結(jié)構(gòu)可一步到位,而且大幅度提升了分類準(zhǔn)確率。本文我們首先介紹VGG、GoogleNet、ResNet三個經(jīng)典的模型;然后基于CIFAR10數(shù)據(jù)集,介紹如何使用PaddlePaddle配置和訓(xùn)練CNN模型,尤其是VGG和ResNet模型;最后介紹如何使用PaddlePaddle的API接口對圖片進行預(yù)測和特征提取。對于其他數(shù)據(jù)集比如ImageNet,配置和訓(xù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/


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

上一篇:語義角色標(biāo)注下一篇:詞向量