天天看點

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

本系列文章面向深度學習研發者,希望通過Image Caption Generation,一個有意思的具體任務,深入淺出地介紹深度學習的知識。本系列文章涉及到很多深度學習流行的模型,如CNN,RNN/LSTM,Attention等。本文為第13篇。

作者:李理

目前就職于環信,即時通訊雲平台和全媒體智能客服平台,在環信從事智能客服和智能機器人相關工作,緻力于用深度學習來提高智能機器人的性能。

相關文章:

李理:從Image Caption Generation了解深度學習(part I)

李理:從Image Caption Generation了解深度學習(part II)

李理:從Image Caption Generation了解深度學習(part III)

李理:自動梯度求解 反向傳播算法的另外一種視角

李理:自動梯度求解——cs231n的notes

李理:自動梯度求解——使用自動求導實作多層神經網絡

李理:詳解卷積神經網絡

李理:Theano tutorial和卷積神經網絡的Theano實作 Part1

李理:Theano tutorial和卷積神經網絡的Theano實作 Part2

李理:卷積神經網絡之Batch Normalization的原理及實作

李理:卷積神經網絡之Dropout

李理:三層卷積網絡和vgg的實作

6. 使用caffe在imagnet上訓練AlexNet

接下來我們介紹一下怎麼用caffe訓練ILSVRC2012。我們之前為了讓大家了解卷積神經網絡的細節原理,是以都是自己實作。但是在實際工作中,我們更傾向于使用成熟的架構來進行深度學習。caffe是一個老牌的深度學習架構,尤其是在計算機視覺領域很受歡迎。我們這裡會簡單的介紹一下怎麼使用caffe訓練ILSVRC2012,包括怎麼用pycaffe使用訓練好的模型,這在後面的Image Caption裡會用到。

6.1 ILSVRC2012和ImageNet介紹

我們之前也多次提到這兩個詞,有時也不加區分的使用它們。這裡我們稍微澄清一下它們的關系和差別。

首先ImangeNet是Stanford視覺實驗室标注的一個圖像資料庫,有上萬個分類和上千萬的圖檔。截止到本文發表,最新的資料是“14,197,122 images, 21841 synsets indexed”。我們這個系列教程中多次涉及的課程cs231n就是stanford視覺實驗室李飛飛教授開設的課程。

為了讓學術界有個公開标準的圖像分類【也包括Location】,Stanford視覺實驗室從2010年開始就開始 ILSVRC(ImageNet Large Scale Visual Recognition Challenge) ,翻譯成中文就是ImageNet大規模視覺競賽。在2012年之前,最好的top5分類錯誤率在20%以上,而2012年AlexNet首次在比賽中使用了深層的卷積網絡,取得了16%的錯誤率,一時讓深度學習名聲大噪。之後每年都有新的好成績出現,2014年是GoogLeNet和VGG,而2015年是ResNet參差網絡。目前最好系統的top5分類錯誤率在5%一下了。

ILSVRC2012是2012年的比賽資料,是最流行的一個大規模分類資料,它包含1000個分類和100,000+訓練資料。

6.2 caffe簡介

caffe是berkeley視覺實驗室開源的一個流行深度學習架構,更多細節讀者可以參考一些官網資料。因為本文的目的也不是介紹各種工具,為了避免文字過于冗長,就不仔細介紹了,下面介紹訓練步驟時簡要的做一些解釋。讀者有了自己實作CNN的基礎,了解怎麼使用caffe應該不會太難。安裝【包括cuda的支援】請參考官網,建議讀者用Ubuntu的系統,安裝比較簡單,記得安裝python的支援。

6.3 擷取ILSVRC2012資料

最開始下載下傳ILSVRC2012需要注冊才能獲得下載下傳位址,現在已經完全開放,如果讀者想訓練ILSVRC2012,建議使用GPU,否則就得等幾個月。另外機器要有比較大的硬碟空間來存放原始的資料【100多G】。

如果讀者不打算自己訓練,也不影響,因為網上有很多訓練好的模型,我們可以直接拿過來用,是以可以跳過本節。

下載下傳位址:http://www.image-net.org/challenges/LSVRC/2012/nonpub-downloads

這個比賽包括分類和定位,由于我們隻做分類,是以隻需要下載下傳

  1. Training images (Task 1 & 2) 138GB. MD5: 1d675b47d978889d74fa0da5fadfb00e
  2. Validation images (all tasks) 6.3GB. MD5: 29b22e2961454d5413ddabcf34fc5622
  3. Test images (all tasks) 13GB. MD5: fe64ceb247e473635708aed23ab6d839
  4. Training bounding box annotations (Task 1 & 2 only)
  5. Validation bounding box annotations (all tasks)

資料比較大,可能要下載下傳一兩天,用wget可以用-c選項支援斷點續傳。建議中原標準時間白天下載下傳,對于美國來說是晚上,速度較快,最快可以到1MB/s。下載下傳完了記得用md5檢驗一下

下載下傳完了解就行了。

訓練資料是這樣的目錄結果:/path/to/imagenet/train/n01440764/n01440764_10026.JPEG

其中n01440764就是代表一個類别,讀者可以選擇一些圖檔看看。

測試資料是:/path/to/imagenet/val/ILSVRC2012_val_00000001.JPEG

6.4 資料預處理

1. 輔助資料下載下傳

首先要下載下傳一下輔助的資料,進入caffe的根目錄執行

$./data/ilsvrc12/get_ilsvrc_aux.sh

這個腳本會下載下傳一些資料到data/ilsvrc12。包括train.txt val.txt和test.txt,這些檔案包含圖檔路徑【相對路徑】和分類的對應關系

2. 縮放圖檔

由于圖檔的大小不一樣,我們一般需要預先縮放圖檔。另外caffe使用leveldb來存儲圖檔【為了統一管理和queue處理,上百G的圖檔不可能想mnist資料那樣直接放到記憶體,我們隻能把要訓練的部分資料放到記憶體裡,為了提供效率,那麼需要隊列這樣的集中來prefetch圖檔到記憶體】,這都統一在腳本examples/imagenet/create_imagenet.sh裡了。

我們需要修改一下這個腳本,主要是imagenet圖檔的位置,輸出的目錄。下面是我的配置,請參考後修改

EXAMPLE=/bigdata/lili/imagenet
DATA=data/ilsvrc12
TOOLS=build/tools

TRAIN_DATA_ROOT=/bigdata/lili/imagenet/train/
VAL_DATA_ROOT=/bigdata/lili/imagenet/val/

# Set RESIZE=true to resize the images to x256. Leave as false if images have
# already been resized using another tool.
RESIZE=true
           

然後在caffe的根目錄運作這個腳本,注意這個腳本可能要運作好幾個小時【我記不清楚了,反正第一天運作第二天才完成】

6.5 計算圖檔的mean值

就像batch normaliztion一樣,我們一般需要把資料變成均值0的資料,計算mean就是這個用途。

./examples/imagenet/make_imagenet_mean.sh

這個腳本将生成data/ilsvrc12/imagenet_mean.binaryproto這個檔案

注意:我使用這個腳本生成的mean檔案訓練時不收斂,不知道什麼原因,去caffe的論壇發現别人也有碰到類似的問題,請參考 https://github.com/BVLC/caffe/issues/5212和https://github.com/BVLC/caffe/issues/4482。

我後來之後上網下載下傳了一個mean檔案就能訓練了。需要的讀者可以從這裡下載下傳:http://dl.caffe.berkeleyvision.org/caffe_ilsvrc12.tar.gz,解壓後就有imagenet_mean.binaryproto,放到data/ilsvrc12/下就行了。

6.6 修改配置檔案

1. models/bvlc_reference_caffenet/train_val.prototxt

修改訓練和測試的source:

data_param {
    #source: "examples/imagenet/ilsvrc12_train_lmdb"
    source: "/bigdata/lili/imagenet/ilsvrc12_train_lmdb"
    batch_size: 
    backend: LMDB
  }


    data_param {
    #source: "examples/imagenet/ilsvrc12_val_lmdb"
    source: "/bigdata/lili/imagenet/ilsvrc12_val_lmdb"
    batch_size: 
    backend: LMDB
  }
           

2. models/bvlc_reference_caffenet/train_val.prototxt

修改模型snapshot目錄的位置:

6.7 訓練

./build/tools/caffe train –solver=models/bvlc_reference_caffenet/solver.prototxt

如果是一個GPU,一般兩三天也就收斂了,我最終得到的準确率在58%左右。

7. 使用訓練好的模型進行分類

caffe代碼裡包含一個examples/00-classification.ipynb。我們用ipython notebook就可以像之前那樣工作了。當然很可能我們是在某個GPU伺服器上訓練的資料,包括那些圖檔也放在上面,那麼我們可以使用ipython的 –ip參數讓ipython監聽在非localhost上,這樣我們使用筆記本也可以通路【這其實是ipython notebook好用之處,我們不需要在伺服器上用vim編寫代碼或者在本地和伺服器直接拷貝代碼了,我們用這種方式就可以在本地筆記本開發python代碼,而直接在伺服器上調試和運作了】

ipython notebook –ip 這個機器的内網或者外網ip

啟動後在終端會出現帶token的連結: http://xxxx:8888/?token=35f30c22abf0a6c5d755e631cac5e7a9444aac829a5082b

這就在筆記本打開這個連結就行了。

7.1 cell1

初始化的代碼,直接運作

7.2 cell2

設定caffe_root。直接運作應該就可以,如果有路徑問題修改caffe_root為caffe的絕對路徑肯定是不會有問題的。

7.3 cell3

下載下傳模型,如果你訓練了自己的模型,可以跳過這一步,否則這段代碼會去下載下傳網上已有的模型。注意模型上百M

7.4 cell4

定義和加載模型。如果你使用自己訓練的模型,可以參考我的修改,否則不需要更改直接運作。

7.5 cell5

資料預處理對象caffe.io.Transformer

Our default CaffeNet is configured to take images in BGR format. Values are expected to start in the range [0, 255] and then have the mean ImageNet pixel value subtracted from them. In addition, the channel dimension is expected as the first (outermost) dimension.

預設的CaffeNet是使用BGR格式的圖像【這和我們一般圖像處理工具得到RBG是不一樣的】。并且取值是0-255【另外一種表示方法就是0-1】,然後會減去RGB的平均值。另外Caffe的Blob是(N, C, H, W)的,我們從圖像裡得到的一般是(H, W, C)。

是以需要經過預處理把我們讀到的資料轉成Caffe需要的格式。Caffe提供了一個類caffe.io.Transformer,我們直接用這個類就可以友善的完成這些工作【當然你自己實作也是完全沒有問題的,那麼不需要這個類了】

# load the mean ImageNet image (as distributed with Caffe) for subtraction
mu = np.load(caffe_root + 'python/caffe/imagenet/ilsvrc_2012_mean.npy')
mu = mu.mean().mean()  # average over pixels to obtain the mean (BGR) pixel values
print 'mean-subtracted values:', zip('BGR', mu)

# create transformer for the input called 'data'
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})

transformer.set_transpose('data', (,,))  # move image channels to outermost dimension
transformer.set_mean('data', mu)            # subtract the dataset-mean value in each channel
transformer.set_raw_scale('data', )      # rescale from [, ] to [, ]
transformer.set_channel_swap('data', (,,))  # swap channels from RGB to BGR
           

前面3行是讀取mean檔案。

第一行是讀取mean檔案,得到一個(3,256,256)的ndarray,代表BGR3個channel在每個像素點的平均值。

但是我們使用的時候是對每個channel減去同一個值域【不是每個位置減不同的值】,是以我們需要平均每個channel的256*256個值,這就可以用第二行代碼mu=mu.mean(1).mean(1)來完成,這行代碼的意思就是先對H求平均,得到一個(3,256)的ndarray,然後在對W求平均【注意這個時候H的次元下标從2變成1了】

其實用mu.mean(2).mean(1)也是等價的。總之就是對每個channel的256個值求平均。

第三行隻是列印出來看看,我這裡列印出來是:

mean-subtracted values: [('B', ), ('G', ), ('R', )]
           

接下來構造一個caffe.io.Transformer對象,它變化的資料是輸入層net.blobs[‘data’].data,是以它變換的資料的shape是net.blobs[‘data’].data.shape

對caffe不熟悉的讀者可以閱讀一下caffe的官方文檔。

我們【下面】代碼讀入的圖檔是(N, H, W, C)的ndarray,如果一次讀取一個圖檔,那麼N就是1,H和W分别是圖檔的高度和寬度,而C就是3代表RGB

但是Caffe要求(N, C, H, W),是以需要下面這個transpose:

接下來這行transformer.set_mean(‘data’, mu) 告訴它要減去我們之前得到的BGR3個channel的均值

而transformer.set_raw_scale(‘data’, 255) 讓它把0-1的範圍縮放到0-255。

最後因為我們讀入的channel順序是RGB,而caffe要求BGR,是以再加上最後這一行:

7.6 cell6

這個cell顯示了我們一次分類多個資料,其實也可以把batch改成1,這裡隻是為了示範可以一次分類多個,這會比一次分類一個加起來快。

# set the size of the input (we can skip this if we're happy
#  with the default; we can also change it later, e.g., for different batch sizes)
net.blobs['data'].reshape(,        # batch size
                          ,         # -channel (BGR) images
                          , )  # image size is x227
           

注意,我們之前訓練imagenet時會把圖像縮放成227*227的,參考models/bvlc_reference_caffenet/train_val.prototxt:

layer {
  name: "data"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    mirror: true
    crop_size: 
    mean_file: "data/ilsvrc12/imagenet_mean.binaryproto"
  }
           

7.7 cell7

接下來我們從example裡讀取一個圖檔,這是一隻很可愛的貓。

image = caffe.io.load_image(caffe_root + 'examples/images/cat.jpg')
transformed_image = transformer.preprocess('data', image)
plt.imshow(image)
           
李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

7.8 cell8

我們用模型來預測:

# copy the image data into the memory allocated for the net
net.blobs['data'].data[...] = transformed_image

### perform classification
output = net.forward()

output_prob = output['prob'][]  # the output probability vector for the first image in the batch

print 'predicted class is:', output_prob.argmax()
           

其實就是把data輸入層放置成我們的這個圖檔,注意,我們之前定義的輸入是(50,3,227,227),但是我們這裡隻傳了一張(1,3,227,227)的圖檔,是以它會broadcasting成50個一樣的圖檔,當然實際我們應該傳人50個不同的圖檔。這裡當然隻是個示範。

然後直接調用net.forward(),然後取出output[‘prob’][0],得到第一個圖檔的輸出機率。這個prob在哪裡定義的呢?感興趣的讀者可以打開models/bvlc_reference_caffenet/deploy.prototxt閱讀。

注意,我們訓練和測試的時候用的是models/bvlc_reference_caffenet/train_val.prototxt,那個時候我們隻關心loss(train phase)和accuracy(test phase)。而真正用來預測的時候,我們關心的是softmax的輸出機率。

如果模型訓練的沒有問題的話,這隻貓的分類應該是:

predicted class is: 281

再往下面的内容這個系列教程不會用到,我們隻要能夠讀取caffe的model,然後用它來預測就可以了,是以後面的cell就不一一介紹了,感興趣的讀者可以自行閱讀。

8. ResNet

ResNet注意可以參考兩篇論文: Deep Residual Learning for Image Recognition 和 Identity Mappings in Deep Residual Networks。 這裡有不少有用的連結,包括icml16上的tutorial和一些ResNet的實作代碼。

ResNet是Residual Network的縮寫,翻譯成中文就是殘差網絡。在介紹這種網絡結構之前,我們來回顧一下之前的經驗。

根據之前的經驗,神經網絡越深,效果越好。但是網絡變深了之後就不好優化,模型訓練時不容易收斂,當然我們可以用一些參數初始化和batch normalization的技術讓我們可以訓練較深的網絡,比如十幾層的VGG。

另外本文作者還提出了一個有意思的現象degradation問題——網絡變得非常深以後,效果反而不如淺層的網絡,甚至在訓練資料上都不如(是以不是overfitting的問題)。【注:Inception v4那篇論文裡作者似乎不太同意這個觀點,他們沒有用殘差結構也能訓練很深的網絡,這說明本文作者參數沒調好導緻沒收斂?但是他還是承認使用了殘差結構确實可以加快訓練的收斂速度】

作者在cifar10資料集行比較了,下圖說明如果不用殘差網絡,深的網絡效果反而不如淺的。

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

56層的網絡效果反而不如20層的。作者在ImageNet上也發現同樣的現象。

我覺得可能原因是網絡層數多,參數多,不使用ResNet比較難收斂。

為了解決訓練收斂慢的問題,作者提出了殘差結構的想法。

這個想法非常簡單。比如一個網絡(比如是兩個卷積加relu)需要學習(拟合)一個H(x)函數,之前我們就是直接學習函數H(x)。而殘差網絡讓它學習另外一個函數F(x)=H(x)-x。如下圖所示:

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

圖上在輸入和第二個relu之間增加了一個x的直接連接配接。當然這裡要求x的次元和F(x)是一樣的,否則沒法相加,這種情況一般會給x再乘以一個W使得Wx的次元和F(x)一樣。

就是這麼一點點改動,作者通過實驗驗證效果就是比沒有改動要好。

首先作者在ImageNet上訓練了普通的34層的網絡,然後又訓練了加入殘差的34層網絡,另外使用了VGG18作為baseline,同時也訓練了帶殘差的18層網絡,實驗對比如下:

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

可以看到,沒有殘差結果,34層的網絡還不如18層。加了殘差之後,34層的要比18層的好,當然18層的殘差網絡也比普通的18層VGG好。

之後就是訓練了著名的152層的網絡,在ILSVRC15上top5的error講到了3.57%

不過15年的文章并沒有怎麼解釋為什麼加了這樣的殘差結構就能訓練更深的網絡,隻是實驗驗證了想法而已。

16年的文章做了更深入一些的分析,分析為什麼加入殘差結構能使得訓練更深的網絡變得可能。

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

如上圖(a)所示殘差網絡就是許多殘差單元(Residual Units)組成的網絡。每個殘差單元都是這樣的形式:

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

其中輸入是 xl ,輸出是 xl+1 (輸出就是下一層l+1層的輸入)。

在15年的文章裡,h(xl)=xl而f是relu。

這裡我們先做一些簡化,假設 h(xl)=xl以及 f(x)=x。

我們任意考察殘差網絡的第l層到第L層。

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

這裡我們能發現這種結構很好的一些特性:

1. 任意更深的L層 xL 能表示成 xl 加上一個形如

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

的殘差之和。

2.

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

第L層是輸入加上殘差。而普通的網絡如果忽略BN和ReLU的話是

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

如果我們用error對 xlxl 求梯度,會得到:

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

因為公式複雜那一項不會總是-1,是以error從L層 ∂e/∂xL 傳到l層不會消失。是以這是可以訓練深層網絡的關鍵。

前面我們假設 h(xl)=xl,也就是通路是個identity mapping。如果我們稍微變化一下讓 h(xl)=λlxl會是怎麼樣呢?

經過簡單的推導,我們得到:

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

我們看到括号裡不是1而是

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

如果 λi<1 則容易梯度消失,反正會爆炸,總之就沒法訓練好了。

出來Identity map也就是 h(x)=x ,作者對比很多其它的資訊通路,結果發現效果都不好。原因就是通過Identity map,可以讓任何兩個層直接可以直接傳到資訊,也就是有一條直接的通路【說明:可以是說可能性,而不是一定會直接傳遞,這取決于資料的驅動讓它是否這麼做,就像我們後面會說LSTM相對與可以學習到更long distance的依賴一樣】。

基于之上的分析,作者提出了下圖b的新結構:

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

圖a是15年的工作,從第l層直接沒有到l+1層的資訊通路,因為這條通路要經過ReLU,不過可能ReLU至少在大于0時是完全可以通過的【不知道換成softmax會不會效果差很多,因為15年的文章沒有用過做實驗,也不好猜測】

而圖b從l層到l+1層是有直接資訊通路的,效果會更好。

大緻的思路就講到這裡吧,如果要自己實作應該也很簡單,有興趣的讀者可以自己實作一個ResNet在cifar資料上跑一跑。論文最好的結果是小于5%的錯誤率。上面的連結裡也有很多現成的ResNet實作,讀者可以參考。

9. Inception結構

主要是 Going Deeper with Convolutions、 Rethinking the Inception Architecture for Computer Vision、 Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning 這三篇論文。

  • 第一篇就是inception v1,也就是GoogLeNet
  • 第二篇是v2和v3
  • 第三篇是v4

第一篇和ResNet一樣,沒有太多理論依據,隻是實驗,建議大緻閱讀一下,重點閱讀第二篇。

首先是Inception這個詞。《盜夢空間》?這個單詞的意思是”起初“,”獲得學位“。

這似乎和電影的情節沒半毛錢關系,台灣翻譯成《全面啟動》就是這個單詞的直接翻譯,不過知乎的這個解釋還是有些道理的。我不知道作者把這樣的網絡結構叫做Inception是什麼意思。

Inception結構的基本思想就是用小的filter組合來替代大的filter,進而減少參數和計算量,這樣相同的計算資源也就能訓練更深的網絡,另外一個好處就是在一些終端裝置占用的記憶體和cpu更少。

我們看到,VGG都是使用3 3的filter,小的filter參數少,但是小的filter不能capture遠距離的依賴,相當于損失了模型的表達能力。雖然我們設計網絡是層次的,前面的層學習底層的特征,然後後面的層學習更高的特征。但是即使是底層特征,可能有些也沒法用太小的filter來學到。是以最早的設計直覺是前面用比較大的filter比如7 7的,随着圖檔的變小和網絡的加深,我們再使用更多更小的filter。我們可以看到7 7的計算量是3 3的5.4倍。

inception的想法就是用多個小的filter來替代大的filter。

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

我們可以用兩個3 3的filter stack起來替代一個5 5的filter,當然它們不是完全等價的,因為上層的3 3filter移動一步之後和之前是有重疊的部分的。

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

另外如上圖,我們也可以用一些非對稱的比如1 3和3 1的filter組合起來替代3 3的filter

此外還有就是1 1的filter也是有用的,它可以起到”降維“壓縮資訊的作用,比如輸入是(C1,H,W),用C2個1 1的filter後就變成了(C2,H,W)的圖像。這種壓縮的特點是卷積的局部的特性。

大概的思路就是這樣,細節就不羅嗦了,有興趣的讀者請參考論文。

10. 總結

通過上面的介紹,我們大緻了解了Image Classification裡最主流和state of the art的一些卷積網絡結構,當然這隻是視覺的一個任務,這裡沒有涉及到Location/Detection和Segmentation等其它任務,是以也沒有設計R-CNN fast/faster R-CNN,YOLO SSD等。有興趣的讀者有了上面的基礎應該是可以閱讀相關的論文了解它們的原理。關于卷積神經網絡的内容就介紹到這裡,下一篇文章将會講解RNN/LSTM的相關内容,敬請關注!

李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術
李理:Caffe訓練ImageNet簡介及深度卷積網絡最新技術

繼續閱讀