Шаг 213.
Глубокое обучение на Python. ... . Обучение сверточной нейронной сети с нуля на небольшом наборе данных. Предварительная обработка данных

    На этом шаге мы рассмотрим выполнение предварительной обработки данных.

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

  1. Прочитать файлы с изображениями.

  2. Декодировать содержимое из формата PNG в матрицы пикселей RGB.

  3. Преобразовать их в тензоры с вещественными числами.

  4. Привести к единому размеру (в нашем случае 180 × 180).

  5. Организовать в пакеты (мы будем использовать пакеты по 32 изображения в каждом).

    Этот порядок действий может показаться немного сложным, но, к счастью, в Keras имеются утилиты, способные выполнить его автоматически. В частности, вспомогательная функция image_dataset_from_directory(), которая позволит быстро настроить конвейер обработки для автоматического преобразования файлов с изображениями в пакеты готовых тензоров. Именно ее мы и возьмем.

    Вызов image_dataset_from_directory(directory) сначала составит список подкаталогов в каталоге directory и предположит, что каждый содержит изображения, принадлежащие одному из классов. Затем проиндексирует файлы изображений в каждом подкаталоге и, наконец, создаст и вернет объект tf.data.Dataset, подготовленный для чтения файлов, перемешивания, преобразования в тензоры, приведения к общему размеру и упаковки в пакеты.


Пример 8.9. Чтение изображений с помощью функции image_dataset_from_directory()
from tensorflow.keras.utils import image_dataset_from_directory

train_dataset = image_dataset_from_directory(
    new_base_dir / "train",
    image_size=(180, 180),
    batch_size=32)
validation_dataset = image_dataset_from_directory(
    new_base_dir / "validation",
    image_size=(180, 180),
    batch_size=32)
test_dataset = image_dataset_from_directory(
    new_base_dir / "test",
    image_size=(180, 180),
    batch_size=32)
Found 2000 files belonging to 2 classes.
Found 1000 files belonging to 2 classes.
Found 2000 files belonging to 2 classes.


Объект Dataset в TensorFlow

    В библиотеке TensorFlow имеется модуль tf.data, позволяющий создавать эффективные конвейеры ввода для моделей машинного обучения. Его основу составляет класс tf.data.Dataset.

    Объект Dataset - это итератор: его можно использовать в цикле for. Обычно он возвращает пакеты входных данных и меток. Dataset можно также передавать непосредственно в вызов метода fit() модели Keras.

    Класс Dataset предлагает множество важных функций, избавляя вас от сложностей их реализации вручную: в частности, асинхронную предварительную выборку данных (подготовку следующего пакета данных, пока предыдущий обрабатывается моделью, что обеспечивает непрерывность потока выполнения).

    Класс Dataset также поддерживает API в функциональном стиле для изменения наборов данных. Вот короткий пример. Создадим экземпляр Dataset из массива NumPy случайных чисел с набором из 1000 образцов, каждый из которых является вектором, содержащим 16 чисел:

import numpy as np
import tensorflow as tf
random_numbers = np.random.normal(size=(1000, 16))
#  Метод класса from_tensor_slices() можно использовать для создания экземпляра 
#  Dataset из массива NumPy, а также из кортежа
#  или словаря с массивами NumPy
dataset = tf.data.Dataset.from_tensor_slices(random_numbers)

    По умолчанию наш набор данных dataset возвращает образцы поодиночке:

>>> for i, element in enumerate(dataset):
>>>     print(element.shape) 
>>>         if i >= 2:
>>>             break
(16,)
(16,)
(16,)

    Но если добавить вызов метода .batch(), образцы будут возвращаться пакетами:

>>> batched_dataset = dataset.batch(32)
>>> for i, element in enumerate(batched_dataset):
>>>     print(element.shape)
>>>     if i >= 2:
>>>         break
(32, 16)
(32, 16)
(32, 16)

    Более того, в вашем распоряжении есть целый ряд удобных методов для работы с набором данных, таких как:

    Особенно часто вам будет нужен метод .map(). Вот пример его использования для преобразования элементов с формой (16,) в нашем наборе данных в элементы с формой (4, 4):

>>> reshaped_dataset = dataset.map(lambda x: tf.reshape(x, (4, 4)))
>>> for i, element in enumerate(reshaped_dataset):
>>>     print(element.shape)
>>>     if i >= 2:
>>>         break
(4, 4)
(4, 4)
(4, 4)

    Далее вы увидите еще несколько примеров использования метода map().


    Рассмотрим вывод одного из таких объектов Dataset: он возвращает пакеты изображений 180 × 180 в формате RGB (с формой (32, 180, 180, 3)) и целочисленные метки (с формой (32,)) . В каждом пакете содержится 32 образца (в соответствии с параметром batch_size).


Пример 8.10. Вывод форм данных и меток, возвращаемых объектом Dataset
for data_batch, labels_batch in train_dataset:
  print("data batch shape:", data_batch.shape)
  print("labels batch shape:", labels_batch.shape)
  break

data batch shape: (32, 180, 180, 3)
labels batch shape: (32,)

    Давайте обучим модель на нашем наборе данных и возьмем аргумент validation_data метода fit() для наблюдения за изменением метрик на этапе проверки с использованием отдельного объекта Dataset.

    Кроме того, применим обратный вызов ModelCheckpoint, сохраняющий модель после каждой эпохи, и настроим его, указав путь к файлу для сохранения и аргументы save_best_only=True и monitor="val_loss": с ними обратный вызов будет сохранять новый файл с весами модели (перезаписывая любой предыдущий), только если текущее значение метрики val_loss окажется ниже, чем когда-либо раньше во время обучения. Благодаря этому сохраненный файл всегда будет содержать состояние модели, соответствующее наиболее эффективной эпохе обучения с точки зрения величины потерь на этапе проверки, и нам не придется повторно обучать новую модель с меньшим количеством эпох, если будет достигнут эффект переобучения: мы сможем просто загрузить сохраненный файл.


Пример 8.11. Обучение модели с использованием объекта Dataset
callbacks = [
    keras.callbacks.ModelCheckpoint(
        filepath="convnet_from_scratch.keras",
        save_best_only=True,
        monitor="val_loss")
]

history = model.fit(
    train_dataset,
    epochs=30,
    validation_data=validation_dataset,
    callbacks=callbacks)

    Создадим графики изменения точности и потерь модели на обучающих и проверочных данных в процессе обучения (рисунок 1).


Пример 8.12. Формирование графиков изменения потерь и точности в процессе обучения
import matplotlib.pyplot as plt

accuracy = history.history["accuracy"]
val_accuracy = history.history["val_accuracy"]
loss = history.history["loss"]
val_loss = history.history["val_loss"]
epochs = range(1, len(accuracy) + 1)
plt.plot(epochs, accuracy, "bo", label="Точность на этапе обучения")
plt.plot(epochs, val_accuracy, "b", label="Точность на этапе проверки")
plt.title("Точность на этапах обучения и проверки")
plt.legend()
plt.figure()
plt.plot(epochs, loss, "bo", label="Потери на этапе обучения")
plt.plot(epochs, val_loss, "b", label="Потери на этапе проверки")
plt.title("Потери на этапах обучения и проверки")
plt.legend()
plt.show()



Рис.1. Графики изменения метрик на этапе проверки при обучении простой сверточной сети

    На графиках четко наблюдается эффект переобучения. Точность на обучающих данных линейно растет и приближается к 100%, тогда как точность на проверочных данных останавливается на отметке 75%. Потери на этапе проверки достигают минимума всего после пяти эпох и затем замирают, а потери на этапе обучения продолжают линейно уменьшаться.

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


Пример 8.13. Оценка модели на контрольном наборе
test_model = keras.models.load_model("convnet_from_scratch.keras")
test_loss, test_acc = test_model.evaluate(test_dataset)
print(f"Test accuracy: {test_acc:.3f}")

Блокнот с этим примером можно взять здесь.

    Мы получили точность 71,7%. (Первоначальные веса нейронной сети инициализируются случайными числами, поэтому вы можете получить немного другой результат, с разницей в пределах одного процента.)

    Поскольку у нас относительно немного обучающих образцов (2000), переобучение становится проблемой номер один. Вы уже знаете несколько методов, помогающих смягчить ее, таких как прореживание и сокращение весов L2-регуляризация). Теперь вы познакомиесь еще с одним способом, характерным для распознавания образов и используемым почти повсеместно при обработке изображений с применением моделей глубокого обучения: способом обогащения данных (data augmentation).

    На следующем шаге мы рассмотрим этот способ.




Предыдущий шаг Содержание Следующий шаг