注意: 本教程適用于對Tensorflow有豐富經驗的用戶,并假定用戶有機器學習相關領域的專業(yè)知識和經驗。
對CIFAR-10 數據集的分類是機器學習中一個公開的基準測試問題,其任務是對一組大小為32x32的RGB圖像進行分類,這些圖像涵蓋了10個類別:
飛機, 汽車, 鳥, 貓, 鹿, 狗, 青蛙, 馬, 船以及卡車。
http://wiki.jikexueyuan.com/project/tensorflow-zh/images/cifar_samples.png" alt="CIFAR-10 Samples" title="CIFAR-10 Samples, from http://www.cs.toronto.edu/~kriz/cifar.html" />
想了解更多信息請參考CIFAR-10 page,以及Alex Krizhevsky寫的技術報告
本教程的目標是建立一個用于識別圖像的相對較小的卷積神經網絡,在這一過程中,本教程會:
選擇CIFAR-10是因為它的復雜程度足以用來檢驗TensorFlow中的大部分功能,并可將其擴展為更大的模型。與此同時由于模型較小所以訓練速度很快,比較適合用來測試新的想法,檢驗新的技術。
CIFAR-10 教程演示了在TensorFlow上構建更大更復雜模型的幾個種重要內容:
我們也提供了模型的多GPU版本,用以表明:
我們希望本教程給大家開了個頭,使得在Tensorflow上可以為視覺相關工作建立更大型的CNN模型
本教程中的模型是一個多層架構,由卷積層和非線性層(nonlinearities)交替多次排列后構成。這些層最終通過全連通層對接到softmax分類器上。這一模型除了最頂部的幾層外,基本跟Alex Krizhevsky提出的模型一致。
在一個GPU上經過幾個小時的訓練后,該模型最高可以達到86%的精度。細節(jié)請查看下面的描述以及代碼。模型中包含了1,068,298個學習參數,對一副圖像進行分類大概需要19.5M個乘加操作。
本教程的代碼位于tensorflow/models/image/cifar10/.
| 文件 | 作用 |
|---|---|
cifar10_input.py |
讀取本地CIFAR-10的二進制文件格式的內容。 |
cifar10.py |
建立CIFAR-10的模型。 |
cifar10_train.py |
在CPU或GPU上訓練CIFAR-10的模型。 |
cifar10_multi_gpu_train.py |
在多GPU上訓練CIFAR-10的模型。 |
cifar10_eval.py |
評估CIFAR-10模型的預測性能。 |
CIFAR-10 網絡模型部分的代碼位于
cifar10.py.
完整的訓練圖中包含約765個操作。但是我們發(fā)現(xiàn)通過下面的模塊來構造訓練圖可以最大限度的提高代碼復用率:
inputs() 、 distorted_inputs()等一些操作,分別用于讀取CIFAR的圖像并進行預處理,做為后續(xù)評估和訓練的輸入; inference()等一些操作,用于進行統(tǒng)計計算,比如在提供的圖像進行分類;
adds operations that perform inference, i.e. classification, on supplied images.loss() and train()等一些操作,用于計算損失、計算梯度、進行變量更新以及呈現(xiàn)最終結果。輸入模型是通過 inputs() 和distorted_inputs()函數建立起來的,這2個函數會從CIFAR-10二進制文件中讀取圖片文件,由于每個圖片的存儲字節(jié)數是固定的,因此可以使用tf.FixedLengthRecordReader函數。更多的關于Reader類的功能可以查看Reading Data。
圖片文件的處理流程如下:
對于訓練,我們另外采取了一系列隨機變換的方法來人為的增加數據集的大?。?/p>
可以在Images頁的列表中查看所有可用的變換,對于每個原始圖我們還附帶了一個image_summary,以便于在TensorBoard中查看。這對于檢查輸入圖像是否正確十分有用。

從磁盤上加載圖像并進行變換需要花費不少的處理時間。為了避免這些操作減慢訓練過程,我們在16個獨立的線程中并行進行這些操作,這16個線程被連續(xù)的安排在一個TensorFlow隊列中。
模型的預測流程由inference()構造,該函數會添加必要的操作步驟用于計算預測值的 logits,其對應的模型組織方式如下所示:
| Layer 名稱 | 描述 |
|---|---|
conv1 |
實現(xiàn)卷積 以及 rectified linear activation. |
pool1 |
max pooling. |
norm1 |
局部響應歸一化. |
conv2 |
卷積 and rectified linear activation. |
norm2 |
局部響應歸一化. |
pool2 |
max pooling. |
local3 |
基于修正線性激活的全連接層. |
local4 |
基于修正線性激活的全連接層. |
softmax_linear |
進行線性變換以輸出 logits. |
這里有一個由TensorBoard繪制的圖形,用于描述模型建立過程中經過的步驟:

練習:
inference的輸出是未歸一化的logits,嘗試使用tf.softmax()修改網絡架構后返回歸一化的預測值。
inputs() 和 inference() 函數提供了評估模型時所需的所有構件,現(xiàn)在我們把講解的重點從構建一個模型轉向訓練一個模型。
練習:
inference()中的模型跟cuda-convnet中描述的CIFAR-10模型有些許不同,其差異主要在于其頂層不是全連接層而是局部連接層,可以嘗試修改網絡架構來準確的復制全連接模型。
訓練一個可進行N維分類的網絡的常用方法是使用多項式邏輯回歸,又被叫做softmax 回歸。Softmax 回歸在網絡的輸出層上附加了一個softmax nonlinearity,并且計算歸一化的預測值和label的1-hot encoding的交叉熵。在正則化過程中,我們會對所有學習變量應用權重衰減損失。模型的目標函數是求交叉熵損失和所有權重衰減項的和,loss()函數的返回值就是這個值。
在TensorBoard中使用scalar_summary來查看該值的變化情況:
http://wiki.jikexueyuan.com/project/tensorflow-zh/images/cifar_loss.png" alt="CIFAR-10 Loss" title="CIFAR-10 Total Loss" />
我們使用標準的梯度下降算法來訓練模型(也可以在Training中看看其他方法),其學習率隨時間以指數形式衰減。
http://wiki.jikexueyuan.com/project/tensorflow-zh/images/cifar_lr_decay.png" alt="CIFAR-10 Learning Rate Decay" title="CIFAR-10 Learning Rate Decay" />
train() 函數會添加一些操作使得目標函數最小化,這些操作包括計算梯度、更新學習變量(詳細信息請查看GradientDescentOptimizer)。train() 函數最終會返回一個用以對一批圖像執(zhí)行所有計算的操作步驟,以便訓練并更新模型。
我們已經把模型建立好了,現(xiàn)在通過執(zhí)行腳本cifar10_train.py來啟動訓練過程。
python cifar10_train.py
注意: 當第一次在CIFAR-10教程上啟動任何任務時,會自動下載CIFAR-10數據集,該數據集大約有160M大小,因此第一次運行時泡杯咖啡小棲一會吧。
你應該可以看到如下類似的輸出:
Filling queue with 20000 CIFAR images before starting to train. This will take a few minutes.
2015-11-04 11:45:45.927302: step 0, loss = 4.68 (2.0 examples/sec; 64.221 sec/batch)
2015-11-04 11:45:49.133065: step 10, loss = 4.66 (533.8 examples/sec; 0.240 sec/batch)
2015-11-04 11:45:51.397710: step 20, loss = 4.64 (597.4 examples/sec; 0.214 sec/batch)
2015-11-04 11:45:54.446850: step 30, loss = 4.62 (391.0 examples/sec; 0.327 sec/batch)
2015-11-04 11:45:57.152676: step 40, loss = 4.61 (430.2 examples/sec; 0.298 sec/batch)
2015-11-04 11:46:00.437717: step 50, loss = 4.59 (406.4 examples/sec; 0.315 sec/batch)
...
腳本會在每10步訓練過程后打印出總損失值,以及最后一批數據的處理速度。下面是幾點注釋:
第一批數據會非常的慢(大概要幾分鐘時間),因為預處理線程要把20,000個待處理的CIFAR圖像填充到重排隊列中;
打印出來的損失值是最近一批數據的損失值的均值。請記住損失值是交叉熵和權重衰減項的和;
練習: 當實驗時,第一階段的訓練時間有時會非常的長,長到足以讓人生厭。可以嘗試減少初始化時初始填充到隊列中圖片數量來改變這種情況。在
cifar10.py中搜索NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN并修改之。
cifar10_train.py 會周期性的在檢查點文件中保存模型中的所有參數,但是不會對模型進行評估。cifar10_eval.py會使用該檢查點文件來測試預測性能(詳見下面的描述:評估模型)。
如果按照上面的步驟做下來,你應該已經開始訓練一個CIFAR-10模型了。恭喜你!
cifar10_train.py輸出的終端信息中提供了關于模型如何訓練的一些信息,但是我們可能希望了解更多關于模型訓練時的信息,比如:
TensorBoard提供了該功能,可以通過cifar10_train.py中的SummaryWriter周期性的獲取并顯示這些數據。
比如我們可以在訓練過程中查看local3的激活情況,以及其特征維度的稀疏情況:


相比于總損失,在訓練過程中的單項損失尤其值得人們的注意。但是由于訓練中使用的數據批量比較小,損失值中夾雜了相當多的噪聲。在實踐過程中,我們也發(fā)現(xiàn)相比于原始值,損失值的移動平均值顯得更為有意義。請參閱腳本ExponentialMovingAverage了解如何實現(xiàn)。
現(xiàn)在可以在另一部分數據集上來評估訓練模型的性能。腳本文件cifar10_eval.py對模型進行了評估,利用 inference()函數重構模型,并使用了在評估數據集所有10,000張CIFAR-10圖片進行測試。最終計算出的精度為1:N,N=預測值中置信度最高的一項與圖片真實label匹配的頻次。(It calculates the precision at 1: how often the top prediction matches the true label of the image)。
為了監(jiān)控模型在訓練過程中的改進情況,評估用的腳本文件會周期性的在最新的檢查點文件上運行,這些檢查點文件是由cifar10_train.py產生。
python cifar10_eval.py
注意:不要在同一塊GPU上同時運行訓練程序和評估程序,因為可能會導致內存耗盡。盡可能的在其它單獨的GPU上運行評估程序,或者在同一塊GPU上運行評估程序時先掛起訓練程序。
你可能會看到如下所示輸出:
2015-11-06 08:30:44.391206: precision @ 1 = 0.860
...
評估腳本只是周期性的返回precision@1 (The script merely returns the precision @ 1 periodically)--在該例中返回的準確率是86%。cifar10_eval.py 同時也返回其它一些可以在TensorBoard中進行可視化的簡要信息。可以通過這些簡要信息在評估過程中進一步的了解模型。
訓練腳本會為所有學習變量計算其移動均值,評估腳本則直接將所有學習到的模型參數替換成對應的移動均值。這一替代方式可以在評估過程中提升模型的性能。
練習: 通過precision @ 1測試發(fā)現(xiàn),使用均值參數可以將預測性能提高約3%,在
cifar10_eval.py中嘗試修改為不采用均值參數的方式,并確認由此帶來的預測性能下降。
現(xiàn)代的工作站可能包含多個GPU進行科學計算。TensorFlow可以利用這一環(huán)境在多個GPU卡上運行訓練程序。
在并行、分布式的環(huán)境中進行訓練,需要對訓練程序進行協(xié)調。對于接下來的描述,術語模型拷貝(model replica)特指在一個數據子集中訓練出來的模型的一份拷貝。
如果天真的對模型參數的采用異步方式更新將會導致次優(yōu)的訓練性能,這是因為我們可能會基于一個舊的模型參數的拷貝去訓練一個模型。但與此相反采用完全同步更新的方式,其速度將會變得和最慢的模型一樣慢(Conversely, employing fully synchronous updates will be as slow as the slowest model replica.)。
在具有多個GPU的工作站中,每個GPU的速度基本接近,并且都含有足夠的內存來運行整個CIFAR-10模型。因此我們選擇以下方式來設計我們的訓練系統(tǒng):
在每個GPU上放置單獨的模型副本;
下圖示意了該模型的結構::

可以看到,每一個GPU會用一批獨立的數據計算梯度和估計值。這種設置可以非常有效的將一大批數據分割到各個GPU上。
這一機制要求所有GPU能夠共享模型參數。但是眾所周知在GPU之間傳輸數據非常的慢,因此我們決定在CPU上存儲和更新所有模型的參數(對應圖中綠色矩形的位置)。這樣一來,GPU在處理一批新的數據之前會更新一遍的參數。
圖中所有的GPU是同步運行的。所有GPU中的梯度會累積并求平均值(綠色方框部分)。模型參數會利用所有模型副本梯度的均值來更新。
在多個設備中設置變量和操作時需要做一些特殊的抽象。
我們首先需要把在單個模型拷貝中計算估計值和梯度的行為抽象到一個函數中。在代碼中,我們稱這個抽象對象為“tower”。對于每一個“tower”我們都需要設置它的兩個屬性:
在一個tower中為所有操作設定一個唯一的名稱。tf.name_scope()通過添加一個范圍前綴來提供該唯一名稱。比如,第一個tower中的所有操作都會附帶一個前綴tower_0,示例:tower_0/conv1/Conv2D;
tf.device() 提供該信息。比如,在第一個tower中的所有操作都位于 device('/gpu:0')范圍中,暗含的意思是這些操作應該運行在第一塊GPU上;為了在多個GPU上共享變量,所有的變量都綁定在CPU上,并通過tf.get_variable()訪問??梢圆榭?a rel="nofollow" >Sharing Variables以了解如何共享變量。
如果你的機器上安裝有多塊GPU,你可以通過使用cifar10_multi_gpu_train.py腳本來加速模型訓練。該腳本是訓練腳本的一個變種,使用多個GPU實現(xiàn)模型并行訓練。
python cifar10_multi_gpu_train.py --num_gpus=2
訓練腳本的輸出如下所示:
Filling queue with 20000 CIFAR images before starting to train. This will take a few minutes.
2015-11-04 11:45:45.927302: step 0, loss = 4.68 (2.0 examples/sec; 64.221 sec/batch)
2015-11-04 11:45:49.133065: step 10, loss = 4.66 (533.8 examples/sec; 0.240 sec/batch)
2015-11-04 11:45:51.397710: step 20, loss = 4.64 (597.4 examples/sec; 0.214 sec/batch)
2015-11-04 11:45:54.446850: step 30, loss = 4.62 (391.0 examples/sec; 0.327 sec/batch)
2015-11-04 11:45:57.152676: step 40, loss = 4.61 (430.2 examples/sec; 0.298 sec/batch)
2015-11-04 11:46:00.437717: step 50, loss = 4.59 (406.4 examples/sec; 0.315 sec/batch)
...
需要注意的是默認的GPU使用數是1,此外,如果你的機器上只有一個GPU,那么所有的計算都只會在一個GPU上運行,即便你可能設置的是N個。
練習:
cifar10_train.py中的批處理大小默認配置是128。嘗試在2個GPU上運行cifar10_multi_gpu_train.py腳本,并且設定批處理大小為64,然后比較2種方式的訓練速度。
恭喜你! 你已經完成了CIFAR-10教程。 如果你對開發(fā)和訓練自己的圖像分類系統(tǒng)感興趣,我們推薦你新建一個基于該教程的分支,并修改其中的內容以建立解決您問題的圖像分類系統(tǒng)。
練習: 下載Street View House Numbers (SVHN) 數據集。新建一個CIFAR-10教程的分支,并將輸入數據替換成SVHN。嘗試改變網絡結構以提高預測性能。
原文:Convolutional Neural Networks 翻譯:oskycar 校對:KK4SBB