機(jī)器學(xué)習(xí)初學(xué)者入門實(shí)踐:怎樣輕松創(chuàng)造高精度分類網(wǎng)絡(luò)
這是一個(gè)為沒有人工智能背景的程序員提供的機(jī)器學(xué)習(xí)上手指南。使用神經(jīng)網(wǎng)絡(luò)不需要博士學(xué)位,你也不需要成為實(shí)現(xiàn)人工智能下一個(gè)突破的人,你只需要使用現(xiàn)有的技術(shù)就行了——畢竟我們現(xiàn)在已經(jīng)實(shí)現(xiàn)的東西已經(jīng)很突破了,而且還非常有用。我認(rèn)為我們越來越多的人將會(huì)和機(jī)器學(xué)習(xí)打交道就像我們之前越來越多地使用開源技術(shù)一樣——而不再僅僅將其看作是一個(gè)研究主題。在這份指南中,我們的目標(biāo)是編寫一個(gè)可以進(jìn)行高準(zhǔn)確度預(yù)測的程序——僅使用圖像本身來分辨 data/untrained-samples 中程序未見過的樣本圖像中是海豚還是海馬。下面是兩張圖像樣本:
為了實(shí)現(xiàn)我們的目標(biāo),我們將訓(xùn)練和應(yīng)用一個(gè)卷積神經(jīng)網(wǎng)絡(luò)(CNN)。我們將從實(shí)踐的角度來接近我們的目標(biāo),而不是闡釋其基本原理。目前人們對人工智能有很大的熱情,但其中很多都更像是讓物理學(xué)教授來教你自行車技巧,而不是讓公園里你的朋友來教你。
為此,我(GitHub 用戶 humphd/David Humphrey)決定在 GitHub 上寫下我的指南,而不是直接發(fā)在博客上,因?yàn)槲抑牢蚁旅娴膶懙囊磺锌赡軙?huì)有些誤導(dǎo)、天真或錯(cuò)誤。我目前仍在自學(xué),我發(fā)現(xiàn)現(xiàn)在還很缺乏可靠的初學(xué)者文檔。如果你覺得文章有錯(cuò)誤或缺失了某些重要的細(xì)節(jié),請發(fā)送一個(gè) pull 請求。下面就讓我教你「自行車的技巧」吧!
指南地址:https://github.com/humphd
概述
我們將在這里探索以下內(nèi)容:
- 設(shè)置和使用已有的、開源的機(jī)器學(xué)習(xí)技術(shù),尤其是 Caffe 和 DIDITS
- 創(chuàng)建一個(gè)圖像數(shù)據(jù)集
- 從頭開始訓(xùn)練一個(gè)神經(jīng)網(wǎng)絡(luò)
- 在我們的神經(jīng)網(wǎng)絡(luò)從未見過的圖像上對其進(jìn)行測試
- 通過微調(diào)已有的神經(jīng)網(wǎng)絡(luò)(AlexNet 和 GoogLeNet)來提升我們的神經(jīng)網(wǎng)絡(luò)的準(zhǔn)確度
- 部署和使用我們的神經(jīng)網(wǎng)絡(luò)
問:我知道你說過我們不會(huì)談?wù)撋窠?jīng)網(wǎng)絡(luò)理論,但我覺得在我們開始動(dòng)手之前至少應(yīng)該來一點(diǎn)總體概述。我們應(yīng)該從哪里開始?
對于神經(jīng)網(wǎng)絡(luò)的理論問題,你能在網(wǎng)上找到海量的介紹文章——從短帖子到長篇論述到在線課程。根據(jù)你喜歡的學(xué)習(xí)形式,這里推薦了三個(gè)比較好的起點(diǎn)選擇:
J Alammar 的博客《A Visual and Interactive Guide to the Basics of Neural Networks》非常贊,使用直觀的案例介紹了神經(jīng)網(wǎng)絡(luò)的概念:https://jalammar.github.io/visual-interactive-guide-basics-neural-networks/
Brandon Rohrer 的這個(gè)視頻是非常好的卷積神經(jīng)網(wǎng)絡(luò)介紹:https://www.youtube.com/watch?v=FmpDIaiMIeA
如果你想了解更多理論上的知識(shí),我推薦 Michael Nielsen 的在線書籍《Neural Networks and Deep Learning》:http://neuralnetworksanddeeplearning.com/index.html
設(shè)置
安裝 Caffe
Caffe 地址:http://caffe.berkeleyvision.org/
首先,我們要使用來自伯克利視覺和學(xué)習(xí)中心(Berkely Vision and Learning Center)的 Caffe 深度學(xué)習(xí)框架(BSD 授權(quán))。
問:稍等一下,為什么選擇 Caffe?為什么不選現(xiàn)在人人都在談?wù)摰?TensorFlow?
沒錯(cuò),我們有很多選擇,你也應(yīng)該了解一下所有的選項(xiàng)。TensorFlow 確實(shí)很棒,你也應(yīng)該試一試。但是這里選擇 Caffe 是基于以下原因:
- 這是為計(jì)算機(jī)視覺問題定制的
- 支持 C++ 和 Python(即將支持 node.js:https://github.com/silklabs/node-caffe)(https://github.com/silklabs/node-caffe%EF%BC%89)
- 快速且穩(wěn)定
但是我選擇 Caffe 的頭號(hào)原因是不需要寫任何代碼就能使用它。你可以聲明性地完成所有工作(Caffe 使用結(jié)構(gòu)化的文本文件來定義網(wǎng)絡(luò)架構(gòu)),并且也可以使用命令行工具。另外,你也可以為 Caffe 使用一些漂亮的前端,這能讓你的訓(xùn)練和驗(yàn)證過程簡單很多?;谕瑯拥脑颍旅嫖覀儠?huì)選擇 NVIDIA 的 DIGITS。
Caffe 的安裝有點(diǎn)麻煩。這里有不同平臺(tái)的安裝說明,包括一些預(yù)構(gòu)建的 Docker 或 AWS 配置:http://caffe.berkeleyvision.org/installation.html
注:當(dāng)我在進(jìn)行練習(xí)的時(shí)候,我使用了來自 GitHub 的尚未發(fā)布的 Caffe 版本:https://github.com/BVLC/caffe/commit/5a201dd960840c319cefd9fa9e2a40d2c76ddd73
在 Mac 要配置成功則難得多,這個(gè)版本有一些版本問題會(huì)在不同的步驟終止你的進(jìn)度。我用了好幾天時(shí)間來試錯(cuò),我看了十幾個(gè)指南,每一個(gè)都有一些不同的問題。最后發(fā)現(xiàn)這個(gè)最為接近:https://gist.github.com/doctorpangloss/f8463bddce2a91b949639522ea1dcbe4。另外我還推薦:https://eddiesmo.wordpress.com/2016/12/20/how-to-set-up-caffe-environment-and-pycaffe-on-os-x-10-12-sierra/,這篇文章比較新而且鏈接了許多類似的討論。
到目前為止,安裝 Caffe 就是我們做的最難的事情,這相當(dāng)不錯(cuò),因?yàn)槟憧赡茉瓉磉€以為人工智能方面會(huì)更難呢!
如果安裝遇到問題請不要放棄,痛苦是值得的。如果我會(huì)再來一次,我可能會(huì)使用一個(gè) Ubuntu 虛擬機(jī),而不是直接在 Mac 上安裝。如果你有問題要問,可以到 Caffe 用戶討論組:https://groups.google.com/forum/#!forum/caffe-users
問:我需要一個(gè)強(qiáng)大的硬件來訓(xùn)練神經(jīng)網(wǎng)絡(luò)嗎?要是我沒法獲取一個(gè)強(qiáng)大的 GPU 怎么辦?
是的,深度神經(jīng)網(wǎng)絡(luò)確實(shí)需要大量的算力和能量……但那是在從頭開始訓(xùn)練并且使用了巨型數(shù)據(jù)集的情況。我們不需要那么做。我們可以使用一個(gè)預(yù)訓(xùn)練好的網(wǎng)絡(luò)(其它人已經(jīng)為其投入了數(shù)百小時(shí)的計(jì)算和訓(xùn)練),然后根據(jù)你的特定數(shù)據(jù)進(jìn)行微調(diào)即可。我們后面會(huì)介紹如何實(shí)現(xiàn)這一目標(biāo),但首先我要向你說明:后面的一切工作都是在一臺(tái)沒有強(qiáng)大 GPU 的一年前的 MacBook 上完成的。
另外說明一點(diǎn),因?yàn)槲矣幸粔K集成英特爾顯卡,而不是英偉達(dá)的 GPU,所以我決定使用 OpenCL Caffe 分支:https://github.com/BVLC/caffe/tree/opencl,它在我的筆記本電腦上效果良好!
當(dāng)你安裝完 Caffe 之后,你應(yīng)該有或能夠做下列事情:
- 一個(gè)包含了你構(gòu)建的 Caffe 的目錄。如果你是按標(biāo)準(zhǔn)方式做的,應(yīng)該會(huì)有一個(gè) build/ 目錄包含了運(yùn)行 Caffe 所需的一切、捆綁的 Python 等等,build/ 的父目錄將是你的 CAFFE_ROOT(后面我們會(huì)用到它)
- 運(yùn)行 make test && make runtest,應(yīng)該會(huì)通過
- 安裝了所有的 Python 依賴包之后(在 python/ 中執(zhí)行 for req in $(cat requirements.txt); do pip install $req; done;運(yùn)行 make pycaffe && make pytest 應(yīng)該會(huì)通過
- 你也應(yīng)該運(yùn)行 make distribute 以在 distribute/ 中創(chuàng)建一個(gè)帶有所有必要的頭文件、二進(jìn)制文件等的可分發(fā)的 Caffe 版本
在我的機(jī)器上,Caffe 完全構(gòu)建后,我的 CAFFE_ROOT 目錄有以下基本布局:
- caffe/
- build/
- python/
- lib/
- tools/
- caffe ← this is our main binary
- distribute/
- python/
- lib/
- include/
- bin/
- proto/
到現(xiàn)在,我們有了訓(xùn)練、測試和編程神經(jīng)網(wǎng)絡(luò)所需的一切。下一節(jié)我們會(huì)為 Caffe 增加一個(gè)用戶友好的基于網(wǎng)頁的前端 DIGITS,這能讓我們對網(wǎng)絡(luò)的訓(xùn)練和測試變得更加簡單。
安裝 DIGITS
DIGITS 地址:https://github.com/NVIDIA/DIGITS
英偉達(dá)的深度學(xué)習(xí) GPU 訓(xùn)練系統(tǒng)(Deep Learning GPU Training System/DIGITS)是一個(gè)用于訓(xùn)練神經(jīng)網(wǎng)絡(luò)的 BSD 授權(quán)的 Python 網(wǎng)頁應(yīng)用。盡管我們可以在 Caffe 中用命令行或代碼做到 DIGITS 所能做到的一切,但使用 DIGITS 能讓我們的工作變得更加簡單。而且因?yàn)?DIGITS 有很好的可視化、實(shí)時(shí)圖表等圖形功能,我覺得使用它也能更有樂趣。因?yàn)槟阏趪L試和探索學(xué)習(xí),所以我強(qiáng)烈推薦你從 DIGITS 開始。
在 https://github.com/NVIDIA/DIGITS/tree/master/docs 有一些非常好的文檔,包括一些安裝、配置和啟動(dòng)的頁面。我強(qiáng)烈建議你在繼續(xù)之前通讀一下。我并不是一個(gè)使用 DIGITS 的專家,如果有問題可以在公開的 DIGITS 用戶組查詢或詢問:https://groups.google.com/forum/#!forum/digits-users
安裝 DIGITS 的方式有很多種,從 Docker 到 Linux 上的 pre-baked package,或者你也可以從源代碼構(gòu)建。我用的 Mac,所以我就是從源代碼構(gòu)建的。
注:在我的實(shí)踐中,我使用了 GitHub 上未發(fā)布的 DIGITS 版本:https://github.com/NVIDIA/DIGITS/commit/81be5131821ade454eb47352477015d7c09753d9
因?yàn)?DIGITS 只是一些 Python 腳本,所以讓它們工作起來很簡單。在啟動(dòng)服務(wù)器之前你要做的事情是設(shè)置一個(gè)環(huán)境變量,告訴 DIGITS 你的 CAFFE_ROOT 的位置在哪里:
- export CAFFE_ROOT=/path/to/caffe
- ./digits-devserver
注:在 Mac 上,這些服務(wù)器腳本出現(xiàn)了一些問題,可能是因?yàn)槲业?Python 二進(jìn)制文件叫做 python2,其中我只有 python2.7。
你可以在 /usr/bin 中 symlink 它或在你的系統(tǒng)上修改 DIGITS 啟動(dòng)腳本以使用合適的二進(jìn)制文件。
一旦服務(wù)器啟動(dòng),你可以在你的瀏覽器中通過 http://localhost:5000 來完成一切后續(xù)工作。
訓(xùn)練一個(gè)神經(jīng)網(wǎng)絡(luò)
訓(xùn)練神經(jīng)網(wǎng)絡(luò)涉及到幾個(gè)步驟:
1. 準(zhǔn)備一個(gè)帶有分類圖像的數(shù)據(jù)集
2. 定義網(wǎng)絡(luò)架構(gòu)
3. 使用準(zhǔn)備好的數(shù)據(jù)集訓(xùn)練和驗(yàn)證這個(gè)網(wǎng)絡(luò)
下面我們會(huì)做這三個(gè)步驟,以體現(xiàn)從頭開始和使用預(yù)訓(xùn)練的網(wǎng)絡(luò)之間的差異,同時(shí)也展示如何使用 Caffe 和 DIGITS 上最常用的兩個(gè)預(yù)訓(xùn)練的網(wǎng)絡(luò) AlexNet、 GoogLeNet。
對于我們的訓(xùn)練,我們將使用一個(gè)海豚(Dolphins)和海馬(Seahorses)圖像的小數(shù)據(jù)集。這些圖像放置在 data/dolphins-and-seahorses。你至少需要兩個(gè)類別,可以更多(有些我們將使用的網(wǎng)絡(luò)在 1000 多個(gè)類別上進(jìn)行了訓(xùn)練)。我們的目標(biāo)是:給我們的網(wǎng)絡(luò)展示一張圖像,它能告訴我們圖像中的是海豚還是海馬。
準(zhǔn)備數(shù)據(jù)集
- dolphins-and-seahorses/
- dolphin/
- image_0001.jpg
- image_0002.jpg
- image_0003.jpg
- ...
- seahorse/
- image_0001.jpg
- image_0002.jpg
- image_0003.jpg
最簡單的開始方式就是將你的圖片按不同類別建立目錄:
在上圖中的每一個(gè)目錄都是按將要分類的類別建立的,所建文件夾目錄下是將以用于訓(xùn)練和驗(yàn)證的圖片。
問:所有待分類和驗(yàn)證的圖片必須是同樣大小嗎?文件夾的命名有影響嗎?
回答都是「否」。圖片的大小會(huì)在圖片輸入神經(jīng)網(wǎng)絡(luò)之前進(jìn)行規(guī)范化處理,我們最終需要的圖片大小為 256×256 像素的彩色圖片,但是 DIGITS 可以很快地自動(dòng)裁切或縮放(我們采用縮放)我們的圖像。文件夾的命名沒有任何影響——重要的是其所包含的圖片種類。
問:我能對這些類別做更精細(xì)的區(qū)分嗎?
當(dāng)然可以。詳見 https://github.com/NVIDIA/DIGITS/blob/digits-4.0/docs/ImageFolderFormat.md。
我們要用這些圖片來創(chuàng)建一個(gè)新的數(shù)據(jù)集,準(zhǔn)確的說是一個(gè)分類數(shù)據(jù)集(Classification Dataset)。
我們會(huì)使用 DIGITS 的默認(rèn)設(shè)置,并把我們的訓(xùn)練圖片文件路徑設(shè)置到 data/dolphins-and-seahorses 文件夾。如此一來,DIGITS 將會(huì)使用這些標(biāo)簽(dolphin 和 seahorse)來創(chuàng)建一個(gè)圖像縮放過的數(shù)據(jù)集——圖片的大小將會(huì)是 256×256,其中 75% 的為訓(xùn)練圖片,25% 的為測試圖片。
給你的數(shù)據(jù)集起一個(gè)名字,如 dolphins-and-seahorses,然后鼠標(biāo)點(diǎn)擊創(chuàng)建(Create)。
通過上面的步驟我們已經(jīng)創(chuàng)建了一個(gè)數(shù)據(jù)集了,在我的筆記本上只需要 4 秒就可以完成。最終在所建的數(shù)據(jù)集里有 2 個(gè)類別的 92 張訓(xùn)練圖片(其中 49 張 dolphin,43 張 seahorse),另外還有 30 張驗(yàn)證圖片(16 張 dolphin 和 14 張 seahorse)。不得不說這的確是一個(gè)非常小的數(shù)據(jù)集,但是對我們的示范試驗(yàn)和 DIGITS 操作學(xué)習(xí)來說已經(jīng)足夠了,因?yàn)檫@樣網(wǎng)絡(luò)的訓(xùn)練和驗(yàn)證就不會(huì)用掉太長的時(shí)間了。
你可以在這個(gè)數(shù)據(jù)庫文件夾里查看壓縮之后的圖片。
訓(xùn)練嘗試 1:從頭開始
回到 DIGITS 的主頁,我們需要?jiǎng)?chuàng)建一個(gè)新的分類模型(Classification Model):
我們將開始用上一步所建立的 dolphins-and-seahorses 數(shù)據(jù)集來訓(xùn)練模型,仍然使用 DIGITS 的默認(rèn)設(shè)置。對于第一個(gè)神經(jīng)網(wǎng)絡(luò)模型,我們可以從提供的神經(jīng)網(wǎng)絡(luò)架構(gòu)中選取一個(gè)既有的標(biāo)準(zhǔn)模型,即 AlexNet。AlexNet 的網(wǎng)絡(luò)結(jié)構(gòu)在 2012 年的計(jì)算機(jī)視覺競賽 ImageNet 中獲勝過(ImageNet 為計(jì)算機(jī)視覺頂級比賽)。在 ImageNet 競賽里需要完成 120 萬張圖片中 1000 多類圖片的分類。
Caffe 使用結(jié)構(gòu)化文本文件(structured text files)來定義網(wǎng)絡(luò)架構(gòu),其所使用的文本文件是基于谷歌的 Protocol Buffer。你可以閱讀 Caffe 采用的方案:https://github.com/BVLC/caffe/blob/master/src/caffe/proto/caffe.proto。其中大部分內(nèi)容在這一部分的神經(jīng)網(wǎng)絡(luò)訓(xùn)練的時(shí)候都不會(huì)用到,但是了解這些構(gòu)架對于使用者還是很有用的,因?yàn)樵诤竺娴牟襟E里我們將會(huì)對它們進(jìn)行調(diào)整。AlexNet 的 prototxt 文件是這樣的,一個(gè)實(shí)例: https://github.com/BVLC/caffe/blob/master/models/bvlc_alexnet/train_val.prototxt。
我們將會(huì)對這個(gè)神經(jīng)網(wǎng)絡(luò)進(jìn)行 30 次 epochs,這意味著網(wǎng)絡(luò)將會(huì)進(jìn)行學(xué)習(xí)(運(yùn)用我們的訓(xùn)練圖片)并自行測試(運(yùn)用我們的測試圖片),然后根據(jù)訓(xùn)練的結(jié)果調(diào)整網(wǎng)絡(luò)中各項(xiàng)參數(shù)的權(quán)重值,如此重復(fù) 30 次。每一次 epoch 都會(huì)輸出一個(gè)分類準(zhǔn)確值(Accuracy,介于 0% 到 100% 之間,當(dāng)然值越大越好)和一個(gè)損失度(Loss,所有錯(cuò)誤分類的比率,值越小越好)。理想的情況是我們希望所訓(xùn)練的網(wǎng)絡(luò)能夠有較高的準(zhǔn)確率(Accuracy)和較小的損失度(Loss)。
初始訓(xùn)練的時(shí)候,所訓(xùn)練網(wǎng)絡(luò)的準(zhǔn)確率低于 50%。這是情理之中的,因?yàn)榈谝淮?epoch,網(wǎng)絡(luò)只是在隨意猜測圖片的類別然后任意設(shè)置權(quán)重值。經(jīng)過多次 epochs 之后,最后能夠有 87.5% 的準(zhǔn)確率,和 0.37 的損失度。完成 30 次的 epochs 只需不到 6 分鐘的時(shí)間。
我們可以上傳一張圖片或者用一個(gè) URL 地址的圖片來測試訓(xùn)練完的網(wǎng)絡(luò)。我們來測試一些出現(xiàn)在我們訓(xùn)練和測試數(shù)據(jù)集中的圖片:
網(wǎng)絡(luò)的分類結(jié)果非常完美,當(dāng)我們測試一些不屬于我們訓(xùn)練和測試數(shù)據(jù)集的其他圖片時(shí):
分類的準(zhǔn)確率直接掉下來了,誤把 seahorse 分類為 dolphin,更糟糕的是網(wǎng)絡(luò)對這樣的錯(cuò)誤分類有很高的置信度。
事實(shí)是我們的數(shù)據(jù)集太小了,根本無法用來訓(xùn)練一個(gè)足夠好的神經(jīng)網(wǎng)絡(luò)。我們需要數(shù)萬乃至數(shù)百萬張圖片才能訓(xùn)練一個(gè)有用的神經(jīng)網(wǎng)絡(luò),用這么多的圖片也意味著需要很強(qiáng)勁的計(jì)算能力來完成所有的計(jì)算過程。
訓(xùn)練嘗試 2:微調(diào) AlexNet
怎么微調(diào)網(wǎng)絡(luò)
從頭設(shè)計(jì)一個(gè)神經(jīng)網(wǎng)絡(luò),收集足量的用以訓(xùn)練這個(gè)網(wǎng)絡(luò)的數(shù)據(jù)(如,海量的圖片),并在 GPU 上運(yùn)行數(shù)周來完成網(wǎng)絡(luò)的訓(xùn)練,這些條件遠(yuǎn)非我們大多數(shù)人可以擁有。能夠以更加實(shí)際——用較小一些的數(shù)據(jù)集來進(jìn)行訓(xùn)練,我們運(yùn)用一個(gè)稱為遷移學(xué)習(xí)(Transfer Learning)或者說微調(diào)(Fine Tuning)的技術(shù)。Fine tuning 借助深度學(xué)習(xí)網(wǎng)絡(luò)的輸出,運(yùn)用已訓(xùn)練好的神經(jīng)網(wǎng)絡(luò)來完成最初的目標(biāo)識(shí)別。
試想使用神經(jīng)網(wǎng)絡(luò)的過程就好比使用一個(gè)雙目望遠(yuǎn)鏡看遠(yuǎn)處的景物。那么當(dāng)你第一次把雙目望遠(yuǎn)鏡放到眼前的時(shí)候,你看到的是一片模糊。當(dāng)你開始調(diào)焦的時(shí)候,你慢慢可以看出顏色、線、形狀,然后最終你可以分辨出鳥的外形,在此之上你進(jìn)一步調(diào)試從而可以識(shí)別出鳥的種類。
在一個(gè)多層網(wǎng)絡(luò)中,最開始的幾層是用于特征提取的(如,邊線),之后的網(wǎng)絡(luò)層通過這些提取的特征來識(shí)別外形「shape」(如,一個(gè)輪子,一只眼睛),然后這些輸出將會(huì)輸入到最后的分類層,分類層將會(huì)根據(jù)之前所有層的特征積累來確定待分類目標(biāo)的種類(如,判斷為貓還是狗)。一個(gè)神經(jīng)網(wǎng)絡(luò)從像素、線形、眼睛、兩只眼睛的確定位置,這樣的步驟來一步步確立分類目標(biāo)的種類(這里是貓)。
我們在這里所做的就是給新的分類圖片指定一個(gè)已訓(xùn)練好的網(wǎng)絡(luò)用于初始化網(wǎng)絡(luò)的權(quán)重值,而不是用新構(gòu)建網(wǎng)絡(luò)自己的初始權(quán)重。因?yàn)橐延?xùn)練好的網(wǎng)絡(luò)已經(jīng)具備「看」圖片特征的功能的,我們所需要的是這個(gè)已訓(xùn)練的網(wǎng)絡(luò)能「看」我們所建圖片數(shù)據(jù)集——這一具體任務(wù)中特定類型的圖片。我們不需要從頭開始訓(xùn)練大部分的網(wǎng)絡(luò)層——我們只需要將已訓(xùn)練網(wǎng)絡(luò)中已經(jīng)學(xué)習(xí)的層轉(zhuǎn)接到我們新建的分類任務(wù)上來。不同于我們的上一次的實(shí)驗(yàn),在上次實(shí)驗(yàn)中網(wǎng)絡(luò)的初始權(quán)重值是隨機(jī)賦予的,這次實(shí)驗(yàn)中我們直接使用已經(jīng)訓(xùn)練網(wǎng)絡(luò)的最終權(quán)重值作為我們新建網(wǎng)絡(luò)的初始權(quán)重值。但是,必須去除已經(jīng)訓(xùn)練好的網(wǎng)絡(luò)的最后分類層并用我們自己的圖片數(shù)據(jù)集再次訓(xùn)練這個(gè)網(wǎng)絡(luò),即在我們自己的圖片類上微調(diào)已訓(xùn)練的網(wǎng)絡(luò)。
對于這次實(shí)驗(yàn),我們需要一個(gè)與經(jīng)由與我們訓(xùn)練數(shù)據(jù)足夠相似的數(shù)據(jù)集所訓(xùn)練的網(wǎng)絡(luò),只有這樣已訓(xùn)練網(wǎng)絡(luò)的權(quán)重值才對我們有用。幸運(yùn)的是,我們下面所使用的網(wǎng)絡(luò)是在海量數(shù)據(jù)集(自然圖片集 ImageNet)上訓(xùn)練得到的,這樣的已訓(xùn)練網(wǎng)絡(luò)能滿足大部分分類任務(wù)的需要。
這種技術(shù)已經(jīng)被用來做一些很有意思的任務(wù)如醫(yī)學(xué)圖像的眼疾篩查,從海里收集到的顯微圖像中識(shí)別浮游生物物種,給 Flickr 上的圖片進(jìn)行藝術(shù)風(fēng)格分類。
完美的完成這些任務(wù),就像所有的機(jī)器學(xué)習(xí)一樣,你需要很好的理解數(shù)據(jù)以及神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)——你必須對數(shù)據(jù)的過擬合格外小心,你或許需要調(diào)整一些層的設(shè)置,也很有可能需要插入一些新的網(wǎng)絡(luò)層,等等類似的調(diào)整。但是,我們的經(jīng)驗(yàn)表明大部分時(shí)候還是可以完成任務(wù)的「Just work」,而且用我們這么原始的方法去簡單嘗試一下看看結(jié)果如何是很值得的。
上傳預(yù)訓(xùn)練網(wǎng)絡(luò)
在我們的第一次嘗試中,我們使用了 AlexNet 的架構(gòu),但是網(wǎng)絡(luò)各層的權(quán)重是隨機(jī)分布的。我們需要做的就是需要下載使用一個(gè)已經(jīng)經(jīng)過大量數(shù)據(jù)集訓(xùn)練的 AlexNet。
AlexNet 的快照(Snapshots)如下,可供下載:https://github.com/BVLC/caffe/tree/master/models/bvlc_alexnet。我們需要一個(gè)二進(jìn)制文件 .caffemodel,含有訓(xùn)練好的權(quán)重,可供下載 http://dl.caffe.berkeleyvision.org/bvlc_alexnet.caffemodel。在你下載這些與訓(xùn)練模型的時(shí)候,讓我們來趁機(jī)多學(xué)點(diǎn)東西。2014 年的 ImageNet 大賽中,谷歌利用其開源的 GoogLeNet (https://research.google.com/pubs/pub43022.html)(一個(gè) 22 層的神經(jīng)網(wǎng)絡(luò))贏得了比賽。GoogLeNet 的快照如下,可供下載: https://github.com/BVLC/caffe/tree/master/models/bvlc_googlenet。在具備了所有的預(yù)訓(xùn)練權(quán)重之后,我們還需要.caffemodel 文件,可供下載:http://dl.caffe.berkeleyvision.org/bvlc_googlenet.caffemodel.
有了 .caffemodel 文件之后,我們既可以將它們上傳到 DIGITS 當(dāng)中。在 DIGITS 的主頁當(dāng)中找到預(yù)訓(xùn)練模型(Pretrained Models)的標(biāo)簽,選擇上傳預(yù)訓(xùn)練模型(Upload Pretrained Model):
對于這些預(yù)訓(xùn)練的模型,我們可以使用 DIGITS 的默認(rèn)值(例如,大小為 256×256 像素的彩色圖片)。我們只需要提供 Weights (.caffemodel) 和 Model Definition (original.prototxt)。點(diǎn)擊這些按鈕來選擇文件。
模型的定義,GoogLeNet 我們可以使用 https://github.com/BVLC/caffe/blob/master/models/bvlc_googlenet/train_val.prototxt,AlexNet 可以使用 https://github.com/BVLC/caffe/blob/master/models/bvlc_alexnet/train_val.prototxt。我們不打算使用這些網(wǎng)絡(luò)的分類標(biāo)簽,所以我們可以直接添加一個(gè) labels.txt 文件:
在 AlexNet 和 GoogLeNet 都重復(fù)這一過程,因?yàn)槲覀冊谥蟮牟襟E當(dāng)中兩者我們都會(huì)用到。
問題:有其他的神經(jīng)網(wǎng)絡(luò)能作為微調(diào)的基礎(chǔ)嗎?
回答:Caffe Model Zoo 有許多其他預(yù)訓(xùn)練神經(jīng)網(wǎng)絡(luò)可供使用,詳情請查看 https://github.com/BVLC/caffe/wiki/Model-Zoo
使用預(yù)訓(xùn)練 Caffe 模型進(jìn)行人工神經(jīng)網(wǎng)絡(luò)訓(xùn)練就類似于從頭開始實(shí)現(xiàn),雖然我們只需要做一些調(diào)整。首先我們需要將學(xué)習(xí)速率由 0.01 調(diào)整到 0.001,因?yàn)槲覀兿陆挡介L不需要這么大(我們會(huì)進(jìn)行微調(diào))。我們還將使用預(yù)訓(xùn)練網(wǎng)絡(luò)(Pretrained Network)并根據(jù)實(shí)際修改它。
在預(yù)訓(xùn)練模型的定義(如原文本)中,我們需要對最終完全連接層(輸出結(jié)果分類的地方)的所有 references 重命名。我們這樣做是因?yàn)槲覀兿MP湍軓默F(xiàn)在的數(shù)據(jù)集重新學(xué)習(xí)新的分類,而不是使用以前最原始的訓(xùn)練數(shù)據(jù)(我們想將當(dāng)前最后一層丟棄)。我們必須將最后的全連接層由「fc8」重命名為一些其他的(如 fc9)。最后我們還需要將分類類別從 1000 調(diào)整為 2,這里需要調(diào)整 num_output 為 2。
下面是我們需要做的一些調(diào)整代碼:
- @@ -332,8 +332,8 @@
- }
- layer {- name: "fc8"+ name: "fc9"
- type: "InnerProduct"
- bottom: "fc7"- top: "fc8"+ top: "fc9"
- param {
- lr_mult: 1@@ -345,5 +345,5 @@
- }
- inner_product_param {- num_output: 1000+ num_output: 2
- weight_filler {
- type: "gaussian"@@ -359,5 +359,5 @@
- name: "accuracy"
- type: "Accuracy"- bottom: "fc8"+ bottom: "fc9"
- bottom: "label"
- top: "accuracy"@@ -367,5 +367,5 @@
- name: "loss"
- type: "SoftmaxWithLoss"- bottom: "fc8"+ bottom: "fc9"
- bottom: "label"
- top: "loss"@@ -375,5 +375,5 @@
- name: "softmax"
- type: "Softmax"- bottom: "fc8"+ bottom: "fc9"
- top: "softmax"
- include { stage: "deploy" }
我已經(jīng)將所有的改進(jìn)文件放在 src/alexnet-customized.prototxt 里面。
這一次,我們的準(zhǔn)確率由 60% 多先是上升到 87.5%,然后到 96% 一路到 100%,同時(shí)損失度也穩(wěn)步下降。五分鐘后,我們的準(zhǔn)確率到達(dá)了 100%,損失也只有 0.0009。
測試海馬圖像時(shí)以前的網(wǎng)絡(luò)會(huì)出錯(cuò),現(xiàn)在我們看到完全相反的結(jié)果,即使是小孩畫的海馬,系統(tǒng)也 100% 確定是海馬,海豚的情況也一樣。
即使你認(rèn)為可能很困難的圖像,如多個(gè)海豚擠在一起,并且它們的身體大部分在水下,系統(tǒng)還是能識(shí)別。
訓(xùn)練嘗試 3:微調(diào) GoogLeNet
像前面我們微調(diào) AlexNet 模型那樣,同樣我們也能用 GoogLeNet。修改這個(gè)網(wǎng)絡(luò)會(huì)有點(diǎn)棘手,因?yàn)槟阋呀?jīng)定義了三層全連接層而不是只有一層
在這個(gè)案例中微調(diào) GoogLeNet,我們需要再次創(chuàng)建一個(gè)新的分類模型:我們需要重命名三個(gè)全連接分類層的所有 references,即 loss1/classifier、loss2/classifier 和 loss3/classifier,并重新定義結(jié)果類別數(shù)(num_output: 2)。下面是我們需要將三個(gè)分類層重新命名和從 1000 改變輸出類別數(shù)為 2 的一些代碼實(shí)現(xiàn)。
- @@ -917,10 +917,10 @@
- exclude { stage: "deploy" }
- }
- layer {- name: "loss1/classifier"+ name: "loss1a/classifier"
- type: "InnerProduct"
- bottom: "loss1/fc"- top: "loss1/classifier"+ top: "loss1a/classifier"
- param {
- lr_mult: 1
- decay_mult: 1@@ -930,7 +930,7 @@
- decay_mult: 0
- }
- inner_product_param {- num_output: 1000+ num_output: 2
- weight_filler {
- type: "xavier"
- std: 0.0009765625@@ -945,7 +945,7 @@
- layer {
- name: "loss1/loss"
- type: "SoftmaxWithLoss"- bottom: "loss1/classifier"+ bottom: "loss1a/classifier"
- bottom: "label"
- top: "loss1/loss"
- loss_weight: 0.3@@ -954,7 +954,7 @@
- layer {
- name: "loss1/top-1"
- type: "Accuracy"- bottom: "loss1/classifier"+ bottom: "loss1a/classifier"
- bottom: "label"
- top: "loss1/accuracy"
- include { stage: "val" }@@ -962,7 +962,7 @@
- layer {
- name: "loss1/top-5"
- type: "Accuracy"- bottom: "loss1/classifier"+ bottom: "loss1a/classifier"
- bottom: "label"
- top: "loss1/accuracy-top5"
- include { stage: "val" }@@ -1705,10 +1705,10 @@
- exclude { stage: "deploy" }
- }
- layer {- name: "loss2/classifier"+ name: "loss2a/classifier"
- type: "InnerProduct"
- bottom: "loss2/fc"- top: "loss2/classifier"+ top: "loss2a/classifier"
- param {
- lr_mult: 1
- decay_mult: 1@@ -1718,7 +1718,7 @@
- decay_mult: 0
- }
- inner_product_param {- num_output: 1000+ num_output: 2
- weight_filler {
- type: "xavier"
- std: 0.0009765625@@ -1733,7 +1733,7 @@
- layer {
- name: "loss2/loss"
- type: "SoftmaxWithLoss"- bottom: "loss2/classifier"+ bottom: "loss2a/classifier"
- bottom: "label"
- top: "loss2/loss"
- loss_weight: 0.3@@ -1742,7 +1742,7 @@
- layer {
- name: "loss2/top-1"
- type: "Accuracy"- bottom: "loss2/classifier"+ bottom: "loss2a/classifier"
- bottom: "label"
- top: "loss2/accuracy"
- include { stage: "val" }@@ -1750,7 +1750,7 @@
- layer {
- name: "loss2/top-5"
- type: "Accuracy"- bottom: "loss2/classifier"+ bottom: "loss2a/classifier"
- bottom: "label"
- top: "loss2/accuracy-top5"
- include { stage: "val" }@@ -2435,10 +2435,10 @@
- }
- }
- layer {- name: "loss3/classifier"+ name: "loss3a/classifier"
- type: "InnerProduct"
- bottom: "pool5/7x7_s1"- top: "loss3/classifier"+ top: "loss3a/classifier"
- param {
- lr_mult: 1
- decay_mult: 1@@ -2448,7 +2448,7 @@
- decay_mult: 0
- }
- inner_product_param {- num_output: 1000+ num_output: 2
- weight_filler {
- type: "xavier"
- }@@ -2461,7 +2461,7 @@
- layer {
- name: "loss3/loss"
- type: "SoftmaxWithLoss"- bottom: "loss3/classifier"+ bottom: "loss3a/classifier"
- bottom: "label"
- top: "loss"
- loss_weight: 1@@ -2470,7 +2470,7 @@
- layer {
- name: "loss3/top-1"
- type: "Accuracy"- bottom: "loss3/classifier"+ bottom: "loss3a/classifier"
- bottom: "label"
- top: "accuracy"
- include { stage: "val" }@@ -2478,7 +2478,7 @@
- layer {
- name: "loss3/top-5"
- type: "Accuracy"- bottom: "loss3/classifier"+ bottom: "loss3a/classifier"
- bottom: "label"
- top: "accuracy-top5"
- include { stage: "val" }@@ -2489,7 +2489,7 @@
- layer {
- name: "softmax"
- type: "Softmax"- bottom: "loss3/classifier"+ bottom: "loss3a/classifier"
- top: "softmax"
- include { stage: "deploy" }
- }
我己經(jīng)將完整的文件放在 src/googlenet-customized.prototxt 里面。
問題:這些神經(jīng)網(wǎng)絡(luò)的原文本(prototext)定義需要做什么修改嗎?我們修改了全連接層名和輸出結(jié)果分類類別數(shù),那么在什么情況下其它參數(shù)也能或也需要修改的?
回答:問得好,這也是我有一些疑惑的地方。例如,我知道我們能「固定」確切的神經(jīng)網(wǎng)絡(luò)層級,并保證層級之間的權(quán)重不改變。但是要做其它的一些改變就涉及到理解我們的神經(jīng)網(wǎng)絡(luò)層級是如何起作用的,這已經(jīng)超出了這份入門向?qū)У姆秶?,同樣也超出了這份向?qū)ё髡攥F(xiàn)有的能力。
就像我們對 AlexNet 進(jìn)行微調(diào),將下降的學(xué)習(xí)速率由 0.01 減少十倍到 0.001 一樣。
問:還有什么修改是對這些網(wǎng)絡(luò)微調(diào)有意義的?遍歷所有數(shù)據(jù)的次數(shù)(numbers of epochs)不同怎么樣,改變批量梯度下降的大小(batch sizes)怎么樣,求解器的類型(Adam、 AdaDelta 和 AdaGrad 等)呢?還有下降學(xué)習(xí)速率、策略(Exponential Decay、Inverse Decay 和 Sigmoid Decay 等)、步長和 gamma 值呢?
問得好,這也是我有所疑惑的。我對這些只有一個(gè)模糊的理解,如果你知道在訓(xùn)練中如何修改這些值,那么我們很可能做出些改進(jìn),并且這需要更好的文檔。
因?yàn)?GoogLeNet 比 AlexNet 有更復(fù)雜的網(wǎng)絡(luò)構(gòu)架,所以微調(diào)需要更多的時(shí)間。在我的筆記本電腦上,用我們的數(shù)據(jù)集重新訓(xùn)練 GoogLeNet 需要 10 分鐘,這樣才能實(shí)現(xiàn) 100% 的準(zhǔn)確率,同時(shí)損失函數(shù)值只有 0.0070。
正如我們看到的 AlexNet 微調(diào)版本,我們修改過的 GoogLeNet 表現(xiàn)得十分驚人,是我們目前最好的。
使用我們的模型
我們的網(wǎng)絡(luò)在訓(xùn)練和檢測之后,就可以下載并且使用了。我們利用 DIGITS 訓(xùn)練的每一個(gè)模型都有了一下載模型(Download Model)鍵,這也是我們在訓(xùn)練過程中選擇不同 snapshots 的一種方法(例如 Epoch #30):
在點(diǎn)擊 Download Model 之后,你就會(huì)下載一個(gè) tar.gz 的文檔,里面包含以下文件:
- deploy.prototxt
- mean.binaryproto
- solver.prototxt
- info.json
- original.prototxt
- labels.txt
- snapshot_iter_90.caffemodel
- train_val.prototxt
在 Caffe 文檔中對我們所建立的模型使用有一段非常好的描述。如下:
一個(gè)網(wǎng)絡(luò)是由其設(shè)計(jì),也就是設(shè)計(jì)(prototxt)和權(quán)重(.caffemodel)決定。在網(wǎng)絡(luò)被訓(xùn)練的過程中,網(wǎng)絡(luò)權(quán)重的當(dāng)前狀態(tài)被存儲(chǔ)在一個(gè).caffemodel 中。這些東西我們可以從訓(xùn)練/檢測階段移到生產(chǎn)階段。在它的當(dāng)前狀態(tài)中,網(wǎng)絡(luò)的設(shè)計(jì)并不是為了部署的目的。在我們可以將我們的網(wǎng)絡(luò)作為產(chǎn)品發(fā)布之前,我們通常需要通過幾種方法對它進(jìn)行修改:
1. 移除用來訓(xùn)練的數(shù)據(jù)層,因?yàn)樵诜诸悤r(shí),我們已經(jīng)不再為數(shù)據(jù)提供標(biāo)簽了。
2. 移除所有依賴于數(shù)據(jù)標(biāo)簽的層。
3. 設(shè)置接收數(shù)據(jù)的網(wǎng)絡(luò)。
4. 讓網(wǎng)絡(luò)輸出結(jié)果。
DIGITS 已經(jīng)為我們做了這些工作,它已經(jīng)將我們 prototxt 文件中所有不同的版本都分離了出來。這些文檔我們在使用網(wǎng)絡(luò)時(shí)會(huì)用到:
- deploy.prototxt -是關(guān)于網(wǎng)絡(luò)的定義,準(zhǔn)備接收圖像輸入數(shù)據(jù)
- mean.binaryproto - 我們的模型需要我們減去它處理的每張圖像的圖像均值,所產(chǎn)生的就是平均圖像(mean image)。
- labels.txt - 標(biāo)簽列表 (dolphin, seahorse),以防我們想要把它們打印出來,否則只有類別編號(hào)。
- snapshot_iter_90.caffemodel -這些是我們網(wǎng)絡(luò)的訓(xùn)練權(quán)重。
利用這些文件,我們可以通過多種方式對新的圖像進(jìn)行分類。例如,在 CAFFE_ROOT 中,我們可以使用 build/examples/cpp_classification/classification.bin 來對一個(gè)圖像進(jìn)行分類:
- $ cd $CAFFE_ROOT/build/examples/cpp_classification
- $ ./classification.bin deploy.prototxt snapshot_iter_90.caffemodel mean.binaryproto labels.txt dolphin1.jpg
這會(huì)產(chǎn)生很多的調(diào)試文本,后面會(huì)跟著對這兩種分類的預(yù)測結(jié)果:
- 0.9997 -「dolphin」
- 0.0003 -「seahorse」
你可以在這個(gè) Caffe 案例中查看完整的 C++ 源碼:https://github.com/BVLC/caffe/tree/master/examples
使用 Python 界面和 DIGITS 進(jìn)行分類的案例:https://github.com/NVIDIA/DIGITS/tree/master/examples/classification
最后,Caffe 的案例中還有一個(gè)非常好的 Python 演示:https://github.com/BVLC/caffe/blob/master/examples/00-classification.ipynb
我希望可以有更多更好的代碼案例、API 和預(yù)先建立的模型等呈現(xiàn)給大家。老實(shí)說,我找到的大多數(shù)代碼案例都非常的簡短,并且文檔介紹很少——Caffe 的文檔雖然有很多,但也有好有壞。對我來說,這似乎意味著會(huì)有人為初學(xué)者建立比 Caffe 更高級的工具。如果說在高級語言中出現(xiàn)了更加簡單的模型,我可以用我們的模型「做正確的事情」;應(yīng)該有人將這樣的設(shè)想付諸行動(dòng),讓使用 Caffe 模型變得像使用 DIGITS 訓(xùn)練它們一樣簡單。當(dāng)然我們不需要對這個(gè)模型或是 Caffe 的內(nèi)部了解那么多。雖然目前我還沒有使用過 DeepDetect,但是它看起來非常的有趣,另外仍然還有其他我不知道的工具。
結(jié)果
文章開頭提到,我們的目標(biāo)是編寫一個(gè)使用神經(jīng)網(wǎng)絡(luò)對 data/untrained-samples 中所有的圖像進(jìn)行高準(zhǔn)確度預(yù)測的程序。這些海豚和海馬的圖像是在訓(xùn)練數(shù)據(jù)或是驗(yàn)證數(shù)據(jù)時(shí)候從未使用過的。
未被訓(xùn)練過的海豚圖像
未被訓(xùn)練過的海馬圖像
接下來,讓我們一起來看看在這一挑戰(zhàn)當(dāng)中存在的三次嘗試的結(jié)果:
模型嘗試 1: 從零開始構(gòu)建 AlexNet(第 3 位)
模型嘗試 2:微調(diào) AlexNet(第 2 位)
模型嘗試 3:微調(diào) GoogLeNet(第 1 位)
結(jié)論
我們的模型運(yùn)作非常好,這可能是通過調(diào)整一個(gè)預(yù)訓(xùn)練的網(wǎng)絡(luò)完成的。很顯然,海豚 vs. 海馬的例子有一些牽強(qiáng),數(shù)據(jù)集也非常的有限——如果我們想擁有一個(gè)強(qiáng)大的網(wǎng)絡(luò),那我們確實(shí)需要更多、更好的數(shù)據(jù)。但因?yàn)槲覀兊哪繕?biāo)是去檢測神經(jīng)網(wǎng)絡(luò)的工具和工作流程,所以這其實(shí)是一種很理想的情況,尤其是它不需要昂貴的設(shè)備或是花費(fèi)大量的時(shí)間。
綜上所述,我希望這些經(jīng)驗(yàn)?zāi)軌蜃屇切┮恢睂C(jī)器學(xué)習(xí)望而卻步的人擺脫對開始學(xué)習(xí)的恐懼。在你看到它的作用之后,再?zèng)Q定是否要在學(xué)習(xí)積極學(xué)習(xí)和神經(jīng)網(wǎng)絡(luò)理論中投入時(shí)間要簡單很多?,F(xiàn)在你已經(jīng)對它的設(shè)置和工作方法都已經(jīng)有所了解,之后你便可以嘗試去做一些分類。你也可以利用 Caffe 和 DIGITS 去做一些其他的事情,例如,在圖像中尋找物體,或是進(jìn)行圖像分割。
【本文為51CTO專欄“機(jī)器之心”原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號(hào)(ID:almosthuman2014)獲取授權(quán)】