圖片分類

在 TensorFlow.org 上檢視 在 Google Colab 中執行 在 GitHub 上檢視原始碼 下載筆記本

本教學課程說明如何使用 tf.keras.Sequential 模型分類花卉圖片,以及如何使用 tf.keras.utils.image_dataset_from_directory 載入資料。其中示範了以下概念

  • 有效率地從磁碟載入資料集。
  • 找出過度擬合並套用技術來緩解,包括資料擴增和 Dropout。

本教學課程遵循基本機器學習工作流程

  1. 檢查並理解資料
  2. 建構輸入管道
  3. 建構模型
  4. 訓練模型
  5. 測試模型
  6. 改善模型並重複此流程

此外,本筆記本也會示範如何將 已儲存模型轉換為 TensorFlow Lite 模型,以便在行動裝置、嵌入式裝置和 IoT 裝置上進行裝置端機器學習。

設定

匯入 TensorFlow 和其他必要程式庫

import matplotlib.pyplot as plt
import numpy as np
import PIL
import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential

下載並探索資料集

本教學課程使用約 3,700 張花卉相片的資料集。此資料集包含五個子目錄,每個類別各一個

flower_photo/
  daisy/
  dandelion/
  roses/
  sunflowers/
  tulips/
import pathlib

dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"
data_dir = tf.keras.utils.get_file('flower_photos.tar', origin=dataset_url, extract=True)
data_dir = pathlib.Path(data_dir).with_suffix('')

下載後,您現在應該已取得資料集的副本。總共有 3,670 張圖片

image_count = len(list(data_dir.glob('*/*.jpg')))
print(image_count)

以下是一些玫瑰

roses = list(data_dir.glob('roses/*'))
PIL.Image.open(str(roses[0]))
PIL.Image.open(str(roses[1]))

以及一些鬱金香

tulips = list(data_dir.glob('tulips/*'))
PIL.Image.open(str(tulips[0]))
PIL.Image.open(str(tulips[1]))

使用 Keras 公用程式載入資料

接下來,使用實用的 tf.keras.utils.image_dataset_from_directory 公用程式,從磁碟載入這些圖片。這會將您從磁碟上的圖片目錄帶到 tf.data.Dataset,只需幾行程式碼即可完成。如果您願意,也可以從頭開始編寫自己的資料載入程式碼,方法是前往載入和預先處理圖片教學課程。

建立資料集

定義載入器的部分參數

batch_size = 32
img_height = 180
img_width = 180

在開發模型時,最好使用驗證分割。使用 80% 的圖片進行訓練,20% 進行驗證。

train_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="training",
  seed=123,
  image_size=(img_height, img_width),
  batch_size=batch_size)
val_ds = tf.keras.utils.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="validation",
  seed=123,
  image_size=(img_height, img_width),
  batch_size=batch_size)

您可以在這些資料集的 class_names 屬性中找到類別名稱。這些名稱與目錄名稱依字母順序對應。

class_names = train_ds.class_names
print(class_names)

視覺化資料

以下是訓練資料集中的前九張圖片

import matplotlib.pyplot as plt

plt.figure(figsize=(10, 10))
for images, labels in train_ds.take(1):
  for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[labels[i]])
    plt.axis("off")

稍後在本教學課程中,您會將這些資料集傳遞至 Keras Model.fit 方法進行訓練。如果您願意,也可以手動疊代資料集並擷取圖片批次

for image_batch, labels_batch in train_ds:
  print(image_batch.shape)
  print(labels_batch.shape)
  break

image_batch 是形狀為 (32, 180, 180, 3) 的張量。這是 32 張形狀為 180x180x3 (最後一個維度是指色彩通道 RGB) 的圖片批次。label_batch 是形狀為 (32,) 的張量,這些是 32 張圖片的對應標籤。

您可以在 image_batchlabels_batch 張量上呼叫 .numpy(),將它們轉換為 numpy.ndarray

設定資料集以提升效能

請務必使用緩衝預先擷取,這樣您就可以從磁碟產生資料,而不會讓 I/O 變成封鎖。這是載入資料時應使用的兩個重要方法

  • Dataset.cache 會在第一個週期期間從磁碟載入圖片後,將圖片保留在記憶體中。這可確保資料集在訓練模型時不會變成瓶頸。如果您的資料集太大而無法放入記憶體,您也可以使用此方法建立高效能的磁碟快取。
  • Dataset.prefetch 會在訓練時重疊資料預先處理和模型執行。

有興趣的讀者可以在 使用 tf.data API 提升效能指南的「預先擷取」章節中,進一步瞭解這兩種方法,以及如何將資料快取到磁碟。

AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

標準化資料

RGB 通道值位於 [0, 255] 範圍內。這對於神經網路來說並不理想;一般來說,您應該盡可能縮小輸入值。

在這裡,您將使用 tf.keras.layers.Rescaling 將值標準化為 [0, 1] 範圍

normalization_layer = layers.Rescaling(1./255)

有兩種方法可以使用此層。您可以透過呼叫 Dataset.map 將其套用至資料集

normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
image_batch, labels_batch = next(iter(normalized_ds))
first_image = image_batch[0]
# Notice the pixel values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image))

或者,您可以將層包含在模型定義中,這樣可以簡化部署。在此處使用第二種方法。

基本 Keras 模型

建立模型

Keras Sequential 模型包含三個卷積區塊 (tf.keras.layers.Conv2D),每個區塊中都有一個最大池化層 (tf.keras.layers.MaxPooling2D)。在其頂端有一個完全連線層 (tf.keras.layers.Dense),其中包含 128 個單元,並由 ReLU 啟動函式 ('relu') 啟動。此模型尚未針對高準確度進行調整;本教學課程的目標是展示標準方法。

num_classes = len(class_names)

model = Sequential([
  layers.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(32, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(64, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Flatten(),
  layers.Dense(128, activation='relu'),
  layers.Dense(num_classes)
])

編譯模型

在本教學課程中,選擇 tf.keras.optimizers.Adam 最佳化工具和 tf.keras.losses.SparseCategoricalCrossentropy 損失函式。若要檢視每個訓練週期的訓練和驗證準確度,請將 metrics 引數傳遞至 Model.compile

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

模型摘要

使用 Keras Model.summary 方法檢視網路的所有層

model.summary()

訓練模型

使用 Keras Model.fit 方法訓練模型 10 個週期

epochs=10
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)

視覺化訓練結果

建立訓練和驗證集的損失和準確度圖表

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

圖表顯示,訓練準確度和驗證準確度差異很大,且模型在驗證集上僅達到約 60% 的準確度。

以下教學課程章節說明如何檢查哪裡出錯,並嘗試提高模型的整體效能。

過度擬合

在上述圖表中,訓練準確度隨著時間線性增加,而驗證準確度在訓練過程中停滯在 60% 左右。此外,訓練準確度和驗證準確度之間的差異也很明顯,這是過度擬合的徵兆。

當訓練範例數量很少時,模型有時會從訓練範例中的雜訊或不必要的細節中學習,以至於對模型在新範例上的效能產生負面影響。這種現象稱為過度擬合。這表示模型很難在新資料集上進行泛化。

有多種方法可以對抗訓練過程中的過度擬合。在本教學課程中,您將使用資料擴增並將 Dropout 新增至模型。

資料擴增

過度擬合通常發生在訓練範例數量很少時。資料擴增採用從您現有的範例產生額外訓練資料的方法,方法是使用隨機轉換來擴增這些範例,以產生看起來可信的圖片。這有助於讓模型接觸更多資料層面並更好地泛化。

您將使用以下 Keras 預先處理層來實作資料擴增:tf.keras.layers.RandomFliptf.keras.layers.RandomRotationtf.keras.layers.RandomZoom。這些可以像其他層一樣包含在模型中,並在 GPU 上執行。

data_augmentation = keras.Sequential(
  [
    layers.RandomFlip("horizontal",
                      input_shape=(img_height,
                                  img_width,
                                  3)),
    layers.RandomRotation(0.1),
    layers.RandomZoom(0.1),
  ]
)

透過將資料擴增多次套用至同一張圖片,視覺化一些擴增範例

plt.figure(figsize=(10, 10))
for images, _ in train_ds.take(1):
  for i in range(9):
    augmented_images = data_augmentation(images)
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(augmented_images[0].numpy().astype("uint8"))
    plt.axis("off")

您會在下一個步驟中將資料擴增新增至模型,然後再進行訓練。

Dropout

減少過度擬合的另一種技術是在網路中引入 Dropout 正規化。

當您將 Dropout 套用至某一層時,它會在訓練過程中隨機捨棄 (透過將啟動設定為零) 該層中的許多輸出單元。Dropout 採用小數作為其輸入值,例如 0.1、0.2、0.4 等。這表示從套用的層中隨機捨棄 10%、20% 或 40% 的輸出單元。

在使用擴增圖片訓練之前,使用 tf.keras.layers.Dropout 建立新的神經網路

model = Sequential([
  data_augmentation,
  layers.Rescaling(1./255),
  layers.Conv2D(16, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(32, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Conv2D(64, 3, padding='same', activation='relu'),
  layers.MaxPooling2D(),
  layers.Dropout(0.2),
  layers.Flatten(),
  layers.Dense(128, activation='relu'),
  layers.Dense(num_classes, name="outputs")
])

編譯和訓練模型

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])
model.summary()
epochs = 15
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)

視覺化訓練結果

套用資料擴增和 tf.keras.layers.Dropout 後,過度擬合的情況比以前少,且訓練和驗證準確度更接近一致

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

在新資料上預測

使用您的模型來分類未包含在訓練或驗證集中的圖片。

sunflower_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg"
sunflower_path = tf.keras.utils.get_file('Red_sunflower', origin=sunflower_url)

img = tf.keras.utils.load_img(
    sunflower_path, target_size=(img_height, img_width)
)
img_array = tf.keras.utils.img_to_array(img)
img_array = tf.expand_dims(img_array, 0) # Create a batch

predictions = model.predict(img_array)
score = tf.nn.softmax(predictions[0])

print(
    "This image most likely belongs to {} with a {:.2f} percent confidence."
    .format(class_names[np.argmax(score)], 100 * np.max(score))
)

使用 TensorFlow Lite

TensorFlow Lite 是一組工具,可讓開發人員在行動裝置、嵌入式裝置和邊緣裝置上執行模型,進而實現裝置端機器學習。

將 Keras Sequential 模型轉換為 TensorFlow Lite 模型

若要搭配裝置端應用程式使用已訓練的模型,請先轉換為更小且更有效率的模型格式,稱為 TensorFlow Lite 模型。

在本範例中,採用已訓練的 Keras Sequential 模型,並使用 tf.lite.TFLiteConverter.from_keras_model 產生 TensorFlow Lite 模型

# Convert the model.
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()

# Save the model.
with open('model.tflite', 'wb') as f:
  f.write(tflite_model)

您在先前步驟中儲存的 TensorFlow Lite 模型可以包含多個函式簽名。Keras 模型轉換器 API 會自動使用預設簽名。進一步瞭解 TensorFlow Lite 簽名

執行 TensorFlow Lite 模型

您可以使用 tf.lite.Interpreter 類別,透過 Python 存取 TensorFlow Lite 已儲存模型的簽名。

使用 Interpreter 載入模型

TF_MODEL_FILE_PATH = 'model.tflite' # The default path to the saved TensorFlow Lite model

interpreter = tf.lite.Interpreter(model_path=TF_MODEL_FILE_PATH)

列印已轉換模型的簽名,以取得輸入 (和輸出) 的名稱

interpreter.get_signature_list()

在本範例中,您有一個名為 serving_default 的預設簽名。此外,'inputs' 的名稱為 'sequential_1_input',而 'outputs' 則稱為 'outputs'。執行 Model.summary 時,您可以查閱這些第一個和最後一個 Keras 層名稱,如本教學課程稍早所示。

現在,您可以透過將簽名名稱傳遞至 tf.lite.Interpreter.get_signature_runner,在範例圖片上執行推論來測試已載入的 TensorFlow 模型,如下所示

classify_lite = interpreter.get_signature_runner('serving_default')
classify_lite

與您在本教學課程稍早所做的類似,您可以使用 TensorFlow Lite 模型來分類未包含在訓練或驗證集中的圖片。

您已張量化該圖片並將其儲存為 img_array。現在,將其傳遞至已載入 TensorFlow Lite 模型 (predictions_lite) 的第一個引數 ( 'inputs'的名稱),計算 Softmax 啟動,然後列印具有最高計算機率的類別的預測。

predictions_lite = classify_lite(sequential_1_input=img_array)['outputs']
score_lite = tf.nn.softmax(predictions_lite)
print(
    "This image most likely belongs to {} with a {:.2f} percent confidence."
    .format(class_names[np.argmax(score_lite)], 100 * np.max(score_lite))
)

Lite 模型產生的預測應與原始模型產生的預測幾乎相同

print(np.max(np.abs(predictions - predictions_lite)))

在五個類別 ('daisy''dandelion''roses''sunflowers''tulips') 中,模型應預測圖片屬於向日葵,這與 TensorFlow Lite 轉換前的結果相同。

後續步驟

本教學課程說明如何訓練圖片分類模型、測試模型、將模型轉換為適用於裝置端應用程式 (例如圖片分類應用程式) 的 TensorFlow Lite 格式,以及使用 Python API 透過 TensorFlow Lite 模型執行推論。

您可以透過教學課程指南進一步瞭解 TensorFlow Lite。