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

TensorFlow

TensorFlow - это пакет машинного обучения (в первую очередь глубокого обучения), разработанный Google с открытым исходным кодом; когда он был первоначально выпущен, TensorFlow был относительно низкоуровневым пакетом для опытных пользователей, однако в последние несколько лет, особенно после выпуска TensorFlow 2.0, теперь он нацелен на более широкий круг пользователей.

Несколько лет назад я провел PoC с одним из наших разработчиков, который рассмотрел возможность автономного запуска моделей TensorFlow в одном из наших мобильных приложений. Хотя мы обнаружили, что это возможно, мы также столкнулись с несколькими проблемами, которые сделали решение довольно неудобным. Вернемся к 2020 году, и TensorFlow значительно улучшился; последняя версия имеет большую интеграцию с API-интерфейсами Keras, она расширяется, чтобы покрыть большую часть конвейера обработки данных, а также разветвлена ​​для поддержки новых языков, причем пакет TensorFlow.js и варианты использования, которые он поддерживает, особенно интересны .

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

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

Пример записной книжки

Соответствующий блокнот Jupyter можно найти здесь.

Предварительное чтение

Прежде всего, если у вас не установлен TensorFlow и вы хотите следовать этому примеру, ознакомьтесь с руководством по установке. Показанный здесь код в основном был взят и адаптирован из двух примеров классификации изображений на веб-сайте TensorFlow. Во-первых, это базовый пример, а во-вторых, этот, который вводит еще несколько концепций для повышения производительности модели.

Данные

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

В этом случае я использовал открытый набор данных, взятый из Kaggle, содержащий 7348 полутоновых изображений 300x300 пикселей рабочих колес погружных насосов. Некоторые из этих рабочих колес были классифицированы как «ОК», в то время как другие были классифицированы как «Неисправные». Задача состоит в том, чтобы обучить модель на этих данных, которые можно использовать для точной классификации изображений как одного или другого.



Настраивать

Сначала импортируйте TensorFlow и подтвердите версию; этот пример был создан с использованием версии 2.3.0.

import tensorflow as tf
print(tf.__version__)

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

color_mode = "grayscale"
number_colour_layers = 1
image_size = (300, 300)
image_shape = image_size + (number_colour_layers,)

Импорт и предварительная обработка данных

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

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

training_data_path = "./casting_data/casting_data/train"
test_data_path = "./casting_data/casting_data/test"
SEED = 42

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

def get_image_data(data_path, color_mode, image_size, seed = None, subset = None, validation_split = None):
    if subset:
        validation_split = 0.2
    return tf.keras.preprocessing.image_dataset_from_directory(
        data_path,
        color_mode=color_mode,
        image_size=image_size,
        seed=seed,
        validation_split=validation_split, 
        subset=subset
    )

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

def get_image_data(data_path, color_mode, image_size, seed = None, subset = None, validation_split = None):
    if subset:
        validation_split = 0.2
    raw_data_set = \
    tf.keras.preprocessing.image_dataset_from_directory(
        data_path,
        color_mode=color_mode,
        image_size=image_size,
        seed=seed,
        validation_split=validation_split, 
        subset=subset
    )
    
    return raw_data_set.cache().prefetch(
        buffer_size = tf.data.experimental.AUTOTUNE
    )

Во-вторых, я хочу вывести количество и имена классов классификации, поэтому я изменю возвращаемый тип с FetchedDataset на dict, содержащий FetchedDataset и список имен классов.

def get_image_data(data_path, color_mode, image_size, seed = None, subset = None, validation_split = None):
    if subset:
        validation_split = 0.2
    raw_data_set = \
    tf.keras.preprocessing.image_dataset_from_directory(
        data_path,
        color_mode=color_mode,
        image_size=image_size,
        seed=seed,
        validation_split=validation_split, 
        subset=subset
    )
    
    raw_data_set.class_names.sort()
    
    return {
        "data": raw_data_set.cache().prefetch(
        buffer_size = tf.data.experimental.AUTOTUNE
        ),
        "classNames": raw_data_set.class_names
    }

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

training_ds = get_image_data(
    training_data_path,
    color_mode,
    image_size,
    SEED,
    subset = "training"
)
validation_ds = get_image_data(
    training_data_path,
    color_mode,
    image_size,
    SEED,
    subset = "validation"
)
test_ds = get_image_data(
    test_data_path,
    color_mode,
    image_size
)

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

equivalence_check = training_ds["classNames"] == validation_ds["classNames"]
assert_fail_message = "Training and Validation classes should match"
assert(equivalence_check), assert_fail_message
class_names = training_ds["classNames"]
number_classes = len(class_names)

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

from os import listdir
from os.path import join
import matplotlib.image as mpimg
import matplotlib.pyplot as plt
%matplotlib inline
image_indexes = [286, 723, 1103]
selected_image_file_paths = dict()
for classification in class_names:
    image_directory = join(training_data_path, classification)
    image_file_names = listdir(image_directory)
    selected_image_file_paths[classification] = [join(image_directory, image_file_names[i]) for i in image_indexes]
plt.figure(figsize=(12, 8))
    
for i,classification in enumerate(class_names):
    for j,image in enumerate(selected_image_file_paths[classification]):
        image_number = (i * len(image_indexes)) + j + 1
        ax = plt.subplot(number_classes,3,image_number)
        plt.title(classification)
        plt.axis("off")
        plt.imshow(mpimg.imread(image))

Наконец, прежде чем приступить к построению модели, я должен учесть еще одну вещь. Значения полутонового канала находятся в диапазоне [0, 255], однако для обучения нейронной сети значения должны быть небольшими, поэтому я хочу изменить масштаб значений, чтобы они находились в диапазоне [0, 1]. При желании я мог бы добавить это в качестве шага в моем get_image_data методе, что-то вроде raw_data_set = raw_data_set / 255.0, однако, если я сделаю этот шаг частью моей подготовки обучающих данных, тогда любой потребитель этой модели (в моем случае некоторый код внешнего интерфейса) также должен будет измените масштаб изображения таким же образом…

Определение модели

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

preprocessing_layers = [
    tf.keras.layers.experimental.preprocessing.Rescaling(1./255, input_shape=image_shape)
]

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

def conv_2d_pooling_layers(filters, number_colour_layers):
    return [
        tf.keras.layers.Conv2D(
            filters,
            number_colour_layers,
            padding='same',
            activation='relu'
        ),
        tf.keras.layers.MaxPooling2D()
    ]
core_layers = \
    conv_2d_pooling_layers(16, number_colour_layers) + \
    conv_2d_pooling_layers(32, number_colour_layers) + \
    conv_2d_pooling_layers(64, number_colour_layers)

Для завершения модели данные сначала необходимо развернуть в 1D, а затем развернуть плотные слои, чтобы на выходе было количество узлов, равное количеству возможных классификаций.

dense_layers = [
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(number_classes)
]

Затем я могу объединить все эти слои в последовательную модель.

model = tf.keras.Sequential(
    preprocessing_layers +
    core_layers +
    dense_layers
)

А затем просто определите параметры компиляции для модели.

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

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

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

callback = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss', min_delta=0, patience=3, verbose=0, mode='auto',
    baseline=None, restore_best_weights=True
)
history = model.fit(
    training_ds["data"],
    validation_data = validation_ds["data"],
    epochs = 20,
    callbacks = [callback]
)

По прошествии 20 периодов обучение модели завершено, и точность очень высока (от 98% до 99%), это более вероятно из-за тщательного контроля условий, в которых были захвачены данные, или некоторого увеличения изображений, а точнее. чем особенно хороша сама модель, так как не была оптимизирована. Часто в реальных сценариях изменчивость данных затрудняет обучение точных моделей без тщательной предварительной обработки самих изображений (нормализация для освещения, фона, качества устройства, угла съемки, расстояния между кадрами и т. Д.).

Оцените модель и обучение

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

model.evaluate(test_ds["data"])

В этом случае модель очень хорошо работает при тестировании (точность 99%), а также при обучении, хотя в некоторых случаях вы можете обнаружить, что модель достигает высокой точности при обучении, но затем плохо работает при тестировании; это могло произойти из-за переобучения, но в нашем случае мы предприняли шаги для защиты от переобучения, или могло случиться так, что набор данных для тестирования и обучения взят из разных распределений, один из подходов к исследованию этого мог бы заключаться в объединении, перемешивании и повторном разделении наборы данных.

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

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs_range = range(len(acc))
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 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(2, 1, 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()

Сохранение модели

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

import tensorflowjs as tfjs
tfjs.converters.save_keras_model(
    model,
    "./tmp/model_js_1"
)

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

Вывод

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

Спасибо

Спасибо Крису Манну, Равираджсинху Дабхи, Шону Хеннелли и Шри Нишанту Раджендрану за их заметки и советы по составлению этого материала.