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

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

練習(xí):
inference的輸出是未歸一化的logits,嘗試使用tf.softmax()修改網(wǎng)絡(luò)架構(gòu)后返回歸一化的預(yù)測值。
inputs() 和 inference() 函數(shù)提供了評估模型時所需的所有構(gòu)件,現(xiàn)在我們把講解的重點從構(gòu)建一個模型轉(zhuǎn)向訓(xùn)練一個模型。
練習(xí):
inference()中的模型跟cuda-convnet中描述的CIFAR-10模型有些許不同,其差異主要在于其頂層不是全連接層而是局部連接層,可以嘗試修改網(wǎng)絡(luò)架構(gòu)來準(zhǔn)確的復(fù)制全連接模型。
訓(xùn)練一個可進行N維分類的網(wǎng)絡(luò)的常用方法是使用多項式邏輯回歸,又被叫做softmax 回歸。Softmax 回歸在網(wǎng)絡(luò)的輸出層上附加了一個softmax nonlinearity,并且計算歸一化的預(yù)測值和label的1-hot encoding的交叉熵。在正則化過程中,我們會對所有學(xué)習(xí)變量應(yīng)用權(quán)重衰減損失。模型的目標(biāo)函數(shù)是求交叉熵損失和所有權(quán)重衰減項的和,loss()函數(shù)的返回值就是這個值。
在TensorBoard中使用scalar_summary來查看該值的變化情況:
http://wiki.jikexueyuan.com/project/tensorflow-zh/images/cifar_loss.png" alt="CIFAR-10 Loss" title="CIFAR-10 Total Loss" />
我們使用標(biāo)準(zhǔn)的梯度下降算法來訓(xùn)練模型(也可以在Training中看看其他方法),其學(xué)習(xí)率隨時間以指數(shù)形式衰減。
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() 函數(shù)會添加一些操作使得目標(biāo)函數(shù)最小化,這些操作包括計算梯度、更新學(xué)習(xí)變量(詳細信息請查看GradientDescentOptimizer)。train() 函數(shù)最終會返回一個用以對一批圖像執(zhí)行所有計算的操作步驟,以便訓(xùn)練并更新模型。
我們已經(jīng)把模型建立好了,現(xiàn)在通過執(zhí)行腳本cifar10_train.py來啟動訓(xùn)練過程。
python cifar10_train.py
注意: 當(dāng)?shù)谝淮卧贑IFAR-10教程上啟動任何任務(wù)時,會自動下載CIFAR-10數(shù)據(jù)集,該數(shù)據(jù)集大約有160M大小,因此第一次運行時泡杯咖啡小棲一會吧。
你應(yīng)該可以看到如下類似的輸出:
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步訓(xùn)練過程后打印出總損失值,以及最后一批數(shù)據(jù)的處理速度。下面是幾點注釋:
第一批數(shù)據(jù)會非常的慢(大概要幾分鐘時間),因為預(yù)處理線程要把20,000個待處理的CIFAR圖像填充到重排隊列中;
打印出來的損失值是最近一批數(shù)據(jù)的損失值的均值。請記住損失值是交叉熵和權(quán)重衰減項的和;
練習(xí): 當(dāng)實驗時,第一階段的訓(xùn)練時間有時會非常的長,長到足以讓人生厭??梢試L試減少初始化時初始填充到隊列中圖片數(shù)量來改變這種情況。在
cifar10.py中搜索NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN并修改之。
cifar10_train.py 會周期性的在檢查點文件中保存模型中的所有參數(shù),但是不會對模型進行評估。cifar10_eval.py會使用該檢查點文件來測試預(yù)測性能(詳見下面的描述:評估模型)。
如果按照上面的步驟做下來,你應(yīng)該已經(jīng)開始訓(xùn)練一個CIFAR-10模型了。恭喜你!
cifar10_train.py輸出的終端信息中提供了關(guān)于模型如何訓(xùn)練的一些信息,但是我們可能希望了解更多關(guān)于模型訓(xùn)練時的信息,比如:
TensorBoard提供了該功能,可以通過cifar10_train.py中的SummaryWriter周期性的獲取并顯示這些數(shù)據(jù)。
比如我們可以在訓(xùn)練過程中查看local3的激活情況,以及其特征維度的稀疏情況:


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

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