angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Автоматизация оптимизации гиперпараметров Keras с помощью Talos

Модель Deep Learning - это НЕ "черный ящик". Она требует настройки для хорошей производительности.

2 февраля 2022
post main image
https://www.pexels.com/nl-nl/@monoar-rahman-22660

В двух предыдущих постах я показал вам свои первые шаги в работе с Keras. Я использовал примеры, найденные в интернете, и изменил набор данных на что-то тривиальное, то есть я сам генерирую данные и знаю ожидаемые значения. Но я также сказал вам, что понятия не имел, почему такие параметры, как нейроны, эпохи, batch_size имеют такие значения.
Итак, то, что мы имеем, на самом деле не является черным ящиком. Снаружи есть некоторые переключатели и винтики, которые очень нуждаются в нашем внимании. В этом посте я использую программу Talos, 'Hyperparameter Optimization for Keras, TensorFlow (tf.keras) and PyTorch', см. ссылки ниже, которая предназначена для автоматизации процесса выбора оптимальных параметров.

В конце этого сообщения приведен код, чтобы вы могли попробовать его самостоятельно.

Гиперпараметры

Такие параметры, как нейроны, эпохи и размер партии, называются гиперпараметрами, и их настройка необходима для хорошей работы модели. В Интернете есть несколько интересных статей о том, как настраивать эти параметры. Это можно назвать святым Граалем нейронных сетей: оптимизация гиперпараметров. Сайт Merriam-Webster: Святой Грааль - это предмет или цель, к которой стремятся из-за ее огромного значения. Если вы не знаете, что делаете, легко выбрать неоптимальные или даже совершенно неправильные параметры.

Функции потерь

Для проведения оптимизации нам нужны значения, которые показывают, насколько хорошо работает наша модель. Эти значения рассчитываются с помощью функций потерь. Мы используем различные функции потерь для регрессии и классификации. Смотрите, например, статью 'Overview of loss functions for Machine Learning', см. ссылки ниже.

Регрессионные функции потерь:

  • Mean Squared Error (MSE)
  • Mean Absolute Error (MAE)
  • Huber
  • Log-Cosh
  • Quantile

Classфункции потерь при классификации:

  • Binary Cross Entropy
  • Multi-Class Cross Entropy

Talos резюме

Мне нравится предложение в статье "Оптимизация гиперпараметров с помощью Keras", см. ссылки ниже:

Не делайте ошибок; КАЖДЫЙ РАЗ, КОГДА МЫ ДЕЛАЕМ GET МЕТРИКУ PERFORMANCE ПРАВИЛЬНО (да, я кричу), нам нужно рассмотреть, что происходит в процессе оптимизации модели.

С помощью Talos мы параметризуем нашу модель. Количество комбинаций эпох, размера партии и т.д. может быть огромным. Talos выбирает случайным образом несколько комбинаций и создает новую модель с обучающими и проверочными данными. Это может занять минуты, но также часы или даже дни.
После завершения мы можем использовать полученные оценки, изменить значения параметров и/или добавить некоторые параметры и запустить снова. В это время мы можем заняться другими делами, например, выпить кофе, поговорить с другом или, что еще лучше, попытаться узнать больше об оптимизации Machine Learning . Давайте посмотрим, как это работает.

Пример

Я запущу Talos для очень простой модели Neural Network , основанной на 'Keras 101: A simple (and interpretable) Neural Network model for House Pricing regression', см. ссылки ниже.
Набор данных генерируется с помощью этой функции:

# define input sequence
def fx(x0, x1):
    y = x0 + 2*x1
    return y

Модель:

model = Sequential()
# add layers
model.add(Dense(100, input_shape=(2,), activation='relu', name='layer_input'))
model.add(Dense(50, activation='relu', name='layer_hidden_1')
model.add(Dense(1, activation='linear', name='layer_output')
# compile
model.compile(optimizer='adam', loss='mse', metrics=['mean_absolute_error'])

И функция подгонки:

history = model.fit(X_train, y_train, epochs=100, validation_split=0.05)

Как я уже говорил, я понятия не имею, почему параметры имеют такие значения. Ладно, ну немного.

Talos

Чтобы использовать это в Talos , мы заменим жестко закодированные параметры переменными, которыми можно управлять в Talos. Сначала мы создадим словарь параметров со значениями, которые мы хотим изменить. Не начинайте со всех параметров, которые вы хотите изменить. Вы не успеете оглянуться, как будете ждать и ждать. Кроме того, я начал, например, параметр batch_size с двух значений, которые находятся далеко друг от друга, чтобы получить представление. С приведенными ниже параметрами Talos сделал 16 запусков, и общее время на моем ПК (без GPU) составило 42 секунды.

# parameters
p = dict(
	first_neuron=[24, 192],
	activation=['relu', 'elu'],
	epochs=[50, 200],
	batch_size=[8, 32]
)

Затем мы меняем модель для Talos:

model = Sequential()
# add layers
model.add(Dense(
    params['first_neuron'],
    input_shape=(2,), 
    activation=params['activation'],
    name='layer_input')
)
model.add(Dense(
    50,
    activation=params['activation'],
    name='layer_hidden_1')
)
model.add(Dense(
    1,
    activation='linear',
    name='layer_output')
)
# compile
model.compile(
    optimizer='adam',
    loss='mse',
    metrics=['mean_absolute_error'],
)

И функция подгонки становится:

history = model.fit(
    x=x_train, 
    y=y_train,
    validation_data=[x_val, y_val],
    epochs=params['epochs'],
    batch_size=params['batch_size'],
    verbose=0,
)

Выполнить Talos и проанализировать.

Время запуска! После запуска я печатаю полную таблицу результатов.

# perform scan
so = talos.Scan(
	x=X,
	y=y,
	model=dlm.model2scan, 
	params=p,
	experiment_name='model2scan',
	val_split=0.3,
)
print('scan details {}'.format(so.details))
print('analyze ...')
a = talos.Analyze(so)
a_table = a.table('val_loss', sort_by='val_loss', exclude=['start', 'end', 'duration'])
print('a_table = \n{}'.format(a_table))

Вот таблица. Обратите внимание, что она отсортирована по val_loss, то есть последняя строка является победителем:

   activation  epochs  round_epochs  first_neuron  val_mean_absolute_error  mean_absolute_error  batch_size   val_loss        loss
4        relu      50            50            24                 7.786795            10.516150          32  94.053894  143.701889
12        elu      50            50            24                 5.021943             2.713378          32  30.090796   10.598367
13        elu      50            50           192                 2.880891             2.392172          32  10.355382    7.829942
5        relu      50            50           192                 2.306647             1.435794          32   8.680595    3.228125
8         elu      50            50            24                 2.205534             1.599179           8   5.929257    3.529320
9         elu      50            50           192                 1.564395             0.991934           8   3.059430    1.329003
1        relu      50            50           192                 0.813473             0.418985           8   1.315141    0.284857
14        elu     200           200            24                 0.934512             0.557581          32   1.210560    0.448432
0        relu      50            50            24                 0.680375             0.463401           8   0.818936    0.343270
15        elu     200           200           192                 0.773358             0.466824          32   0.776512    0.313476
6        relu     200           200            24                 0.510728             0.256091          32   0.515720    0.105524
7        relu     200           200           192                 0.473588             0.219744          32   0.471352    0.076440
2        relu     200           200            24                 0.562183             0.213781           8   0.431688    0.075045
3        relu     200           200           192                 0.261547             0.062857           8   0.179677    0.006133
10        elu     200           200            24                 0.327835             0.207104           8   0.140866    0.063864
11        elu     200           200           192                 0.218479             0.116624           8   0.071611    0.028682

Глядя на данные, мы видим, что epochs=200 дает гораздо лучшие результаты, чем epochs=50. Мы можем изменить его на 100, чтобы посмотреть, будет ли это также хорошо. batch_size=8 также дает лучшие результаты, чем batch_size=32. Мы можем изменить его на 16 и посмотреть, будет ли это также хорошо. Мы также можем попробовать уменьшить first_neuron. Новые параметры для Talos:

p = dict(
	first_neuron=[128, 192],
	activation=['relu', 'elu'],
	epochs=[100, 200],
	batch_size=[8, 16]
)

Давайте повторим запуск с этими значениями. Результат:

    batch_size activation  epochs  val_mean_absolute_error  val_loss      loss  round_epochs  first_neuron  mean_absolute_error
13          16        elu     100                 0.884498  1.053809  0.726826           100           192             0.706334
12          16        elu     100                 0.779756  0.834097  0.669265           100           128             0.696979
4           16       relu     100                 0.415210  0.235131  0.124713           100           128             0.291552
9            8        elu     100                 0.395605  0.204896  0.157592           100           192             0.321956
5           16       relu     100                 0.290187  0.109819  0.064319           100           192             0.211642
15          16        elu     200                 0.234876  0.108920  0.070559           200           192             0.220270
14          16        elu     200                 0.274542  0.107709  0.075080           200           128             0.216383
0            8       relu     100                 0.280294  0.104701  0.049495           100           128             0.188951
1            8       relu     100                 0.268457  0.100130  0.041598           100           192             0.160658
6           16       relu     200                 0.228168  0.079410  0.033531           200           128             0.138397
11           8        elu     200                 0.175497  0.070203  0.041988           200           192             0.154247
10           8        elu     200                 0.150712  0.057377  0.021882           200           128             0.108372
8            8        elu     100                 0.205943  0.055572  0.048045           100           128             0.187398
2            8       relu     200                 0.182463  0.046856  0.018731           200           128             0.096890
7           16       relu     200                 0.135524  0.025975  0.010142           200           192             0.073783
3            8       relu     200                 0.078327  0.009304  0.004181           200           192             0.042721

Лучшие результаты улучшились, но не так сильно. Эпохи=200 по-прежнему лучшие, как и batch_size=8. Окончательные параметры таковы:

p = dict(
	first_neuron=192,
	activation='relu',
	epochs=200,
	batch_size=8,
)

Полный код

Ниже приведен код на случай, если вы захотите попробовать сами. Выберите 'run_optimizer' для запуска Talos. Набор данных разделяется на обучающие и тестовые данные. Данные для обучения снова разделяются на данные для обучения и данные для проверки.

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

# optimizing keras hyperparameters with talos
from keras.models import Sequential, load_model
from keras.layers import Dense
import numpy as np
from plotly.subplots import make_subplots
import plotly.graph_objects as go
from sklearn.model_selection import train_test_split
import talos

# use plotly or pyplot
from matplotlib import pyplot

# your input: select talos optimizer or normal operation
run_optimizer = False
#run_optimizer = True

# your input: train model or use saved model
use_saved_model = False
#use_saved_model = True

# create dataset
def fx(x0, x1):
    y = x0 + 2*x1
    return y

X_items = []
y_items = []
for x0 in range(0, 18, 3):
    for x1 in range(2, 27, 3):
        y = fx(x0, x1)
        X_items.append([x0, x1])
        y_items.append(y)

X = np.array(X_items).reshape((-1, 2))
y = np.array(y_items)
print('X = {}'.format(X))
print('y = {}'.format(y))
X_data_shape = X.shape
print('X_data_shape = {}'.format(X_data_shape))

class DLM:
    
    def __init__(
        self,
        model_name='my_model',
    ):
        self.model_name = model_name
        self.layer_input_shape=(2, )
        # your input: final model parameters
        self.params = dict(
            # layers
            first_neuron=192,
            activation='relu',
            # compile
            # fit
            val_split=0.3,
            epochs=200,
            batch_size=8,
            verbose=0,
        )

    def data_split_train_test(
        self,
        X,
        y,
    ):
        self.X_train, self.X_test, self.y_train, self.y_test = train_test_split(X, y, test_size=0.3, random_state=1)
        print('self.X_train = {}'.format(self.X_train))
        print('self.X_test = {}'.format(self.X_test))
        print('self.y_train = {}'.format(self.y_train))
        print('self.y_test = {}'.format(self.y_test))

        print('training data row count = {}'.format(len(self.y_train)))
        print('test data row count = {}'.format(len(self.y_test)))

        X_train_data_shape = self.X_train.shape
        print('X_train_data_shape = {}'.format(X_train_data_shape))

    def get_model(
        self,
    ):
        self.model = self.get_model_for_params(self.params)
        return self.model

    def get_model_for_params(self, params):
        model = Sequential()
        # add layers
        model.add(Dense(
            params['first_neuron'],
            input_shape=self.layer_input_shape, 
            activation=params['activation'],
            name='layer_input')
        )
        model.add(Dense(
            50,
            activation=params['activation'],
            name='layer_hidden_1')
        )
        model.add(Dense(
            1,
            activation='linear',
            name='layer_output')
        )
        # compile
        model.compile(
            optimizer='adam',
            loss='mse',
            metrics=['mean_absolute_error'],
        )
        return model

    def model_summary(
        self,
        model,
    ):
        model.summary()

    def fit(
        self, 
        model,
        plot=False,
    ):
        # split training data 
        X_train, X_val, y_train, y_val = train_test_split(self.X_train, self.y_train, test_size=self.params['val_split'], random_state=1)
        print('X_train = {}'.format(X_train))
        print('y_train = {}'.format(y_train))
        print('X_val = {}'.format(X_val))
        print('y_val = {}'.format(y_val))

        print('training data row count = {}'.format(len(y_train)))
        print('validation data row count = {}'.format(len(y_val)))

        history = self.fit_model(model, X_train, y_train, X_val, y_val, self.params)

        if plot:

            fig = go.Figure()
            fig.add_trace(go.Scattergl(y=history.history['loss'], name='Train'))
            fig.add_trace(go.Scattergl(y=history.history['val_loss'], name='Valid'))
            fig.update_layout(height=500, width=700, xaxis_title='Epoch', yaxis_title='Loss')
            fig.show()

            fig = go.Figure()
            fig.add_trace(go.Scattergl(y=history.history['mean_absolute_error'], name='Train'))
            fig.add_trace(go.Scattergl(y=history.history['val_mean_absolute_error'], name='Valid'))
            fig.update_layout(height=500, width=700, xaxis_title='Epoch', yaxis_title='Mean Absolute Error')
            fig.show() 

            pyplot.plot(history.history['loss'], label='Train')
            pyplot.plot(history.history['val_loss'], label='Valid')
            pyplot.legend()
            #pyplot.show()
            pyplot.savefig('ex_dl_loss.png')

        return history

    def fit_model(self, model, x_train, y_train, x_val, y_val, params):
        history = model.fit(
            x=x_train, 
            y=y_train,
            validation_data=[x_val, y_val],
            epochs=params['epochs'],
            batch_size=params['batch_size'],
            verbose=0,
        )
        return history

    def model2scan(self, x_train, y_train, x_val, y_val, params):
        model = self.get_model_for_params(params)
        history = self.fit_model(model, x_train, y_train, x_val, y_val, params)
        return history, model

    def evaluate(
        self, 
        model,
    ):
        score = model.evaluate(self.X_test, self.y_test)
        print('test data - loss = {}'.format(score[0]))
        print('test data - mean absolute error = {}'.format(score[1]))
        return score

    def predict(
        self,
        model,
        x0,
        x1,
        fx=None,
    ):
        x = np.array([[x0, x1]]).reshape((-1, 2))
        predictions = model.predict(x)
        expected = ''
        if fx is not None:
            expected = ', expected = {}'.format(fx(x0, x1))
        print('for x = {}, predictions = {}{}'.format(x, predictions, expected))
        return predictions

    def save_model(
        self,
        model,
    ):
        model.save(self.model_name)

    def load_saved_model(
        self,
    ):
        self.model = load_model(self.model_name)
        return self.model


dlm = DLM()

if not run_optimizer:
    # create & save or used saved
    if use_saved_model:
        model = dlm.load_saved_model()    
    else:
        dlm.data_split_train_test(X, y)
        model = dlm.get_model()
        # remove plot=True for no plot
        dlm.fit(model, plot=True)
        dlm.evaluate(model)
        dlm.save_model(model)

    # predict
    dlm.predict(model, 4, 17, fx=fx)
    dlm.predict(model, 23, 79, fx=fx)
    dlm.predict(model, 40, 33, fx=fx)
    dlm.predict(model, 140, 68, fx=fx)
else:
    # talos
    # your input: parameters run 1
    p = dict(
        first_neuron=[24, 192],
        activation=['relu', 'elu'],
        epochs=[50, 200],
        batch_size=[8, 32]
    )
    # your input: parameters run 2 (change p2 to p)
    p2 = dict(
        first_neuron=[128, 192],
        activation=['relu', 'elu'],
        epochs=[100, 200],
        batch_size=[8, 16]
    )
    # perform scan
    so = talos.Scan(
        x=X,
        y=y,
        model=dlm.model2scan, 
        params=p,
        experiment_name='model2scan',
        val_split=0.3,
    )
    print('scan details {}'.format(so.details))
    print('analyze ...')
    a = talos.Analyze(so)
    # dump table
    a_table = a.table('val_loss', sort_by='val_loss', exclude=['start', 'end', 'duration'])
    print('a_table = \n{}'.format(a_table))

Некоторые мысли

Действительно ли мы движемся в правильном направлении? Это очень сложная проблема. У нас есть N-мерный мир с максимумами и минимумами повсюду. Это означает, что начальная точка может быть очень важна. В конечном итоге вы всегда должны выполнять прогон с максимально возможным количеством параметров. Если у вас большой набор данных, вам следует начать с его уменьшения, выбирая случайные выборки.

Надеюсь, понятно, что вам нужно очень хорошо понимать, что вы делаете. Но система, оптимизированная по скорости (дорогая GPU), также очень помогает.

Резюме

Talos - это очень хороший инструмент, который делает много работы за вас. Документацию можно было бы улучшить, но я не жалуюсь. У него гораздо больше возможностей, но я не пробовал их все. Я не смог заставить мои (многоэтапные) примеры univariate LSTM работать с ним из-за более сложной природы набора данных. Нужно изучить этот вопрос подробнее.
Можем ли мы оптимизировать оптимизатор? Конечно. В качестве следующего шага мы могли бы взять результаты таблицы и автоматически проанализировать их, чтобы сделать новый выбор параметров для следующего запуска.

Ссылки / кредиты

10 Hyperparameter optimization frameworks
https://towardsdatascience.com/10-hyperparameter-optimization-frameworks-8bc87bc8b7e3

5 Regression Loss Functions All Machine Learners Should Know
https://heartbeat.comet.ml/5-regression-loss-functions-all-machine-learners-should-know-4fb140e9d4b0

How do I choose the optimal batch size?
https://ai.stackexchange.com/questions/8560/how-do-i-choose-the-optimal-batch-size

How to Develop LSTM Models for Time Series Forecasting
https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting

How to get reproducible results in keras
https://stackoverflow.com/questions/32419510/how-to-get-reproducible-results-in-keras

How to Manually Optimize Machine Learning Model Hyperparameters
https://machinelearningmastery.com/manually-optimize-hyperparameters

How to tune the number of epochs and batch_size in Keras-tuner?
https://kegui.medium.com/how-to-tune-the-number-of-epochs-and-batch-size-in-keras-tuner-c2ab2d40878d

Hyperparameter Optimization for Keras, TensorFlow (tf.keras) and PyTorch
https://github.com/autonomio/talos

Hyperparameter Optimization with Keras
https://towardsdatascience.com/hyperparameter-optimization-with-keras-b82e6364ca53

Keras 101: A simple (and interpretable) Neural Network model for House Pricing regression
https://towardsdatascience.com/keras-101-a-simple-and-interpretable-neural-network-model-for-house-pricing-regression-31b1a77f05ae

Overview of loss functions for Machine Learning
https://medium.com/analytics-vidhya/overview-of-loss-functions-for-machine-learning-61829095fa8a

What is batch size in neural network?
https://stats.stackexchange.com/questions/153531/what-is-batch-size-in-neural-network

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.