主題介紹:使用 TensorFlow,能夠將深度機器學習從一個研究領域轉變成一個主流的軟件工程方法。在這個視頻中,Martin Görner 演示瞭如何構建和訓練一個用於識別手寫數字的神經網絡。在這個過程當中,他將描述一些在神經網絡設計中所使用的權衡技巧,最後他將使得其模型的識別準確度超過 99%。本教程的內容適用於各類水平的軟件開發者。即便是經驗豐富的機器學習愛好者,本視頻也能經過卷積網絡等已知的模型來帶你瞭解 TensorFlow。這是一個技術密集的視頻,是爲想要快速上手機器學習的初學者而設計的。react
第一部分算法
本教程將以如何進行手寫數字識別爲例進行講解。網絡
首先,Gorner 給出了一個很是簡單的能夠分類數字的模型:softmax 分類。對於一張 28×28 像素的數字圖像,其共有 784 個像素(MNIST 的狀況)。將它們進行分類的最簡單的方法就是使用 784 個像素做爲單層神經網絡的輸入。神經網絡中的每一個「神經元」對其全部的輸入進行加權求和,並添加一個被稱爲「偏置(bias)」的常數,而後經過一些非線性激活函數(softmax 是其中之一)來反饋結果。機器學習
爲了將數字分爲 10 類(0 到 9),須要設計一個具備 10 個輸出神經元的單層神經網絡。對於分類問題,softmax 是一個不錯的激活函數。經過取每一個元素的指數,而後歸一化向量(使用任意的範數(norm,L1 或 L2),好比向量的普通歐幾里得距離)從而將 softmax 應用於向量。分佈式
那麼爲何「softmax」會被稱爲 softmax 呢?指數是一種驟增的函數。這將加大向量中每一個元素的差別。它也會迅速地產生一個巨大的值。而後,當進行向量的標準化時,支配範數(norm)的最大的元素將會被標準化爲一個接近 1 的數字,其餘的元素將會被一個較大的值分割並被標準化爲一個接近 0 的數字。所獲得的向量清楚地顯示出了哪一個是其最大的值,即「max」,可是卻又保留了其值的原始的相對排列順序,所以即爲「soft」。ide
咱們如今將使用矩陣乘法將這個單層的神經元的行爲總結進一個簡單的公式當中。讓咱們直接這樣作:100 個圖像的「mini-batch」做爲輸入,產生 100 個預測(10 元素向量)做爲輸出。函數
使用加權矩陣 W 的第一列權重,咱們計算第一個圖像全部像素的加權和。該和對應於第一神經元。使用第二列權重,咱們對第二個神經元進行一樣的操做,直到第 10 個神經元。而後,咱們能夠對剩餘的 99 個圖像重複操做。若是咱們把一個包含 100 個圖像的矩陣稱爲 X,那麼咱們的 10 個神經元在這 100 張圖像上的加權和就是簡單的 X.W(矩陣乘法)。post
每個神經元都必須添加其偏置(一個常數)。由於咱們有 10 個神經元,咱們一樣擁有 10 個偏置常數。咱們將這個 10 個值的向量稱爲 b。它必須被添加到先前計算的矩陣中的每一行當中。使用一個稱爲「broadcast」的魔法,咱們將會用一個簡單的加號寫出它。性能
是 Python 和 numpy(Python 的科學計算庫)的一個標準技巧。它擴展了對不兼容維度的矩陣進行正常操做的方式。「Broadcasting add」意味着「若是你由於兩個矩陣維度不一樣的緣由而不能將其相加,那麼你能夠根據須要嘗試複製一個小的矩陣使其工做。」學習
咱們最終應用 softmax 激活函數而且獲得一個描述單層神經網絡的公式,並將其應用於 100 張圖像:
在 TensorFlow 中則寫成這樣:
接下來咱們須要訓練神經網絡來本身找到咱們所須要的權重和偏置。
接下來,Gorner 介紹瞭如何對神經網絡進行訓練。
要讓神經網絡從輸入圖像中產生預測,咱們須要知道它們能夠作到什麼樣的程度,即在咱們知道的事實和網絡的預測之間到底有多大的距離。請記住,咱們對於這個數據集中的全部圖像都有一個真實的標籤。
任何一種定義的距離均可以進行這樣的操做,普通歐幾里得距離是能夠的,可是對於分類問題,被稱爲「交叉熵(cross-entropy)」的距離更加有效。交叉熵是一個關於權重、偏置、訓練圖像的像素和其已知標籤的函數。
這裏用到了 one-hot 編碼。「one-hot」編碼意味着你使用一個 10 個值的向量,其中除了第 6 個值爲 1 之外的全部值都是 0。這很是方便,由於這樣的格式和咱們神經網絡預測輸出的格式很是類似,同時它也做爲一個 10 值的向量。
在這裏可視化演示了這個動態過程(參見視頻)。
準確度(左上圖):這個準確度只是正確識別的數字的百分比,是在訓練和測試集上計算出的。若是訓練順利,它便會上升。
交叉熵損失(中上圖):爲了驅動訓練,須要定義損失函數,即一個展現出系統數字識別能力有多糟的值,而且系統會盡力將其最小化。損失函數(loss function,此處爲「交叉熵」)的選擇稍後會作出解釋。你會看到,隨着訓練的進行,訓練和測試數據的損失會減小,而這個現象是好的,意味着神經網絡正在學習。X 軸表示了學習過程當中的迭代。
權重(左下圖)和偏置(中下圖):說明了內部變量所取的全部值的擴展,即隨訓練進行而變化的權重和偏置。好比偏置從 0 開始,且最終獲得的值大體均勻地分佈在-1.5 和 1.5 之間。若是系統不能很好地收斂,那麼這些圖可能有用。假若你發現權重和誤差擴展到上百或上千,那麼就可能有問題了。
訓練數字(右上圖):訓練數字每次 100 個被送入訓練迴路;也能夠看到當前訓練狀態下的神經網絡是已將數字正確識別(白色背景)仍是誤分類(紅色背景,左側印有正確的標示,每一個數字右側印有計算錯誤的標示)。此數據集中有 50,000 個訓練數字。咱們在每次迭代(iteration)中將 100 個數字送入訓練循環中,所以系統將在 500 次迭代以後看到全部訓練數字一次。咱們稱之爲一個「epoch」。
測試數字(右下圖):爲了測試在現實條件下的識別質量,咱們必須使用系統在訓練期間從未看過的數字。不然,它可能記住了全部的訓練數字,卻仍沒法識別我剛纔寫的「8」。MNIST 數據集包含了 10,000 個測試數字。此處你能看到每一個數字對應的大約 1000 種書寫形式,其中全部錯誤識別的數字列在頂部(有紅色背景)。左邊的刻度會給你一個粗略的分辨率精確度(正確識別的百分比)。
「訓練」一個神經網絡實際上就是使用訓練圖像和標籤來調整權重和偏置,以便最小化交叉熵損失函數。
那麼咱們在 TensorFlow 中如何實現它呢?
咱們首先定義 TensorFlow 的變量和佔位符(placeholder),即權重和偏置。
佔位符是在訓練期間填充實際數據的參數,一般是訓練圖像。持有訓練圖像的張量的形式是 [None, 28, 28, 1],其中的參數表明:
28, 28, 1: 圖像是 28x28 每像素 x 1(灰度)。最後一個數字對於彩色圖像是 3 但在這裏並不是是必須的。
None: 這是表明圖像在小批量(mini-batch)中的數量。在訓練時能夠獲得。
接下來是定義模型:
第一行是咱們單層神經網絡的模型。公式是咱們在前面的理論部分創建的。tf.reshape 命令將咱們的 28×28 的圖像轉化成 784 個像素的單向量。在 reshape 中的「-1」意味着「計算機,計算出來,這隻有一種可能」。在實際當中,這會是圖像在小批次(mini-batch)中的數量。
而後,咱們須要一個額外的佔位符用於訓練標籤,這些標籤與訓練圖像一塊兒被提供。
如今咱們有了模型預測和正確的標籤,因此咱們計算交叉熵。tf.reduce_sum 是對向量的全部元素求和。
最後兩行計算了正確識別數字的百分比。
纔是 TensorFlow 發揮它力量的地方。你選擇一個適應器(optimiser,有許多可供選擇)而且用它最小化交叉熵損失。在這一步中,TensorFlow 計算相對於全部權重和全部偏置(梯度)的損失函數的偏導數。這是一個形式衍生(formal derivation),並不是是一個耗時的數值型衍生。
梯度而後被用來更新權重和偏置。學習率爲 0.003。
那麼梯度和學習率是什麼呢?
梯度:若是咱們相對於全部的權重和全部的偏置計算交叉熵的偏導數,咱們就獲得一個對於給定圖像、標籤和當前權重和偏置的「梯度」。請記住,咱們有 7850 個權重和偏置,因此計算梯度須要大量的工做。幸運的是,TensorFlow 能夠來幫咱們作這項工做。梯度的數學意義在於它指向「上(up)」。由於咱們想要到達一個交叉熵低的地方,那麼咱們就去向相反的方向。咱們用一小部分的梯度更新權重和偏置而且使用下一批訓練圖像再次作一樣的事情。咱們但願的是,這可使咱們到達交叉熵最小的凹點的低部。梯度降低算法遵循着一個最陡的坡度降低到局部最小值的路徑。訓練圖像在每一次迭代中一樣會被改變,這使得咱們向着一個適用於全部圖像的局部最小值收斂。
學習率(learning rate): 在整個梯度的長度上,你不能在每一次迭代的時候都對權重和偏置進行更新。這就會像是你穿着七裏靴卻試圖到達一個山谷的底部。你會直接從山谷的一邊到達另外一邊。爲了到達底部,你須要一些更小的步伐,即只使用梯度的一部分,一般在 1/1000 區域中。咱們稱這個部分爲「學習率」。
接下來該運行訓練循環了。到目前爲止,全部的 TensorFlow 指令都在內存中準備了一個計算圖,可是還未進行計算。
TensorFlow 的「延遲執行(deferred execution)」模型:TensorFlow 是爲分佈式計算構建的。它必須知道你要計算的是什麼、你的執行圖(execution graph),而後纔開始發送計算任務到各類計算機。這就是爲何它有一個延遲執行模型,你首先使用 TensorFlow 函數在內存中創造一個計算圖,而後啓動一個執行 Session 而且使用 Session.run 執行實際計算任務。在此時,圖沒法被更改。
因爲這個模型,TensorFlow 接管了分佈式運算的大量運籌。例如,假如你指示它在計算機 1 上運行計算的一部分,而在計算機 2 上運行另外一部分,它能夠自動進行必要的數據傳輸。
計算須要將實際數據反饋進你在 TensorFlow 代碼中定義的佔位符。這是以 Python 的 dictionary 的形式給出的,其中的鍵是佔位符的名稱。
在這裏執行的 train_step 是當咱們要求 TensorFlow 最小化交叉熵時得到的。這是計算梯度和更新權重和偏置的步驟。
最終,咱們還須要一些值來顯示,以便咱們能夠追蹤咱們模型的性能。
經過在饋送 dictionary 中提供測試而不是訓練數據,能夠對測試數據進行一樣的計算(例如每 100 次迭代計算一次。有 10,000 個測試數字,因此會耗費 CPU 一些時間)。
最後一行代碼用於在訓練迴路中計算準確度和交叉熵(例如每 10 次迭代)。
下面是全部代碼:
這個簡單的模型已經能識別 92% 的數字了。但這個準確度還不夠好,可是你如今要顯著地改善它。怎麼作呢?深度學習就是要深,要更多的層!
讓咱們來試試 5 個全鏈接層。
咱們繼續用 softmax 來做爲最後一層的激活函數,這也是爲何在分類這個問題上它性能優異的緣由。但在中間層,咱們要使用最經典的激活函數:sigmoid 函數。
下面開始寫代碼。爲了增長一個層,你須要爲中間層增長一個額外的權重矩陣和一個額外的偏置向量:
這樣增長多個層:
但 sigmoid 不是全能的。在深度網絡裏,sigmoid 激活函數也能帶來不少問題。它把全部的值都擠到了 0 到 1 之間,並且當你重複作的時候,神經元的輸出和它們的梯度都歸零了。修正線性單元(ReLU)也是一種很常使用的激活函數:
對比一下在 300 次迭代時 sigmoid 函數(淺色線)和 ReLU(深色線)的效果,能夠看到 ReLU 在準確度和交叉熵損失上的表現都顯著更好。
用 ReLU 替換你全部的 sigmoid,而後你會獲得一個更快的初始收斂而且當咱們繼續增長層的時候也避免了一些後續問題的產生。僅僅在代碼中簡單地用 tf.nn.relu 來替換 tf.nn.sigmoid 就能夠了。
但收斂過快也有問題:
這些曲線很嘈雜,看看測試精確度吧:它在全百分比範圍內跳上跳下。這意味着即便 0.003 的學習率咱們仍是太快了。但咱們不能僅僅將學習率除以十或者永遠不停地作訓練。一個好的解決方案是開始很快隨後將學習速率指數級衰減至好比說 0.0001。
這個小改變的影響是驚人的。你會看到大部分的噪聲消失了而且測試精確度持續穩定在 98% 以上。
再看看訓練精確度曲線。在好多個 epoch 裏都達到了 100%(一個 epoch=500 次迭代=所有訓練圖片訓練一次)。第一次咱們能很好地識別訓練圖片了。
但右邊的圖是什麼狀況?
在數千次迭代以後,測試和訓練數據的交叉熵曲線開始不相連。學習算法只是在訓練數據上作工做並相應地優化訓練的交叉熵。它再也看不到測試數據了,因此這一點也不奇怪:過了一下子它的工做再也不對測試交叉熵產生任何影響,交叉熵中止了降低,有時甚至反彈回來。它不會馬上影響你模型對於真實世界的識別能力,可是它會使你運行的衆多迭代毫無用處,並且這基本上是一個信號——告訴咱們訓練已經不能再爲模型提供進一步改進了。這種狀況一般會被稱爲「過擬合(overfitting)」。
爲了解決這個問題,你能夠嘗試採用一種規範化(regularization)技術,稱之爲「dropout」。
在 dropout 裏,在每一次訓練迭代的時候,你能夠從網絡中隨機地放棄一些神經元。你能夠選擇一個使神經元繼續保留的機率 pkeep,一般是 50% 到 75% 之間,而後在每一次訓練的迭代時,隨機地把一些神經元連同它們的權重和偏置一塊兒去掉。在一次迭代裏,不一樣的神經元能夠被一塊兒去掉(並且你也一樣須要等比例地促進剩餘神經元的輸出,以確保下一層的激活不會移動)。當測試你神經網絡性能的時候,你再把全部的神經元都裝回來 (pkeep=1)。
TensorFlow 提供一個 dropout 函數能夠用在一層神經網絡的輸出上。它隨機地清零一些輸出而且把剩下的提高 1/pkeep。你能夠在網絡中每一箇中間層之後插入 dropout。
下面咱們集中看一下改進的狀況。
當使用 sigmoid 函數,學習率爲 0.003 時:
而後使用 ReLU 替代 sigmoid:
而後再將學習率衰減到 0.0001:
增長 dropout:
解決了過擬合,準確度達到了 98%,可是噪聲又回來了。看起來不管咱們作什麼,咱們看上去都不可能很顯著地解決 98% 的障礙,並且咱們的損失曲線依然顯示「過擬合」沒法鏈接。什麼是真正的「過擬合」?過擬合發生在該神經網絡學得「很差」的時候,在這種狀況下該神經網絡對於訓練樣本作得很好,對真實場景卻並非很好。有一些像 dropout 同樣的規範化技術可以迫使它學習得更好,不過過擬合還有更深層的緣由。
基本的過擬合發生在一個神經網絡針對手頭的問題有太多的自由度的時候。想象一下咱們有如此多的神經元以致於所組成的網絡能夠存儲咱們全部的訓練圖像並依靠特徵匹配來識別它們。它會在真實世界的數據裏迷失。一個神經網絡必須有某種程度上的約束以使它可以概括推理它在學習中所學到的東西。
若是你只有不多的訓練數據,甚至一個很小的網絡都可以用心學習它。通常來講,你老是須要不少數據來訓練神經網絡。
最後,若是你已經作完了全部的步驟,包括實驗了不一樣大小的網絡以確保它的自由度已經約束好了、採用了 dropout、而且訓練了大量的數據,你可能會發現你仍是被卡在了當前的性能層次上再也上不去了。這說明你的神經網絡在它當前的形態下已經沒法從你提供的數據中抽取到更多的信息了,就像咱們這個例子這樣。
還記得咱們如何使用咱們的圖像嗎?是全部的像素都展平到一個向量裏麼?這是一個很糟糕的想法。手寫的數字是由一個個形狀組成的,當咱們把像素展平後咱們會丟掉這些形狀信息。不過,有一種神經網絡能夠利用這些形狀信息:卷積網絡(convolutional network)。讓咱們來試試。
在卷積網絡層中,一個「神經元」僅對該圖像上的一個小部分的像素求加權和。而後,它一般會添加一個偏置單元,而且將獲得的加權和傳遞給激活函數。與全鏈接網絡相比,其最大的區別在於卷積網絡的每一個神經元重複使用相同的權重,而不是每一個神經元都有本身的權重。
在上圖中,你能夠看到經過連續修改圖片上兩個方向的權重(卷積),可以得到與圖片上的像素點數量相同的輸出值(儘管在邊緣處須要填充(padding))。
要產生一個輸出值平面,咱們使用了一張 4x4 大小的彩色圖片做爲出輸入。在上圖當中,咱們須要 4x4x3=48 個權重,這還不夠,爲了增長更多自由度,咱們還須要選取不一樣組的權重值重複實驗。
經過向權重張量添加一個維度,可以將兩組或更多組的權重重寫爲一組權重,這樣就給出了一個卷積層的權重張量的通用實現。因爲輸入、輸出通道的數量都是參數,咱們能夠開始堆疊式(stacking)和鏈式(chaining)的卷積層。
最後,咱們須要提取信息。在最後一層中,咱們僅僅想使用 10 個神經元來分類 0-9 十個不一樣的數字。傳統上,這是經過「最大池化(max-pooling)」層來完成的。即便今天有許多更簡單的方法可以實現這分類任務,可是,「最大池化」可以幫助咱們直覺地理解卷積神經網絡是怎麼工做的。若是你認爲在訓練的過程當中,咱們的小塊權重會發展成可以過濾基本形狀(水平線、垂直線或曲線等)的過濾器(filter),那麼,提取有用信息的方式就是識別輸出層中哪一種形狀具備最大的強度。實際上,在最大池化層中,神經元的輸出是在 2x2 的分組中被處理,最後僅僅保留輸出最大強度的神經元。
這裏有一種更簡單的方法:若是你是以一步兩個像素移動圖片上的滑塊而不是以每步一個像素地移動圖片上的滑塊。這種方法就是有效的,今天的卷積網絡僅僅使用了卷積層。
讓咱們創建一個用於手寫數字識別的卷積網絡。在頂部,咱們將使用 3 個卷積層;在底部,咱們使用傳統的 softmax 讀出層,並將它們用徹底鏈接層鏈接。
注意,第二與第三卷積層神經元數量以 2x2 爲倍數減小,這就解釋了爲何它們的輸出值從 28x28 減小爲 14x14,而後再到 7x7。卷積層的大小變化使神經元的數量在每層降低約爲:28x28x14≈3000->14x14x8≈1500 → 7x7x12≈500 → 200。下一節中,咱們將給出該網絡的具體實現。
那咱們如何在 TensorFlow 中實現它呢?爲了將咱們的代碼轉化爲卷積模型,咱們須要爲卷積層定義適當的權重張量,而後將該卷積層添加到模型中。咱們已經理解到卷積層須要如下形式的權重張量。下面代碼是用 TensorFlow 語法來對其初始化:
而後實現其模型:
在 TensorFlow 中,使用 tf.nn.conv2d 函數實現卷積層,該函數使用提供的權重在兩個方向上掃描輸入圖片。這僅僅是神經元的加權和部分,你須要添加偏置單元並將加權和提供給激活函數。不要過度在乎 stride 的複雜語法,查閱文檔就能獲取完整的詳細信息。這裏的填充(padding)策略是爲了複製圖片的邊緣的像素。全部的數字都在一個統一的背景下,因此這僅僅是擴展了背景,而且不該該添加不須要的任何樣式。
完成這一步以後,咱們應該能達到 99% 的準確度了吧?
然而並無!什麼狀況?
還記得前面咱們怎麼解決這個「過擬合」問題的嗎?使用 dropout。
咱們該怎麼對其進行優化呢?調整你的神經網絡的一個好方法是先去實現一個限制較多的神經網絡,而後給它更多的自由度而且增長 dropout,使神經網絡避免過擬合。最終你將獲得一個至關不錯的神經網絡。
例如,咱們在第一層卷積層中僅僅使用了 4 個 patch,若是這些權重的 patch 在訓練的過程當中發展成不一樣的識別器,你能夠直觀地看到這對於解決咱們的問題是不夠的。手寫數字模式遠多於 4 種基本樣式。
所以,讓咱們稍微增長 patch 的數量,將咱們卷積層中 patch 的數量從 4,8,12 增長到 6,12,24,而且在全鏈接層上添加 dropout。它們的神經元重複使用相同的權重,在一次訓練迭代中,經過凍結(限制)一些不會對它們起做用的權重,dropout 可以有效地工做。
而後模型的準確度就突破 99% 了!
對比以前的結果能夠看到明顯的進步:
相關資源:
第二部:創建循環神經網絡
在這一部分 Gorner 講解了如何使用 TensorFlow 創建循環神經網絡。
RNN 的結構與訓練
首先,RNN 的結構以下,其中第二層爲 softmax 層(讀取出東西)。但特殊的是,中間綠色層的輸出在下一步驟中會返回到輸入中。
下面是循環神經網絡裏面的等式。
那麼接下來如何訓練 RNN?以天然語言處理爲例:輸入一般爲字符(character)。以下圖中所示,咱們輸入字符,反向傳播經過該神經網絡、反向傳播經過 softmax 層,咱們會獲得字符的輸出。若是獲得的字符不是咱們想要的,對比一下獲得的與咱們想要的,咱們就對網絡中的權重進行調整,從而獲得更好的結果。
但若是結果是錯的怎麼辦?並且不是由於網絡中的權重偏見,而是由於狀態輸入 H-1 是錯的。在此問題中,輸入是連續的,有些無能爲力的感受。在這個問題上卡住了,那麼解決方案是什麼?
解決方案就是複製該 cell,再次使用一樣的權重。下圖演示了你該如何訓練循環神經網絡,在屢次迭代中共享一樣的權重和誤差。
另外,值得一提的是若是你想往深處作,能夠堆疊 cells(以下圖)。
然後,Gorner 以句子爲例講解了如何使用 TensorFlow 創建循環神經網絡。在如下示例中,咱們是用單詞而非字符做爲輸入,創建這樣的模型中就有一個典型的問題:長期依存關係。而要把下面的長句輸入,須要很是深的循環神經網絡,而若是網絡太深,訓練時候又不太好收斂。在數學細節上要提到的就是梯度消失的問題,梯度成 0 了。
對此問題的一種解決方案是 LSTM。下圖從數學角度解釋了該解決方案爲什麼有效。
在實踐中,LSTM 有效是由於它基於了門(gates) 的概念:
GRU 的等式:
在 TensorFlow 中實現 RNN 語言模型
接下來就是如何用 TensorFlow 實現語言模型的循環神經網絡了。在教授語言模型預測單詞的下一個字符是什麼的例子中,Gorner 使用了 TensorFlow 中更高等級的 API。圖中的 GRUCell 有着多層的循環神經網絡層、兩個門。而後把網絡作的更深,3 個 GRU 堆疊在一塊兒。接下來,展開整個網絡,在 TensorFlow 中,這被稱爲動態 RNN 功能。最終獲得以下結果。
下圖演示瞭如何在 TensorFlow 中實現 Softmax 層。
就行正確理解 RNN 的工做原理很難同樣,向它們正確的輸入數據也很難,你會發現裏面裏面有不少偏差。接下來 Gorner 嘗試瞭如何作出正確的輸入、獲得正確的輸出。依此爲例,他講解了所選擇的 batchsize、cellsize 和層(以下)。
各個步驟實現的代碼以下:
在 TensorFlow 中實現語言模型的完整代碼以下:
最後,Gorne 打開 TensorFlow 演示瞭如何實際建模語言模型,而且演示了 RNN 在文本翻譯、圖像描述等領域的應用。看完以後我決定先下個 TensorFlow。