Обратные вызовы 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, что дает нам взгляд на процесс обучения, отличный от того, который мы получаем при анализе кривых обучения модели.