На этом шаге мы рассмотрим выполнение предварительной обработки данных.
Как вы уже знаете, перед передачей в модель данные должны быть преобразованы в тензоры с вещественными числами. В настоящее время данные хранятся в виде файлов PNG, поэтому их нужно подготовить, выполнив следующие шаги.
Этот порядок действий может показаться немного сложным, но, к счастью, в Keras имеются утилиты, способные выполнить его автоматически. В частности, вспомогательная функция image_dataset_from_directory(), которая позволит быстро настроить конвейер обработки для автоматического преобразования файлов с изображениями в пакеты готовых тензоров. Именно ее мы и возьмем.
Вызов image_dataset_from_directory(directory) сначала составит список подкаталогов в каталоге directory и предположит, что каждый содержит изображения, принадлежащие одному из классов. Затем проиндексирует файлы изображений в каждом подкаталоге и, наконец, создаст и вернет объект tf.data.Dataset, подготовленный для чтения файлов, перемешивания, преобразования в тензоры, приведения к общему размеру и упаковки в пакеты.
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.
В библиотеке 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).
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 окажется ниже, чем когда-либо раньше во время обучения. Благодаря этому сохраненный файл всегда будет содержать состояние модели, соответствующее наиболее эффективной эпохе обучения с точки зрения величины потерь на этапе проверки, и нам не придется повторно обучать новую модель с меньшим количеством эпох, если будет достигнут эффект переобучения: мы сможем просто загрузить сохраненный файл.
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).
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%. Потери на этапе проверки достигают минимума всего после пяти эпох и затем замирают, а потери на этапе обучения продолжают линейно уменьшаться.
Проверим точность модели на контрольных данных. Для этого загрузим модель, сохраненную в файл до того, как начал проявляться эффект переобучения.
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).
На следующем шаге мы рассмотрим этот способ.