Сохраните дату! Google I / O возвращается 18-20 мая Зарегистрируйтесь сейчас
Эта страница переведена с помощью Cloud Translation API.
Switch to English

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

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

В этом уроке показано, как классифицировать изображения цветов. Он создает классификатор изображений с использованием модели keras.Sequential и загружает данные с помощью preprocessing.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 фотографий цветов. Набор данных содержит 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
228818944/228813984 [==============================] - 2s 0us/step

После загрузки у вас должна быть доступна копия набора данных. Всего 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.preprocessing

Давайте загрузим эти изображения с диска с помощью полезной утилиты image_dataset_from_directory . Это перенесет вас из каталога изображений на диске вtf.data.Dataset всего за пару строк кода. Если хотите, вы также можете написать свой собственный код загрузки данных с нуля, посетив руководство по загрузке изображений .

Создать набор данных

Определите некоторые параметры для загрузчика:

batch_size = 32
img_height = 180
img_width = 180

При разработке модели рекомендуется использовать разделение валидации. Давайте использовать 80% изображений для обучения и 20% для проверки.

train_ds = tf.keras.preprocessing.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.preprocessing.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")

PNG

Вы model.fit модель, используя эти наборы данных, model.fit передав их в 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() labels_batch тензоров image_batch и labels_batch чтобы преобразовать их в numpy.ndarray .

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

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

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

Dataset.prefetch() перекрывает предварительную обработку данных и выполнение модели во время обучения.

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

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] , используя слой Rescaling.

normalization_layer = layers.experimental.preprocessing.Rescaling(1./255)

Есть два способа использовать этот слой. Вы можете применить его к набору данных, вызвав 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 pixels values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image))
0.0 0.9407408

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

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

Модель состоит из трех блоков свертки с максимальным уровнем пула в каждом из них. Есть полностью связанный слой со 128 модулями поверх него, который активируется relu активации relu . Эта модель не была настроена для обеспечения высокой точности, цель этого руководства - показать стандартный подход.

num_classes = 5

model = Sequential([
  layers.experimental.preprocessing.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)
])

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

Для этого руководства выберите optimizers.Adam optimizer и losses.SparseCategoricalCrossentropy loss function. Чтобы просмотреть точность обучения и проверки для каждой эпохи обучения, передайте аргумент metrics .

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

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

Просмотрите все слои сети, используя метод 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 (MaxPooling2 (None, 45, 45, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 45, 45, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 22, 22, 64)        0         
_________________________________________________________________
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 [==============================] - 5s 27ms/step - loss: 1.7103 - accuracy: 0.2977 - val_loss: 1.2054 - val_accuracy: 0.4986
Epoch 2/10
92/92 [==============================] - 1s 11ms/step - loss: 1.0959 - accuracy: 0.5544 - val_loss: 1.0688 - val_accuracy: 0.5763
Epoch 3/10
92/92 [==============================] - 1s 11ms/step - loss: 0.9331 - accuracy: 0.6329 - val_loss: 0.9369 - val_accuracy: 0.6267
Epoch 4/10
92/92 [==============================] - 1s 11ms/step - loss: 0.8064 - accuracy: 0.6951 - val_loss: 0.9546 - val_accuracy: 0.6335
Epoch 5/10
92/92 [==============================] - 1s 11ms/step - loss: 0.5643 - accuracy: 0.7933 - val_loss: 0.9191 - val_accuracy: 0.6444
Epoch 6/10
92/92 [==============================] - 1s 11ms/step - loss: 0.3910 - accuracy: 0.8717 - val_loss: 1.2451 - val_accuracy: 0.5954
Epoch 7/10
92/92 [==============================] - 1s 11ms/step - loss: 0.2849 - accuracy: 0.9094 - val_loss: 1.2454 - val_accuracy: 0.6335
Epoch 8/10
92/92 [==============================] - 1s 11ms/step - loss: 0.1562 - accuracy: 0.9567 - val_loss: 1.2909 - val_accuracy: 0.6322
Epoch 9/10
92/92 [==============================] - 1s 11ms/step - loss: 0.1154 - accuracy: 0.9622 - val_loss: 1.4424 - val_accuracy: 0.6322
Epoch 10/10
92/92 [==============================] - 1s 11ms/step - loss: 0.0649 - accuracy: 0.9866 - val_loss: 1.6675 - val_accuracy: 0.6308

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

Создайте графики потерь и точности на наборах для обучения и проверки.

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 в свою модель.

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

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

Вы будете реализовывать увеличение данных, используя слои из tf.keras.layers.experimental.preprocessing . Они могут быть включены в вашу модель, как и другие слои, и запускаться на графическом процессоре.

data_augmentation = keras.Sequential(
  [
    layers.experimental.preprocessing.RandomFlip("horizontal", 
                                                 input_shape=(img_height, 
                                                              img_width,
                                                              3)),
    layers.experimental.preprocessing.RandomRotation(0.1),
    layers.experimental.preprocessing.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 , форму регуляризации .

Когда вы применяете Dropout к слою, он случайным образом отбрасывает (путем установки активации на ноль) некоторое количество выходных единиц из слоя во время процесса обучения. Dropout принимает дробное число в качестве входного значения в такой форме, как 0,1, 0,2, 0,4 и т. Д. Это означает случайное исключение 10%, 20% или 40% выходных единиц из применяемого слоя.

Давайте создадим новую нейронную сеть с помощью layers.Dropout , а затем layers.Dropout ее с помощью дополненных изображений.

model = Sequential([
  data_augmentation,
  layers.experimental.preprocessing.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 (MaxPooling2 (None, 90, 90, 16)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 90, 90, 32)        4640      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 45, 45, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 45, 45, 64)        18496     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 22, 22, 64)        0         
_________________________________________________________________
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 13ms/step - loss: 1.5399 - accuracy: 0.3400 - val_loss: 1.2366 - val_accuracy: 0.4986
Epoch 2/15
92/92 [==============================] - 1s 11ms/step - loss: 1.0382 - accuracy: 0.5900 - val_loss: 0.9774 - val_accuracy: 0.6090
Epoch 3/15
92/92 [==============================] - 1s 11ms/step - loss: 0.9348 - accuracy: 0.6472 - val_loss: 0.9294 - val_accuracy: 0.6362
Epoch 4/15
92/92 [==============================] - 1s 11ms/step - loss: 0.8891 - accuracy: 0.6486 - val_loss: 0.9018 - val_accuracy: 0.6621
Epoch 5/15
92/92 [==============================] - 1s 11ms/step - loss: 0.8445 - accuracy: 0.6727 - val_loss: 0.9248 - val_accuracy: 0.6485
Epoch 6/15
92/92 [==============================] - 1s 11ms/step - loss: 0.8139 - accuracy: 0.6912 - val_loss: 0.8845 - val_accuracy: 0.6485
Epoch 7/15
92/92 [==============================] - 1s 11ms/step - loss: 0.7541 - accuracy: 0.7093 - val_loss: 0.7796 - val_accuracy: 0.6921
Epoch 8/15
92/92 [==============================] - 1s 11ms/step - loss: 0.7094 - accuracy: 0.7369 - val_loss: 0.7921 - val_accuracy: 0.6757
Epoch 9/15
92/92 [==============================] - 1s 11ms/step - loss: 0.6755 - accuracy: 0.7444 - val_loss: 0.8087 - val_accuracy: 0.6880
Epoch 10/15
92/92 [==============================] - 1s 11ms/step - loss: 0.6329 - accuracy: 0.7627 - val_loss: 0.7770 - val_accuracy: 0.7016
Epoch 11/15
92/92 [==============================] - 1s 11ms/step - loss: 0.6456 - accuracy: 0.7634 - val_loss: 0.7409 - val_accuracy: 0.7180
Epoch 12/15
92/92 [==============================] - 1s 11ms/step - loss: 0.5807 - accuracy: 0.7889 - val_loss: 0.7696 - val_accuracy: 0.7098
Epoch 13/15
92/92 [==============================] - 1s 11ms/step - loss: 0.5716 - accuracy: 0.7772 - val_loss: 0.7634 - val_accuracy: 0.6989
Epoch 14/15
92/92 [==============================] - 1s 11ms/step - loss: 0.5665 - accuracy: 0.7859 - val_loss: 0.7610 - val_accuracy: 0.7112
Epoch 15/15
92/92 [==============================] - 1s 11ms/step - loss: 0.5303 - accuracy: 0.7959 - val_loss: 0.7306 - val_accuracy: 0.7371

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

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

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 = keras.preprocessing.image.load_img(
    sunflower_path, target_size=(img_height, img_width)
)
img_array = keras.preprocessing.image.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
This image most likely belongs to sunflowers with a 97.38 percent confidence.