搭配使用 Keras 和 TPU,翻新 convnets、squeezenet 和 Xception 模型

1. 總覽

在本實驗室中,您將瞭解新式卷積架構,並運用所學知識實作名為「squeezenet」的簡單但有效的卷積神經網路。

本實驗室包含卷積類神經網路的必要理論說明,是開發人員學習深度學習的絕佳起點。

本研究室是「Keras on TPU」系列的第 4 部分。您可以依照下列順序執行這些操作,也可以獨立執行。

ca8cc21f6838eccc.png

課程內容

  • 掌握 Keras 函式風格
  • 為了使用擠檸檬架構建構模型
  • 如何使用 TPU 快速訓練模型並重複執行架構
  • 如何使用 tf.data.dataset 實作資料擴增功能
  • 如何在 TPU 上微調預先訓練的大型模型 (Xception)

意見回饋

如果您在這個程式碼研究室中發現不尋常的狀況,請告訴我們。您可以透過 GitHub 問題提供意見 [feedback link]。

2. Google Colaboratory 快速入門

這個實驗室使用 Google Collaboratory,您不需要進行任何設定。Colaboratory 是用於教育用途的線上筆記平台。提供免費 CPU、GPU 和 TPU 訓練。

688858c21e3beff2.png

您可以開啟這個範例筆記本並執行多個儲存格,熟悉 Colaboratory。

c3df49e90e5a654f.png Welcome to Colab.ipynb

選取 TPU 後端

8832c6208c99687d.png

在 Colab 選單中,依序選取「執行階段」>「變更執行階段類型」,然後選取「TPU」。在本程式碼研究室中,您將使用強大的 TPU (Tensor Processing Unit),以便進行硬體加速訓練。系統會在第一次執行時自動連線至執行階段,您也可以使用右上角的「連線」按鈕。

執行筆記本

76d05caa8b4db6da.png

按一下儲存格並使用 Shift + Enter 鍵,即可逐一執行儲存格。您也可以依序點選「Runtime」>「Run all」執行整個筆記本

Table of contents

429f106990037ec4.png

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

隱藏的儲存格

edc3dba45d26f12a.png

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

驗證

cdd4b41413100543.png

Colab 有機會存取您的私人 Google Cloud Storage 值區,但您必須使用已授權的帳戶進行驗證。上方的程式碼片段會觸發驗證程序。

3. [INFO] 什麼是 Tensor Processing Unit (TPU)?

概述

f88cf6facfc70166.png

這段程式碼會在 Keras 的 TPU 上訓練模型 (如果無法使用 TPU,則會改回使用 GPU 或 CPU):

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

今天我們將使用 TPU 以互動式速度 (每個訓練執行作業只需幾分鐘) 建構及最佳化花朵分類器。

688858c21e3beff2.png

選用 TPU 的理由

現代的 GPU 是以可程式化的「核心」為基礎,這個架構非常靈活有彈性,可處理 3D 轉譯、深度學習、實體模擬等各種工作。另一方面,TPU 會將傳統向量處理器與專用矩陣乘積單元配對,並在需要大量矩陣乘積的任務 (例如神經網路) 中表現出色。

8eb3e718b8e2ed08.png

插圖:密集的類神經網路層以矩陣乘法運算為矩陣,會一次透過類神經網路處理一批八張圖像,請透過一行 x 資料欄的乘法驗證,確認這確實是圖片中所有像素值的加權總和。卷積層也可以用矩陣乘法表示,雖然情況有些許複雜 ( 請參閱 此處的第 1 節說明)。

硬體

MXU 和 VPU

TPU v2 核心是由矩陣乘積單元 (MXU) 組成,後者會執行矩陣乘法和向量處理器 (VPU),可執行啟動、softmax 等所有其他工作。VPU 會處理 float32 和 int32 運算。另一方面,MXU 會以混合精度 16-32 位元浮點格式運作。

7d68944718f76b18.png

混合精確度浮點和 bfloat16

MXU 會使用 bfloat16 輸入和 float32 輸出來計算矩陣相乘運算。中間累加作業會以 float32 精確度執行。

19c5fc432840c714.png

類神經網路訓練通常足以應付低浮點精確度所產生的雜訊。在某些情況下,雜訊甚至能協助最佳化工具收縮。通常使用 16 位元浮點精確度來加速運算,但 float16 和 float32 格式有非常不同的範圍。將精確度從 float32 降至 float16 通常會導致溢位和欠流。現有解決方案存在,但通常需要額外工作才能讓 float16 運作。

因此,Google 在 TPU 中推出了 bfloat16 格式。bfloat16 是經過截斷的 float32,其指數位元和範圍與 float32 完全相同。加上 TPU 會以混合精確度計算矩陣相乘,並使用 bfloat16 做為輸入值,但輸出值為 float32,這表示您通常不需要變更程式碼,即可享有降低精確度所帶來的效能提升。

Systolic array

MXU 會使用所謂的「脈動陣列」架構,在硬體中實作矩陣乘法,在這種架構中,資料元素會流經一組硬體運算單元。(在醫學中,「脈衝」是指心臟收縮和血流,此處指向資料的流動)。

矩陣相乘的基本元素是矩陣的列與另一個矩陣的列之間的內積 (請參閱本節頂端的插圖)。若為矩陣乘法 Y=X*W,則結果一個元素會是:

Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]

在 GPU 上,其中一個會將這個內積分裝成 GPU 的「核心」,然後在盡可能平行執行大量「核心」上執行該積點,嘗試同時計算產生的矩陣的每個值。如果產生的矩陣為 128x128,就需要 128x128=16K 個「核心」,但這通常是不可能的。最大的 GPU 大約有 4,000 個核心。另一方面,TPU 在 MXU 中運算單元使用的硬體最低只有 bfloat16 x bfloat16 => float32 乘數加總,除此之外不用其他。這些矩陣非常小,因此 TPU 可以在 128x128 的 MXU 中實作 16K 個矩陣,並一次處理這個矩陣乘法。

f1b283fc45966717.gif

插圖:MXU 脈動陣列。運算元素為乘積。一個矩陣的值會載入陣列 (紅點)。其他矩陣的值會透過陣列 (灰色圓點) 流動。垂直線會向上傳遞值。水平線會傳播部分和。請使用者自行驗證,當資料流經陣列時,您會從右側取得矩陣相乘的結果。

此外,在 MXU 中計算內積時,中間的總和會在相鄰運算單元之間流動。不需要儲存及擷取至/自記憶體,甚至是登錄檔案。最終結果是,在計算矩陣乘法時,TPU 收縮陣列架構具有顯著的密度和效能優勢,以及相較於 GPU 的速度優勢。

Cloud TPU

如果您在 Google Cloud Platform 上要求一個「Cloud TPU v2」,就能獲得一個具備 PCI 連接的 TPU 電路板的虛擬機器 (VM)。TPU 電路板有四個雙核心 TPU 晶片。每個 TPU 核心都有一個 VPU (向量處理器) 和 128x128 MXU (MatriX 乘數)。接著,這個「Cloud TPU」通常會透過網路連線至提出要求的 VM。完整的圖片如下所示:

dfce5522ed644ece.png

插圖:VM 搭配網路連結的「Cloud TPU」加速器。「Cloud TPU」本身是由 VM 和 PCI 連接的 TPU 主機板組成,後者含有四個雙核心 TPU 晶片。

TPU 中樞

在 Google 資料中心中,TPU 會連線至高效能運算 (HPC) 互連網路,讓 TPU 看起來像是一部超大型加速器。Google 會呼叫 Pod,且最多可包含 512 個 TPU v2 核心或 2048 個 TPU v3 核心。

2ec1e0d341e7fc34.jpeg

插圖:TPU v3 Pod。透過 HPC 互連網路連結的 TPU 板和機架。

在訓練期間,系統會使用全減算演算法在 TPU 核心之間交換梯度 (這裡有全減算的詳細說明)。受訓練的模型可利用大型批量進行訓練,以充分運用硬體。

d97b9cc5d40fdb1d.gif

插圖:在 Google TPU 的 2D 環狀網格 HPC 網路上,使用全減算法同步處理訓練期間的梯度。

軟體

大型批量訓練

TPU 的理想批次大小為每個 TPU 核心 128 個資料項目,但硬體已能顯示每個 TPU 核心 8 個資料項目有良好的使用率。請注意,一個 Cloud TPU 有 8 個核心。

在本程式碼研究室中,我們將使用 Keras API。在 Keras 中,您指定的批次是整個 TPU 的全球批次大小。系統會自動將批次分割成 8 個,並在 TPU 的 8 個核心上執行。

da534407825f01e3.png

如需其他效能提示,請參閱 TPU 效能指南。如果批次大小非常大,某些模型可能需要特別處理,詳情請參閱 LARSOptimizer

深入解析:XLA

TensorFlow 程式會定義運算圖形。TPU 不會直接執行 Python 程式碼,而是執行 TensorFlow 程式所定義的運算圖表。理論上,名為 XLA 的編譯器 (加速線性代數編譯器) 會將運算節點的 Tensorflow 圖形轉換為 TPU 機器程式碼。這個編譯器也會對程式碼和記憶體配置執行許多進階最佳化作業。系統會在工作傳送至 TPU 時自動編譯。您不需要在建構鏈結中明確加入 XLA。

edce61112cd57972.png

插圖:如要在 TPU 上執行,Tensorflow 程式定義的運算圖形會先轉譯為 XLA (加速線性代數編譯器) 表示法,再由 XLA 編譯成 TPU 機器碼。

在 Keras 中使用 TPU

自 TensorFlow 2.1 起,Keras API 就支援 TPU。Keras 支援適用於 TPU 和 TPU Pod。以下是適用於 TPU、GPU 和 CPU 的範例:

try: # detect TPUs
    tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
    strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines

# use TPUStrategy scope to define model
with strategy.scope():
  model = tf.keras.Sequential( ... )
  model.compile( ... )

# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)

在這個程式碼片段中:

  • TPUClusterResolver().connect() 會在網路中找到 TPU。在大多數 Google Cloud 系統 (AI 平台工作、Colaboratory、Kubeflow、透過「ctpu up」公用程式建立的深度學習 VM) 上,無需參數即可運作。這些系統知道 TPU_NAME 環境變數的 TPU 位置。如果您手動建立 TPU,請在使用 TPU 的 VM 上設定 TPU_NAME 環境變數,或是以明確的參數呼叫 TPUClusterResolverTPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy 是實作分佈的部分,以及「all-reduce」梯度同步演算法的部分。
  • 策略會透過範圍套用。模型必須在 strategy scope() 中定義。
  • tpu_model.fit 函式會預期 tf.data.Dataset 物件,做為 TPU 訓練的輸入內容。

常見 TPU 移植工作

  • 在 TensorFlow 模型中載入資料的方法很多,但如果是 TPU,則必須使用 tf.data.Dataset API。
  • TPU 速度飛快,且執行時擷取資料經常成為瓶頸。請參閱 TPU 效能指南,瞭解您可以使用哪些工具偵測資料瓶頸和其他效能提示。
  • int8 或 int16 數字會視為 int32。TPU 沒有在 32 位元以下運作的整數硬體。
  • 不支援部分 Tensorflow 作業。查看清單。好消息是,這項限制僅適用於訓練程式碼,也就是前後通過模型的程式碼。您仍可在資料輸入管道中使用所有 TensorFlow 作業,因為 TensorFlow 將在 CPU 上執行。
  • TPU 不支援 tf.py_func

4. [INFO] 類神經網路分類器 101

摘要

如果您已熟悉下一段文字中所有以 粗體顯示的詞彙,可以直接進行下一個練習。如果您是深度學習新手,歡迎繼續閱讀。

對於以一系列層建構的模型,Keras 提供 Sequential API。舉例來說,使用三個密集層的圖像分類器可在 Keras 中編寫為:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
    tf.keras.layers.Dense(500, activation="relu"),
    tf.keras.layers.Dense(50, activation="relu"),
    tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 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

密集類神經網路

這是將圖片分類最簡單的類神經網路。它由分層排列的「神經元」組成。第一層會處理輸入資料,並將輸出內容饋送至其他層。這稱為「密集」,因為每個神經元都會連接至上一層的所有神經元。

c21bae6dade487bc.png

您可以將圖片全部的 RGB 值壓平成長向量,並將其做為輸入內容,提供給此類網路使用。這不是圖片辨識的最佳技術,但我們稍後會加以改進。

神經元、啟用、RELU

「神經元」會計算所有輸入內容的加權總和,加上名為「bias」的值,然後透過名為「活化函式」將結果傳送給結果。一開始不知道權重和偏誤。模型會以隨機方式初始化,並透過大量的已知資料訓練類神經網路,藉此「學習」。

644f4213a4ee70e5.png

最常見的活化函式是 RELU,代表「線性整流函數」。如上圖所示,這是非常簡單的函式。

Softmax 啟用

我們將花朵分類為 5 個類別 (玫瑰、鬱金香、蒲公英、雛菊、向日葵),因此上方的網路以 5 個神經元層結束。而中間層中的神經元是透過傳統版 RELU 活化函式啟用。但在上一層中,我們想要計算 0 到 1 之間的數字,代表這朵花成為玫瑰、鬱金香等的可能性。我們會使用名為「softmax」的啟用函式。

在向量上套用軟性最大值,方法是取每個元素的指數,然後將向量標準化,通常使用 L1 範數 (絕對值的總和),讓值加總為 1,並可解讀為機率。

ef0d98c0952c262d.png d51252f75894479e.gif

交叉熵損失

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

任何距離都可以,但就分類問題而言,所謂的「交叉熵距離」最有效。我們會稱此錯誤或「損失」函式:

7bdf8753d20617fb.png

漸層下降

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

交叉熵是訓練圖片及其已知類別的權重、偏差、像素函數。

如果我們針對所有權重和所有偏誤,計算出交叉熵的部分導數,就會取得「梯度」,根據指定圖片、標籤以及權重和偏誤的現值計算。請注意,我們可能有數百萬個權重和偏差,因此計算梯度聽起來很麻煩。幸好 TensorFlow 會自動執行這項操作。漸層的數學特性是指向「上方」。由於我們希望交叉熵偏低,因此我們會朝相反方向前進。我們會根據梯度的一部分更新權重和偏差。接著,我們會在訓練迴圈中使用下一批的訓練圖片和標籤,再次執行相同的操作。希望這會收斂到交叉熵最小的位置,但沒有任何保證這個最小值是唯一的。

gradient descent2.png

微批次和發展動能

您可以只針對單一示例圖片計算漸層,並立即更新權重和偏差,但如果針對一批 (例如 128 張) 圖片執行此操作,所得的漸層將更能代表不同示例圖片的限制,因此可能會更快收斂至解決方案。迷你批次的大小是可調整的參數。

這種技術有時稱為「隨機梯度下降法」,還有另一個更實用的優點:使用批次也代表使用較大的矩陣,而這些矩陣通常更容易在 GPU 和 TPU 上進行最佳化。

然而,收斂法還是有點混亂,即使漸層向量全為零,甚至會停止。這是否表示我們已找到最小值?不一定。漸層元件可在最小值或最大值上設為零。如果某個漸層向量有數百萬個元素,但全都是零,則每個零對應最小,且都不對應到最大點的機率極小。在多維空間中,鞍點相當常見,我們不希望停在這些點上。

52e824fe4716c4a0.png

插圖:馬鞍縫。漸層為 0,但並非在所有方向的最小值。(圖片出處 維基百科:由 Nicoguaro 提供,為自己的作品,CC BY 3.0)

解決方法是為最佳化演算法增加成長動能,讓演算法不必停下腳步,順利滑行。

詞彙

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

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

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

特徵:神經網路的輸入有時稱為「特徵」。找出資料集的哪些部分 (或部分組合) 可輸入神經網路,以便取得良好預測結果的技術,稱為「特徵工程」。

標籤:在監督式分類問題中,「類別」或正確答案的另一個名稱

學習率:在訓練迴圈的每次疊代中,用來更新權重和偏差的梯度分數。

logits:套用活化函式前一層神經元層的輸出內容稱為「logits」。這個詞源自「邏輯函式」(也稱為「S 函數」),這是過去最受歡迎的活化函式。「神經元輸出值 (在邏輯函式之前)」縮寫為「logit」。

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

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

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

relu:線性整流函數。神經元經常使用的活化函數。

sigmoid:另一個曾經流行的啟用函式,在特殊情況下仍很實用。

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

張量:「張量」就像矩陣,但有任意維度數量。1 維度張量是向量。2 維張量是矩陣。然後,您可以使用 3、4、5 或更多維度的張量。

5. [資訊] 卷積類神經網路

概述

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

convolutional.gif

範例:使用兩個連續濾鏡 (每個濾鏡的學習權重為 4x4x3=48) 來濾除圖片。

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

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

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

688858c21e3beff2.png

卷積類神經網路入門

在卷積網路層中,一個「神經元」只會針對圖片的小區域,進行正上方像素的加權總和。它會加入偏差,並透過活化函數提供總和,就像一般密集層中的神經元一樣。然後,使用相同的權重重複執行這項作業,以便處理整個圖片。請記住,在稠密層中,每個神經元都有各自的權重。此處會把一張重量「修補」在圖片的兩向上滑動 (「卷積」),輸出值的數量與圖片中的像素數量相同 (但邊緣需要一些邊框)。這是篩選運算,使用 4x4x3=48 權重濾鏡。

不過,48 個權重是不夠的。為了增加自由度,我們會使用新的權重重複相同的運算。這會產生一組新的篩選器輸出內容。現在我們稱之為類比的「管道」,也就是輸入圖片中的 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 視窗範圍) 可縮小水平維度的資料方塊。

C卷積分類器

最後,我們會簡化最後一個資料立方體,並透過稠密的 softmax-activated 層提供資料,以附加分類頭。典型的卷積分類器如下所示:

4a61aaffb6cba3d1.png

插圖:使用卷積和 Softmax 層的圖像分類器。並使用 3x3 和 1x1 篩選器。maxpool 層會取 2x2 資料點組的最大值。分類頭以啟用 softmax 的稠密層。

在 Keras 中

上述的卷積堆疊可使用 Keras 編寫,如下所示:

model = tf.keras.Sequential([
  # input: images of size 192x192x3 pixels (the three stands for RGB channels)    
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(pool_size=2),
  tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
  tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
  tf.keras.layers.Flatten(),
  # classifying into 5 categories
  tf.keras.layers.Dense(5, activation='softmax')
])

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

6. [新資訊] 現代卷積架構

摘要

7968830b57b708c0.png

插圖:卷積「模組」。目前最適合的做法是什麼?最大值池化層後接 1x1 卷積層,或是其他層的組合?請嘗試使用所有選項,連結結果,讓網路決定。右側:使用這類模組的「inception」卷積架構。

在 Keras 中,您必須使用「函式」模型樣式,建立資料流入/分支的模型。範例如下:

l = tf.keras.layers # syntax shortcut

y = l.Conv2D(filters=32, kernel_size=3, padding='same',
             activation='relu', input_shape=[192, 192, 3])(x) # x=input image

# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation

# many more layers ...

# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)

688858c21e3beff2.png

其他超值秘訣

小型 3x3 濾鏡

40a7b15fb7dbe75c.png

在這張插圖中,您會看到兩個連續 3x3 濾鏡的結果。試著追溯造成結果的資料點:這兩個連續的 3x3 篩選器會計算出一個 5x5 區域的任意組合。這與 5x5 濾波器會計算的組合並不完全相同,但值得嘗試,因為兩個連續 3x3 篩選器的費用比一個 5x5 篩選器便宜。

1x1 卷積運算?

fd7cac16f8ecb423.png

在數學術語中,「1x1」卷積是乘以常數,並非非常實用的概念。不過在卷積類神經網路中,篩選器會套用至資料立方體,而不只是 2D 圖片。因此,「1x1」濾鏡會計算 1x1 資料欄的加權總和 (如插圖所示),當您在資料上滑動時,您會取得輸入通道的線性組合。這其實很有用。如果將通道視為個別篩選作業的結果,例如「尖耳朵」篩選器、「觸鬚」篩選器和「細長眼睛」篩選器,那麼「1x1」卷積層會計算這些特徵的多種可能線性組合,這在尋找「貓咪」時可能很有用。除此之外,1x1 層使用的權重更少。

7. 擠檸檬

「Squeezenet」論文中展示了將這些概念結合的簡單方法。作者建議採用非常簡單的卷積模組設計,只使用 1x1 和 3x3 卷積層。

1730ac375379269b.png

插圖:以「火模組」為基礎的 SqueezeNet 架構。他們會交替 1x1 層,以垂直維度「擠壓」傳入的資料,後面接著兩個平行的 1x1 層和 3x3 卷積層,再次「擴展」資料深度。

動手做

接著在先前的筆記本中,建立以 SqueezeNet 為靈感的卷積類神經網路。您必須將模型程式碼變更為 Keras「函式樣式」。

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

其他資訊

在本練習中,定義 squeezenet 模組的輔助函式會很有幫助:

def fire(x, squeeze, expand):
  y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
  y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
  y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
  return tf.keras.layers.concatenate([y1, y3])

# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
  return lambda x: fire(x, squeeze, expand)

# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)

這次目標是達到 80% 的準確率。

建議做法

請先從單一卷積層開始,然後接著使用「fire_modules」,並交替使用 MaxPooling2D(pool_size=2) 層。在網路中,您可以使用 2 至 4 個上限的集區層進行實驗,並且在最大池層之間建立連續 1、2 或 3 個連續的火焰模組。

在發射模組中,「squeeze」參數通常應小於「expand」參數。這些參數其實是篩選器的數量。通常介於 8 至 196 之間。您可以嘗試不同的架構,讓篩選器數量透過網路逐漸增加,或者是單純的架構 (所有啟動模組都具有相同數量的篩選器)。

範例如下:

x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB

y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)

model = tf.keras.Model(x, y)

此時,您可能會發現實驗成效不佳,且 80% 的準確度目標似乎遙不可及。該回答幾個較便宜的小技巧。

批次正規化

批次規範可協助解決您遇到的收斂問題。我們會在下一場工作坊中詳細說明這項技巧,但目前請先將其視為黑箱「魔法」輔助工具,在網路中的每個卷積層後方新增這行程式碼,包括 fire_module 函式中的層:

y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context

由於資料集較小,因此動量參數必須從預設值 0.99 降低至 0.9。暫時不用處理此詳細資料。

資料擴增

您可以使用簡單的轉換來擴充資料 (例如飽和度變化的左方翻轉),藉此增加兩個百分點:

4ed2958e09b487ca.png

ad795b70334e0d6b.png

在 Tensorflow 中使用 tf.data.Dataset API 十分簡單。為資料定義新的轉換函式:

def data_augment(image, label):
    image = tf.image.random_flip_left_right(image)
    image = tf.image.random_saturation(image, lower=0, upper=2)
    return image, label

然後用於最終資料轉換 (儲存格「訓練和驗證資料集」的函式「get_batched_dataset」):

dataset = dataset.repeat() # existing line
# insert this
if augment_data:
  dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line

別忘了將資料擴增為選用,並加入必要程式碼,確保只有訓練資料集才會擴增。您不需要擴充驗證資料集。

現在,35 個週期的 80% 準確率應該在觸及範圍內。

解決方案

以下是解決方案筆記本。如有需要,您可以使用這項功能。

c3df49e90e5a654f.png Keras_Flowers_TPU_squeezenet.ipynb

涵蓋內容

  • 🤔 Keras「功能式」模型
  • 🤓 Squeezenet 架構
  • 🤓 使用 tf.data.dataset 進行資料擴充

請花點時間在腦中過一遍這份檢查清單。

8. Xception 微調

可分離卷積運算

最近,大家越來越常採用卷積層的捲積層,也就是可區分的捲積。我知道這項技術有許多細節,但概念相當簡單。在 TensorFlow 和 Keras 中,這些函式會以 tf.keras.layers.SeparableConv2D 的形式實作。

可分離卷積也會對圖片執行濾鏡,但會為輸入圖片的每個管道使用不同的權重。接著是「1x1 卷積」,一系列內積會產生經過權重加總的篩除管道。每次都會使用新的權重,因此系統會視需要計算每個管道的加權重新組合。

615720b803bf8dda.gif

插圖:可分離的捲積,第 1 階段:每個管道都有獨立的篩選器,帶來卷積。第 2 階段:管道的線性重組。使用一組新的權重重複,直到達到所需的輸出通道數。第 1 階段也可以重複,每次都會使用新的權重,但實際上很少見。

最新的捲積網路架構 (MobileNetV2、Xception、EfficientNet) 採用可隔離的捲積。順帶一提,MobileNetV2 是您先前用於遷移學習的模型。

它的捲積便宜,比一般的捲積便宜,而且經研究發現,實務效率同樣有效。以下是上述範例的權重計數:

卷積層:4 x 4 x 3 x 5 = 240

可分離卷積層:4 x 4 x 3 + 3 x 5 = 48 + 15 = 63

這可以做為練習,讓讀取器計算和套用每個卷積層以同樣的方式縮放所需要的乘數多。可分割的捲積較小,且運算效率更高。

實作課程

從「遷移學習」遊樂場工作階段重新開始,但這次請選取 Xception 做為預先訓練的模型。Xception 只使用可分離的捲積。將所有權重設為「可訓練」屬性。我們會微調資料上預先訓練的權重,而非使用預先訓練的層。

c3df49e90e5a654f.png Keras Flowers transfer learning (playground).ipynb

目標:準確度 > 95% (很有可能,這是有可能的!)

這是最後一項練習,需要更多程式碼和數據科學工作。

微調的其他資訊

Xception 可用於 tf.keras.application 中的標準預先訓練模型。*請務必讓所有權重都能訓練。

pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
                                                  include_top=False)
pretrained_model.trainable = True

如要在微調模型時獲得良好結果,您必須留意學習率,並使用含有逐步增加期間的學習率時間表。如下所示:

9b1af213b2b36d47.png

從標準學習率開始,會影響模型預先訓練的權重。開始逐步保留這些值,直到模型鎖定您的資料,並能以合理的方式修改這些值為止。提高學習率後,您可以穩定或以指數方式下降的學習率。

在 Keras 中,學習率是透過回呼指定,您可以計算每個訓練週期的適當學習率。Keras 會將正確的學習率傳遞至每個訓練週期的最佳化工具。

def lr_fn(epoch):
  lr = ...
  return lr

lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)

model.fit(..., callbacks=[lr_callback])

解決方案

以下是解決方案筆記本。如有需要,您可以使用這項功能。

c3df49e90e5a654f.png 07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb

涵蓋內容

  • 🤔 可分離深度的卷積
  • 🤓 學習率時間表
  • 😈? 微調預先訓練模型。

請花點時間研讀這份檢查清單,

9. 恭喜!

您已成功建立第一個現代化卷積類神經網路,並將該網路訓練至 90% 以上的準確率。有了 TPU,您就能在幾分鐘內完成連續訓練的疊代作業。本節結束了 4 個「Keras 在 TPU 上的程式碼研究室」:

實務應用 TPU

Cloud AI Platform 提供 TPU 和 GPU:

最後,我們非常喜歡使用者的意見。如果您在這個研究室中發現任何錯誤,或您認為需要改善,請告訴我們。您可以透過 GitHub 問題提供意見 [feedback link]。

HR.png

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

tensorflow logo.jpg
www.tensorflow.org