TensorFlow、Keras 和深度學習,不需要博士

1. 總覽

本教學課程已針對 Tensorflow 2.2 更新!

74f6fbd758bf19e6.png

在本程式碼研究室中,您將學習如何建構及訓練可辨識手寫數字的類神經網路。一路走來,隨著您強化類神經網路以達到 99% 的準確率,您也會發現深度學習專業人士用來有效率地訓練模型時,會用到哪些工具。

本程式碼研究室使用 MNIST 資料集,共有 60,000 位數已加上標籤的數字,一直以來都忙著處理將近二十年的 PhD 作品。必須使用不超過 100 行的 Python / TensorFlow 程式碼,才能解決這個問題。

課程內容

  • 什麼是類神經網路及訓練方式
  • 如何使用 tf.keras 建構基本的一層類神經網路
  • 如何加入更多圖層
  • 如何設定學習率時間表
  • 如何建立卷積類神經網路
  • 如何使用正規化技術:丟棄、批次正規化
  • 什麼是過度配適

軟硬體需求

只需瀏覽器即可。你可以透過 Google Colaboratory 完全透過這個研討會進行。

意見回饋

如果您在研究室中發現任何錯誤,或認為需要改善,請告訴我們。我們透過 GitHub 問題處理意見回饋 [feedback link]。

2. Google Colaboratory 快速入門

本研究室使用 Google Colaboratory,您不需要進行任何設定。你可以透過 Chromebook 執行。請開啟下方檔案並執行儲存格,熟悉 Colab 筆記本。

c3df49e90e5a654f.png Welcome to Colab.ipynb

其他操作說明如下:

選取 GPU 後端

hsy7H7O5qJNvKcRnHRiZoyh0IznlzmrO60wR1B6pqtfdc8Ie7gLsXC0f670zsPzGsNy3QAJuZefYv9CwTHmjiMyywG2pTpnMCE6SlkfYV8e6EmqEmfpsAV8

在 Colab 選單中,依序選取「執行階段」>「執行階段」變更執行階段類型,然後選取 GPU。首次執行時,系統會自動連線至執行階段,你也可以使用「連線」選項。

執行筆記本

evlBKSO15ImjocdEcsIo8unzEe6oDGYnKFe8CoHS_7QiP3sDbrs2jB6lbyitEtE7Gt_1UsCdU5dJA-_2IgBWh9ofYf4yVDE740PwJ6kiQwuXNOLkgktzzf0E_k5VN5mq29ZXI5wb7Q

按一下儲存格並使用 Shift + Enter 鍵,即可逐一執行儲存格。您也可以透過「執行階段 >」全部執行

Table of contents

OXeYYbtKdLCNnw_xovSMeMwSdD7CL_w25EfhnpRhhhO44bYp3zZpU72J5tKaSuo8wpas0GK5B2sTBlIMiFmdGxFRQ9NmwJ7JIRYy5XtpWKQCPdxQVRPy_0J_LshGIKjtw8P9fXozaA

所有筆記本都有目錄。您可以使用左側的黑色箭頭開啟報表。

隱藏的儲存格

GXTbXUO8xpPFKiGc6Q-cFwFHxHvOa105hHg3vk77EDpStyhU4AQMN3FYenbiBusHXUSk-yGXbRDcK-Cwx18XbDtyqB5WRr3_2jhnLvFxW8a7H_4cGvVDKrEMto_QxhfTeO0hwmrfng

部分儲存格只會顯示標題。這是 Colab 專屬的筆記本功能。您可以按兩下這些程式碼,以查看裡面的程式碼,但不太有趣。通常支援或視覺化函式。您仍需執行這些儲存格,才能定義其中的函式。

3. 訓練類神經網路

我們會先觀看類神經網路訓練影片。請開啟下方的筆記本並瀏覽所有儲存格。請先不要注意程式碼,稍後才會開始說明。

c3df49e90e5a654f.png keras_01_mnist.ipynb

執行筆記本時,請將焦點放在視覺化。請見下方說明。

訓練資料

我們有人工手寫數字資料集,並加上標籤,以便我們瞭解每張圖片代表的意義,例如介於 0 到 9 之間的數字。在筆記本中,您會看到內容摘錄:

ad83f98e56054737.png

我們將建構的類神經網路將手寫數字分類在其 10 個類別中 (0、..、9)。此方法會根據內部參數需要具有正確的值,才能順利分類。這是「正確的值」是透過訓練程序學習,這個過程需要「已加上標籤的資料集」並附上圖片和相關正確答案。

如何得知訓練過的類神經網路效能良好?使用訓練資料集來測試網路作弊。這項服務在訓練期間已多次看到該資料集,而且在特定情況下表現最佳。我們需要另一個在訓練期間從未見過的已加標籤資料集,才能評估「真實」藉此確保網路效能稱為「驗證資料集

訓練

隨著訓練的進行,每次訓練一個批次的資料時,內部模型參數會不斷更新,而模型在辨識手寫數字方面會越來越好。您可以在訓練圖表中查看:

3f7b405649301ea.png

右側的「準確率」就是正確辨識數字的百分比。隨著訓練的進展與時俱進,這是很好

左側是「損失」。為了提供訓練課程,我們將定義一個「損失」函式,代表系統辨識數字的準確度,並嘗試將數字最小化。如您所見,隨著訓練的進度,訓練資料和驗證資料的損失都會下降,那就是很好。這代表類神經網路正在學習

X 軸代表「週期」的數量反覆測試整個資料集

預測

模型訓練完成後,就能用來辨識手寫數字。接下來的視覺化圖表將顯示本機字型 (第一行) 轉譯出的幾個數字,以及驗證資料集內 10,000 位數的成效表現。如果預測類別有誤,每個數字下方會以紅色顯示。

c0699216ba0effdb.png

如您所見,這個初始模型並不完善,但仍可正確辨識某些數字。最終的驗證準確率約為 90%,對於我們一開始採用的簡單模型來說並沒有那麼糟,但這仍表示 10,000 組沒有 1000 組驗證數字。遠大於可顯示次數,因此所有答案似乎有誤 (紅色)。

張量

資料儲存在矩陣。28x28 像素的灰階圖片適合 28x28 二維矩陣。但如果是彩色圖片,就需要更多尺寸。每個像素有 3 種顏色值 (紅、綠、藍色),因此需要採 3D 表格和尺寸 [28、28、3]。此外,如要儲存一批 128 張彩色圖片,就需要有尺寸 [128、28、28、3] 的 4D 表格。

這些多維度表格稱為「張量」,維度清單則為「形狀」

4. [INFO]:類神經網路 101

概述

如果您知道下個段落中的所有以粗體顯示的字詞,則可進行下一個練習。如果您剛開始學習深度學習,歡迎繼續閱讀。

witch.png

如果是以多層圖層建構的模型,Keras 便提供 Sequential API。舉例來說,使用三個稠密層的圖片分類器,可在 Keras 中編寫如下:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[28, 28, 1]),
    tf.keras.layers.Dense(200, activation="relu"),
    tf.keras.layers.Dense(60, activation="relu"),
    tf.keras.layers.Dense(10, activation='softmax') # classifying into 10 classes
])

# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
  optimizer='adam',
  loss= 'categorical_crossentropy',
  metrics=['accuracy']) # % of correct answers

# train the model
model.fit(dataset, ... )

688858c21e3beff2.png

單一稠密層

MNIST 資料集中的手寫數字是 28x28 像素的灰階圖片。最簡單的分類方法,就是使用 28x28=784 像素做為單層類神經網路的輸入內容。

Screen Shot 2016-07-26 at 12.32.24.png

類神經網路中的每個「神經元」會計算其所有輸入內容的加權總和,然後新增稱為「偏誤」的常數然後透過一些非線性啟用函式提供結果。"weights""biases" 是透過訓練決定的參數。這類物件一開始是用隨機值進行初始化,

上圖呈現的是有 10 個輸出神經元的單層類神經網路,因為我們想要將數字分類為 10 個類別 (0 到 9)。

利用矩陣乘法

下列是處理圖像集合的類神經網路層如何用矩陣乘法表示:

matmul.gif

使用權重矩陣 W 的第一欄,計算第一張圖片所有像素的加權總和。這個總和對應至第一個神經元。使用權重的第二欄,為第二個神經元進行相同的操作,以此類推至第 10 個神經元。接著,我們可以重複此作業,處理其餘 99 張圖片。如果呼叫含有 100 張圖片的 X 矩陣,則 10 個神經元的所有加權總和,100 張圖片的加權總和為 X.W,即矩陣乘法。

每個神經元現在必須新增偏誤 (常數)。因為有 10 個神經元,我們有 10 個偏誤常數。我們將 10 個值的向量稱為 b必須加到先前計算的矩陣的每一行中。使用一項名為「廣播」的魔法我們輸入一個簡單的加號

最後,我們要套用活化函數 例如「softmax」並取得描述單層類神經網路的公式,適用於 100 張圖片:

Screen Shot 2016-07-26 at 16.02.36.png

在 Keras 中

使用 Keras 等高階類神經網路程式庫時,我們不需要實作這個公式。但請務必瞭解,類神經網路層只是許多乘法和加法運算而已。在 Keras 中,稠密層的寫法如下:

tf.keras.layers.Dense(10, activation='softmax')

深入探索

鏈結類神經網路層十分簡單。第一層會計算像素的加權總和。後續圖層會計算前一個圖層輸出的加權總和。

fba0638cc213a29.png

除了神經元數量之外,唯一的差異會是活化函數選擇。

啟用函式:relu、softmax 和 sigmoid

您通常會使用 "relu"啟用函數,不過最後一個圖層除外。在分類器中,最後一層會使用「softmax」啟用。

644f4213a4ee70e5.png

同樣地,「神經元」計算所有輸入內容的加權總和,然後加上名為「偏誤」的值並透過啟用函式將結果提供給動態饋給。

最常見的活化函數稱為 「RELU」,如上圖所示,這是非常簡單的函式。

類神經網路中傳統的啟動函式是「sigmoid」,而「relu」是事實證明,幾乎每個地方都能享有更優質的收容性,現在是更理想的選擇。

41fc82288c4aff5d.png

針對分類啟用 Softmax

我們的類神經網路最後一層有 10 個神經元,因為我們想將手寫數字分類為 10 個類別 (0,..9)。而是會輸出 0 到 1 之間的 10 個數字,代表該數字是 0、1、2 等。在最後一層中,我們會使用名為 "softmax" 的活化函式。

在向量上套用 softmax 時,方法是取得每個元素的指數,然後將向量正規化,一般是將向量除以「L1」範數 (也就是絕對值的總和),讓正規化值的總和等於 1,可以解讀為機率。

啟用前最後一層的輸出內容有時稱為 "logits"。如果這個向量為 L = [L0、L1、L2、L3、L4、L5、L6、L7、L8、L9],則:

ef0d98c0952c262d.png d51252f75894479e.gif

交叉熵損失

現在類神經網路根據輸入的圖片產生預測結果,因此需要測量圖片的品質,也就是網路提供給我們與正確答案之間的距離 (通常稱為「標籤」)。請記住,資料集中的所有圖片都有正確的標籤。

任何距離都可行,但就分類問題而言,我們稱為「交叉熵距離」是最有效的做法。我們將此錯誤稱為「損失」函式:

6dbba1bce3cadc36.png

漸層下降

「訓練」類神經網路實際上是指使用訓練圖片和標籤來調整權重和偏誤,藉此盡量減少交叉熵損失函式。運作方式如下:

交叉熵是指權重、偏誤、訓練圖片像素及其已知類別的功能。

如果我們針對所有權重和所有偏誤,計算出交叉熵的部分導數,就會取得「梯度」,根據指定圖片、標籤以及權重和偏誤的現值計算。請記住,我們可以擁有數百萬個權重和偏誤,因此計算梯度聽起來好像是許多工作。幸好,TensorFlow 就是我們辦不到的。漸層的數學特性是指向「上方」。由於我們要前往十字區的低點,所以方向是相反的。我們會更新部分漸層的權重和偏誤。接著,我們會在訓練迴圈中使用下一批的訓練圖片和標籤,再次執行相同的操作。希望這個例子能創造出交叉熵,但不保證這個下限不會重複。

漸層 descent2.png

最小批次處理和發展趨勢

您可以只用一張範例圖片計算漸層,並立即更新權重和偏誤,但以批次方式進行,例如,128 張圖片能夠產生較理想的漸層,更能充分代表不同範例圖片所施加的限制,因此可能更快地聚集在解決方案中。迷你批次的大小是可調整的參數,

這項技巧有時也稱為「隨機梯度下降」還有另一項更實用的優點:處理批次也意味著處理更大型的矩陣,且這些運算通常較容易在 GPU 和 TPU 上最佳化。

然而,收斂法還是有點混亂,而且即使漸層向量全為零,甚至會停止。這代表我們已經找到最低限度了?不一定。漸層元件可以是最小值或最大值。如果某個漸層向量有數百萬個元素,但全都是零,則每個零對應最小,且都不對應到最大點的機率極小。而且在許多維度的空間中很常見,我們不想停下來。

cc544924671fa208.png

插圖:馬鞍縫。漸層為 0,但並非在所有方向的最小值。(圖片出處 Wikimedia: by Nicoguaro - Own Work, CC BY 3.0)

解決方法是為最佳化演算法增加成長動能,讓演算法能在不停歇的加速點前進。

詞彙

batchmini-batch:一律對訓練資料和標籤進行訓練。這有助於演算法收斂。「批次」通常是資料張量的第一個維度。舉例來說,[100, 192, 192, 3] 的張量包含 100 張 192x192 像素和三個值 (RGB) 的 100 張圖片。

交叉熵損失:分類器中經常使用的特殊損失函式。

稠密層:神經元層,每個神經元都會連接至上一層的所有神經元。

features (特徵):類神經網路的輸入內容有時也稱為「特徵」。要準確判斷資料集的哪些部分 (或零件組合) 並提供給類神經網路以獲得準確的預測結果,就稱為「特徵工程」。

labels:「類別」的另一個名稱或是更正監督式分類問題的答案

學習率:在訓練迴圈的每個疊代中更新權重和偏誤的梯度比例。

logits:套用活化函式前一層神經元層的輸出內容稱為「logits」。字詞取自「邏輯函式」a.k.a.「sigmoid 函式」這個模型過去是最常使用的活化函數,「Logistic 函式前方的遠端輸出」已縮短為「logits」。

loss:比較類神經網路輸出內容與正確答案的錯誤函式

neuron:計算輸入內容的加權總和、加上偏誤,並透過活化函式提供結果。

one-hot 編碼:類別 3 之 3 會編碼為 5 個元素的向量,除了第三個 1 之外,其他所有零。

relu:固定線性單元。神經元經常使用的活化函數。

sigmoid:過去廣受歡迎的活化函數,在特殊情況下仍可派上用場。

softmax:一種特殊的活化函數,用於向量、增加最大元件與所有其他元件之間的差異,並將向量正規化為 1 的總和,讓該向量被解為機率向量。做為分類器中的最後一個步驟。

tensor:Tensor就像矩陣,不過可以任意數量的維度1 維張量是一種向量。2 維度張量就是矩陣。接著,就能使用有 3、4、5 或更多維度的張量。

5. 現在就來探討

回到研究筆記本,這次我們來朗讀程式碼。

c3df49e90e5a654f.png keras_01_mnist.ipynb

我們來逐一查看這個筆記本中的所有儲存格

儲存格「參數」

資料檔案的批量、訓練週期數和資料檔案位置會在此定義。資料檔案會託管於 Google Cloud Storage (GCS) 值區,因此位址開頭為 gs://

「匯入」儲存格

所有必要的 Python 程式庫都會匯入此處,包括 TensorFlow 和 matDrawlib 用於視覺化呈現。

儲存格「視覺化公用程式 [RUN ME]****」

這個儲存格含有不感興趣的視覺化程式碼。系統預設會收合內容,但如果您有空時再點選,即可查看程式碼。

儲存格 "tf.data.Dataset:剖析檔案並準備訓練和驗證資料集"

這個儲存格使用 tf.data.Dataset API,將 MNIST 資料集載入資料檔案。不需要花太多時間使用這個儲存格。如果您對 tf.data.Dataset API 感興趣,請參閱 TPU 速度資料管道的相關教學課程。目前基本功如下:

MNIST 資料集的圖片和標籤 (正確答案) 會以長度固定的記錄儲存在 4 個檔案中。您可以使用專屬固定記錄函式載入檔案:

imagedataset = tf.data.FixedLengthRecordDataset(image_filename, 28*28, header_bytes=16)

現在我們已經有圖片位元組的資料集。需要解碼為圖片。我們會定義用於這項操作的函式。圖片不會壓縮,因此函式不需要解碼 (decode_raw 基本上不會執行任何程式碼)。接著,圖片會轉換成 0 到 1 之間的浮點值。我們可以在此將其重新塑形為 2D 圖片,但實際上我們會保留大小為 28*28 的平面陣列,因為這是我們初始密集圖層的預期。

def read_image(tf_bytestring):
    image = tf.io.decode_raw(tf_bytestring, tf.uint8)
    image = tf.cast(image, tf.float32)/256.0
    image = tf.reshape(image, [28*28])
    return image

我們使用 .map 將這個函式套用至資料集,並取得圖片資料集:

imagedataset = imagedataset.map(read_image, num_parallel_calls=16)

我們對標籤的讀取和解碼作業相同,同時會.zip 圖片和標籤:

dataset = tf.data.Dataset.zip((imagedataset, labelsdataset))

我們現已完成一組資料集 (圖片、標籤)。這就是模型預期的結果。我們目前還無法在訓練函式中使用這個函式:

dataset = dataset.cache()
dataset = dataset.shuffle(5000, reshuffle_each_iteration=True)
dataset = dataset.repeat()
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

tf.data.Dataset API 具有準備資料集的所有必要公用程式函式:

.cache 會在 RAM 中快取資料集。這個資料集相當小,因此也能發揮作用。.shuffle 會以包含 5000 個元素的緩衝區隨機播放。請務必將訓練資料隨機分組。.repeat 會循環處理資料集。我們會多次進行訓練 (多個訓練週期)。.batch 會將多個圖片和標籤一起提取到迷你批次。最後,.prefetch 可以使用 CPU 準備下一個批次,同時開始在 GPU 上訓練目前批次。

驗證資料集會以類似方式準備。我們現在已準備好定義模型,並使用這個資料集訓練。

儲存格「Keras 型號」

我們所有的模型都是直線序列,因此我們可以使用 tf.keras.Sequential 樣式來建立這些模型。這裡一開始是單一密集層。我們將手寫數字分為 10 個類別,因此有 10 個神經元。使用「softmax」啟用原因,因為這是分類器中的最後一個層。

Keras 模型也需要瞭解輸入內容的形狀。tf.keras.layers.Input 可以用來定義相關資訊。這裡的輸入向量是長度為 28*28 像素的平面向量。

model = tf.keras.Sequential(
  [
    tf.keras.layers.Input(shape=(28*28,)),
    tf.keras.layers.Dense(10, activation='softmax')
  ])

model.compile(optimizer='sgd',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# print model layers
model.summary()

# utility callback that displays training curves
plot_training = PlotTraining(sample_rate=10, zoom=1)

使用 model.compile 函式在 Keras 中設定模型。在這裡,我們使用基本最佳化工具 'sgd' (Stochastic Gradient Descent)。分類模型需要跨熵損失函式,在 Keras 中稱為 'categorical_crossentropy'。最後,我們會要求模型計算 'accuracy' 指標,也就是正確分類圖片的百分比。

Keras 提供非常實用的 model.summary() 公用程式,可輸出您建立的模型詳細資料。種類講師新增了 PlotTraining 公用程式 (由「視覺化公用程式」儲存格定義),會在訓練期間顯示各種訓練曲線。

「訓練及驗證模型」儲存格

這時訓練工具就是呼叫 model.fit,並傳入訓練和驗證資料集。根據預設,Keras 會在每個週期結束時執行驗證作業。

model.fit(training_dataset, steps_per_epoch=steps_per_epoch, epochs=EPOCHS,
          validation_data=validation_dataset, validation_steps=1,
          callbacks=[plot_training])

在 Keras 中,您可以使用回呼在訓練期間新增自訂行為。這就是我們為這場研討會實作動態更新訓練圖的方式。

儲存格「Visualize Prediction」儲存格

模型訓練完畢後,我們可以呼叫 model.predict() 來取得預測結果:

probabilities = model.predict(font_digits, steps=1)
predicted_labels = np.argmax(probabilities, axis=1)

此處我們準備了使用本機字型呈現的一組印刷數字做為測試。請記住,類神經網路會從最終的「softmax」傳回 10 個機率的向量。要取得標籤時,我們必須找出哪個機率最高。來自 numpy 程式庫的 np.argmax

如要瞭解為何需要使用 axis=1 參數,請注意,我們已處理一批 128 張圖片,因此模型會傳回 128 個機率向量。輸出張量的形狀為 [128, 10]。系統正在針對每張圖片傳回的 10 個可能性計算 argmax,因此 axis=1 (第一軸為 0)。

這個簡單的模型已能辨識 90% 的數字。雖然差不多,不過現在您將大幅改善這項技術。

396c54ef66fad27f.png

6. 新增圖層

godeep.png

為了提高辨識準確率,我們會在類神經網路中加入更多層。

Screen Shot 2016-07-27 at 15.36.55.png

我們保留 softmax 以做為最後一層的活化函式,因為這最適合分類。但在中間層上,我們會使用最傳統的啟動函式:s 函數:

41fc82288c4aff5d.png

舉例來說,您的模型看起來可能如下所示 (別忘記半形逗號,tf.keras.Sequential 會取用以半形逗號分隔的圖層清單):

model = tf.keras.Sequential(
  [
      tf.keras.layers.Input(shape=(28*28,)),
      tf.keras.layers.Dense(200, activation='sigmoid'),
      tf.keras.layers.Dense(60, activation='sigmoid'),
      tf.keras.layers.Dense(10, activation='softmax')
  ])

查看「摘要」模型參數現在數量至少多了 10 倍。應該比 10 倍更好!但基於某些原因,並不是 ...

5236f91ba6e07d85.png

損失似乎也透過屋頂拍攝。發生錯誤。

7. 深度網路特別照護

你在 80 和 90 年代設計過類神經網路,是不少相關經驗的老到地。難怪他們的想法不盡相同,因此融合了所謂的「AI 冬季」。事實上,隨著您增加層,類神經網路會越來越難收縮。

結果顯示,多層 (20、50,甚至目前 100 個) 的深層類神經網路非常有效,提供幾個數學的骯髒技巧,讓模型集成流暢。在 2010 年代,發現了這些簡單的秘訣,是我們瞭解深度學習的原因之一。

啟用 RELU

relu.png

S 函數在深層網路中實際上很有問題。它會壓縮介於 0 到 1 之間的所有值,而當您重複計算時,神經元輸出內容及其梯度可能會完全消失。文中提到這句話是基於歷史因素,但現代網路使用的是 RELU (直線型線性單元),如下所示:

1abce89f7143a69c.png

另一隻手指的導數至少在右側是 1。透過 RELU 啟用時,即使某些神經元的梯度為零,其他神經元的梯度一律不會是零,但其他非零的梯度和訓練也能保持良好的步調。

更優異的最佳化工具

在像這樣的高維度空間中,我們是依照 10,000 個權重和偏誤的順序 (「鋸齒狀」) 排序這些點並非本機 minima,但漸層不代表就是零,而且梯度下降器會停留在該地。TensorFlow 提供全套的可用最佳化器,包括一些與多種慣性運作的最佳化器,並能安全地滑過馬鞍點。

隨機初始化

在訓練之前,先初始化權重偏誤的藝術本來就有研究領域,且許多論文都與這個主題相關。您可以在這裡查看 Keras 中所有可用的初始化器。幸好,Keras 預設會執行正確的操作,而且使用 'glorot_uniform' 初始化器,在幾乎所有情況下都是最佳的選擇。

您無須採取任何行動,因為 Keras 已經做到了。

NaN ???

跨熵公式包含對數,且 log(0) 不是數字 (NaN,您可自行決定是否使用數字當機)。交叉熵的輸入值可以為 0 嗎?輸入內容來自 softmax,這個上限本質上為指數,且指數一律不得為 0。我們很安全!

真的嗎?在美妙的數學世界中,我們很安全,但在電腦世界中,exp(-150) 以 float32 格式表示,跟它是零,與交叉熵當機。

幸好,您不必採取任何行動,因為 Keras 會特別留意這點,並以特別謹慎的方式計算 softmax,再計算交叉熵,藉此確保數值穩定性,並避免發生難以理解的 NaN。

成功?

e1521c9dd936d9bc.png

現在,您應該能獲得 97% 的準確率。本工作坊的目標在於將大幅提高 99% 以上,所以我們繼續朝這個目標邁進。

如果遇到困難,目前可以解決這個問題:

c3df49e90e5a654f.png keras_02_mnist_dense.ipynb

8. 學習率衰減

或許我們可以嘗試加快訓練速度?Adam 最佳化工具的預設學習率為 0.001。請試著提高上限。

加快速度似乎對你沒有太大幫助,但到底有什麼不同?

d4fd66346d7c480e.png

訓練曲線真的很吵雜,而且檢查兩個驗證曲線:它們都上下跳動。換言之,我們動作要過快我們可以回到先前的速度,但有更好的方法。

慢速.png

理想的做法是盡快開始學習,並以指數方式大幅下降學習率。在 Keras 中,您可以使用 tf.keras.callbacks.LearningRateScheduler 回呼來執行此操作。

適合複製貼上的實用程式碼:

# lr decay function
def lr_decay(epoch):
  return 0.01 * math.pow(0.6, epoch)

# lr schedule callback
lr_decay_callback = tf.keras.callbacks.LearningRateScheduler(lr_decay, verbose=True)

# important to see what you are doing
plot_learning_rate(lr_decay, EPOCHS)

別忘了使用您建立的 lr_decay_callback。將其新增至 model.fit 中的回呼清單:

model.fit(...,  callbacks=[plot_training, lr_decay_callback])

這項小改變的影響相當顯著。您可以看到大部分的雜訊已經消失,測試準確率也持續超過 98%。

8c1ae90976c4a0c1.png

9. 丟棄,過度配適

這個模型現在似乎很順利地收斂。讓我們來深入探討。

是否有幫助?

e36c09a3088104c6.png

事實上,準確率仍維持在 98%,並留意驗證損失。會朝上!學習演算法只會處理訓練資料,並據此最佳化訓練損失。它完全看不到驗證資料,因此不用擔心,經過一個時間,其工作已經不再對驗證損失產生影響,甚至會停止減少,有時甚至會跳出。

這不會立即影響模型的實際辨識能力,但會讓您無法執行多次疊代,且通常代表訓練已無正面影響。

dropout.png

這種中斷情形通常稱為「過度配適」。然後嘗試套用所謂的「丟棄」的正規化技巧每次訓練疊代時,丟棄技術就會隨機射出神經元。

最終成效

43fd33801264743f.png

噪音重新出現 (因為有掉落的原理)。雖然失去了驗證結果,但驗證損失似乎沒什麼改變,但整體來說比沒有放棄驗證來得高。而且驗證準確率也下降了。這結果相當令人失望。

掉落並不是正確的解決方案,或可能「過度配適」屬於較複雜的概念,有些原因並非「丟棄」修正?

什麼是「過度配適」?當類神經網路學習「惡意」學習時,就會發生過度配適的情形。這樣的學習方法適用於訓練範例,對實際資料卻並不理想。有些正規化技巧 (例如丟棄) 可以強制使學習者獲得更好的學習方式,但過度配適也會有更深的根源。

overfitting.png

如果類神經網路無法隨手發生問題,就會發生基礎過度配適的情形。假設有太多神經元,網路可以儲存所有訓練圖像,並透過模式比對來辨識。而這在實際資料上會完全失敗。類神經網路必須受到一些限制,才能強制模型在訓練過程中學習到的意義。

如果您的訓練資料很少,即使是小型網路也能從心中學習,並且您會看見「過度配適」。一般來說,類神經網路都需要大量資料才能訓練。

最後,如果您對整本書籍都進行了實驗,請測試不同網路大小的自由度,確保能夠自由運用網路的自由度,以及利用大量資料進行訓練,可能仍有無法改進的表現。正如我們的範例所示,目前的類神經網路無法擷取更多資訊。

還記得我們如何將圖片整併成單一向量嗎?真不敢相信。手寫數字是由形狀組成,而且我們在平平像素時捨棄形狀資訊。不過,某種類神經網路可運用形狀資訊:卷積網路。讓我們來試試看。

如果遇到困難,目前可以解決這個問題:

c3df49e90e5a654f.png keras_03_mnist_dense_lrdecay_dropout.ipynb

10. [資訊] 卷積網路

概述

如果您知道下個段落中的所有以粗體顯示的字詞,則可進行下一個練習。如果您剛開始使用卷積類神經網路,請繼續閱讀本文。

convolutional.gif

插圖:篩選圖片,以及兩個由 4x4x3=48 可學習權重組成的兩個連續濾鏡。

以下是簡易卷積類神經網路在 Keras 中的模樣:

model = tf.keras.Sequential([
    tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1)),
    tf.keras.layers.Conv2D(kernel_size=3, filters=12, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=24, strides=2, activation='relu'),
    tf.keras.layers.Conv2D(kernel_size=6, filters=32, strides=2, activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(10, activation='softmax')
])

688858c21e3beff2.png

在卷積網路層中,一個「神經元」計算圖片上方小區域的像素加權總和。它會增加偏誤並透過活化函數提供總和,就像一般密集層中的神經元一樣。接著,系統會為整張圖片,以相同的權重重複執行這項作業。請記住,在稠密層中,每個神經元都有各自的權重。在這個例子中,的權重滑過圖片中左右兩側 (「卷積」)。輸出內容的值與圖片中的像素數量一樣多 (但邊緣需要一些邊框間距)。這是篩選作業。在上圖中,使用的是 4x4x3=48 權重的篩選器。

但是 48 個權重是不夠的。為了增加更多的自由度,我們以一組新的權重重複同一項作業。這會產生一組新的篩選器輸出內容。命名為「channel」輸出圖片中的 R、G、B 管道表示輸出內容。

Screen Shot 2016-07-29 at 16.02.37.png

透過新增維度,即可將兩組或更多的權重加總為一個張量。這就是卷積層的權重張量的一般形狀。由於輸入和輸出通道的數量是參數,因此我們可以開始堆疊及鏈結卷積層。

d1b557707bcd1cb9.png

插圖:卷積類神經網路改變了「方塊」將資料匯出至其他「立方體」資料安全

碰撞卷積,最大集區

透過 2 或 3 序列執行卷積,我們也可縮小產生的資料方塊水平維度。有兩種常見方式:

  • 網格卷積:相同滑步濾鏡,但步長大於 1
  • 最大集區:套用 MAX 運算的滑動窗口 (通常在 2x2 修補程式中,每 2 個像素重複一次)

2b2d4263bb8470b.gif

插圖:將運算視窗滑動 3 像素,輸出值就會較少。傾斜的捲積或最大集區 (最長可滑動 2 點的 2x2 視窗範圍) 可縮小水平維度的資料方塊。

最後一層

在最後一個卷積層之後,資料格式為「立方體」。有兩種方法可透過最終稠密層提供資料。

第一個做法是將資料方塊壓平化至向量,再動態饋給至 softmax 層,有時,您甚至可以在 softmax 層之前加入稠密層。權重數量通常十分昂貴。卷積網路尾端的稠密層可能包含整個類神經網路的一半以上權重。

比起使用昂貴的稠密層,我們也可以分割傳入的資料「cube」盡可能彙整到多個類別,然後平均計算其值,再透過 softmax 啟動函式將這些值提供給這些部分。這種建構分類成本的方法可花費 0 個權重。Keras 中有層:tf.keras.layers.GlobalAveragePooling2D()

a44aa392c7b0e32a.png

跳到下一節,為手邊問題建構卷積網路。

11. 卷積網路

我們來建構卷積網路,用於辨識手寫數字辨識功能。我們會在最頂端使用三個卷積層,也就是底部的傳統 softmax 讀取層,並將其與一個完全連結的層連接:

e1a214a170957da1.png

請注意,第二和第三個卷積層有兩層,解釋為何輸出值從 28x28 降低至 14x14,然後是 7x7。

我們開始編寫 Keras 程式碼。

必須在第一個卷積層前特別留意。的確是 3D 的「立方體」但我們的資料集目前已經為密集圖層建立而成,所有圖片像素都會壓平成向量。我們需要將圖片重塑為 28x28x1 的圖片 (1 個灰階圖片):

tf.keras.layers.Reshape(input_shape=(28*28,), target_shape=(28, 28, 1))

您可以使用此行取代現有的 tf.keras.layers.Input 圖層。

在 Keras 中,「relu」啟用的捲積層語法如下:

140f80336b0e653b.png

tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu')

至於緊張的捲積,您可以寫成:

tf.keras.layers.Conv2D(kernel_size=6, filters=24, padding='same', activation='relu', strides=2)

如何將資料方塊壓平化為向量,方便稠密層使用:

tf.keras.layers.Flatten()

而稠密層的語法則維持不變:

tf.keras.layers.Dense(200, activation='relu')

您的模型是否突破了 99% 的準確度阻礙?差一點...但要看驗證損失曲線您是否有印象呢?

ecc5972814885226.png

同時參考預測結果。在第一次使用時,您會看到系統現在可正確識別大部分的 10,000 個測試數字。偵測錯誤的次數只有大約 41⁄2 列 (約 110 位數,共 10,000 位數)

37e4cbd3f390c89e.png

如果遇到困難,目前可以解決這個問題:

c3df49e90e5a654f.png keras_04_mnist_convolutional.ipynb

12. 再次丟棄

先前的訓練課程明顯顯示過度配適 (但準確度仍低於 99%)。要不要再試一次?

這次怎麼樣?

63e0cc982cee2030.png

這次似乎沒有成功。如今驗證損失的次數不會再減少,最終的準確率應為超過 99%。恭喜!

第一次嘗試套用丟棄時,我們原本猜想發生過度配適的問題,但實際上問題是類神經網路的架構。如果沒有捲積層,我們就無法進一步探索,也沒有其他發現。

這次看似過度配適,是導致問題發生,丟棄的情況實際上確實有所幫助。請記住,有許多因素會導致訓練與驗證損失曲線間產生斷線,並使驗證損失增加。過度配適 (自由度太多,網路遭人不當使用) 只是其中一種。如果資料集太小或類神經網路的架構不足,您可能會在損失曲線中看到類似的行為,但這樣就不會有幫助。

13. 批次正規化

oggEbikl2I6_sOo7FlaX2KLdNeaYhJnVSS8GyG8FHXid75PVJX73CRiOynwpMZpLZq6_xAy69wgyez5T-ZlpuC2XSlcmjk7oVcOzefKKTFhTEoLO3kljz2RDyKcaFtHvtTey-I4VpQ

最後,讓我們試著新增批次正規化功能。

其實就是理論上,請記得以下幾項規則:

我們現在來播放這本書,並在每個類神經網路層 (除了最後一個) 中新增批次正規層。不要加到最後一個「softmax」執行這無法在那裡派上用場。

# Modify each layer: remove the activation from the layer itself.
# Set use_bias=False since batch norm will play the role of biases.
tf.keras.layers.Conv2D(..., use_bias=False),
# Batch norm goes between the layer and its activation.
# The scale factor can be turned off for Relu activation.
tf.keras.layers.BatchNormalization(scale=False, center=True),
# Finish with the activation.
tf.keras.layers.Activation('relu'),

目前的準確率如何?

ea48193334c565a1.png

只要稍加調整 (BATCH_SIZE=64,學習率衰減參數 0.666,稠密層 0.3 的放棄率),再加上有點幸能將得到 99.5% 的結果。已按照「最佳做法」調整學習率和放棄率使用批次常態:

  • 批次正規化有助於凝聚類神經網路,通常能加快訓練速度。
  • 批次正規化是正規化工具,通常可以減少減少耗用量,甚至完全不使用丟棄量。

解決方案筆記本執行了 99.5% 的訓練:

c3df49e90e5a654f.png keras_05_mnist_batch_norm.ipynb

14. 在強大的硬體上進行雲端訓練:AI 平台

d7d0282e687bdad8.png

您可以在 GitHub 上的 mlengine 資料夾找到適用於雲端的程式碼版本,以及在 Google Cloud AI Platform 上執行該程式的操作說明。如要執行這個部分,請先建立 Google Cloud 帳戶並啟用計費功能。完成研究室所需的資源應少於數美元 (假設在一個 GPU 上需要 1 小時的訓練時間)。如要完成帳戶準備工作,請按照下列步驟操作:

  1. 建立 Google Cloud Platform 專案 ( http://cloud.google.com/console)。
  2. 啟用計費功能。
  3. 安裝 GCP 指令列工具 ( 按這裡取得 GCP SDK)。
  4. 建立 Google Cloud Storage 值區 (位於區域 us-central1)。用於暫存訓練程式碼及儲存訓練過的模型。
  5. 啟用必要的 API 並要求所需的配額 (執行訓練指令一次後,您應該會看到錯誤訊息,說明要啟用的項目)。

15. 恭喜!

您已成功建立第一個類神經網路,並完成訓練達到 99% 的準確率。一路上學到的技術與 MNIST 資料集無關,實際上在處理類神經網路時已廣泛使用。做為一份禮物,一起使用「懸崖的音符」研究室版本資訊卡您可以用它來記住您已學到的內容:

懸崖附註 tensorflow lab.png

後續步驟

  • 完成完全連線和卷積網路後,您應該要查看循環類神經網路
  • Google Cloud 提供 AI Platform,方便您在雲端的分散式基礎架構中執行訓練或推論。
  • 最後,我們非常喜歡使用者的意見。如果您在研究室中發現任何錯誤,或認為需要改善,請告訴我們。我們透過 GitHub 問題處理意見回饋 [feedback link]。

HR.png

Martin Görner ID small.jpg作者:Martin GörnerTwitter:@martin_gorner

這個研究室中的所有卡通圖片著作權:alexpokusay / 123RF 圖庫相片