0
| 本文作者: 陳鳴鳩 | 2017-01-18 09:39 |
雷鋒網按:本文是介紹用TensorFlow構建圖像識別系統的第三部分。 在前兩部分中,我們構建了一個softmax分類器來標記來自CIFAR-10數據集的圖像,實現了約25-30%的精度。 因為有10個不同可能性的類別,所以我們預期的隨機標記圖像的精度為10%。25-30%的結果已經比隨機標記的結果好多了,但仍有很大的改進空間。在這篇文章中,作者Wolfgang Beyer將介紹如何構建一個執行相同任務的神經網絡。看看可以提高預測精度到多少!雷鋒網對全文進行編譯,未經許可不得轉載。
關于前兩部分,可以參看《機器學習零基礎?手把手教你用TensorFlow搭建圖像識別系統》(一)和(二)。

神經網絡是基于生物大腦的工作原理設計的,由許多人工神經元組成,每個神經元處理多個輸入信號并返回單個輸出信號,然后輸出信號可以用作其他神經元的輸入信號。我們先來看看一個單獨的神經元,大概長這樣:

一個人工神經元:其輸出是其輸入加權和的ReLU函數值。
在單個神經元中發生的情況與在softmax分類器中發生的情況非常相似。一個神經元有一個輸入值的向量和一個權重值的向量,權重值是神經元的內部參數。輸入向量和權重值向量包含相同數量的值,因此可以使用它們
WeightedSum=input1×w1+input2×w2+...
到目前為止,我們正在做與softmax分類器完全相同的計算,現在開始,我們要進行一些不同的處理:只要加權和的結果是正值,神經元的輸出是這個值;但是如果加權和是負值,就忽

由 f(x) = max(0, x)定義的整流線性單元
使用ReLU的原因是其具備非線性特點,因而現在神經元的輸出并不是嚴格的輸入線性組合(也就是加權和)。當我們不再從單個神經元而是從整個網絡來看時,會發現非線性很有用處。
人工神經網絡中的神經元通常不是彼此隨機連接的,大多數時候是分層排列的:

人工神經網絡具有隱藏層和輸出層2個層。
輸入并不被當作一層,因為它只是將數據(不轉換它)饋送到第一個合適的層。
輸入圖像的像素值是第1層網絡中的神經元的輸入。第1層中的神經元的輸出是第2層網絡的神經元的輸入,后面的層之間以此類推。如果沒有每層的ReLU,我們只是得到一個加權和的序列;并且堆積的加權和可以被合并成單個加權和,這樣一來,多個層并沒有比單層網絡有任何改進之處。這就是為什么要具有非線性的重要原因。ReLU非線性解決了上述問題,它使每個附加層的確給網絡添加了一些改進。
我們所關注的是圖像類別的分數,它是網絡的最后一層的輸出。在這個網絡架構中,每個神經元連接到前一層的所有神經元,因此這種網絡被稱為完全連接的網絡。我們將會在本教程的第3部分中看到一些不同于此的其他情況。
對神經網絡理論的簡短介紹到此結束。 讓我們開始建立一個真正的神經網絡!
此示例的完整代碼在Github上提供。它需要TensorFlow和CIFAR-10數據集(雷鋒網的此前文章有提及)。
如果你已經通過我以前的博客文章,你會看到神經網絡分類器的代碼非常類似于softmax分類器的代碼。 除了切換出定義模型的代碼部分之外,我還添加了一些小功能使TensorFlow可以做以下一些事情:
正則化:這是一種非常常見的技術,用于防止模型過擬合。它的工作原理是在優化過程中施加反作用力,其目的是保持模型簡單
使用TensorBoard可視化模型:TensorBoard包含TensorFlow,允許您根據模型和模型生成的數據生成表格和圖形。這有助于分析您的模型,并且對調試特別有用。
檢查點:此功能允許您保存模型的當前狀態以供以后使用。訓練一個模型可能需要相當長的時間,所以它是必要的,當您想再次使用模型時不必從頭開始。
這次代碼被分成兩個文件:定義模型two_layer_fc.py和運行模型run_fc_model.py(提示:'fc'代表完全連接的意思)。
讓我們先看看模型本身,然后進行一些運行和訓練處理。two_layer_fc.py包含以下函數:
inference(),使我們從輸入數據到類分數。
loss(),從類分數中計算損失值。
training(),執行單個訓練步驟。
evaluation(),計算網絡的精度。
inference()描述了通過網絡的正向傳遞。那么,類分數是如何從輸入圖片開始被計算的呢?

參數images是包含實際圖像數據的TensorFlow占位符。接下來的三個參數描述網絡的形狀或大小。 image_pixels是每個輸入圖像的像素數,classes是不同輸出標簽的數量,hidden_units是網絡的第一個層或者隱藏層中的神經元數量。
每個神經元從上一層獲取所有值作為輸入,并生成單個輸出值。因此,隱藏層中的每個神經元都具有image_pixels輸入,并且該層作為整體生成hidden_units輸出。然后將這些輸入到輸出層的類神經元中,生成類輸出值,每個類一個分數。
reg_constant是正則化常數。TensorFlow允許我們非常容易地通過自動處理大部分計算來向網絡添加正則化。 當使用到損失函數時,我會進一步講述細節。

由于神經網絡有2個相似的圖層,因此將為每個層定義一個單獨的范圍。 這允許我們在每個作用域中重復使用變量名。變量biases以我們熟悉的tf.Variable()方式來定義。
此處會更多地涉及到weights變量的定義。tf.get_variable()允許我們添加正則化。weights是以hidden_units(輸入向量大小乘以輸出向量大小)為維度的image_pixels矩陣。initialier參數描述了weights變量的初始值。目前為止我們已經將weights變量初始化為0,但此處并不會起作用。關于單層中的神經元,它們都接收完全相同的輸入值,如果它們都具有相同的內部參數,則它們將進行相同的計算并且輸出相同的值。為了避免這種情況,需要隨機化它們的初始權重。我們使用了一個通常可以很好運行的初始化方案,將weights初始化為正態分布值。丟棄與平均值相差超過2個標準偏差的值,并且將標準偏差設置為輸入像素數量的平方根的倒數。幸運的是TensorFlow為我們處理了所有這些細節,我們只需要指定調用truncated_normal_initializer便可完成上述工作。
weights變量的最終參數是regularizer。現在要做的是告訴TensorFlow要為weights變量使用L2-正則化。我將在這里討論正則化。
第一層的輸出等于images矩陣乘以weights矩陣,再加上bisa變量。這與上一篇博文中的softmax分類器完全相同。然后應用tf.nn.relu(),取ReLU函數的值作為隱藏層的輸出。

第2層與第1層非常相似,其輸入值為hidden_units,輸出值為classes,因此weights矩陣的維度是是[hidden_units,classes]。 由于這是我們網絡的最后一層,所以不再需要ReLU。 通過將輸入(hidden)互乘以weights,再加上bias就可得到類分數(logits)。
tf.histogram_summary()允許我們記錄logits變量的值,以便以后用TensorBoard進行分析。這一點稍后會介紹。
總而言之,整個inference()函數接收輸入圖像并返回類分數。這是一個訓練有素的分類器需要做的,但為了得到一個訓練有素的分類器,首先需要測量這些類分數表現有多好,這是損失函數要做的工作。

首先,我們計算logits(模型的輸出)和labels(來自訓練數據集的正確標簽)之間的交叉熵,這已經是我們對softmax分類器的全部損失函數,但是這次我們想要使用正則化,所以必須給損失添加另一個項。
讓我們先放一邊吧,先看看通過使用正則化能實現什么。
當捕獲數據中隨機噪聲的統計模型是被數據訓練出來的而不是真實的數據基礎關系時,就被稱為過擬合。

紅色和藍色圓圈表示兩個不同的類。綠線代表過擬合模型,而黑線代表具有良好擬合的模型。
在上面的圖像中有兩個不同的類,分別由藍色和紅色圓圈表示。綠線是過度擬合的分類器。它完全遵循訓練數據,同時也嚴重依賴于訓練數據,并且可能在處理未知數據時比代表正則化模型的黑線表現更差。因此,我們的正則化目標是得到一個簡單的模型,不附帶任何不必要的復雜。我們選擇L2-正則化來實現這一點,L2正則化將網絡中所有權重的平方和加到損失函數。如果模型使用大權重,則對應重罰分,并且如果模型使用小權重,則小罰分。
這就是為什么我們在定義權重時使用了regularizer參數,并為它分配了一個l2_regularizer。這告訴了TensorFlow要跟蹤l2_regularizer這個變量的L2正則化項(并通過參數reg_constant對它們進行加權)。所有正則化項被添加到一個損失函數可以訪問的集合——tf.GraphKeys.REGULARIZATION_LOSSES。將所有正則化損失的總和與先前計算的交叉熵相加,以得到我們的模型的總損失。

global_step是跟蹤執行訓練迭代次數的標量變量。當在我們的訓練循環中重復運行模型時,我們已經知道這個值,它是循環的迭代變量。直接將這個值添加到TensorFlow圖表的原因是想要能夠拍攝模型的快照,這些快照應包括有關已執行了多少訓練步驟的信息。
梯度下降優化器的定義很簡單。我們提供學習速率并告訴優化器它應該最小化哪個變量。 此外,優化程序會在每次迭代時自動遞增global_step參數。

模型精度的計算與softmax情況相同:將模型的預測與真實標簽進行比較,并計算正確預測的頻率。 我們還對隨著時間的推移精度如何演變感興趣,因此添加了一個跟蹤accuracy的匯總操作。 將在關于TensorBoard的部分中介紹這一點。
總結我們迄今為止做了什么,已經定義了使用4個函數的2層人工神經網絡的行為:inference()構成通過網絡的正向傳遞并返回類分數。loss()比較預測和真實的類分數并生成損失值。 training()執行訓練步驟,并優化模型的內部參數。evaluation()測量模型的性能。
現在神經網絡已經定義完畢,讓我們看看run_fc_model.py是如何運行、訓練和評估模型的。

在強制導入之后,將模型參數定義為外部標志。 TensorFlow有自己的命令行參數模塊,這是一個圍繞Python argparse的小封裝包。 在這里使用它是為了方便,但也可以直接使用argparse。
在代碼開頭兩行中定義了命令行參數。每個標志的參數是標志的名稱(其默認值和一個簡短的描述)。 使用-h標志執行文件將顯示這些描述。第二個代碼塊調用實際解析命令行參數的函數,然后將所有參數的值打印到屏幕上。

用常數定義每個圖像的像素數(32 x 32 x 3)和不同圖像類別的數量。

使用一個時鐘來記錄運行時間。

我們想記錄關于訓練過程的一些信息,并使用TensorBoard顯示該信息。 TensorBoard要求每次運行的日志都位于單獨的目錄中,因此我們將日期和時間信息添加到日志目錄的名稱地址。

load_data()加載CIFAR-10數據,并返回包含獨立訓練和測試數據集的字典。

定義TensorFlow占位符。 當執行實際計算時,這些將被填充訓練和測試數據。
images_placeholder將每張圖片批處理成一定尺寸乘以像素的大小。 批處理大小設定為“None”允許運行圖片時可隨時設定大小(用于訓練網絡的批處理大小可以通過命令行參數設置,但是對于測試,我們將整個測試集作為一個批處理) 。
labels_placeholder是一個包含每張圖片的正確類標簽的整數值向量。

這里引用了我們之前在two_layer_fc.py中描述的函數。
inference()使我們從輸入數據到類分數。
loss()從類分數中計算損失值。
training()執行單個訓練步驟。
evaluation()計算網絡的精度。

為TensorBoard定義一個summary操作函數 (更多介紹可參見前文).

生成一個保存對象以保存模型在檢查點的狀態(更多介紹可參見前文)。

開始TensorFlow會話并立即初始化所有變量。 然后我們創建一個匯總編輯器,使其定期將日志信息保存到磁盤。
這些行負責生成批輸入數據。讓我們假設我們有100個訓練圖像,批次大小為10.在softmax示例中,我們只為每次迭代選擇了10個隨機圖像。這意味著,在10次迭代之后,每個圖像將被平均選取一次。但事實上,一些圖像將被選擇多次,而一些圖像不會被添加到任何一個批次。但只要重復的次數夠頻發,所有圖片被隨機分到不同批次的情況會有所改善。
這一次我們要改進抽樣過程。要做的是首先對訓練數據集的100個圖像隨機混洗。混洗之后的數據的前10個圖像作為我們的第一個批次,接下來的10個圖像是我們的第二批,后面的批次以此類推。 10批后,在數據集的末尾,再重復混洗過程,和開始步驟一致,依次取10張圖像作為一批次。這保證沒有任何圖像比任何其它圖像被更頻繁地拾取,同時仍然確保圖像被返回的順序是隨機的。
為了實現這一點,data_helpers()中的gen_batch()函數返回一個Python generator,它在每次評估時返回下一個批次。generator原理的細節超出了本文的范圍(這里有一個很好的解釋)。使用Python的內置zip()函數來生成一個來自[(image1,label1),(image2,label2),...]的元組列表,然后將其傳遞給生成函數。

next(batch)返回下一批數據。 因為它仍然是[(imageA,labelA),(imageB,labelB),...]的形式,需要先解壓它以從標簽中分離圖像,然后填充feed_dict,字典包含用單批培訓數據填充的TensorFlow占位符。
每100次迭代之后模型的當前精度會被評估并打印到屏幕上。此外,正在運行summary操作,其結果被添加到負責將摘要寫入磁盤的summary_writer(看此章節)。
此行運行train_step操作(之前定義為調用two_layer_fc.training(),它包含用于優化變量的實際指令)。
當訓練模型需要較長的時間,有一個簡單的方法來保存你的進度的快照。 這允許您以后回來并恢復模型在完全相同的狀態。 所有你需要做的是創建一個tf.train.Saver對象(我們之前做的),然后每次你想拍攝快照時調用它的save()方法。恢復模型也很簡單,只需調用savever的restore()。 代碼示例請看gitHub存儲庫中的restore_model.py文件。
在訓練完成后,最終模型在測試集上進行評估(記住,測試集包含模型到目前為止還沒有看到的數據,使我們能夠判斷模型是否能推廣到新的數據)。
讓我們使用默認參數通過“python run_fc_model.py”運行模型。 我的輸出如下所示:

可以看到訓練的準確性開始于我們所期望到隨機猜測水平(10級 - > 10%的機會選擇到正確的)。 在第一次約1000次迭代中,精度增加到約50%,并且在接下來的1000次迭代中圍繞該值波動。 46%的測試精度不低于訓練精度。 這表明我們的模型沒有顯著過度擬合。 softmax分級器的性能約為30%,因此46%的改進約為50%。不錯!
TensorBoard允許您從不同方面可視化TensorFlow圖形,并且對于調試和改進網絡非常有用。 讓我們看看TensorBoard相關的代碼。
在 two_layer_fc.py 我可以看到以下代碼:
這三行中的每一行都創建一個匯總操作。通過定義一個匯總操作告訴TensorFlow收集某些張量(在本例中logits,loss和accuracy)的摘要信息。匯總操作的其他參數就只是一些想要添加到總結的標簽。
有不同種類的匯總操作。使用scalar_summary記錄有關標量(非矢量)值以及histogram_summary收集有關的多個值分布信息(有關各種匯總運算更多信息可以在TensorFlow文檔中找到)。
在 run_fc_model.py 是關于TensorBoard 可視化的一些代碼:

TensorFlow中的一個操作本身不運行,您需要直接調用它或調用依賴于它的另一個操作。由于我們不想在每次要收集摘要信息時單獨調用每個摘要操作,因此使用tf.merge_all_summaries創建一個運行所有摘要的單個操作。
在TensorFlow會話的初始化期間,創建一個摘要寫入器,摘要編入器負責將摘要數據實際寫入磁盤。在摘要寫入器的構造函數中,logdir是日志的寫入地址。可選的圖形參數告訴TensorBoard渲染顯示整個TensorFlow圖形。每100次迭代,我們執行合并的匯總操作,并將結果饋送到匯總寫入器,將它們寫入磁盤。要查看結果,我們通過“tensorboard --logdir = tf_logs”運行TensorBoard,并在Web瀏覽器中打開localhost:6006。在“事件”標簽中,我們可以看到網絡的損失是如何減少的,以及其精度是如何隨時間增加而增加的。
tensorboard圖顯示模型在訓練中的損失和精度。
“Graphs”選項卡顯示一個已經定義的可視化的tensorflow圖,您可以交互式地重新排列直到你滿意。我認為下面的圖片顯示了我們的網絡結構非常好。

Tensorboard1以交互式可視化的方式顯示Tensorboard圖像
有關在“分布”和“直方圖”標簽的信息可以進一步了解tf.histogram_summary操作,這里不做進一步的細節分析,更多信息可在官方tensorflow文件相關部分。
也許你正在想訓練softmax分類器的計算時間比神經網絡少了很多。事實確實如此,但即使把訓練softmax分類器的時間增加到和神經網絡來訓練所用的時間一樣長,前者也不會達到和神經網絡相同的性能,前者訓練時間再長,額外的收益和一定程度的性能改進幾乎是微乎其微的。我們也已經在神經網絡中也驗證也這點,額外的訓練時間不會顯著提高準確性,但還有別的事情我們可以做。
已選的默認參數值表現是相當不錯的,但還有一些改進的余地。通過改變參數,如隱藏層中的神經元的數目或學習率,應該能夠提高模型的準確性,模型的進一步優化使測試精度很可能大于50%。如果這個模型可以調整到65%或更多,我也會相當驚喜。但還有另一種類型的網絡結構能夠比較輕易實現這一點:卷積神經網絡,這是一類不完全連通的神經網絡,相反,它們嘗試在其輸入中理解局部特征,這對于分析圖像非常有用。它使得在解讀圖像獲取空間信息的時候有非常直觀的意義。在本系列的下一部分中,我們將看到卷積神經網絡的工作原理,以及如何構建一個自己的神經網絡.。
雷鋒網將關注下一部分關于卷積神經網絡的介紹,敬請期待。
via wolfib,雷鋒網編譯