1. 總覽
本教學課程已針對 Tensorflow 2.2 更新!
在本程式碼研究室中,您將學習如何建構及訓練可辨識手寫數字的類神經網路。一路走來,隨著您強化類神經網路以達到 99% 的準確率,您也會發現深度學習專業人士用來有效率地訓練模型時,會用到哪些工具。
本程式碼研究室使用 MNIST 資料集,共有 60,000 位數已加上標籤的數字,一直以來都忙著處理將近二十年的 PhD 作品。必須使用不超過 100 行的 Python / TensorFlow 程式碼,才能解決這個問題。
課程內容
- 什麼是類神經網路及訓練方式
- 如何使用 tf.keras 建構基本的一層類神經網路
- 如何加入更多圖層
- 如何設定學習率時間表
- 如何建立卷積類神經網路
- 如何使用正規化技術:丟棄、批次正規化
- 什麼是過度配適
軟硬體需求
只需瀏覽器即可。你可以透過 Google Colaboratory 完全透過這個研討會進行。
意見回饋
如果您在研究室中發現任何錯誤,或認為需要改善,請告訴我們。我們透過 GitHub 問題處理意見回饋 [feedback link]。
2. Google Colaboratory 快速入門
本研究室使用 Google Colaboratory,您不需要進行任何設定。你可以透過 Chromebook 執行。請開啟下方檔案並執行儲存格,熟悉 Colab 筆記本。
其他操作說明如下:
選取 GPU 後端
在 Colab 選單中,依序選取「執行階段」>「執行階段」變更執行階段類型,然後選取 GPU。首次執行時,系統會自動連線至執行階段,你也可以使用「連線」選項。
執行筆記本
按一下儲存格並使用 Shift + Enter 鍵,即可逐一執行儲存格。您也可以透過「執行階段 >」全部執行
Table of contents
所有筆記本都有目錄。您可以使用左側的黑色箭頭開啟報表。
隱藏的儲存格
部分儲存格只會顯示標題。這是 Colab 專屬的筆記本功能。您可以按兩下這些程式碼,以查看裡面的程式碼,但不太有趣。通常支援或視覺化函式。您仍需執行這些儲存格,才能定義其中的函式。
3. 訓練類神經網路
我們會先觀看類神經網路訓練影片。請開啟下方的筆記本並瀏覽所有儲存格。請先不要注意程式碼,稍後才會開始說明。
執行筆記本時,請將焦點放在視覺化。請見下方說明。
訓練資料
我們有人工手寫數字資料集,並加上標籤,以便我們瞭解每張圖片代表的意義,例如介於 0 到 9 之間的數字。在筆記本中,您會看到內容摘錄:
我們將建構的類神經網路將手寫數字分類在其 10 個類別中 (0、..、9)。此方法會根據內部參數需要具有正確的值,才能順利分類。這是「正確的值」是透過訓練程序學習,這個過程需要「已加上標籤的資料集」並附上圖片和相關正確答案。
如何得知訓練過的類神經網路效能良好?使用訓練資料集來測試網路作弊。這項服務在訓練期間已多次看到該資料集,而且在特定情況下表現最佳。我們需要另一個在訓練期間從未見過的已加標籤資料集,才能評估「真實」藉此確保網路效能稱為「驗證資料集」
訓練
隨著訓練的進行,每次訓練一個批次的資料時,內部模型參數會不斷更新,而模型在辨識手寫數字方面會越來越好。您可以在訓練圖表中查看:
右側的「準確率」就是正確辨識數字的百分比。隨著訓練的進展與時俱進,這是很好
左側是「損失」。為了提供訓練課程,我們將定義一個「損失」函式,代表系統辨識數字的準確度,並嘗試將數字最小化。如您所見,隨著訓練的進度,訓練資料和驗證資料的損失都會下降,那就是很好。這代表類神經網路正在學習
X 軸代表「週期」的數量反覆測試整個資料集
預測
模型訓練完成後,就能用來辨識手寫數字。接下來的視覺化圖表將顯示本機字型 (第一行) 轉譯出的幾個數字,以及驗證資料集內 10,000 位數的成效表現。如果預測類別有誤,每個數字下方會以紅色顯示。
如您所見,這個初始模型並不完善,但仍可正確辨識某些數字。最終的驗證準確率約為 90%,對於我們一開始採用的簡單模型來說並沒有那麼糟,但這仍表示 10,000 組沒有 1000 組驗證數字。遠大於可顯示次數,因此所有答案似乎有誤 (紅色)。
張量
資料儲存在矩陣。28x28 像素的灰階圖片適合 28x28 二維矩陣。但如果是彩色圖片,就需要更多尺寸。每個像素有 3 種顏色值 (紅、綠、藍色),因此需要採 3D 表格和尺寸 [28、28、3]。此外,如要儲存一批 128 張彩色圖片,就需要有尺寸 [128、28、28、3] 的 4D 表格。
這些多維度表格稱為「張量」,維度清單則為「形狀」。
4. [INFO]:類神經網路 101
概述
如果您知道下個段落中的所有以粗體顯示的字詞,則可進行下一個練習。如果您剛開始學習深度學習,歡迎繼續閱讀。
如果是以多層圖層建構的模型,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, ... )
單一稠密層
MNIST 資料集中的手寫數字是 28x28 像素的灰階圖片。最簡單的分類方法,就是使用 28x28=784 像素做為單層類神經網路的輸入內容。
類神經網路中的每個「神經元」會計算其所有輸入內容的加權總和,然後新增稱為「偏誤」的常數然後透過一些非線性啟用函式提供結果。"weights" 和 "biases" 是透過訓練決定的參數。這類物件一開始是用隨機值進行初始化,
上圖呈現的是有 10 個輸出神經元的單層類神經網路,因為我們想要將數字分類為 10 個類別 (0 到 9)。
利用矩陣乘法
下列是處理圖像集合的類神經網路層如何用矩陣乘法表示:
使用權重矩陣 W 的第一欄,計算第一張圖片所有像素的加權總和。這個總和對應至第一個神經元。使用權重的第二欄,為第二個神經元進行相同的操作,以此類推至第 10 個神經元。接著,我們可以重複此作業,處理其餘 99 張圖片。如果呼叫含有 100 張圖片的 X 矩陣,則 10 個神經元的所有加權總和,100 張圖片的加權總和為 X.W,即矩陣乘法。
每個神經元現在必須新增偏誤 (常數)。因為有 10 個神經元,我們有 10 個偏誤常數。我們將 10 個值的向量稱為 b必須加到先前計算的矩陣的每一行中。使用一項名為「廣播」的魔法我們輸入一個簡單的加號
最後,我們要套用活化函數 例如「softmax」並取得描述單層類神經網路的公式,適用於 100 張圖片:
在 Keras 中
使用 Keras 等高階類神經網路程式庫時,我們不需要實作這個公式。但請務必瞭解,類神經網路層只是許多乘法和加法運算而已。在 Keras 中,稠密層的寫法如下:
tf.keras.layers.Dense(10, activation='softmax')
深入探索
鏈結類神經網路層十分簡單。第一層會計算像素的加權總和。後續圖層會計算前一個圖層輸出的加權總和。
除了神經元數量之外,唯一的差異會是活化函數選擇。
啟用函式:relu、softmax 和 sigmoid
您通常會使用 "relu"啟用函數,不過最後一個圖層除外。在分類器中,最後一層會使用「softmax」啟用。
同樣地,「神經元」計算所有輸入內容的加權總和,然後加上名為「偏誤」的值並透過啟用函式將結果提供給動態饋給。
最常見的活化函數稱為 「RELU」,如上圖所示,這是非常簡單的函式。
類神經網路中傳統的啟動函式是「sigmoid」,而「relu」是事實證明,幾乎每個地方都能享有更優質的收容性,現在是更理想的選擇。
針對分類啟用 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],則:
交叉熵損失
現在類神經網路根據輸入的圖片產生預測結果,因此需要測量圖片的品質,也就是網路提供給我們與正確答案之間的距離 (通常稱為「標籤」)。請記住,資料集中的所有圖片都有正確的標籤。
任何距離都可行,但就分類問題而言,我們稱為「交叉熵距離」是最有效的做法。我們將此錯誤稱為「損失」函式:
漸層下降
「訓練」類神經網路實際上是指使用訓練圖片和標籤來調整權重和偏誤,藉此盡量減少交叉熵損失函式。運作方式如下:
交叉熵是指權重、偏誤、訓練圖片像素及其已知類別的功能。
如果我們針對所有權重和所有偏誤,計算出交叉熵的部分導數,就會取得「梯度」,根據指定圖片、標籤以及權重和偏誤的現值計算。請記住,我們可以擁有數百萬個權重和偏誤,因此計算梯度聽起來好像是許多工作。幸好,TensorFlow 就是我們辦不到的。漸層的數學特性是指向「上方」。由於我們要前往十字區的低點,所以方向是相反的。我們會更新部分漸層的權重和偏誤。接著,我們會在訓練迴圈中使用下一批的訓練圖片和標籤,再次執行相同的操作。希望這個例子能創造出交叉熵,但不保證這個下限不會重複。
最小批次處理和發展趨勢
您可以只用一張範例圖片計算漸層,並立即更新權重和偏誤,但以批次方式進行,例如,128 張圖片能夠產生較理想的漸層,更能充分代表不同範例圖片所施加的限制,因此可能更快地聚集在解決方案中。迷你批次的大小是可調整的參數,
這項技巧有時也稱為「隨機梯度下降」還有另一項更實用的優點:處理批次也意味著處理更大型的矩陣,且這些運算通常較容易在 GPU 和 TPU 上最佳化。
然而,收斂法還是有點混亂,而且即使漸層向量全為零,甚至會停止。這代表我們已經找到最低限度了?不一定。漸層元件可以是最小值或最大值。如果某個漸層向量有數百萬個元素,但全都是零,則每個零對應最小,且都不對應到最大點的機率極小。而且在許多維度的空間中很常見,我們不想停下來。
插圖:馬鞍縫。漸層為 0,但並非在所有方向的最小值。(圖片出處 Wikimedia: by Nicoguaro - Own Work, CC BY 3.0)
解決方法是為最佳化演算法增加成長動能,讓演算法能在不停歇的加速點前進。
詞彙
batch 或 mini-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. 現在就來探討
回到研究筆記本,這次我們來朗讀程式碼。
我們來逐一查看這個筆記本中的所有儲存格
儲存格「參數」
資料檔案的批量、訓練週期數和資料檔案位置會在此定義。資料檔案會託管於 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% 的數字。雖然差不多,不過現在您將大幅改善這項技術。
6. 新增圖層
為了提高辨識準確率,我們會在類神經網路中加入更多層。
我們保留 softmax 以做為最後一層的活化函式,因為這最適合分類。但在中間層上,我們會使用最傳統的啟動函式:s 函數:
舉例來說,您的模型看起來可能如下所示 (別忘記半形逗號,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 倍更好!但基於某些原因,並不是 ...
損失似乎也透過屋頂拍攝。發生錯誤。
7. 深度網路特別照護
你在 80 和 90 年代設計過類神經網路,是不少相關經驗的老到地。難怪他們的想法不盡相同,因此融合了所謂的「AI 冬季」。事實上,隨著您增加層,類神經網路會越來越難收縮。
結果顯示,多層 (20、50,甚至目前 100 個) 的深層類神經網路非常有效,提供幾個數學的骯髒技巧,讓模型集成流暢。在 2010 年代,發現了這些簡單的秘訣,是我們瞭解深度學習的原因之一。
啟用 RELU
S 函數在深層網路中實際上很有問題。它會壓縮介於 0 到 1 之間的所有值,而當您重複計算時,神經元輸出內容及其梯度可能會完全消失。文中提到這句話是基於歷史因素,但現代網路使用的是 RELU (直線型線性單元),如下所示:
另一隻手指的導數至少在右側是 1。透過 RELU 啟用時,即使某些神經元的梯度為零,其他神經元的梯度一律不會是零,但其他非零的梯度和訓練也能保持良好的步調。
更優異的最佳化工具
在像這樣的高維度空間中,我們是依照 10,000 個權重和偏誤的順序 (「鋸齒狀」) 排序這些點並非本機 minima,但漸層不代表就是零,而且梯度下降器會停留在該地。TensorFlow 提供全套的可用最佳化器,包括一些與多種慣性運作的最佳化器,並能安全地滑過馬鞍點。
隨機初始化
在訓練之前,先初始化權重偏誤的藝術本來就有研究領域,且許多論文都與這個主題相關。您可以在這裡查看 Keras 中所有可用的初始化器。幸好,Keras 預設會執行正確的操作,而且使用 'glorot_uniform'
初始化器,在幾乎所有情況下都是最佳的選擇。
您無須採取任何行動,因為 Keras 已經做到了。
NaN ???
跨熵公式包含對數,且 log(0) 不是數字 (NaN,您可自行決定是否使用數字當機)。交叉熵的輸入值可以為 0 嗎?輸入內容來自 softmax,這個上限本質上為指數,且指數一律不得為 0。我們很安全!
真的嗎?在美妙的數學世界中,我們很安全,但在電腦世界中,exp(-150) 以 float32 格式表示,跟它是零,與交叉熵當機。
幸好,您不必採取任何行動,因為 Keras 會特別留意這點,並以特別謹慎的方式計算 softmax,再計算交叉熵,藉此確保數值穩定性,並避免發生難以理解的 NaN。
成功?
現在,您應該能獲得 97% 的準確率。本工作坊的目標在於將大幅提高 99% 以上,所以我們繼續朝這個目標邁進。
如果遇到困難,目前可以解決這個問題:
8. 學習率衰減
或許我們可以嘗試加快訓練速度?Adam 最佳化工具的預設學習率為 0.001。請試著提高上限。
加快速度似乎對你沒有太大幫助,但到底有什麼不同?
訓練曲線真的很吵雜,而且檢查兩個驗證曲線:它們都上下跳動。換言之,我們動作要過快我們可以回到先前的速度,但有更好的方法。
理想的做法是盡快開始學習,並以指數方式大幅下降學習率。在 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%。
9. 丟棄,過度配適
這個模型現在似乎很順利地收斂。讓我們來深入探討。
是否有幫助?
事實上,準確率仍維持在 98%,並留意驗證損失。會朝上!學習演算法只會處理訓練資料,並據此最佳化訓練損失。它完全看不到驗證資料,因此不用擔心,經過一個時間,其工作已經不再對驗證損失產生影響,甚至會停止減少,有時甚至會跳出。
這不會立即影響模型的實際辨識能力,但會讓您無法執行多次疊代,且通常代表訓練已無正面影響。
這種中斷情形通常稱為「過度配適」。然後嘗試套用所謂的「丟棄」的正規化技巧每次訓練疊代時,丟棄技術就會隨機射出神經元。
最終成效
噪音重新出現 (因為有掉落的原理)。雖然失去了驗證結果,但驗證損失似乎沒什麼改變,但整體來說比沒有放棄驗證來得高。而且驗證準確率也下降了。這結果相當令人失望。
掉落並不是正確的解決方案,或可能「過度配適」屬於較複雜的概念,有些原因並非「丟棄」修正?
什麼是「過度配適」?當類神經網路學習「惡意」學習時,就會發生過度配適的情形。這樣的學習方法適用於訓練範例,對實際資料卻並不理想。有些正規化技巧 (例如丟棄) 可以強制使學習者獲得更好的學習方式,但過度配適也會有更深的根源。
如果類神經網路無法隨手發生問題,就會發生基礎過度配適的情形。假設有太多神經元,網路可以儲存所有訓練圖像,並透過模式比對來辨識。而這在實際資料上會完全失敗。類神經網路必須受到一些限制,才能強制模型在訓練過程中學習到的意義。
如果您的訓練資料很少,即使是小型網路也能從心中學習,並且您會看見「過度配適」。一般來說,類神經網路都需要大量資料才能訓練。
最後,如果您對整本書籍都進行了實驗,請測試不同網路大小的自由度,確保能夠自由運用網路的自由度,以及利用大量資料進行訓練,可能仍有無法改進的表現。正如我們的範例所示,目前的類神經網路無法擷取更多資訊。
還記得我們如何將圖片整併成單一向量嗎?真不敢相信。手寫數字是由形狀組成,而且我們在平平像素時捨棄形狀資訊。不過,某種類神經網路可運用形狀資訊:卷積網路。讓我們來試試看。
如果遇到困難,目前可以解決這個問題:
10. [資訊] 卷積網路
概述
如果您知道下個段落中的所有以粗體顯示的字詞,則可進行下一個練習。如果您剛開始使用卷積類神經網路,請繼續閱讀本文。
插圖:篩選圖片,以及兩個由 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')
])
在卷積網路層中,一個「神經元」計算圖片上方小區域的像素加權總和。它會增加偏誤並透過活化函數提供總和,就像一般密集層中的神經元一樣。接著,系統會為整張圖片,以相同的權重重複執行這項作業。請記住,在稠密層中,每個神經元都有各自的權重。在這個例子中,的權重滑過圖片中左右兩側 (「卷積」)。輸出內容的值與圖片中的像素數量一樣多 (但邊緣需要一些邊框間距)。這是篩選作業。在上圖中,使用的是 4x4x3=48 權重的篩選器。
但是 48 個權重是不夠的。為了增加更多的自由度,我們以一組新的權重重複同一項作業。這會產生一組新的篩選器輸出內容。命名為「channel」輸出圖片中的 R、G、B 管道表示輸出內容。
透過新增維度,即可將兩組或更多的權重加總為一個張量。這就是卷積層的權重張量的一般形狀。由於輸入和輸出通道的數量是參數,因此我們可以開始堆疊及鏈結卷積層。
插圖:卷積類神經網路改變了「方塊」將資料匯出至其他「立方體」資料安全
碰撞卷積,最大集區
透過 2 或 3 序列執行卷積,我們也可縮小產生的資料方塊水平維度。有兩種常見方式:
- 網格卷積:相同滑步濾鏡,但步長大於 1
- 最大集區:套用 MAX 運算的滑動窗口 (通常在 2x2 修補程式中,每 2 個像素重複一次)
插圖:將運算視窗滑動 3 像素,輸出值就會較少。傾斜的捲積或最大集區 (最長可滑動 2 點的 2x2 視窗範圍) 可縮小水平維度的資料方塊。
最後一層
在最後一個卷積層之後,資料格式為「立方體」。有兩種方法可透過最終稠密層提供資料。
第一個做法是將資料方塊壓平化至向量,再動態饋給至 softmax 層,有時,您甚至可以在 softmax 層之前加入稠密層。權重數量通常十分昂貴。卷積網路尾端的稠密層可能包含整個類神經網路的一半以上權重。
比起使用昂貴的稠密層,我們也可以分割傳入的資料「cube」盡可能彙整到多個類別,然後平均計算其值,再透過 softmax 啟動函式將這些值提供給這些部分。這種建構分類成本的方法可花費 0 個權重。Keras 中有層:tf.keras.layers.GlobalAveragePooling2D()
。
跳到下一節,為手邊問題建構卷積網路。
11. 卷積網路
我們來建構卷積網路,用於辨識手寫數字辨識功能。我們會在最頂端使用三個卷積層,也就是底部的傳統 softmax 讀取層,並將其與一個完全連結的層連接:
請注意,第二和第三個卷積層有兩層,解釋為何輸出值從 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」啟用的捲積層語法如下:
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% 的準確度阻礙?差一點...但要看驗證損失曲線您是否有印象呢?
同時參考預測結果。在第一次使用時,您會看到系統現在可正確識別大部分的 10,000 個測試數字。偵測錯誤的次數只有大約 41⁄2 列 (約 110 位數,共 10,000 位數)
如果遇到困難,目前可以解決這個問題:
12. 再次丟棄
先前的訓練課程明顯顯示過度配適 (但準確度仍低於 99%)。要不要再試一次?
這次怎麼樣?
這次似乎沒有成功。如今驗證損失的次數不會再減少,最終的準確率應為超過 99%。恭喜!
第一次嘗試套用丟棄時,我們原本猜想發生過度配適的問題,但實際上問題是類神經網路的架構。如果沒有捲積層,我們就無法進一步探索,也沒有其他發現。
這次看似過度配適,是導致問題發生,丟棄的情況實際上確實有所幫助。請記住,有許多因素會導致訓練與驗證損失曲線間產生斷線,並使驗證損失增加。過度配適 (自由度太多,網路遭人不當使用) 只是其中一種。如果資料集太小或類神經網路的架構不足,您可能會在損失曲線中看到類似的行為,但這樣就不會有幫助。
13. 批次正規化
最後,讓我們試著新增批次正規化功能。
其實就是理論上,請記得以下幾項規則:
我們現在來播放這本書,並在每個類神經網路層 (除了最後一個) 中新增批次正規層。不要加到最後一個「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'),
目前的準確率如何?
只要稍加調整 (BATCH_SIZE=64,學習率衰減參數 0.666,稠密層 0.3 的放棄率),再加上有點幸能將得到 99.5% 的結果。已按照「最佳做法」調整學習率和放棄率使用批次常態:
- 批次正規化有助於凝聚類神經網路,通常能加快訓練速度。
- 批次正規化是正規化工具,通常可以減少減少耗用量,甚至完全不使用丟棄量。
解決方案筆記本執行了 99.5% 的訓練:
14. 在強大的硬體上進行雲端訓練:AI 平台
您可以在 GitHub 上的 mlengine 資料夾找到適用於雲端的程式碼版本,以及在 Google Cloud AI Platform 上執行該程式的操作說明。如要執行這個部分,請先建立 Google Cloud 帳戶並啟用計費功能。完成研究室所需的資源應少於數美元 (假設在一個 GPU 上需要 1 小時的訓練時間)。如要完成帳戶準備工作,請按照下列步驟操作:
- 建立 Google Cloud Platform 專案 ( http://cloud.google.com/console)。
- 啟用計費功能。
- 安裝 GCP 指令列工具 ( 按這裡取得 GCP SDK)。
- 建立 Google Cloud Storage 值區 (位於區域
us-central1
)。用於暫存訓練程式碼及儲存訓練過的模型。 - 啟用必要的 API 並要求所需的配額 (執行訓練指令一次後,您應該會看到錯誤訊息,說明要啟用的項目)。
15. 恭喜!
您已成功建立第一個類神經網路,並完成訓練達到 99% 的準確率。一路上學到的技術與 MNIST 資料集無關,實際上在處理類神經網路時已廣泛使用。做為一份禮物,一起使用「懸崖的音符」研究室版本資訊卡您可以用它來記住您已學到的內容:
後續步驟
- 完成完全連線和卷積網路後,您應該要查看循環類神經網路。
- Google Cloud 提供 AI Platform,方便您在雲端的分散式基礎架構中執行訓練或推論。
- 最後,我們非常喜歡使用者的意見。如果您在研究室中發現任何錯誤,或認為需要改善,請告訴我們。我們透過 GitHub 問題處理意見回饋 [feedback link]。
作者:Martin GörnerTwitter:@martin_gorner |
這個研究室中的所有卡通圖片著作權:alexpokusay / 123RF 圖庫相片