LSTM mehrstufige hyperparameter Optimierung mit Keras Tuner
Unsere Blackbox wird von jemand anderem eingestellt - schön. Aber das kostet auch eine Menge Zeit und hat einen großen Kohlenstoff-Fußabdruck, nicht so schön.
In einem früheren Beitrag ging es um die Optimierung von Hyperparameter mit Talos. Ich konnte dies nicht mit meinem LSTM -Modell für univariate -Mehrschritt-Zeitreihenvorhersage zum Laufen bringen, wegen der 3D-Eingabe, also wechselte ich zu Keras Tuner.
In diesem Beitrag versuche ich, die nächste Periode einer Sinuswelle mit dem Hyperband -Abstimmungsalgorithmus vorherzusagen. Um die Abstimmzeit zu reduzieren, habe ich die Anzahl der hyperparameters, die optimiert werden können, reduziert und auch die möglichen Werte für jeden Parameter begrenzt.
Spoiler-Alarm: Nehmen Sie immer einen Optimierer wie Keras Tuner in Ihren Code auf, er wird Ihnen viel Zeit sparen.
Behalten Sie die hyperparameters an einem Ort
In den meisten Beispielen befinden sich die Parameter irgendwo im Code des Modells. Ich möchte, dass sie an einer Stelle stehen und habe einige Klassen hinzugefügt, um dies zu handhaben. Im Moment verwende ich nur die Option 'Choice' für die hyperparameters, die ein Array mit diskreten Werten annimmt. So habe ich das gemacht:
# tuner parameters
class TunerParameter:
def __init__(self, name, val):
self.name = name
self.val = val
self.args = (name, val)
class TunerParameters:
def __init__(self, pars=None):
self.tpars = []
for p in pars:
tpar = TunerParameter(p[0], p[1])
self.tpars.append(tpar)
setattr(self, p[0], tpar)
def get_pars(self):
return self.tpars
# hyperparameters, name and value, are the inputs for hp.Choice()
tpars = TunerParameters(
pars=[
('first_neuron', [64, 128]),
('second_neuron', [64, 128]),
('third_neuron', [64, 128]),
('learning_rate', [1e-4, 1e-2]),
('batch_size', [8, 32]),
],
)
Jetzt können wir uns auf die Parameter beziehen, zum Beispiel als:
tpars.learning_rate.args
Hyperband und der Parameter batch_size
Standardmäßig können Sie für den Parameter batch_size keine "Wahl" angeben. Um dies zu tun, müssen wir die Klasse Hyperband unterklassifizieren, wie in 'How to tune the number of epochs and batch_size? #122' beschrieben, siehe Links unten. Wir stimmen die Epochen nicht ab, da Hyperband die Epochen für das Training über seine eigene Logik festlegt. Hyperparameters, die wir hinzugefügt und zur Abstimmung übergeben haben, müssen aus den Parametern, die an Hyperband übergeben werden, entfernt werden.
# subclass tuner to add hyperparameters (here, batch_size)
class MyTuner(kt.tuners.Hyperband):
def __init__(self, *args, **kwargs):
self.tpars = None
if 'tpars' in kwargs:
self.tpars = kwargs.pop('tpars')
super(MyTuner, self).__init__(*args, **kwargs)
def run_trial(self, trial, *args, **kwargs):
if self.tpars is not None:
for tpar in self.tpars:
kwargs[tpar.name] = trial.hyperparameters.Choice(tpar.name, tpar.val)
return super(MyTuner, self).run_trial(trial, *args, **kwargs)
Erzeugung und Verarbeitung von Eingabedaten
Wir generieren die Zeitreihendaten mit der Funktion Python sin(). In diesem Beispiel werden die Eingabedaten in Trainingsdaten und Testdaten aufgeteilt. Wir erzeugen 4 Perioden für die Trainingsdaten und 2 Perioden für die Testdaten. Dann wird die nächste Periode vorhergesagt. Die Trainingsdaten werden auch zur Validierung verwendet (validation_split=0.33). Beachten Sie, dass wir die Eingabedaten beim Splitten nicht (!) mischen.
Wir verwenden n_steps_in=5 Eingabewerte. Um eine ganze Periode vorherzusagen, setzen wir die Anzahl der Vorhersageschritte, n_steps_out, gleich der Anzahl der Datenpunkte in einer Periode. Hier verwenden wir 20 Datenpunkte pro Periode. Weitere Informationen über LSTM univariate Mehrschrittprognosen finden Sie in dem Beitrag 'How to Develop LSTM Models for Time Series Forecasting', siehe Links unten.
Mit n_steps_in=5 und n_steps_out=20 haben wir einen "Block von 25 (n_steps_in=5 + n_steps_out=20) Datenpunkten", der sich über alle Datenpunkte erstreckt. Das bedeutet, dass wir nach der Konvertierung 120 - 25 = 95 Werte als Eingabedaten haben, und der Beginn des ersten Wertes liegt beim Offset 5 Schritte.
Wir machen eine Vorhersage mit den letzten n_steps_in-Werten unseres Datensatzes, dies ist unten dargestellt.
<--------------------- input data -------------------->
<----------- training data ---------><---test data --->
0 1 2 3 4 5
|--------|--------|--------|--------|--------|--------|
0 120 datapoints
blocks of n_steps_in + n_steps_out
.....iiiioooooooooo....................................
to make a prediction we need the last n_steps_in values:
6
|--------|
iiii
Der Code
Es gibt nicht viel mehr über den Code zu sagen. Wir verwenden EarlyStopping, ich weiß immer noch nicht, ob das eine gute oder schlechte Praxis ist. Natürlich verkürzt sich die Gesamtzeit eines Durchlaufs, aber wir verpassen vielleicht die besten Parameter. Aber so machen wir es, wenn wir die Deep Learning -Optimierung verwenden. Wir geben einige Werte vor und verwenden ein Ergebnis, das "gut genug" ist. Vergessen Sie nicht, den Code mit anderen Eingaben auszuführen.
# optimizing hyperparameters with keras tuner
from keras.callbacks import EarlyStopping
from keras.models import Sequential
from keras.layers import Dense, LSTM
import keras_tuner as kt
import numpy as np
import plotly.graph_objects as go
from tensorflow.keras.optimizers import Adam
from sklearn.model_selection import train_test_split
import sys
# tuner parameters
class TunerParameter:
def __init__(self, name, val):
self.name = name
self.val = val
self.args = (name, val)
class TunerParameters:
def __init__(self, pars=None):
self.tpars = []
for p in pars:
tpar = TunerParameter(p[0], p[1])
self.tpars.append(tpar)
setattr(self, p[0], tpar)
def get_pars(self):
return self.tpars
# hyperparameters name and value, are the inputs for hp.Choice()
tpars = TunerParameters(
pars=[
('first_neuron', [64, 128]),
('second_neuron', [64, 128]),
('third_neuron', [64, 128]),
('learning_rate', [1e-4, 1e-2]),
('batch_size', [8, 32]),
],
)
# split a univariate sequence into samples
# see:
# How to Develop LSTM Models for Time Series Forecasting
# https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting
def split_sequence(sequence, n_steps_in, n_steps_out):
X, y = list(), list()
for i in range(len(sequence)):
# find the end of this pattern
end_ix = i + n_steps_in
out_end_ix = end_ix + n_steps_out
# check if we are beyond the sequence
if out_end_ix > len(sequence):
break
# gather input and output parts of the pattern
seq_x, seq_y = sequence[i:end_ix], sequence[end_ix:out_end_ix]
X.append(seq_x)
y.append(seq_y)
return np.array(X), np.array(y)
# choose a number of time steps, prediction = 20 steps
n_steps_in = 5
n_steps_out = 20
# train:test = 2:1 (0.33 split)
n_periods_train = 4
n_periods_test = 2
# total periods
n_periods = n_periods_train + n_periods_test
# data points per period, predict a full period (n_steps_out)
period_points = n_steps_out
# generate sine wave data points
xs = np.linspace(0, n_periods * 2 * np.pi, n_periods * period_points)
raw_seq = np.sin(xs)
# plot the input data
dp = [i for i in range(len(raw_seq))]
fig = go.Figure()
fig.add_trace(go.Scattergl(y=raw_seq, x=dp, name='Sin'))
fig.update_layout(height=500, width=700, xaxis_title='datapoints', yaxis_title='Sine wave')
fig.show()
'''
# another dataset you may want try
raw_seq = []
for n in range(0, n_periods * period_points):
raw_seq.append(n)
print('raw_seq = {}'.format(raw_seq))
print('len(raw_seq) = {}'.format(len(raw_seq)))
'''
# split into samples
X, y = split_sequence(raw_seq, n_steps_in, n_steps_out)
# reshape from [samples, timesteps] into [samples, timesteps, features]
n_features = 1
X = X.reshape((X.shape[0], X.shape[1], n_features))
X_len = len(X)
y_len = len(y)
print('X_len = {}'.format(X_len))
print('y_len = {}'.format(y_len))
x_pred = np.array(raw_seq[-1*n_steps_in:])
x_pred = x_pred.reshape((1, n_steps_in, n_features))
class DLM:
def __init__(
self,
n_steps_in=None,
n_steps_out=None,
n_features=None,
tpars=None,
):
self.n_steps_in = n_steps_in
self.n_steps_out = n_steps_out
self.n_features = n_features
self.tpars = tpars
# input_shape
self.layer_input_shape = (self.n_steps_in, self.n_features)
print('layer_input_shape = {}'.format(self.layer_input_shape))
def data_split(self, X, y):
X0, X1, y0, y1 = train_test_split(X, y, test_size=0.33, shuffle=False)
return X0, X1, y0, y1
def get_model(self, hp):
model = Sequential()
# add layers
a = ('first_neuron', [64, 128])
model.add(LSTM(
hp.Choice(*self.tpars.first_neuron.args),
activation='relu',
return_sequences=True,
input_shape=self.layer_input_shape,
))
model.add(LSTM(
hp.Choice(*self.tpars.second_neuron.args),
activation='relu',
return_sequences=True,
))
model.add(LSTM(
hp.Choice(*self.tpars.third_neuron.args),
activation='relu',
))
model.add(Dense(n_steps_out))
# compile
model.compile(
optimizer=Adam(learning_rate=hp.Choice(*self.tpars.learning_rate.args)),
loss='mean_absolute_error',
metrics=['mean_absolute_error'],
run_eagerly=True,
)
return model
def get_X_pred(self, x):
a = []
for i in range(0, self.n_steps_in):
a.append(x[i][0])
a = [x[i][0] for i in range(0, self.n_steps_in)]
X_pred = np.array(a)
X_pred = X_pred.reshape((1, self.n_steps_in, self.n_features))
return X_pred
def get_plot_data(self, model, X, y, x_offset):
x_plot = []
y_plot = []
y_predict_plot = []
for i, x in enumerate(X):
y_plot.append(y[i][0])
X_pred = self.get_X_pred(x)
predictions = model.predict(X_pred)
y_predict_plot.append(predictions[0][0])
x_plot = [x + x_offset for x in range(len(X))]
return x_plot, y_plot, y_predict_plot
# subclass tuner to add hyperparameters (here, batch_size)
class MyTuner(kt.tuners.Hyperband):
def __init__(self, *args, **kwargs):
self.tpars = None
if 'tpars' in kwargs:
self.tpars = kwargs.pop('tpars')
super(MyTuner, self).__init__(*args, **kwargs)
def run_trial(self, trial, *args, **kwargs):
if self.tpars is not None:
for tpar in self.tpars:
kwargs[tpar.name] = trial.hyperparameters.Choice(tpar.name, tpar.val)
return super(MyTuner, self).run_trial(trial, *args, **kwargs)
dlm = DLM(
n_steps_in=n_steps_in,
n_steps_out=n_steps_out,
n_features=n_features,
tpars=tpars,
)
# split data
X_train, X_test, y_train, y_test = dlm.data_split(X, y)
print('len(X_train) = {}, len(X_test) = {}, X0_shape = {}'.format(len(X_train), len(X_test), X_train.shape))
# use subclassed HyperBand tuner
tuner = MyTuner(
dlm.get_model,
objective=kt.Objective('val_mean_absolute_error', direction='min'),
allow_new_entries=True,
tune_new_entries=True,
hyperband_iterations=2,
max_epochs=260,
directory='keras_tuner_dir',
project_name='keras_tuner_demo',
tpars=[tpars.batch_size],
)
print('\n{}\ntuner.search_space_summary()\n{}\n'.format('-'*60, '-'*60))
tuner.search_space_summary()
tuner.search(
X_train,
y_train,
validation_split=0.33,
callbacks=[EarlyStopping('val_loss', patience=3)]
)
print('\n{}\ntuner.results_summary()\n{}\n'.format('-'*60, '-'*60))
tuner.results_summary()
print('\n{}\nbest_hps\n{}\n'.format('-'*60, '-'*60))
best_hps = tuner.get_best_hyperparameters()[0]
for tpar in tpars.get_pars():
print('- {} = {}'.format(tpar.name, best_hps[tpar.name]))
h_model = tuner.hypermodel.build(best_hps)
print('\n{}\nh_model.summary()\n{}\n'.format('-'*60, '-'*60))
h_model.summary()
# plot test data performance
num_epochs = 100
history = h_model.fit(X_test, y_test, epochs=num_epochs, validation_split=0.33)
fig = go.Figure()
fig.add_trace(go.Scattergl(y=history.history['mean_absolute_error'], name='Test'))
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()
# plot train using predict(), test using predict(), and actual prediction
x_offset = n_steps_in
x_train_plot, y_train_plot, y_train_predict_plot = dlm.get_plot_data(h_model, X_train, y_train, x_offset)
x_offset += len(x_train_plot)
x_test_plot, y_test_plot, y_test_predict_plot = dlm.get_plot_data(h_model, X_test, y_test, x_offset)
x_offset += len(x_test_plot)
# use x_pred to get the prediction and show the n_steps_out
predictions = h_model.predict(x_pred)
y_predict_plot = predictions[0]
x_offset = n_periods * period_points
x_predict_plot = [x + x_offset for x in range(n_steps_out)]
fig = go.Figure()
fig.add_trace(go.Scattergl(y=y_train_predict_plot, x=x_train_plot, name='Train-pred'))
fig.add_trace(go.Scattergl(y=y_test_predict_plot, x=x_test_plot, name='Test-pred'))
fig.add_trace(go.Scattergl(y=y_predict_plot, x=x_predict_plot, name='Prediction'))
fig.update_layout(height=500, width=700, xaxis_title='Epoch', yaxis_title='Train, Test and Prediction')
fig.show()
h_eval_dict = h_model.evaluate(X_test, y_test, return_dict=True)
print('h_eval_dict = {}.'.format(h_eval_dict))
Ergebnisse
Ich habe diesem Beitrag keine Diagramme beigefügt. Wenn Sie sie sehen möchten, führen Sie den Code aus. Beachten Sie, dass im letzten Diagramm die für die Trainingsdaten und die Testdaten vorhergesagten Werte angezeigt werden. Die vorhergesagten Trainingsdaten beginnen bei Schritt n_steps=5 und die vorhergesagten Testdaten enden bei (n_periods * period_points)=120 - n_steps_out=20, was korrekt ist. Die angeforderte Vorhersage beginnt bei Schritt 120 und hat 20 Schritte.
Das Ergebnis der Sinuswellenvorhersage ist für einen so kleinen Datensatz nicht schlecht. Als ich eine Steigung ausprobierte (siehe Code), war das Ergebnis der Vorhersage viel stärker verrauscht. Ich vermute, dass dies mit der begrenzten Anzahl von Parametern in der Optimierung und der Tatsache zu tun hat, dass die vorhergesagten Werte der Sinuswelle im Bereich der Trainings- und Testdaten liegen. Dennoch sieht die Vorhersage für die Steigung gut aus, und bei Verwendung von Mehrschrittverfahren erhalten wir mehr Werte und können einen Trend erkennen.
Energieverbrauch und Abstimmung des neuronalen Netzes
Ein Großteil des heutigen Energieverbrauchs entfällt auf Systeme, die neuronale Netze abstimmen. In dem Artikel "Maschinen brauchen viel Energie, um zu lernen - warum AI so stromhungrig ist" (siehe Links unten) wird erwähnt, dass das einmalige Trainieren von BERT (Bidirectional Encoder Representations from Transformers) den Kohlenstoff-Fußabdruck eines Passagiers verursacht, der einen Hin- und Rückflug zwischen New York und San Francisco unternimmt. Durch mehrmaliges Trainieren und Abstimmen wurden die Kosten zum Äquivalent von 315 Passagieren oder einem ganzen 747er-Jet.
Wie viele neuronale Netze werden täglich trainiert? Und wie viele sind wie BERT? Gehen wir von 12.000 BERT-ähnlichen Netzen aus, die alle zwei Monate optimiert werden (ich glaube, es sind viel mehr). Dann haben wir (12.000/60=) 200 volle 747, die jeden Tag zwischen New York und San Francisco hin und her fliegen! Und dann sind da noch die kleineren Netze. Hmmm ....
Zusammenfassung
Hyperparameter Tuning erschien mit Keras Tuner nicht schwierig, ich mag es sogar sehr, weil ich möchte, dass das neuronale Netz eine Blackbox ist. Bei Keras Tuner sind die Schalter und Schrauben immer noch da, aber jemand anderes stellt sie ein. Das ist schön. Aber der Abstimmungsprozess kostet viel Zeit und Energie. Leider gibt es im Moment keine andere Möglichkeit.
Links / Impressum
How to Develop LSTM Models for Time Series Forecasting
https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting
How to tune the number of epochs and batch_size? #122
https://github.com/keras-team/keras-tuner/issues/122
It takes a lot of energy for machines to learn – here's why AI is so power-hungry
https://theconversation.com/it-takes-a-lot-of-energy-for-machines-to-learn-heres-why-ai-is-so-power-hungry-151825
KerasTuner API
https://keras.io/api/keras_tuner
Mehr erfahren
Deep Learning Machine Learning
Neueste
- Ausblenden der Primärschlüssel der Datenbank UUID Ihrer Webanwendung
- Don't Repeat Yourself (DRY) mit Jinja2
- SQLAlchemy, PostgreSQL, maximale Anzahl von Zeilen pro user
- Anzeige der Werte in den dynamischen Filtern SQLAlchemy
- Sichere Datenübertragung mit Public Key Verschlüsselung und pyNaCl
- rqlite: eine hochverfügbare und distverteilte SQLite -Alternative
Meistgesehen
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verwendung von UUIDs anstelle von Integer Autoincrement Primary Keys mit SQLAlchemy und MariaDb
- Verbindung zu einem Dienst auf einem Docker -Host von einem Docker -Container aus
- PyInstaller und Cython verwenden, um eine ausführbare Python-Datei zu erstellen
- SQLAlchemy: Verwendung von Cascade Deletes zum Löschen verwandter Objekte
- Flask RESTful API Validierung von Anfrageparametern mit Marshmallow-Schemas