Обратные вызовы Keras: сохранение и визуализация прогнозов на каждую эпоху обучения

Вступление

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

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

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

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

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

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

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

Если вам интересно узнать больше о том, как создавать эти модели и как сделать их очень точными, а не просто точными – прочтите наш обширный и подробный практический прогноз цен на недвижимость – глубокое обучение на Python с помощью Keras !

Построение и оценка модели глубокого обучения с помощью Keras

Для наглядности давайте построим простую модель Кераса. Мы быстро пройдемся по этому разделу с минимальным вниманием и вниманием – это не руководство по построению регрессионных моделей. Мы будем работать с набором данных California Housing , полученным с помощью модуля Scikit-Learn datasets, который представляет собой набор данных, предназначенный для регрессии .

Давайте продолжим и импортируем библиотеки и статические методы, которые мы будем использовать:

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import tensorflow as tf
from tensorflow import keras
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split

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

X, y = fetch_california_housing(as_frame=True, return_X_y=True)
x_train, x_test, y_train, y_test = train_test_split(x, y)

plt.figure(figsize=(12, 8))
sns.scatterplot(data=x, x='Longitude', y='Latitude', size=y, alpha=0.5, hue=y, palette='magma')
plt.show()
визуализация набора данных калифорния

Похоже на Калифорнию! Поскольку данные загружены правильно, мы можем определить простую последовательную модель Keras:

checkpoint = keras.callbacks.ModelCheckpoint("california.h5", save_best_only=True)

model = keras.Sequential([
    keras.layers.Dense(64, activation='relu', kernel_initializer='normal', kernel_regularizer="l2", input_shape=[x_train.shape[1]]),
    keras.layers.Dropout(0.2),
    keras.layers.BatchNormalization(),
    
    keras.layers.Dense(64, activation='relu', kernel_initializer='normal', kernel_regularizer="l2"),
    keras.layers.Dropout(0.2),
    keras.layers.BatchNormalization(),
  
    keras.layers.Dense(1)
])

model.compile(loss='mae',
              optimizer=keras.optimizers.RMSprop(learning_rate=1e-2, decay=0.1),
              metrics=['mae'])
              
history = model.fit(
    x_train, y_train,
    epochs=150,
    batch_size=64,
    validation_split=0.2,
    callbacks=[checkpoint]
)

Здесь у нас есть простой MLP с небольшим количеством выпадения и пакетной нормализации для борьбы с переобучением, оптимизированный с помощью оптимизатора RMSprop и потери средней абсолютной ошибки . Мы приспособили модель для 150 эпох с разделением валидации 0.2и ModelCheckpoint обратным вызовом для сохранения весов в файле. Выполнение этого приводит к:

...
Epoch 150/150
387/387 [==============================] - 3s 7ms/step - loss: 0.6279 - mae: 0.5976 - val_loss: 0.6346 - val_mae: 0.6042

Мы могли бы визуализировать кривые обучения, чтобы получить общее представление о том, как проходило обучение, но это не говорит нам всей истории – это просто совокупные средства для наборов обучения и проверки во время обучения:

model_history = pd.DataFrame(history.history)
model_history['epoch'] = history.epoch

fig, ax = plt.subplots(1, figsize=(8,6))
num_epochs = model_history.shape[0]

ax.plot(np.arange(0, num_epochs), model_history["mae"], 
        label="Training MAE")
ax.plot(np.arange(0, num_epochs), model_history["val_mae"], 
        label="Validation MAE")
ax.legend()

plt.tight_layout()
plt.show()

Это приводит к:

кривые обучения модели глубокого обучения

И мы можем оценить нашу модель с помощью:

model.evaluate(x_test, y_test)
162/162 [==============================] - 0s 2ms/step - loss: 0.5695 - mae: 0.5451 - mape: 32.2959

Поскольку целевая переменная измеряется кратными 100 000 долларов США , это означает, что наша сеть упускает цену примерно на 54 000 долларов США , что представляет собой среднюю абсолютную процентную ошибку ~ 32%. Большинство традиционных методов машинного обучения, таких как случайная регрессия леса, даже после более обширной предварительной обработки данных для этого набора данных достигают около 52000 долларов США с настроенными гиперпараметрами – так что на самом деле это довольно приличный результат, хотя его можно улучшить с помощью дополнительной предварительной обработки, лучше тюнинг и разные архитектуры.

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

Более иллюстративный способ оценить , как рабочее в модели канаве агрегированных ошибок Среднего Абсолютных и средние абсолютная процентную Ошибку полностью, и мы можем построить на график рассеяния из прогнозируемых цен против фактических цен . Если они равны – нанесенные маркеры будут следовать по прямой траектории по диагонали. Для справки и объема – мы также можем построить диагональную линию и оценить, насколько близко каждый маркер находится к линии:

test_predictions = model.predict(x_test)
test_labels = y_test

fig, ax = plt.subplots(figsize=(8,4))
plt.scatter(test_labels, test_predictions, alpha=0.6, 
            color='#FF0000', lw=1, ec='black')
lims = [0, 5]

plt.plot(lims, lims, lw=1, color='#0000FF')
plt.ticklabel_format(useOffset=False, style='plain')
plt.xticks(fontsize=18)
plt.yticks(fontsize=18)
plt.xlim(lims)
plt.ylim(lims)

plt.tight_layout()
plt.show()

Выполнение этого кода приводит к:

оценка эффективности регрессии глубокого обучения

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

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

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

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

Пользовательский прогнозирование обратного вызова Keras с графиками

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

Создание настраиваемого обратного вызова сводится к расширению Callback класса и переопределению любого из методов, которые он предоставляет – те, которые вы не переопределяете, сохраняют свое поведение по умолчанию:

class PerformancePlotCallback(keras.callbacks.Callback):
       
    def on_train_end(self, logs=None):
      ...
    def on_epoch_begin(self, epoch, logs=None):
      ...
    def on_epoch_end(self, epoch, logs=None):
      ...
    def on_test_begin(self, logs=None):
      ...
    def on_test_end(self, logs=None):
      ...
    # Etc.

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

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

class PerformancePlotCallback(keras.callbacks.Callback):
    def __init__(self, x_test, y_test):
        self.x_test = x_test
        self.y_test = y_test
        
    def on_epoch_end(self, epoch, logs=None):
        print('Evaluating Model...')
        print('Model Evaluation: ', self.model.evaluate(self.x_test))   

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

Если бы мы создали и добавили этот обратный вызов к модели, и fit() это снова, мы бы увидели результат, отличный от предыдущего:

performance_simple = PerformancePlotCallback(x_test, y_test)

# Model definition and compilation...

history = model.fit(
    x_train, y_train,
    epochs=150,
    validation_split=0.2,
    callbacks=[performance_simple]
)

Это приводит к:

Epoch 1/150
387/387 [==============================] - 3s 7ms/step - loss: 1.0785 - mae: 1.0140 - val_loss: 0.9455 - val_mae: 0.8927
Evaluating Model...
162/162 [==============================] - 0s 1ms/step - loss: 0.0528 - mae: 0.0000e+00
Model Evaluation:  [0.05277165770530701, 0.0]
Epoch 2/150
387/387 [==============================] - 3s 7ms/step - loss: 0.9048 - mae: 0.8553 - val_loss: 0.8547 - val_mae: 0.8077
Evaluating Model...
162/162 [==============================] - 0s 1ms/step - loss: 0.0471 - mae: 0.0000e+00
Model Evaluation:  [0.04705655574798584, 0.0]
...

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

Чтобы упростить ситуацию, мы получим обратный вызов для сохранения изображений в папку, чтобы мы могли позже сшить их вместе в видео или Gif. Мы также добавим model_name в конструктор, чтобы помочь нам различать модели при генерации изображений и их имен файлов:

class PerformancePlotCallback(keras.callbacks.Callback):
    def __init__(self, x_test, y_test, model_name):
        self.x_test = x_test
        self.y_test = y_test
        self.model_name = model_name
        
    def on_epoch_end(self, epoch, logs={}):
        y_pred = self.model.predict(self.x_test)
        fig, ax = plt.subplots(figsize=(8,4))
        plt.scatter(y_test, y_pred, alpha=0.6, 
            color='#FF0000', lw=1, ec='black')
        
        lims = [0, 5]

        plt.plot(lims, lims, lw=1, color='#0000FF')
        plt.ticklabel_format(useOffset=False, style='plain')
        plt.xticks(fontsize=18)
        plt.yticks(fontsize=18)
        plt.xlim(lims)
        plt.ylim(lims)

        plt.tight_layout()
        plt.title(f'Prediction Visualization Keras Callback - Epoch: {epoch}')
        plt.savefig('model_train_images/'+self.model_name+"_"+str(epoch))
        plt.close()

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

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

Теперь, давайте снова использовать эту пользовательскую функцию обратного вызова, обеспечивая название модели в дополнение к x_testи y_test наборов:

checkpoint = keras.callbacks.ModelCheckpoint("california.h5", save_best_only=True)
performance = PerformancePlotCallback(x_test, y_test, "california_model")

# Model definition and compilation...
              
history = model.fit(
    x_train, y_train,
    epochs=150,
    validation_split=0.2,
    callbacks=[checkpoint, performance]
)

PerformancePlotCallback Идет в полном разгаре, и в указанной папке формирует изображение на производительность каждой эпохи. В model_train_images папке теперь 150 графиков:

сохранение прогнозов на каждую эпоху керас

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

анимированный процесс обучения на каждую эпоху с керасом

Заключение

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

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

Ответить