2017年7月5日 星期三

很犯規的 caffe 初體驗: 省略訓練直接跳到影像辨識

U. C. Berkeley 的 BVLC (現在改名叫做 BAIR) 所撰寫的人工智慧引擎 caffe 有一個 model zoo 頁面, 裡面都是現成的類神經網路模型, 有一些甚至已有訓練好的權重矩陣 (weight matrices) 可以下載, 連曠日廢時的訓練過程都可以省下來, 完全不需要 受 nvidia 的鳥氣。 對於不求甚解 (而且沒有 GPU) 的工程師來說, 這真是一個玩具天堂, 光是想要把這裡的資源都接上 I/O 變成簡單的命令列, 就已經有很多東西 (可惜很少文件) 可以玩了。 延續 不求甚解工程師的人工智慧學習捷徑地圖, 這次我寫了 caffenet-classify.py 圖像辨識指令, 當成我的第一個 caffe 外包裝小程式習作, 分享給大家玩, 也順便記錄一些心得。

一、 準備 floydhub/dl-docker

假設你的電腦已安裝 docker。 請先抓回一個含有 caffe 的 docker image:
docker pull floydhub/dl-docker:cpu
docker pull bvlc/caffe:cpu
前者不只 caffe, 還包含了 keras 跟 tensorflow 等等更多環境, 也包含 jupyter notebook, 佔 2.87G; 後者是 BVLC/BAIR 官方打包的 caffe docker, 佔 1.6G。

啟動, 把這個 docker 命名為 ailab、 把 host 的 /var/lib/ailab 目錄對應到 docker 的 /root/shared 目錄以便交換檔案、 把 (可以從 docker 內啟動的) jupyter notebook 服務接到 host 的 7588 port 去:
docker run -it --name ailab -v /var/lib/ailab:/root/shared -p 7588:8888 -p 7506:6006 floydhub/dl-docker:cpu bash
docker run -it --name ailab -v /var/lib/ailab:/root/shared bvlc/caffe:cpu bash

在 ailab 這個 docker 裡面, 按 ^d 結束。 再用 docker start -ai ailab 重新接回去。 以後都用這個方式離開/回到 ailab。 更多 docker 基本指令, 請見 Docker 新手第二課: 前景/背景/收工休息 圖解狀態轉換

二、 使用 bvlc_reference_caffenet

在 ailab 裡面, 下: env | grep -i caffe 從 CAFFE_ROOT 這個環境變數讀出 caffe 所在的目錄。 如果沒看見這個變數的話, 只好手動找到 caffe 所在的目錄, 並手動設定環境變數。 在 floydhub/dl-docker:cpu 裡面可能是這樣設: export CAFFE_ROOT=/root/caffe 而在 bvlc/caffe:cpu 裡面則可能是這樣設: export CAFFE_ROOT=/opt/caffe。 以下假設你已 cd $CAFFE_ROOT

我們要採用的類神經網路模型放在 models/bvlc_reference_caffenet 底下。 需要備妥以下幾個檔案:

  1. 模型檔: 本來就已收錄在 models/bvlc_reference_caffenet 裡面。 有好幾個 *.prototxt, 要用哪一個呢? 我們沒有要訓練, 只想要拿它來測試, 所以等一下會採用 deploy.prototxt 這個模型檔。 (已寫死在程式碼裡。)
  2. 權重檔: 就是已訓練好的頭腦。 下這個指令: ./scripts/download_model_binary.py models/bvlc_reference_caffenet 下載很~~~久~~~, 抓回來後放在 models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel 。 建議把它複製到 /root/shared 去, 這樣萬一不小心砍掉這個 docker instance , 下次重新 docker run 之後, 可以直接從 host 拷貝回來比較快。
  3. 訓練用影像的平均值: 本來就已收錄在 python/caffe/imagenet/ilsvrc_2012_mean.npy 。
  4. 運算結果的 (數字代號轉) 文字標籤檔: 執行 ./data/ilsvrc12/get_ilsvrc_aux.sh 會抓回 data/ilsvrc12/synset_words.txt 。
  5. 我寫的外包裝小程式 caffenet-classify.py。 抓回來之後把它變成可執行檔: chmod a+x caffenet-classify.py

最後, 執行 ./caffenet-classify.py examples/images/cat.jpg examples/images/fish-bike.jpg 就會得到類神經網路對每張圖片所做的五個猜測, 包含猜測的標籤 (「tabby, tabby cat」) 跟機率 (0.31244674)。 你可以透過 host 的 /var/lib/ailab 目錄 (也就是 docker 的 /root/shared 目錄) 上傳更多相片讓它去辨識。 命令列上一次可以給很多個檔案路徑當作參數。 如果不想看到中間過程所印的訊息, 可以在命令列上加上: 2>/dev/null 不過這有點危險, 會把所有錯誤訊息也一起忽略掉。 也可以這樣從 host 直接執行:

docker start ailab # 啟動 docker, 在背景執行
docker exec ailab /root/caffe/caffenet-classify.py /root/shared/enterprise.jpg

wc $CAFFE_ROOT/data/ilsvrc12/synset_words.txt 數一下, 可以看到它 「只」 認得 1000 類不同的圖片, 所以 太空站+企業號 被判定成是水母或蝎子; 納美人騎靈鳥 被判定成是越野腳踏車還是潛水客, 這也還算蠻有道理的 :-) 現在你可以不必靠網路上的 AI 引擎, 可以寫 shell script 離線在自己的電腦上辨識私密(咦?)的圖像了!

三、 雜記與心得

我的程式主要是從 這篇教學文 改來的。 「批次處理多個圖檔」 則是參考 這個問答

如果你選用的 docker image 是 floydhub/dl-docker:cpu , 還可以在 docker 裡面下: jupyter notebook & 然後就可以從 (host 的) 瀏覽器裡打開 http://localhost:7588 以互動的方式研讀上面那篇教學文或我的程式、 觀察中間變數的值。

想要進一步研究一個 caffe 模型, 可以把它的概觀架構圖畫出來。 先安裝相關套件: apt-get install graphviz ; pip install pydot 然後: ./python/draw_net.py models/bvlc_reference_caffenet/deploy.prototxt ~/shared/caffenet.svg 就可以從 host 的瀏覽器裡打開 file:///var/lib/ailab/caffenet.svg 來看圖。 灰色八角形的是 data blobs、 各種色彩的矩形是各種處理單元。

即使有教學文的程式碼可以抄、 程式也改出來了, 還是沒有完全弄懂介接輸入資料時要做的所有轉換。 這個討論串 裡面, ada...@ucr.edu 的最後一次發言整算是網路上搜尋得到講得最清楚的, 連同比較簡單的部分, 重點摘要如下:

  1. transformer.set_raw_scale('data', 255) 因為輸入資料的範圍是 0-1, 但 caffe 期待的值是 0-255, 所以用這一句把所有的值放大。 (因為 [0,1) 是半開半閉區間, 所以我覺得應該要乘以 256 才對啊...? 隨便啦)
  2. transformer.set_channel_swap('data', (2,1,0)) 這一句的作用是把輸入資料當中 RGB 三種顏色的順序改成 caffe 所需的 BGR 順序。 那三個數字指的是第幾列 (或第幾行)。
  3. transformer.set_transpose('data', (2,0,1)) 這一句也是在重新排列輸入資料的順序, 但是是在做 (三維的) transpose, 也就是在處理 (三維的) row-major 跟 column-major 對調的問題。 那三個數字指的是陣列的第幾個維度。

至於 net.blobs['data'].reshape(10, 3, 227, 227) 其中的 3 就是三個 (RGB) channels。 227x227 是圖片餵入類神經網路之前的大小, 好像這樣直接寫, caffe 就會自動把輸入圖片縮放到適當大小? 至於 10 到底是 batch size 還是 数据增广(Data Augmentation) 實在是找不到比較清楚完整有說服力的文章。 反正在 deploy.prototxt 裡面已寫死 10,3,227,227 那就照抄不要亂改吧。 至於想要批次處理多個圖檔時, 按照上面的問答連結, 直接在最外面加一個維度比較單純, 做出來的結果好像也正確。

「圖片要先減掉訓練用影像的平均值」 這件事我也不理解。 我的問題跟討論串當中 Corey Nolet 在 16/6/29 的提問一樣: 如果要減, 也應該減掉測試影像自己的平均值比較有道理, 不是嗎? (因為相機特性及照像時光線亮度等等因素) 而且 0-255 的原始影像值, 一旦減去 128 左右的值, 它的範圍就會出現負數了, 不是嗎? 請在命令列上試著加上 -v 2 會把像素的大致分佈印出來 (五段的 histogram)。 也請加上 -m "" 選項, 這會省略 「減掉平均值」 這個步驟。 我沒做很多測試; 判斷結果好像沒有差很多?

Caffe 的 model zoo 很讚; 但看起來欠缺文件是最大的障礙啊! 那裡面你看到什麼有趣的模型嗎? 請留言簡介, 也許我可以再來爬文學一下怎麼玩。

沒有留言:

張貼留言