Классификация изображений

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

В этом уроке показано, как классифицировать изображения цветов. Он создает классификатор изображений, используя модель tf.keras.Sequential , и загружает данные, используя tf.keras.utils.image_dataset_from_directory . Вы получите практический опыт работы со следующими понятиями:

  • Эффективная загрузка набора данных с диска.
  • Выявление переобучения и применение методов для его уменьшения, включая увеличение и отсев данных.

В этом руководстве используется базовый рабочий процесс машинного обучения:

  1. Изучайте и анализируйте данные
  2. Создайте конвейер ввода
  3. Построить модель
  4. Обучите модель
  5. Протестируйте модель
  6. Улучшите модель и повторите процесс

Импорт TensorFlow и других библиотек

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

Загрузите и изучите набор данных

В этом руководстве используется набор данных из примерно 3700 фотографий цветов. Набор данных содержит пять подкаталогов, по одному на класс:

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)

После загрузки у вас должна быть доступна копия набора данных. Всего 3670 изображений:

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

Вот несколько роз:

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

png

PIL.Image.open(str(roses[1]))

png

И немного тюльпанов:

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

png

PIL.Image.open(str(tulips[1]))

png

Загрузить данные с помощью утилиты 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)
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']

Визуализируйте данные

Вот первые девять изображений из обучающего набора данных:

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")

png

Вы обучите модель, используя эти наборы данных, передав их в 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) . Это пакет из 32 изображений размером 180x180x3 (последний размер относится к цветовым каналам RGB). label_batch — это тензор формы (32,) , это соответствующие метки для 32 изображений.

Вы можете вызвать .numpy() для тензоров image_batch и labels_batch , чтобы преобразовать их в numpy.ndarray .

Настройте набор данных для производительности

Обязательно используйте предварительную выборку с буферизацией, чтобы вы могли получать данные с диска, не блокируя ввод-вывод. Вот два важных метода, которые вы должны использовать при загрузке данных:

  • Dataset.cache хранит изображения в памяти после их загрузки с диска в течение первой эпохи. Это гарантирует, что набор данных не станет узким местом при обучении вашей модели. Если ваш набор данных слишком велик, чтобы поместиться в память, вы также можете использовать этот метод для создания производительного кэша на диске.
  • Dataset.prefetch перекрывает предварительную обработку данных и выполнение модели во время обучения.

Заинтересованные читатели могут узнать больше об обоих методах, а также о том, как кэшировать данные на диск, в разделе Предварительная выборка руководства Повышение производительности с помощью API tf.data .

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] . Это не идеально для нейронной сети; в общем, вы должны стремиться к тому, чтобы ваши входные значения были небольшими.

Здесь вы будете стандартизировать значения в диапазоне [0, 1] с помощью tf.keras.layers.Rescaling :

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))
0.0 1.0

Или вы можете включить слой в определение вашей модели, что может упростить развертывание. Здесь воспользуемся вторым подходом.

Создайте модель

Последовательная модель состоит из трех блоков свертки ( 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'])

Краткое описание модели

Просмотрите все слои сети, используя метод модели 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 16ms/step - loss: 1.2769 - accuracy: 0.4489 - val_loss: 1.0457 - val_accuracy: 0.5804
Epoch 2/10
92/92 [==============================] - 1s 11ms/step - loss: 0.9386 - accuracy: 0.6328 - val_loss: 0.9665 - val_accuracy: 0.6158
Epoch 3/10
92/92 [==============================] - 1s 11ms/step - loss: 0.7390 - accuracy: 0.7200 - val_loss: 0.8768 - val_accuracy: 0.6540
Epoch 4/10
92/92 [==============================] - 1s 11ms/step - loss: 0.5649 - accuracy: 0.7963 - val_loss: 0.9258 - val_accuracy: 0.6540
Epoch 5/10
92/92 [==============================] - 1s 11ms/step - loss: 0.3662 - accuracy: 0.8733 - val_loss: 1.1734 - val_accuracy: 0.6267
Epoch 6/10
92/92 [==============================] - 1s 11ms/step - loss: 0.2169 - accuracy: 0.9343 - val_loss: 1.3728 - val_accuracy: 0.6499
Epoch 7/10
92/92 [==============================] - 1s 11ms/step - loss: 0.1191 - accuracy: 0.9629 - val_loss: 1.3791 - val_accuracy: 0.6471
Epoch 8/10
92/92 [==============================] - 1s 11ms/step - loss: 0.0497 - accuracy: 0.9871 - val_loss: 1.8002 - val_accuracy: 0.6390
Epoch 9/10
92/92 [==============================] - 1s 11ms/step - loss: 0.0372 - accuracy: 0.9922 - val_loss: 1.8545 - val_accuracy: 0.6390
Epoch 10/10
92/92 [==============================] - 1s 11ms/step - loss: 0.0715 - accuracy: 0.9813 - val_loss: 2.0656 - val_accuracy: 0.6049

Визуализируйте результаты тренировок

Создайте графики потерь и точности на обучающих и проверочных наборах:

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()

png

Графики показывают, что точность обучения и точность проверки сильно отличаются друг от друга, и модель достигла только около 60% точности на проверочном наборе.

Давайте проверим, что пошло не так, и попробуем повысить общую производительность модели.

Переоснащение

На приведенных выше графиках точность обучения линейно увеличивается с течением времени, тогда как точность проверки останавливается примерно на 60% в процессе обучения. Кроме того, заметна разница в точности между обучением и точностью проверки — признак переобучения .

Когда имеется небольшое количество обучающих примеров, модель иногда учится на шумах или нежелательных деталях обучающих примеров — до такой степени, что это негативно влияет на производительность модели на новых примерах. Это явление известно как переоснащение. Это означает, что модели будет трудно обобщить новый набор данных.

Есть несколько способов борьбы с переобучением в тренировочном процессе. В этом руководстве вы будете использовать увеличение данных и добавить Dropout в свою модель.

Увеличение данных

Переобучение обычно происходит, когда имеется небольшое количество обучающих примеров. Расширение данных использует подход создания дополнительных обучающих данных из ваших существующих примеров путем их увеличения с использованием случайных преобразований, которые дают правдоподобно выглядящие изображения. Это помогает представить модель большему количеству аспектов данных и лучше обобщить.

Вы будете реализовывать увеличение данных, используя следующие слои предварительной обработки Keras: tf.keras.layers.RandomFlip , tf.keras.layers.RandomRotation и tf.keras.layers.RandomZoom . Они могут быть включены в вашу модель, как и другие слои, и работать на графическом процессоре.

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")

png

Вы будете использовать увеличение данных для обучения модели через мгновение.

Выбывать

Еще один способ уменьшить переобучение — ввести в сеть регуляризацию отсева .

Когда вы применяете выпадение к слою, оно случайным образом выбрасывает (путем установки активации на ноль) количество выходных единиц из слоя в процессе обучения. 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)
])

Скомпилируйте и обучите модель

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 [==============================] - 2s 14ms/step - loss: 1.3840 - accuracy: 0.3999 - val_loss: 1.0967 - val_accuracy: 0.5518
Epoch 2/15
92/92 [==============================] - 1s 12ms/step - loss: 1.1152 - accuracy: 0.5395 - val_loss: 1.1123 - val_accuracy: 0.5545
Epoch 3/15
92/92 [==============================] - 1s 12ms/step - loss: 1.0049 - accuracy: 0.6052 - val_loss: 0.9544 - val_accuracy: 0.6253
Epoch 4/15
92/92 [==============================] - 1s 12ms/step - loss: 0.9452 - accuracy: 0.6257 - val_loss: 0.9681 - val_accuracy: 0.6213
Epoch 5/15
92/92 [==============================] - 1s 12ms/step - loss: 0.8804 - accuracy: 0.6591 - val_loss: 0.8450 - val_accuracy: 0.6798
Epoch 6/15
92/92 [==============================] - 1s 12ms/step - loss: 0.8001 - accuracy: 0.6945 - val_loss: 0.8715 - val_accuracy: 0.6594
Epoch 7/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7736 - accuracy: 0.6965 - val_loss: 0.8059 - val_accuracy: 0.6935
Epoch 8/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7477 - accuracy: 0.7078 - val_loss: 0.8292 - val_accuracy: 0.6812
Epoch 9/15
92/92 [==============================] - 1s 12ms/step - loss: 0.7053 - accuracy: 0.7251 - val_loss: 0.7743 - val_accuracy: 0.6989
Epoch 10/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6884 - accuracy: 0.7340 - val_loss: 0.7867 - val_accuracy: 0.6907
Epoch 11/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6536 - accuracy: 0.7469 - val_loss: 0.7732 - val_accuracy: 0.6785
Epoch 12/15
92/92 [==============================] - 1s 12ms/step - loss: 0.6456 - accuracy: 0.7500 - val_loss: 0.7801 - val_accuracy: 0.6907
Epoch 13/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5941 - accuracy: 0.7735 - val_loss: 0.7185 - val_accuracy: 0.7330
Epoch 14/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5824 - accuracy: 0.7735 - val_loss: 0.7282 - val_accuracy: 0.7357
Epoch 15/15
92/92 [==============================] - 1s 12ms/step - loss: 0.5771 - accuracy: 0.7851 - val_loss: 0.7308 - val_accuracy: 0.7343

Визуализируйте результаты тренировок

После применения увеличения данных и 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()

png

Предсказывать новые данные

Наконец, давайте используем нашу модель для классификации изображения, которое не было включено в наборы для обучения или проверки.

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
122880/117948 [===============================] - 0s 0us/step
131072/117948 [=================================] - 0s 0us/step
This image most likely belongs to sunflowers with a 89.13 percent confidence.