![]() |
![]() |
![]() |
![]() |
このチュートリアルでは、花の画像を分類する方法を示します。tf.keras.Sequential
モデルを使用して画像分類器を構築し、tf.keras.utils.image_dataset_from_directory
を使用してデータを読み込みます。このチュートリアルでは、次の概念を実際に見ていきます。
- ディスク上のデータセットを効率的に読み込みます。
- 過学習を識別し、データ拡張やドロップアウトなどテクニックを使用して過学習を防ぎます。
このチュートリアルは、基本的な機械学習のワークフローに従います。
- データの調査及び理解
- 入力パイプラインの構築
- モデルの構築
- モデルの学習
- モデルのテスト
- モデルの改善とプロセスの繰り返し
パッケージのインポート
import matplotlib.pyplot as plt
import numpy as np
import os
import PIL
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
2022-08-09 02:04:08.939257: E tensorflow/stream_executor/cuda/cuda_blas.cc:2981] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered 2022-08-09 02:04:09.613740: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvrtc.so.11.1: cannot open shared object file: No such file or directory 2022-08-09 02:04:09.614036: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvrtc.so.11.1: cannot open shared object file: No such file or directory 2022-08-09 02:04:09.614050: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.
データの読み込み
このチュートリアルでは、約3,700枚の花の写真のデータセットを使用します。データセットには、クラスごとに1つずつ、5 つのサブディレクトリが含まれています。
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', origin=dataset_url, untar=True)
data_dir = pathlib.Path(data_dir)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz 228813984/228813984 [==============================] - 2s 0us/step
ダウンロード後、データセットのコピーが利用できるようになります。合計3,670枚の画像があります。
image_count = len(list(data_dir.glob('*/*.jpg')))
print(image_count)
3670
バラの画像です。
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 ユーティリティを使用してデータを読み込む
便利な 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)
Found 3670 files belonging to 5 classes. Using 2936 files for training.
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)
Found 3670 files belonging to 5 classes. Using 734 files for validation.
クラス名は、これらのデータセットのclass_names
属性にあります。 これらはアルファベット順にディレクトリ名に対応します。
class_names = train_ds.class_names
print(class_names)
['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']
データを視覚化する
以下はトレーニングデータセットの最初の 9 枚の画像です。
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")
これらのデータセットをmodel.fit
に渡すことで、これらのデータセットを使用してモデルをトレーニングします。 必要に応じて、データセットを手動で繰り返し、画像のバッチを取得することもできます。
for image_batch, labels_batch in train_ds:
print(image_batch.shape)
print(labels_batch.shape)
break
(32, 180, 180, 3) (32,)
image_batch
は、形状(32, 180, 180, 3)
のテンソルです。これは、形状180x180x3
の 32 枚の画像のバッチです(最後の次元はカラーチャンネル RGB を参照します)。label_batch
は、形状(32,)
のテンソルであり、これらは 32 枚の画像に対応するラベルです。
image_batch
およびlabels_batch
テンソルで.numpy()
を呼び出して、それらをnumpy.ndarray
に変換できます。
パフォーマンスのためにデータセットを構成する
I/O がブロックされることなくディスクからデータを取得できるように、必ずバッファ付きプリフェッチを使用します。これらは、データを読み込むときに使用する必要がある 2 つの重要な方法です。
Dataset.cache()
は、最初のエポック中に画像をディスクから読み込んだ後、メモリに保持します。これにより、モデルのトレーニング中にデータセットがボトルネックになることを回避できます。データセットが大きすぎてメモリに収まらない場合は、この方法を使用して、パフォーマンスの高いオンディスクキャッシュを作成することもできます。Dataset.prefetch
はトレーニング中にデータの前処理とモデルの実行をオーバーラップさせます。
以上の 2 つの方法とデータをディスクにキャッシュする方法についての詳細は、データパフォーマンスガイドの プリフェッチを参照してください。
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)
このレイヤーを使用するには 2 つの方法があります。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))
0.0 0.9810147
または、モデル定義内にレイヤーを含めることができます。これにより、デプロイメントを簡素化できます。 ここでは 2 番目のアプローチを使用します。
注意:以前は、tf.keras.utils.image_dataset_from_directory
の image_size
引数を使用して画像のサイズを変更しました。モデルにサイズ変更ロジックも含める場合は、tf.keras.layers.Resizing
レイヤーを使用できます。
モデルを作成する
Sequential モデルは、それぞれに最大プールレイヤー(tf.keras.layers.MaxPooling2D
)を持つ3つの畳み込みブロック(tf.keras.layers.Conv2D
)で構成されます。ReLU 活性化関数('relu'
)により活性化されたユニットが 128 個ある完全に接続されたレイヤー(tf.keras.layers.Dense
)があります。このチュートリアルの目的は、標準的なアプローチを示すことなので、このモデルは高精度に調整されていません。
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
損失関数を選択します。各トレーニングエポックのトレーニングと検証の精度を表示するには、Model.compile
に metrics
引数を渡します。
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
モデルの概要
モデルの Model.summary
メソッドを使用して、ネットワークのすべてのレイヤーを表示します。
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= rescaling_1 (Rescaling) (None, 180, 180, 3) 0 conv2d (Conv2D) (None, 180, 180, 16) 448 max_pooling2d (MaxPooling2D (None, 90, 90, 16) 0 ) conv2d_1 (Conv2D) (None, 90, 90, 32) 4640 max_pooling2d_1 (MaxPooling (None, 45, 45, 32) 0 2D) conv2d_2 (Conv2D) (None, 45, 45, 64) 18496 max_pooling2d_2 (MaxPooling (None, 22, 22, 64) 0 2D) flatten (Flatten) (None, 30976) 0 dense (Dense) (None, 128) 3965056 dense_1 (Dense) (None, 5) 645 ================================================================= Total params: 3,989,285 Trainable params: 3,989,285 Non-trainable params: 0 _________________________________________________________________
モデルをトレーニングする
epochs=10
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=epochs
)
Epoch 1/10 92/92 [==============================] - 3s 19ms/step - loss: 1.2247 - accuracy: 0.4813 - val_loss: 1.1346 - val_accuracy: 0.5354 Epoch 2/10 92/92 [==============================] - 1s 15ms/step - loss: 0.9529 - accuracy: 0.6267 - val_loss: 0.9023 - val_accuracy: 0.6485 Epoch 3/10 92/92 [==============================] - 1s 15ms/step - loss: 0.7535 - accuracy: 0.7149 - val_loss: 0.8664 - val_accuracy: 0.6649 Epoch 4/10 92/92 [==============================] - 1s 15ms/step - loss: 0.5361 - accuracy: 0.8106 - val_loss: 0.9106 - val_accuracy: 0.6744 Epoch 5/10 92/92 [==============================] - 1s 16ms/step - loss: 0.3431 - accuracy: 0.8764 - val_loss: 0.9713 - val_accuracy: 0.6621 Epoch 6/10 92/92 [==============================] - 1s 15ms/step - loss: 0.2081 - accuracy: 0.9329 - val_loss: 1.1649 - val_accuracy: 0.6785 Epoch 7/10 92/92 [==============================] - 1s 15ms/step - loss: 0.1044 - accuracy: 0.9663 - val_loss: 1.3237 - val_accuracy: 0.6594 Epoch 8/10 92/92 [==============================] - 1s 15ms/step - loss: 0.0479 - accuracy: 0.9884 - val_loss: 1.7254 - val_accuracy: 0.6294 Epoch 9/10 92/92 [==============================] - 1s 15ms/step - loss: 0.0433 - accuracy: 0.9881 - val_loss: 1.6022 - val_accuracy: 0.6553 Epoch 10/10 92/92 [==============================] - 1s 15ms/step - loss: 0.0288 - accuracy: 0.9928 - val_loss: 1.9491 - val_accuracy: 0.6512
トレーニングの結果を視覚化する
トレーニングセットと検証セットで損失と精度のプロットを作成します。
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%のままです。また、トレーニングと検証の精度に大きな違いがあり、これは過剰適合の兆候を示しています。
トレーニングサンプルの数が少ない場合、モデルは、トレーニングサンプルのノイズや不要な詳細から学習し、新しいサンプルでのモデルのパフォーマンスに悪影響を及ぼすことがあります。 この現象は過剰適合として知られています。 これは、モデルが新しいデータセットで一般化する上で問題があることを意味します。
トレーニングプロセスで過剰適合を回避する方法は複数あります。このチュートリアルでは、データ拡張を使用して、モデルにドロップアウトを追加します。
データ拡張
過剰適合は、一般に、トレーニングサンプルの数が少ない場合に発生します。データ拡張は、既存のサンプルに対してランダムな変換を使用してサンプルを拡張することにより、追加のトレーニングデータを生成します。これにより、モデルをデータのより多くの側面でトレーニングし、より一般化することができます。
tf.keras.layers.RandomFlip
、tf.keras.layers.RandomRotation
、および tf.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")
データ拡張を使用して、モデルをトレーニングします。
ドロップアウト
過剰適合を回避するもう 1 つの方法は、ドロップアウト正則化をネットワークに導入することです。
ドロップアウトをレイヤーに適用すると、トレーニングプロセス中にレイヤーからいくつかの出力ユニットがランダムにドロップアウトされます(アクティベーションをゼロに設定することにより)。ドロップアウトは、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)
])
モデルをコンパイルしてトレーニングする
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
model.summary()
Model: "sequential_2" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= sequential_1 (Sequential) (None, 180, 180, 3) 0 rescaling_2 (Rescaling) (None, 180, 180, 3) 0 conv2d_3 (Conv2D) (None, 180, 180, 16) 448 max_pooling2d_3 (MaxPooling (None, 90, 90, 16) 0 2D) conv2d_4 (Conv2D) (None, 90, 90, 32) 4640 max_pooling2d_4 (MaxPooling (None, 45, 45, 32) 0 2D) conv2d_5 (Conv2D) (None, 45, 45, 64) 18496 max_pooling2d_5 (MaxPooling (None, 22, 22, 64) 0 2D) dropout (Dropout) (None, 22, 22, 64) 0 flatten_1 (Flatten) (None, 30976) 0 dense_2 (Dense) (None, 128) 3965056 dense_3 (Dense) (None, 5) 645 ================================================================= Total params: 3,989,285 Trainable params: 3,989,285 Non-trainable params: 0 _________________________________________________________________
epochs = 15
history = model.fit(
train_ds,
validation_data=val_ds,
epochs=epochs
)
Epoch 1/15 92/92 [==============================] - 3s 27ms/step - loss: 1.3658 - accuracy: 0.3913 - val_loss: 1.1980 - val_accuracy: 0.5218 Epoch 2/15 92/92 [==============================] - 2s 26ms/step - loss: 1.0472 - accuracy: 0.5838 - val_loss: 0.9624 - val_accuracy: 0.6294 Epoch 3/15 92/92 [==============================] - 2s 25ms/step - loss: 0.9316 - accuracy: 0.6339 - val_loss: 0.9279 - val_accuracy: 0.6417 Epoch 4/15 92/92 [==============================] - 2s 26ms/step - loss: 0.8792 - accuracy: 0.6557 - val_loss: 0.9192 - val_accuracy: 0.6512 Epoch 5/15 92/92 [==============================] - 2s 26ms/step - loss: 0.8396 - accuracy: 0.6713 - val_loss: 0.8316 - val_accuracy: 0.6785 Epoch 6/15 92/92 [==============================] - 2s 25ms/step - loss: 0.7726 - accuracy: 0.7010 - val_loss: 0.8146 - val_accuracy: 0.6771 Epoch 7/15 92/92 [==============================] - 2s 24ms/step - loss: 0.7517 - accuracy: 0.7119 - val_loss: 0.7949 - val_accuracy: 0.7044 Epoch 8/15 92/92 [==============================] - 2s 25ms/step - loss: 0.7127 - accuracy: 0.7228 - val_loss: 0.7524 - val_accuracy: 0.7193 Epoch 9/15 92/92 [==============================] - 2s 25ms/step - loss: 0.6895 - accuracy: 0.7333 - val_loss: 0.7363 - val_accuracy: 0.7098 Epoch 10/15 92/92 [==============================] - 2s 25ms/step - loss: 0.6604 - accuracy: 0.7449 - val_loss: 0.7850 - val_accuracy: 0.7016 Epoch 11/15 92/92 [==============================] - 2s 24ms/step - loss: 0.6307 - accuracy: 0.7568 - val_loss: 0.7790 - val_accuracy: 0.7112 Epoch 12/15 92/92 [==============================] - 2s 24ms/step - loss: 0.5990 - accuracy: 0.7711 - val_loss: 0.7173 - val_accuracy: 0.7302 Epoch 13/15 92/92 [==============================] - 2s 26ms/step - loss: 0.5846 - accuracy: 0.7841 - val_loss: 0.6794 - val_accuracy: 0.7425 Epoch 14/15 92/92 [==============================] - 2s 25ms/step - loss: 0.5482 - accuracy: 0.7851 - val_loss: 0.7331 - val_accuracy: 0.7180 Epoch 15/15 92/92 [==============================] - 2s 25ms/step - loss: 0.5343 - accuracy: 0.7916 - val_loss: 0.6951 - val_accuracy: 0.7411
トレーニングの結果を視覚化する
データ拡張と 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))
)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg 117948/117948 [==============================] - 0s 0us/step 1/1 [==============================] - 0s 116ms/step This image most likely belongs to sunflowers with a 71.80 percent confidence.