返回文章列表

TensorFlow深度學習訓練與模型最佳化實務

本文探討 TensorFlow 深度學習訓練流程的最佳化技巧,包含批次處理、圖形運算、影像處理效能提升、模型匯出與載入、以及分散式訓練策略等關鍵技術。文章涵蓋 Keras 層預處理、TensorFlow 圖形內運算、tf.py_function() 的使用、SavedModel 與 ONNX

機器學習 Web 開發

深度學習模型訓練的效率提升,與硬體資源運用息息相關。批次大小的調整,需考量GPU記憶體容量,過大或過小皆非最佳解。Keras層的運用,能將預處理步驟向量化,避免逐張影像處理的效能損耗。TensorFlow圖形內運算,能減少CPU與GPU間的資料傳輸,提升整體效能。必要時,可使用tf.py_function()整合Python程式碼。模型的匯出與載入,SavedModel格式為TensorFlow生態系的推薦選擇,也能透過工具轉換為ONNX格式,提升跨平台相容性。簡化的模型呼叫介面,能提升使用者經驗,方便整合至應用系統。

訓練流程最佳化:批次處理與圖形運算最佳化

在深度學習模型的訓練過程中,批次處理(batching)是提高訓練效率的關鍵因素之一。適當的批次大小不僅能提升運算效能,也能充分利用GPU的記憶體資源。

批次大小對訓練效能的影響

實驗結果(見表7-4)顯示,適當增加批次大小可以顯著提升訓練速度,但這種改善效果會隨著批次大小的增加而遞減。過大的批次大小甚至可能導致GPU記憶體不足的問題。

批次大小CPU 時間實際時間
111.4 秒8.09 秒
89.56 秒6.90 秒
169.90 秒6.70 秒
329.68 秒6.37 秒

為何使用Keras層進行預處理

在第六章中,我們將隨機翻轉、色彩扭曲等預處理步驟實作為Keras層,這樣的設計有其必要性。假設我們使用map()函式來進行色彩扭曲:

trainds = tf.data.TFRecordDataset(
    [filename for filename in tf.io.gfile.glob(pattern)]
).map(preproc.read_from_tfr).map(_preproc_img_label
).map(color_distort).batch(32)

其中color_distort()函式實作如下:

def color_distort(image, label):
    contrast = np.random.uniform(0.5, 1.5)
    brightness = np.random.uniform(-0.2, 0.2)
    image = tf.image.adjust_contrast(image, contrast)
    image = tf.image.adjust_brightness(image, brightness)
    image = tf.clip_by_value(image, 0, 1)
    return image, label

內容解密:

  1. 色彩扭曲函式實作:該函式隨機調整影像的對比度和亮度,以增強模型的泛化能力。
  2. 效率考量:直接在map()中使用色彩扭曲會導致逐一處理影像,大幅降低效率。
  3. 最佳實踐:將預處理步驟實作為Keras層,可以在批次層級上進行處理,顯著提升效能。

正確的做法是將預處理步驟放在Keras層中,這樣不僅能提高運算效率,也便於在推理流程中重現相同的預處理邏輯。

保持運算在TensorFlow圖形內

TensorFlow的設計理念是在GPU上執行數學運算以提升效率。因此,將資料從CPU傳輸到GPU後,所有屬於tf.data管線的程式碼都應在GPU上執行。同樣地,Keras模型層的運算也應在GPU上進行。

圖形內運算的重要性

  • 避免資料傳輸開銷:直接在GPU上處理資料可以避免不必要的資料傳輸。
  • 效能最佳化:將所有運算保持在TensorFlow圖形內,可以最大化利用GPU的運算能力。

使用tf.py_function()呼叫純Python程式碼

在某些情況下,我們需要呼叫純Python程式碼來完成特定的任務,例如時區轉換或JSON資料解析。這時可以使用tf.py_function()來實作:

def to_grayscale(img):
    return tf.py_function(to_grayscale_numpy, [img], tf.float32)

內容解密:

  1. tf.py_function()的作用:該函式允許我們在TensorFlow圖形中呼叫純Python函式。
  2. 引數說明:需要指定要包裝的函式名稱、輸入張量和輸出資料型別。
  3. 使用場景:當需要使用Python特定的函式庫(如pytzjson)時,可以透過此方法實作。

這種方法使得我們可以在保持TensorFlow圖形運算效率的同時,利用Python生態系統中的豐富資源。

TensorFlow 影像處理最佳化技術

在深度學習的應用中,影像處理是一項重要的任務。TensorFlow 提供了一系列的 API 來處理影像資料。本文將探討如何使用 TensorFlow 來最佳化影像處理的效能。

將影像轉換為灰階

首先,我們來看看如何將一張彩色影像轉換為灰階影像。原始的做法是使用 NumPy 來進行轉換,但是這樣的做法效率不高。

使用 NumPy 轉換

def to_grayscale_numpy(img):
    img = img.numpy()
    rows, cols, _ = img.shape
    result = np.zeros([rows, cols], dtype=np.float32)
    # ...
    return tf.convert_to_tensor(result)

使用 TensorFlow 的 Slicing 功能

def to_grayscale(img):
    red = img[:, :, 0]
    green = img[:, :, 1]
    blue = img[:, :, 2]
    c_linear = 0.2126 * red + 0.7152 * green + 0.0722 * blue
    gray = tf.where(c_linear > 0.0031308,
                    1.055 * tf.pow(c_linear, 1 / 2.4) - 0.055,
                    12.92 * c_linear)
    return gray

程式碼解析

在這個程式碼中,我們使用了 TensorFlow 的 Slicing 功能來取出影像的紅、綠、藍三個通道。然後,我們使用 tf.where 來進行條件判斷,將 c_linear 的值轉換為灰階值。

  • red = img[:, :, 0]:取出影像的紅色通道。
  • green = img[:, :, 1]:取出影像的綠色通道。
  • blue = img[:, :, 2]:取出影像的藍色通道。
  • c_linear = 0.2126 * red + 0.7152 * green + 0.0722 * blue:計算 c_linear 的值。
  • gray = tf.where(c_linear > 0.0031308, 1.055 * tf.pow(c_linear, 1 / 2.4) - 0.055, 12.92 * c_linear):使用 tf.where 來進行條件判斷,將 c_linear 的值轉換為灰階值。

矩陣運算最佳化

我們可以進一步最佳化 c_linear 的計算,使用矩陣運算來取代 Slicing。

def to_grayscale(img):
    wt = tf.constant([[0.2126], [0.7152], [0.0722]]) 
    c_linear = tf.matmul(img, wt) 
    gray = tf.where(c_linear > 0.0031308,
                    1.055 * tf.pow(c_linear, 1 / 2.4) - 0.055,
                    12.92 * c_linear)
    return gray

程式碼解析

在這個程式碼中,我們使用了矩陣運算來計算 c_linear 的值。

  • wt = tf.constant([[0.2126], [0.7152], [0.0722]]):建立一個常數矩陣 wt
  • c_linear = tf.matmul(img, wt):使用矩陣運算來計算 c_linear 的值。

分批處理

我們可以將多張影像一起處理,以提高效率。

class Grayscale(tf.keras.layers.Layer):
    def __init__(self, **kwargs):
        super(Grayscale, self).__init__(kwargs)

    def call(self, img):
        wt = tf.constant([[0.2126], [0.7152], [0.0722]]) 
        c_linear = tf.matmul(img, wt) 
        gray = tf.where(c_linear > 0.0031308,
                        1.055 * tf.pow(c_linear, 1 / 2.4) - 0.055,
                        12.92 * c_linear)
        return gray 

程式碼解析

在這個程式碼中,我們定義了一個自定義的 Keras Layer,用於將多張影像轉換為灰階。

  • wt = tf.constant([[0.2126], [0.7152], [0.0722]]):建立一個常數矩陣 wt
  • c_linear = tf.matmul(img, wt):使用矩陣運算來計算 c_linear 的值。
  • gray = tf.where(c_linear > 0.0031308, 1.055 * tf.pow(c_linear, 1 / 2.4) - 0.055, 12.92 * c_linear):使用 tf.where 來進行條件判斷,將 c_linear 的值轉換為灰階值。

儲存模型狀態

在訓練完成後,我們需要儲存模型的狀態,以便後續的使用。

儲存模型狀態的原因有兩個:

  1. 進行推斷:儲存模型狀態可以讓我們在後續的使用中,直接載入模型進行推斷。
  2. 繼續訓練:儲存模型狀態可以讓我們在後續的使用中,繼續訓練模型。
preproc_model = tf.keras.Sequential([
    Grayscale(input_shape=(336, 600, 3)),
    tf.keras.layers.Lambda(lambda gray: tf.reduce_mean(gray, axis=[1, 2]))
])

程式碼解析

在這個程式碼中,我們定義了一個 Sequential Model,用於將影像轉換為灰階並計算平均值。

  • Grayscale(input_shape=(336, 600, 3)):建立一個 Grayscale Layer,用於將影像轉換為灰階。
  • tf.keras.layers.Lambda(lambda gray: tf.reduce_mean(gray, axis=[1, 2])):建立一個 Lambda Layer,用於計算灰階影像的平均值。

模型匯出與載入:TensorFlow SavedModel 格式詳解

在機器學習模型的開發與佈署過程中,模型的儲存與匯出是至關重要的步驟。TensorFlow 提供了 SavedModel 格式,這是一種用於儲存和載入模型的方式,特別是在模型佈署和線上預測的場景中。本文將探討 TensorFlow 中的模型匯出、載入及其相關技術細節。

匯出模型

匯出模型是將訓練好的 Keras 模型儲存為 SavedModel 格式的過程。使用 save() 方法可以輕鬆實作這一點:

os.mkdir('export')
model.save('export/flowers_model')

export/flowers_model 目錄下,將會產生一個名為 saved_model.pb 的 protobuf 檔案、變數權重以及模型所需的其他資產檔案,如詞彙表等。

程式碼解析:

  • os.mkdir('export'): 建立一個名為 export 的目錄,用於存放匯出的模型。
  • model.save('export/flowers_model'): 將訓練好的 Keras 模型儲存到指定路徑,採用 SavedModel 格式。

SavedModel 與 ONNX 格式的比較

除了 SavedModel 格式,ONNX(Open Neural Network Exchange)也是一種開放原始碼、框架無關的機器學習模型格式。可以使用 tf2onnx 工具將 TensorFlow 模型轉換為 ONNX 格式,以實作跨框架的相容性。

載入與呼叫模型

可以使用 TensorFlow 提供的 saved_model_cli 命令列工具來檢查 SavedModel 的內容:

saved_model_cli show --tag_set all --dir export/flowers_model

這條命令會顯示模型的輸入輸出簽名,例如:

inputs['random/center_crop_input'] tensor_info:
    dtype: DT_FLOAT
    shape: (-1, 448, 448, 3)
    name: serving_default_random/center_crop_input:0
outputs['flower_prob'] tensor_info:
    dtype: DT_FLOAT
    shape: (-1, 5)
    name: StatefulPartitionedCall:0
Method name is: tensorflow/serving/predict

程式碼解析:

  • saved_model_cli show: 用於顯示 SavedModel 的詳細資訊。
  • --tag_set all: 指定顯示所有標籤集的資訊。
  • --dir export/flowers_model: 指定 SavedModel 的存放路徑。

TensorFlow 函式簽名

TensorFlow 中的函式簽名包含函式名稱、輸入引數及其型別、輸出型別等資訊。為了使模型能夠被正確呼叫,需要明確指定輸入輸出的形狀和型別。

@tf.function(input_signature=[
    tf.TensorSpec([3,5], name='a'),
    tf.TensorSpec([5,8], name='b')
])
def myfunc(a, b):
    return tf.matmul(a, b)

程式碼解析:

  • @tf.function: 將 Python 函式轉換為 TensorFlow 圖函式。
  • input_signature: 指定輸入引數的形狀和型別。
  • tf.TensorSpec: 用於定義張量的形狀和型別。

簡化模型呼叫介面

為了讓客戶端更容易使用模型,可以簡化模型的呼叫介面。例如,將輸入從張量改為 JPEG 檔案,並傳回更易理解的預測結果:

@tf.function(input_signature=[tf.TensorSpec([None,], dtype=tf.string)])
def predict_flower_type(filenames):
    # 省略實作細節
    return {
        'probability': top_prob,
        'flower_type_int': pred_label_index,
        'flower_type_str': pred_label
    }

程式碼解析:

  • input_signature=[tf.TensorSpec([None,], dtype=tf.string)]: 指定輸入為字串張量,用於接收 JPEG 檔案的路徑。
  • return: 傳回一個字典,包含預測的機率、類別索引以及類別名稱。

提升模型效率與儲存狀態的最佳實踐

在前述章節中,我們已經探討瞭如何將模型匯出以進行推論(inference)。本章節將進一步討論如何提升模型的效率以及儲存模型的狀態,以便於繼續訓練。

向量化輸入以提升效率

為了提升模型的效率,我們可以將多個輸入影像組合成批次(batch),一次性進行預測。這種向量化的做法不僅在訓練階段有益,在預測階段同樣能帶來效率的提升。

給定一個檔案名稱的列表,我們可以透過以下方式取得輸入影像:

input_images = [create_preproc_image(f) for f in filenames]

然而,這種做法需要在加速的 TensorFlow 程式碼和未加速的 Python 程式碼之間來回傳遞資料。若我們擁有一個包含檔案名稱的張量(tensor),可以使用 tf.map_fn() 在 TensorFlow 圖形內實作迭代的效果,從而保持所有資料在 TensorFlow 圖形內。

使用 tf.map_fn() 實作向量化輸入

input_images = tf.map_fn(
    create_preproc_image,
    filenames,
    fn_output_signature=tf.float32
)

接著,呼叫模型以取得完整的機率矩陣(probability matrix):

batch_pred = model(input_images)

然後,找到最大機率及其對應的索引:

top_prob = tf.math.reduce_max(batch_pred, axis=1)
pred_label_index = tf.math.argmax(batch_pred, axis=1)

最後,透過 tf.gather() 將預測的標籤索引對映到實際的標籤名稱:

pred_label = tf.gather(params=tf.convert_to_tensor(CLASS_NAMES), indices=pred_label_index)

內容解密:

  1. tf.map_fn():用於在 TensorFlow 圖形內對張量的元素進行迭代操作,避免了 Python 和 TensorFlow 之間的資料傳遞。
  2. tf.math.reduce_max()tf.math.argmax():分別用於計算最大機率和找到最大機率對應的索引。指定 axis=1 是因為我們是在批次(batch)內進行操作。
  3. tf.gather():用於根據索引從張量中收集元素,將預測的標籤索引對映到實際的類別名稱。

儲存模型狀態

在訓練過程中,儲存模型狀態(checkpointing)是非常重要的,不僅可以在訓練結束時儲存模型,也可以在訓練過程中定期儲存,以便於從中斷的地方繼續訓練。

使用 Keras 的 ModelCheckpoint 回撥函式

model_checkpoint_cb = tf.keras.callbacks.ModelCheckpoint(
    filepath='./chkpts',
    monitor='val_accuracy', mode='max',
    save_best_only=True
)
history = model.fit(train_dataset,
                    validation_data=eval_dataset,
                    epochs=NUM_EPOCHS,
                    callbacks=[model_checkpoint_cb])

這裡,我們設定了在驗證準確率(val_accuracy)最高的情況下儲存模型。

結合早停法(Early Stopping)

early_stopping_cb = tf.keras.callbacks.EarlyStopping(
    monitor='val_accuracy', mode='max',
    patience=2
)
callbacks = [model_checkpoint_cb, early_stopping_cb]

早停法可以在驗證準確率連續多個 epoch 沒有提升時自動停止訓練。

分散式策略(Distribution Strategy)

為了在多個處理器、加速器或機器上分佈模型的訓練,需要使用分散式策略。TensorFlow 提供了多種分散式策略,例如 MirroredStrategy,可以簡單地透過以下方式使用:

strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
    layers = [...]
    model = tf.keras.Sequential(layers)
    model.compile(...)
    history = model.fit(...)

內容解密:

  1. tf.distribute.MirroredStrategy():建立一個映象分散式策略,用於在多個 GPU 上同步訓練模型。
  2. with strategy.scope()::確保模型和相關的操作在分散式策略的範圍內建立,從而實作分散式訓練。