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
- класса для эффективной работы с данными на диске для использования с моделью.В этом обучающем примере соблюдается базовая последовательность машинного обучения:
Начнем с импортирования необходимых пакетов. Пакет 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
Преобразуйте изображения в соответствующим образом предобработанные тензоры с плавающей запятой перед подачей в сеть:
К счастью, все эти задачи могут быть выполнены с помощью класса 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')
])
In [ ]:
model.compile(optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy'])
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 в нашу модель.
Обычно переобучение случается, когда число тренировочных данных мало. Один из способов решить эту проблему это увеличить датасет так, чтобы число тренировочных данных стало достаточным. В процессе дополнения данных используется метод получения большего количества обучающих данных из существующих обучающих выборок путем увеличения выборок с использованием случайных преобразований, которые дают правдоподобные изображения. Цель состоит в том, чтобы модель никогда не увидела одну и ту же картинку дважды во время тренировки. Это помогает раскрывать модель для большего количества аспектов данных и лучше обобщать.
В 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 принимает дробное число в качестве входного значения в форме, например, 0,1, 0,2, 0,4 и т.д. Это означает случайное выбрасывание 10%, 20% или 40% выходных единиц случайным образом из примененного слоя.
Применение 0,1 dropout к определенному слою, случайным образом убивает 10% выходных единиц во время каждой эпохи тренировки.
Создайте сетевую архитектуру с помощью этой новой функции dropout и примените ее к различным конволюциям и полносвязным слоям.
Здесь вы применяете 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()