在 TPU 上使用 Keras 和新型收斂公司

1. 總覽

在本實驗室中,您將瞭解如何使用 Keras 和 TensorFlow 2,從頭開始建構、訓練及調整卷積類神經網路。如今,我們可以利用 TPU 的強大功能,在幾分鐘內完成這項工作。您也將探索多種做法,包括非常簡單的遷移學習,以及 Squeezenet 等現代卷積架構。本研究室提供類神經網路的理論解釋,是開發人員學習深度學習的絕佳起點。

閱讀深度學習論文可能並不容易,令人困惑。我們現在就來實作看看現代卷積類神經網路架構。

ca8cc21f6838eccc.png

課程內容

  • 使用 Keras 和 Tensor Processing Unit (TPU) 加快自訂模型的建構速度。
  • 使用 tf.data.Dataset API 和 TFRecord 格式,有效率地載入訓練資料。
  • 如要作弊 😈,請使用遷移學習,而非自行建立模型。
  • 如何使用 Keras 序列和函式模型樣式。
  • 使用 softmax 層和交叉熵損失建立自己的 Keras 分類器。
  • 使用選擇喜歡的捲積層來微調模型。
  • 探索現代化對話網架構構想,例如模組、全球平均集區等。
  • 為了使用 Squeezenet 架構建構簡易的新型 convnet。

意見回饋

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

2. Google Colaboratory 快速入門

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

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,這表示您通常不需要變更程式碼,即可享有降低精確度所帶來的效能提升。

切割陣列

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 Pod

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

2ec1e0d341e7fc34.jpeg

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

在訓練期間,系統會使用 all-reduce 演算法,在 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、透過「ctpuup」公用程式建立的深度學習 VM) 上運作,完全不需要參數。這些系統知道 TPU_NAME 環境變數的 TPU 位置。如要手動建立 TPU,請在目前使用的 VM 上設定 TPU_NAME env. var,或是使用明確參數呼叫 TPUClusterResolverTPUClusterResolver(tp_uname, zone, project)
  • TPUStrategy 是實作分發和「all-reduce」梯度同步處理演算法的部分。
  • 策略會透過範圍套用。模型必須在策略 scope() 內定義。
  • tpu_model.fit 函式預期輸入用於 TPU 訓練的 tf.data.Dataset 物件。

常見 TPU 移植工作

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

4. 載入資料

c0ecb860e4cad0a9.jpeg cc4781a7739c49ae.jpeg 81236b00f8bbf39e.jpeg 961e2228974076bb.jpeg 7517dc163bdffcd5.jpeg 96392df4767f566d.png

我們將使用花朵圖片資料集。目的是學習把它們分為 5 種花朵類型。資料載入作業會使用 tf.data.Dataset API 執行。首先,讓我們來認識 API。

實作

請開啟下列筆記本、執行儲存格 (Shift + ENTER),並按照「WORK REQUIRED」標籤中的指示操作。

c3df49e90e5a654f.png Fun with tf.data.Dataset (playground).ipynb

其他資訊

關於「flowers」資料集

資料集儲存在 5 個資料夾中。每個資料夾都包含一種花朵。資料夾名稱分別為「Sunflowers」、「Daisy」、「Dandelion」、「Tulips」和「Roses」。這些資料託管於 Google Cloud Storage 的公開值區。摘錄:

gs://flowers-public/sunflowers/5139971615_434ff8ed8b_n.jpg
gs://flowers-public/daisy/8094774544_35465c1c64.jpg
gs://flowers-public/sunflowers/9309473873_9d62b9082e.jpg
gs://flowers-public/dandelion/19551343954_83bb52f310_m.jpg
gs://flowers-public/dandelion/14199664556_188b37e51e.jpg
gs://flowers-public/tulips/4290566894_c7f061583d_m.jpg
gs://flowers-public/roses/3065719996_c16ecd5551.jpg
gs://flowers-public/dandelion/8168031302_6e36f39d87.jpg
gs://flowers-public/sunflowers/9564240106_0577e919da_n.jpg
gs://flowers-public/daisy/14167543177_cd36b54ac6_n.jpg

為什麼要使用 tf.data.Dataset?

Keras 和 Tensorflow 接受所有訓練和評估函式中的資料集。在資料集中載入資料後,API 會提供所有常見功能,這些功能可用於訓練神經網路的資料:

dataset = ... # load something (see below)
dataset = dataset.shuffle(1000) # shuffle the dataset with a buffer of 1000
dataset = dataset.cache() # cache the dataset in RAM or on disk
dataset = dataset.repeat() # repeat the dataset indefinitely
dataset = dataset.batch(128) # batch data elements together in batches of 128
AUTOTUNE = tf.data.AUTOTUNE
dataset = dataset.prefetch(AUTOTUNE) # prefetch next batch(es) while training

如要瞭解效能提示和資料集最佳做法,請參閱這篇文章。如需參考文件,請按這裡

tf.data.Dataset 基本概念

資料通常會以多個檔案,在這裡的圖片。您可以呼叫下列指令來建立檔案名稱的資料集:

filenames_dataset = tf.data.Dataset.list_files('gs://flowers-public/*/*.jpg')
# The parameter is a "glob" pattern that supports the * and ? wildcards.

接著,您會將函式「對應」至每個檔案名稱,通常會載入檔案並將其解碼為記憶體中的實際資料:

def decode_jpeg(filename):
  bits = tf.io.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  return image

image_dataset = filenames_dataset.map(decode_jpeg)
# this is now a dataset of decoded images (uint8 RGB format)

如要對資料集進行疊代作業:

for data in my_dataset:
  print(data)

元組資料集

在監督式學習中,訓練資料集通常是以成對的訓練資料與正確答案組成。為此,解碼函式可傳回元組。您將擁有一個元組資料集,並在迭代時傳回元組。回傳的值是 Tensorflow 張量,可供模型使用。您可以對其呼叫 .numpy() 查看原始值:

def decode_jpeg_and_label(filename):
  bits = tf.read_file(filename)
  image = tf.io.decode_jpeg(bits)
  label = ... # extract flower name from folder name
  return image, label

image_dataset = filenames_dataset.map(decode_jpeg_and_label)
# this is now a dataset of (image, label) pairs 

for image, label in dataset:
  print(image.numpy().shape, label.numpy())

結論:逐一載入圖片的速度很慢!

當您對這個資料集進行疊代時,您會發現每秒可以載入 1 到 2 張圖片。速度太慢了!我們用於訓練的硬體加速器可以維持這個速率,可以維持好幾次。請參閱下一節,瞭解我們如何達成此目標。

解決方案

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

c3df49e90e5a654f.png Fun with tf.data.Dataset (solution).ipynb

涵蓋內容

  • 🤔 tf.data.Dataset.list_files
  • 🤔? tf.data.Dataset.map
  • 🤔? 元組的資料集
  • 😀? 反覆查詢不同資料集

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

5. 快速載入資料

本研究室使用的 Tensor Processing Unit (TPU) 硬體加速器非常快速。但要讓他們持續忙碌,通常需要快速提供資料。Google Cloud Storage (GCS) 能夠提供極高的處理量,但如同所有雲端儲存空間系統,啟動連線需要一些網路往返費用。因此,將資料儲存為數千個個別檔案並不理想。我們將以較少的檔案進行批次處理,並使用 tf.data.Dataset 的強大功能,平行讀取多個檔案。

閱讀

以下筆記本會載入圖片檔、將其調整為常見大小,然後儲存至 16 個 TFRecord 檔案,程式碼位於下列筆記本中。請盡速閱讀。因此不需要執行,因為本程式碼研究室的其他部分將會提供正確的 TFRecord 格式資料。

c3df49e90e5a654f.png Flower pictures to TFRecords.ipynb

最適合 GCS 處理量的理想資料版面配置

TFRecord 檔案格式

TensorFlow 偏好的資料儲存檔案格式是 protobuf 格式的 TFRecord。其他序列化格式也適用,但您可以直接透過以下方式從 TFRecord 檔案載入資料集:

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

為獲得最佳效能,建議您使用下列較複雜的程式碼一次讀取多個 TFRecord 檔案。此程式碼會並行讀取 N 個檔案,並忽略資料順序,以便加快讀取速度。

AUTOTUNE = tf.data.AUTOTUNE
ignore_order = tf.data.Options()
ignore_order.experimental_deterministic = False

filenames = tf.io.gfile.glob(FILENAME_PATTERN)
dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=AUTOTUNE)
dataset = dataset.with_options(ignore_order)
dataset = dataset.map(...) # do the TFRecord decoding here - see below

TFRecord 一覽表

可將三種類型的資料儲存在 TFRecords 中:位元組字串 (位元組清單)、64 位元整數和 32 位元浮點。這些元素一律會以清單的形式儲存,單一資料元素會是大小為 1 的清單。您可以使用下列輔助函式,將資料儲存到 TFRecord。

寫入位元組字串

# warning, the input is a list of byte strings, which are themselves lists of bytes
def _bytestring_feature(list_of_bytestrings):
  return tf.train.Feature(bytes_list=tf.train.BytesList(value=list_of_bytestrings))

寫入整數

def _int_feature(list_of_ints): # int64
  return tf.train.Feature(int64_list=tf.train.Int64List(value=list_of_ints))

寫入浮點值

def _float_feature(list_of_floats): # float32
  return tf.train.Feature(float_list=tf.train.FloatList(value=list_of_floats))

使用上述輔助程式寫入 TFRecord

# input data in my_img_bytes, my_class, my_height, my_width, my_floats
with tf.python_io.TFRecordWriter(filename) as out_file:
  feature = {
    "image": _bytestring_feature([my_img_bytes]), # one image in the list
    "class": _int_feature([my_class]),            # one class in the list
    "size": _int_feature([my_height, my_width]),  # fixed length (2) list of ints
    "float_data": _float_feature(my_floats)       # variable length  list of floats
  }
  tf_record = tf.train.Example(features=tf.train.Features(feature=feature))
  out_file.write(tf_record.SerializeToString())

如要讀取 TFRecords 中的資料,您必須先宣告已儲存記錄的版面配置。在宣告中,您可以將任何命名欄位做為固定長度清單或可變長度清單存取:

從 TFRecords 讀取

def read_tfrecord(data):
  features = {
    # tf.string = byte string (not text string)
    "image": tf.io.FixedLenFeature([], tf.string), # shape [] means scalar, here, a single byte string
    "class": tf.io.FixedLenFeature([], tf.int64),  # shape [] means scalar, i.e. a single item
    "size": tf.io.FixedLenFeature([2], tf.int64),  # two integers
    "float_data": tf.io.VarLenFeature(tf.float32)  # a variable number of floats
  }

  # decode the TFRecord
  tf_record = tf.io.parse_single_example(data, features)

  # FixedLenFeature fields are now ready to use
  sz = tf_record['size']

  # Typical code for decoding compressed images
  image = tf.io.decode_jpeg(tf_record['image'], channels=3)

  # VarLenFeature fields require additional sparse.to_dense decoding
  float_data = tf.sparse.to_dense(tf_record['float_data'])

  return image, sz, float_data

# decoding a tf.data.TFRecordDataset
dataset = dataset.map(read_tfrecord)
# now a dataset of triplets (image, sz, float_data)

實用的程式碼片段:

讀取單一資料元素

tf.io.FixedLenFeature([], tf.string)   # for one byte string
tf.io.FixedLenFeature([], tf.int64)    # for one int
tf.io.FixedLenFeature([], tf.float32)  # for one float

讀取固定大小的元素清單

tf.io.FixedLenFeature([N], tf.string)   # list of N byte strings
tf.io.FixedLenFeature([N], tf.int64)    # list of N ints
tf.io.FixedLenFeature([N], tf.float32)  # list of N floats

讀取不同數量的資料項目

tf.io.VarLenFeature(tf.string)   # list of byte strings
tf.io.VarLenFeature(tf.int64)    # list of ints
tf.io.VarLenFeature(tf.float32)  # list of floats

VarLenFeature 會傳回稀疏向量,解碼 TFRecord 後需要執行其他步驟:

dense_data = tf.sparse.to_dense(tf_record['my_var_len_feature'])

您也可以在 TFRecords 中提供選用欄位。如果您在讀取欄位時指定預設值,如果欄位缺少,系統會傳回預設值,而非錯誤。

tf.io.FixedLenFeature([], tf.int64, default_value=0) # this field is optional

涵蓋內容

  • 🤔? 分割資料檔案以便快速存取 GCS
  • 😓 如何寫入 TFRecords。(您忘記語法了嗎?沒關係,將這個頁面加入書籤做為快速參考)
  • 🤔 使用 TFRecordDataset 從 TFRecords 載入資料集

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

6. [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 值壓平成長向量,並將其做為輸入內容,提供給此類網路使用。這不是圖片辨識的最佳技術,但我們稍後會加以改進。

Neuron、啟動、RELU

「神經元」會計算所有輸入值的加權總和,並加入稱為「偏差」的值,然後透過稱為「活化函數」的函式提供結果。一開始無法得知權重和偏誤。這些參數會隨機初始化,並透過大量已知資料訓練類神經網路來「學習」。

644f4213a4ee70e5.png

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

啟用 Softmax

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

在向量上套用 softmax 時,系統會採用每個元素的指數,然後對向量進行正規化,一般會使用 L1 常式 (絕對值總和),讓各個值加總為 1,並解讀為機率。

ef0d98c0952c262d.png d51252f75894479e.gif

交叉熵損失

神經網路已根據輸入圖片產生預測結果,我們需要評估這些結果的準確度,也就是網路的預測結果與正確答案之間的距離,通常稱為「標籤」。請注意,我們已為資料集中的所有圖片加上正確的標籤。

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

7bdf8753d20617fb.png

漸層下降

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

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

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

漸層 descent2.png

微批次和發展動能

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

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

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

52e824fe4716c4a0.png

插圖:馬鞍縫。漸層為 0,但並非在所有方向的最小值。(圖片來源:Wikimedia:由 Nicoguaro 創作,CC BY 3.0)

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

詞彙

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

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

密集層:神經元層,其中每個神經元都會連結至前一層的所有神經元。

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

labels:「課程」的另一個名稱,或是監督式分類問題中的正確答案

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

logit:在套用啟用函式之前,一層神經元的輸出結果稱為「logit」。這個詞源自「邏輯函式」(也稱為「S 函數」),這是過去最受歡迎的活化函式。「Logits 前方的 Neron outputs」會縮短為「logits」。

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

神經元:計算輸入值的加權總和、加入偏差,並透過啟用函式提供結果。

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

relu:線性整流函數。神經元常用的啟用函式。

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

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

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

7. 遷移學習

如果是圖片分類問題,密集圖層可能還不夠。我們必須瞭解卷積層,以及它們的多種排列方式。

但我們也可以使用捷徑!您可以下載已完成訓練的卷積類神經網路。您可以切除最後一層 (Softmax 分類頭),並用自己的層取代。所有經過訓練的權重和偏差都會維持原樣,您只需重新訓練新增的 softmax 層。這項技術稱為轉移學習,令人驚訝的是,只要類神經網路預先訓練的資料集「足夠接近」您的資料集,這項技術就會運作。

動手做

請開啟下列筆記本,執行儲存格 (Shift + ENTER),並按照「WORK REQUIRED」標籤中的指示操作。

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

其他資訊

有了遷移學習,您就能受益於頂尖研究人員所開發的進階卷積類神經網路架構,以及預先訓練大量圖像資料集。在這個案例中,我們要從在 ImageNet 上訓練的網路中遷移學習過程。ImageNet 是一種圖片資料庫,內含許多植物和室外場景的圖片,而這些圖像就足以讓花朵使用。

b8fc1efd2001f072.png

插圖:以黑色方塊的形式訓練複雜的捲積類神經網路,只重訓練分類頭部。這就是遷移學習。我們稍後會見證卷積層的複雜排列方式。目前這是其他人的問題。

在 Keras 中遷移學習

在 Keras 中,您可以從 tf.keras.applications.* 集合例項化預先訓練模型。舉例來說,MobileNet V2 是相當不錯的卷積架構,大小適中。選取 include_top=False 後,您會取得預先訓練模型,但沒有最終的軟性最大值層,因此您可以自行新增:

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

model = tf.keras.Sequential([
    pretrained_model,
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(5, activation='softmax')
])

另請注意 pretrained_model.trainable = False 設定。會凍結預先訓練模型的權重和偏誤,因此您只能訓練 softmax 層。這類程序通常包含相對較少的權重,因此可快速完成,且不需要使用非常龐大的資料集。不過,如果您擁有大量資料,搭配 pretrained_model.trainable = True 使用遷移學習的效果會更好。預先訓練的權重然後提供絕佳的初始值,而且也可以透過訓練進行調整,讓模型更準確地反映您的問題。

最後,請注意在密集軟性最大值層前插入的 Flatten() 層。密集圖層適用於平面資料向量,但我們不知道預先訓練模型傳回的內容為何。這就是為什麼需要簡化。在下一章中,我們將深入探討卷積神經網路架構,並說明卷積層傳回的資料格式。

採用這種做法可將準確率提高近 75%。

解決方案

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

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

涵蓋內容

  • 🤔? 如何在 Keras 中編寫分類器
  • 🤓? 上設定了「softmax」最後一層,且有交叉熵損失
  • 😈 遷移學習
  • 🤔 訓練第一個模型
  • 🧐? 瞭解訓練期間的損失和準確率

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

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

摘要

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

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 像素,可產生較少輸出值。步進卷積或最大值池化 (在 2x2 視窗上以步進 2 滑動至最大值) 是縮小水平維度中資料立方體的方法。

C卷積分類器

最後,我們會透過將最後一個資料立方體扁平化,並透過密集的 softmax 啟用層提供資料,附加分類頭。典型的卷積分類器如下所示:

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'])

9. 您的自訂轉換

動手做

讓我們從頭開始建構並訓練卷積類神經網路。使用 TPU 可讓我們非常快速地疊代。請開啟下列筆記本,執行儲存格 (Shift + ENTER),並按照「WORK REQUIRED」標籤中的指示操作。

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

目標是突破遷移學習模型 75% 的準確率。該模型的優勢在於,他們使用數百萬張圖片的資料集預先進行訓練,而這裡只有 3670 張圖片。至少能與之相符嗎?

其他資訊

有多少層?有多少層?

選擇圖層大小不只是科學而已,您必須在參數 (權重和偏差) 過多和過少之間取得平衡。如果權重太少,神經網路就無法呈現花朵形狀的複雜性。如果素材資源過多,可能會發生「過度配適」的情形,例如專門用培訓圖片,也無法一般化。參數太多也會導致模型訓練速度變慢。在 Keras 中,model.summary() 函式會顯示模型的結構和參數數量:

Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 192, 192, 16)      448       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 192, 192, 30)      4350      
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 96, 96, 30)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 96, 96, 60)        16260     
_________________________________________________________________
 ... 
_________________________________________________________________
global_average_pooling2d (Gl (None, 130)               0         
_________________________________________________________________
dense (Dense)                (None, 90)                11790     
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 455       
=================================================================
Total params: 300,033
Trainable params: 300,033
Non-trainable params: 0
_________________________________________________________________

以下提供幾個訣竅:

  • 擁有多層是「深」類神經網路的優勢。對於這個簡單的花朵辨識問題,5 到 10 個層就足夠了。
  • 使用小型濾鏡。一般來說,3x3 濾鏡在任何地方都很實用。
  • 1x1 濾鏡也能使用,而且價格便宜。它們並不會真正「過濾」任何內容,而是計算通道的線性組合。並交替使用。(詳情請參閱下一節的「1x1 卷積」)。
  • 如果是像這樣的分類問題,請經常使用最大集區層 (或步長 >1 的捲積) 進行向下取樣。您不需要在乎花朵在哪裡,只要是玫瑰花或黃銅,就不重要,因此減少 x 和 y 資訊不重要,而篩選較小區域的費用也比較低。
  • 篩選器數量通常會與聯播網結尾的類別數量類似 (為什麼?請參閱下方的「全球平均集區」技巧)。如果分類為數百個類別,請在連續圖層中逐步提高篩選器數量。若為含有 5 個類別的花卉資料集,只使用 5 個篩選器是不夠的。您可以在大多數圖層中使用相同的濾鏡數量,例如 32,並在接近結尾時減少。
  • 最終稠密層的成本是/昂貴。其權重可能比所有卷積層的權重總和還要多。舉例來說,即使在最後的資料立方為 24x24x10 的資料點中,輸出的結果相當合理,100 個神經元稠密層還是會產生 24x24x10x100=576,000 權重 !!請保持謹慎,或嘗試使用全域平均集區 (請見下方)。

全域平均池化

與其在卷積類神經網路末端使用昂貴的稠密層,您可以將傳入的資料「立方」分割為越多部分,每個部分都有一個類別,然後平均其值,再透過 softmax 活化函數提供這些部分。這種建立分類標頭的方式不會產生權重。在 Keras 中,語法為 tf.keras.layers.GlobalAveragePooling2D().

93240029f59df7c2.png

解決方案

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

c3df49e90e5a654f.png Keras_Flowers_TPU (solution).ipynb

涵蓋內容

  • 🤔? 運用卷積層
  • 🤓? 試驗並嘗試以最大集區法、擊球數、全球平均集區攻擊...
  • 😀 在 TPU 上快速迭代實際模型

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

10. [資訊] 現代卷積架構

摘要

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

其他超值秘訣

小 3 x 3 濾鏡

40a7b15fb7dbe75c.png

這張插圖顯示兩個連續 3x3 濾鏡的結果。請嘗試追蹤哪些資料點影響了結果:這兩個連續的 3x3 濾鏡會計算 5x5 區域的某些組合。這與 5x5 濾波器會計算的組合並不完全相同,但值得嘗試,因為兩個連續 3x3 篩選器的費用比一個 5x5 篩選器便宜。

1x1 卷積運算?

fd7cac16f8ecb423.png

在數學術語中,「1x1」卷積是常數的乘法,不是非常實用的概念。不過,請注意,在卷積神經網路中,過濾器會套用至資料立方體,而非 2D 圖片。因此,「1x1」篩選器會計算 1x1 資料欄的加權總和 (請見圖解),當您將資料滑動至資料時,會得到輸入管道的線性組合。這其實很有用。例如,您可以把管道視為個別篩選運算的結果,例如使用「pointy ears」的篩選器、另一個代表「Whiskers」,以及「whiskers」的篩選器,那麼「1x1」卷積層就會計算這些特徵的多個可能線性組合,您在尋找「貓」時可能會很有用。除此之外,1x1 層使用的權重更少。

11. 擠檸檬

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

1730ac375379269b.png

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

動手做

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

c3df49e90e5a654f.png Keras_Flowers_TPU (playground).ipynb

其他資訊

這在本練習中對於定義擠檸檬模組的輔助函式很有幫助:

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

由於我們的資料集很小,因此 Moumum 參數必須從預設值 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.datset 擴增資料

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

12. 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% (沒錯,真的有可能!)

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

微調作業的其他資訊

tf.keras.application 中的標準預先訓練模型提供 Xception。*別忘了保留所有可訓練的重量。

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

涵蓋內容

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

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

13. 恭喜!

您已建立第一個新式卷積類神經網路,並訓練至 90% 以上的準確度,而且由於有 TPU,您只需幾分鐘就能執行後續訓練。

TPU 實務

Google Cloud 的 Vertex AI 提供 TPU 和 GPU:

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

HR.png

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