图像分类

在 TensorFlow.org 上查看 在 Google Colab 中运行 在 GitHub 上查看源代码 下载笔记本

本教程展示了如何使用 tf.keras.Sequential 模型对花卉图像进行分类,并使用 tf.keras.utils.image_dataset_from_directory 加载数据。它演示了以下概念

  • 高效地从磁盘加载数据集。
  • 识别过拟合并应用技术来缓解它,包括数据增强和 dropout。

本教程遵循基本机器学习工作流程

  1. 检查和理解数据
  2. 构建输入管道
  3. 构建模型
  4. 训练模型
  5. 测试模型
  6. 改进模型并重复此过程

此外,笔记本演示了如何将 保存的模型 转换为 TensorFlow Lite 模型,以便在移动设备、嵌入式设备和物联网设备上进行设备端机器学习。

设置

导入 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)。在它之上有一个具有 128 个单元的全连接层 (tf.keras.layers.Dense),该层由 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% 左右。此外,训练准确率和验证准确率之间的准确率差异很明显,这是 过拟合 的标志。

当训练样本数量很少时,模型有时会从训练样本中的噪声或不需要的细节中学习,以至于会对模型在新样本上的性能产生负面影响。这种现象被称为过拟合。这意味着模型难以在新数据集上进行泛化。

在训练过程中有多种方法可以对抗过拟合。在本教程中,您将使用数据增强并在模型中添加丢弃

数据增强

当训练样本数量很少时,通常会发生过拟合。数据增强 通过使用随机变换生成现有样本的额外训练数据来解决这个问题,这些随机变换会产生看起来可信的图像。这有助于让模型接触到数据的更多方面,并更好地进行泛化。

您将使用以下 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")

您将在下一步中在训练之前将数据增强添加到模型中。

丢弃

另一种减少过拟合的技术是在网络中引入 丢弃 正则化。

当您将丢弃应用于层时,它会在训练过程中随机丢弃(通过将激活设置为零)层中的多个输出单元。丢弃以小数形式作为输入值,例如 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 的信息。