In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует официальной документации на английском языке. Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в tensorflow/docs-l10n репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на docs-ru@tensorflow.org list.

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

Этот обучающий пример показывает как распознать кошку или собаку по картинке. Для этого нужно построить классификатор изображений используя модель tf.keras.Sequential и загрузить данные, используя tf.keras.preprocessing.image.ImageDataGenerator. Ты получишь практический опыт и разовьешь интуицию для следующего:

  • Построение входного конвейера данных, с помощью tf.keras.preprocessing.image.ImageDataGenerator - класса для эффективной работы с данными на диске для использования с моделью.
  • Переобучение (Overfitting) - Как распознать и предотвратить.
  • Data augmentation and dropout — Ключевые техники борьбы с переобучением в задачах компьютерного зрения для включения в конвейер данных и модель классификатора изображений.

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

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

Импортирование пакетов

Начнем с импортирования необходимых пакетов. Пакет os используется для чтения файлов и структуры директорий, NumPy используется для преобразования питоновского списка в массив numpy и для выполнения необходимых операций с матрицами, matplotlib.pyplot используется для построения графиков для тренировочных и проверочных данных.


In [ ]:
from __future__ import absolute_import, division, print_function, unicode_literals

Для построения нашей модели необходимо импортировать классы Tensorflow и Keras.


In [ ]:
try:
  # %tensorflow_version only exists in Colab.
  %tensorflow_version 2.x
except Exception:
  pass
import tensorflow as tf

In [ ]:
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D
from tensorflow.keras.preprocessing.image import ImageDataGenerator

import os
import numpy as np
import matplotlib.pyplot as plt

Загрузка данных

Начнем со скачивания датасета. В этом примере используется отфильтрованная версия Dogs vs Cats датасета из Kaggle. Скачайте архивную версию датасета и сохраните в директории "/tmp/".


In [ ]:
_URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'

path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=_URL, extract=True)

PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')

Структура дирректории у датасета следующая:

cats_and_dogs_filtered
|__ train
    |______ cats: [cat.0.jpg, cat.1.jpg, cat.2.jpg ....]
    |______ dogs: [dog.0.jpg, dog.1.jpg, dog.2.jpg ...]
|__ validation
    |______ cats: [cat.2000.jpg, cat.2001.jpg, cat.2002.jpg ....]
    |______ dogs: [dog.2000.jpg, dog.2001.jpg, dog.2002.jpg ...]

После извлечения содержимого, укажите нужный путь к файлам для тренировочного и проверочного сетов.


In [ ]:
train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')

In [ ]:
train_cats_dir = os.path.join(train_dir, 'cats')  # директория с картинками котов для обучения
train_dogs_dir = os.path.join(train_dir, 'dogs')  # директория с картинками собак для обучения
validation_cats_dir = os.path.join(validation_dir, 'cats')  # директория с картинками котов для проверки
validation_dogs_dir = os.path.join(validation_dir, 'dogs')  # директория с картинками собак для проверки

Понимание данных

Давайте посмотрим сколько изображений котов и собак в тренировочной и валидационной директориях:


In [ ]:
num_cats_tr = len(os.listdir(train_cats_dir))
num_dogs_tr = len(os.listdir(train_dogs_dir))

num_cats_val = len(os.listdir(validation_cats_dir))
num_dogs_val = len(os.listdir(validation_dogs_dir))

total_train = num_cats_tr + num_dogs_tr
total_val = num_cats_val + num_dogs_val

In [ ]:
print('total training cat images:', num_cats_tr)
print('total training dog images:', num_dogs_tr)

print('total validation cat images:', num_cats_val)
print('total validation dog images:', num_dogs_val)
print("--")
print("Total training images:", total_train)
print("Total validation images:", total_val)

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


In [ ]:
batch_size = 128
epochs = 15
IMG_HEIGHT = 150
IMG_WIDTH = 150

Подготовка данных

Преобразуйте изображения в соответствующим образом предобработанные тензоры с плавающей запятой перед подачей в сеть:

  1. Прочитать изображения с диска.
  2. Раскодировать содержимое изображений и сконвертировать в нужный формат сетки в соответствии с их содержимым RGB.
  3. Сконвертировать их в иррационально-численные тензоры (с плавающий запятой).
  4. Перешкалировать тензоры из значений от 0 до 255 до значений от 0 до 1, так как нейронные сети предпочитают иметь дело с маленькими входными данными.

К счастью, все эти задачи могут быть выполнены с помощью класса ImageDataGenerator предоставленного tf.keras. Он может считывать изображения с диска и препроцессировать их в нужные тензоры. Он также настроит генераторы которые превратят эти изображения в batches тензоров (пакеты тензоров), что полезно при обучении сети.


In [ ]:
train_image_generator = ImageDataGenerator(rescale=1./255) # Генератор для тренировочных данных
validation_image_generator = ImageDataGenerator(rescale=1./255) # Генератор для проверочных данных

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


In [ ]:
train_data_gen = train_image_generator.flow_from_directory(batch_size=batch_size,
                                                           directory=train_dir,
                                                           shuffle=True,
                                                           target_size=(IMG_HEIGHT, IMG_WIDTH),
                                                           class_mode='binary')

In [ ]:
val_data_gen = validation_image_generator.flow_from_directory(batch_size=batch_size,
                                                              directory=validation_dir,
                                                              target_size=(IMG_HEIGHT, IMG_WIDTH),
                                                              class_mode='binary')

Визуализация тренировочных изображений

Визуализируйте тренировочные изображения, извлекая batch изображений из тренировочного генератора (в данном примере это 32 изображения), а затем постройте графики для пяти из них с помощью matplotlib.


In [ ]:
sample_training_images, _ = next(train_data_gen)

Функция next возвращает batch из датасета. Полученное значение функции next в формате (x_train, y_train) где x_train - тренировочные признаки, а y_train - их метки. Уберите метки, чтобы визуализировать только тренировочные изображения.


In [ ]:
# Эта функция построит график изображения в табличном виде с одной строкой и 5 колонками, где изображения разместятся в каждой колонке.

def plotImages(images_arr):
    fig, axes = plt.subplots(1, 5, figsize=(20,20))
    axes = axes.flatten()
    for img, ax in zip( images_arr, axes):
        ax.imshow(img)
        ax.axis('off')
    plt.tight_layout()
    plt.show()

In [ ]:
plotImages(sample_training_images[:5])

Создание модели

Модель состоит из трех сверточных блоков со слоем макс-пулинга в каждом из них. Поверх них полносвязный слой с 512 элементами и функцией активации relu.


In [ ]:
model = Sequential([
    Conv2D(16, 3, padding='same', activation='relu', input_shape=(IMG_HEIGHT, IMG_WIDTH ,3)),
    MaxPooling2D(),
    Conv2D(32, 3, padding='same', activation='relu'),
    MaxPooling2D(),
    Conv2D(64, 3, padding='same', activation='relu'),
    MaxPooling2D(),
    Flatten(),
    Dense(512, activation='relu'),
    Dense(1, activation='sigmoid')
])

Компиляция модели

Для этого обучающиго материала были выбраны оптимизатор ADAM и функция потерь двоичная кросс-энтропия. Чтобы оценить тренировочную и проверочные точности для каждой тренировочной эпохи нужно передать аргумент metrics.


In [ ]:
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

Краткое изложение модели

Чтобы посмотреть все слои сети нужно использовать метод summary:


In [ ]:
model.summary()

Обучение модели

Используйте метод fit_generator класса ImageDataGenerator для обучения сети.


In [ ]:
history = model.fit_generator(
    train_data_gen,
    steps_per_epoch=total_train // batch_size,
    epochs=epochs,
    validation_data=val_data_gen,
    validation_steps=total_val // batch_size
)

Визуализация результатов обучения

Теперь визуализируйте результаты после обучения сети.


In [ ]:
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()

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

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

Переобучение

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

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

Есть несколько способов борьбы с переобучением в тренировочном процессе. В этом обучающем примере вы будете использовать data augmentation (увеличение данных) и добавление dropout в нашу модель.

Увеличение данных (Data augmentation)

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

В tf.keras это можно реализовать, используя класс ImageDataGenerator. Передайте различные преобразования в датасет, и он позаботится о его применении в процессе обучения.

Дополнение и визуализация данных

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

Применение горизонтального отображения

Передайте horizontal_flip в качестве аргумента классу ImageDataGenerator и установите для него значение True, чтобы применить это увеличение.


In [ ]:
image_gen = ImageDataGenerator(rescale=1./255, horizontal_flip=True)

In [ ]:
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
                                               directory=train_dir,
                                               shuffle=True,
                                               target_size=(IMG_HEIGHT, IMG_WIDTH))

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


In [ ]:
augmented_images = [train_data_gen[0][0][0] for i in range(5)]

In [ ]:
# Используйте повторно ту же пользовательскую функцию построения, определенную и 
# использованную выше, чтобы визуализировать тренировочные изображения
plotImages(augmented_images)

Случайно повернуть изображение

Давайте посмотрим на другое дополнение, называемое вращение (поворот) и применим рандомный поворот на 45 градусов к тренировочным изображениям.


In [ ]:
image_gen = ImageDataGenerator(rescale=1./255, rotation_range=45)

In [ ]:
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
                                               directory=train_dir,
                                               shuffle=True,
                                               target_size=(IMG_HEIGHT, IMG_WIDTH))

augmented_images = [train_data_gen[0][0][0] for i in range(5)]

In [ ]:
plotImages(augmented_images)

Применение увеличение масштаба

Применим увеличение масштаба к датасету, чтобы случайно увеличить изображения до 50%.


In [ ]:
# zoom_range - диапазон увеличения - от 0 до 1 где 1 = 100%.
image_gen = ImageDataGenerator(rescale=1./255, zoom_range=0.5) #

In [ ]:
train_data_gen = image_gen.flow_from_directory(batch_size=batch_size,
                                               directory=train_dir,
                                               shuffle=True,
                                               target_size=(IMG_HEIGHT, IMG_WIDTH))

augmented_images = [train_data_gen[0][0][0] for i in range(5)]

In [ ]:
plotImages(augmented_images)

Совместим все это вместе

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


In [ ]:
image_gen_train = ImageDataGenerator(
                    rescale=1./255,
                    rotation_range=45,
                    width_shift_range=.15,
                    height_shift_range=.15,
                    horizontal_flip=True,
                    zoom_range=0.5
                    )

In [ ]:
train_data_gen = image_gen_train.flow_from_directory(batch_size=batch_size,
                                                     directory=train_dir,
                                                     shuffle=True,
                                                     target_size=(IMG_HEIGHT, IMG_WIDTH),
                                                     class_mode='binary')

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


In [ ]:
augmented_images = [train_data_gen[0][0][0] for i in range(5)]
plotImages(augmented_images)

Создание генератора для проверочных данных

Как правило, дополнение данных применяется только к тренировочным данным. В данном случае измените только масштаб проверочных изображений и преобразуйте их в batches, используя ImageDataGenerator.


In [ ]:
image_gen_val = ImageDataGenerator(rescale=1./255)

In [ ]:
val_data_gen = image_gen_val.flow_from_directory(batch_size=batch_size,
                                                 directory=validation_dir,
                                                 target_size=(IMG_HEIGHT, IMG_WIDTH),
                                                 class_mode='binary')

Dropout

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

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

Применение 0,1 dropout к определенному слою, случайным образом убивает 10% выходных единиц во время каждой эпохи тренировки.

Создайте сетевую архитектуру с помощью этой новой функции dropout и примените ее к различным конволюциям и полносвязным слоям.

Создание новой сети с Dropouts

Здесь вы применяете dropout к первому и последнему слоям макс-пулинга. Применение dropout случайным образом обнулит 20% нейронов в течение каждой тренировочной эпохи. Это помогает избежать переобучние на тренировочном датасете.


In [ ]:
model_new = Sequential([
    Conv2D(16, 3, padding='same', activation='relu', 
           input_shape=(IMG_HEIGHT, IMG_WIDTH ,3)),
    MaxPooling2D(),
    Dropout(0.2),
    Conv2D(32, 3, padding='same', activation='relu'),
    MaxPooling2D(),
    Conv2D(64, 3, padding='same', activation='relu'),
    MaxPooling2D(),
    Dropout(0.2),
    Flatten(),
    Dense(512, activation='relu'),
    Dense(1, activation='sigmoid')
])

Компиляция модели

После внесения dropouts в сеть, скомпилируйте модель и просмотрите сводку слоев.


In [ ]:
model_new.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

model_new.summary()

Тренировка модели

После успешного добавления дополнений данных в обучающие примеры и добавления dropouts в сеть, натренируем эту новую сеть:


In [ ]:
history = model_new.fit_generator(
    train_data_gen,
    steps_per_epoch=total_train // batch_size,
    epochs=epochs,
    validation_data=val_data_gen,
    validation_steps=total_val // batch_size
)

Визуализация модели

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


In [ ]:
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()