Le notebook de référence pour ce tutoriel est TimeSeries.

Avant de commencer à étudier RNN et CNN pour les time series, commençons par un modèle linéaire très simple.
Modèle linéaire
Le tutoriel qui sert de référence pour celui-ci est Forecasting de Sequences, Time Series and Prediction (Coursera/Semaine 2) et plus particulièrement le notebook S+P Week 2 Lesson 2.ipynb
Les données sont préparées tel que décrit dans notre article Prepare features and labels
def windowed_dataset(series, window_size, batch_size, shuffle_buffer): dataset = tf.data.Dataset.from_tensor_slices(series) dataset = dataset.window(window_size + 1, shift=1, drop_remainder=True) dataset = dataset.flat_map(lambda window: window.batch(window_size + 1)) dataset = dataset.shuffle(shuffle_buffer).map(lambda window: (window[:-1], window[-1])) dataset = dataset.batch(batch_size).prefetch(1) return dataset
Ensuite un modèle est créé :
dataset = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) print(dataset) l0 = tf.keras.layers.Dense(1, input_shape=[window_size]) model = tf.keras.models.Sequential([l0]) model.compile(loss="mse", optimizer=tf.keras.optimizers.SGD(lr=1e-6, momentum=0.9)) model.fit(dataset,epochs=100,verbose=0)
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 1) 21
=================================================================
Total params: 21
Trainable params: 21
Non-trainable params: 0
model.fit(dataset,epochs=100,verbose=0) myweights, mybias=l0.get_weights()
myweights
array([[-0.06807945],
[ 0.00441364],
[ 0.05399206],
[-0.03565589],
[ 0.06098474],
[ 0.06372922],
[-0.10915841],
[ 0.00682205],
[ 0.01581037],
[ 0.04594234],
[-0.08730657],
[ 0.03207239],
[ 0.00571096],
[ 0.02093075],
[-0.04836704],
[ 0.11473672],
[ 0.05038786],
[ 0.16847259],
[ 0.25107303],
[ 0.43803504]], dtype=float32)
mybias
array([0.0149056], dtype=float32)
Une fois l’apprentissage effectué, on regarde les prédictions :
forecast = [] for time in range(len(series) - window_size): forecast.append(model.predict(series[time:time + window_size][np.newaxis])) forecast = forecast[split_time-window_size:] results = np.array(forecast)[:, 0, 0] plt.figure(figsize=(10, 6)) plot_series(time_valid, x_valid) plot_series(time_valid, results)
tf.keras.metrics.mean_absolute_error(x_valid, results).numpy()
5.0760803
ANN
Pour l’ANN, les données sont créées comme précédemment.
Le modèle est séquentiel.
dataset = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Dense(10, input_shape=[window_size], activation="relu"), tf.keras.layers.Dense(10, activation="relu"), tf.keras.layers.Dense(1) ])
model.compile(loss="mse", optimizer=tf.keras.optimizers.SGD(lr=1e-6, momentum=0.9))
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 10) 210
_________________________________________________________________
dense_1 (Dense) (None, 10) 110
_________________________________________________________________
dense_2 (Dense) (None, 1) 11
=================================================================
Total params: 331
Trainable params: 331
Non-trainable params: 0
model.fit(dataset,epochs=100,verbose=0)
forecast = [] for time in range(len(series) - window_size): forecast.append(model.predict(series[time:time + window_size][np.newaxis])) forecast = forecast[split_time-window_size:] results = np.array(forecast)[:, 0, 0]
tf.keras.metrics.mean_absolute_error(x_valid, results).numpy()
4.61267
RNN
RNN a déjà été exploré lors de son utilisation sur le texte. Voir Add RNN and GRU, RNNS, LSTMs, GRUs and CNNs
Un RNN attend 3 dimensions en input : batch size, timestamps, series dimensionality.
Supposons les données précédentes.
tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) train_set = windowed_dataset(x_train, window_size, batch_size=128, shuffle_buffer=shuffle_buffer_size)
Le return_sequences=True dans le 1er RNN car on a à la suite un autre RNN qui prend entrée toutes les sorties du précédent RNN
model = tf.keras.models.Sequential([ tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=-1), input_shape=[None]), tf.keras.layers.SimpleRNN(40, return_sequences=True), tf.keras.layers.SimpleRNN(40), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 100.0) ])
On cherche le meilleur lr en testant le RNN sur une centaine d’epochs.
lr_schedule = tf.keras.callbacks.LearningRateScheduler( lambda epoch: 1e-8 * 10**(epoch / 20)) optimizer = tf.keras.optimizers.SGD(lr=1e-8, momentum=0.9)
La fonction de perte est Huber, une fonction moins sensible aux outliers.
model.compile(loss=tf.keras.losses.Huber(), optimizer=optimizer, metrics=["mae"])
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= lambda (Lambda) (None, None, 1) 0 _________________________________________________________________ simple_rnn (SimpleRNN) (None, None, 40) 1680 _________________________________________________________________ simple_rnn_1 (SimpleRNN) (None, 40) 3240 _________________________________________________________________ dense (Dense) (None, 1) 41 _________________________________________________________________ lambda_1 (Lambda) (None, 1) 0 ================================================================= Total params: 4,961 Trainable params: 4,961 Non-trainable params: 0
history = model.fit(train_set, epochs=100, callbacks=[lr_schedule])
plt.semilogx(history.history["lr"], history.history["loss"]) plt.axis([1e-8, 1e-4, 0, 30])

On recommence avec le nouveau lr : 5e-5 puisque c’est entre e-6 et e-5 que ça dégénère.
tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) dataset = windowed_dataset(x_train, window_size, batch_size=128, shuffle_buffer=shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=-1), input_shape=[None]), tf.keras.layers.SimpleRNN(40, return_sequences=True), tf.keras.layers.SimpleRNN(40), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 100.0) ]) optimizer = tf.keras.optimizers.SGD(lr=5e-5, momentum=0.9) model.compile(loss=tf.keras.losses.Huber(), optimizer=optimizer, metrics=["mae"]) history = model.fit(dataset,epochs=400)
forecast=[] for time in range(len(series) - window_size): forecast.append(model.predict(series[time:time + window_size][np.newaxis])) forecast = forecast[split_time-window_size:] results = np.array(forecast)[:, 0, 0] plt.figure(figsize=(10, 6)) plot_series(time_valid, x_valid) plot_series(time_valid, results)
tf.keras.metrics.mean_absolute_error(x_valid, results).numpy()
6.6527605
LSTM
LSTM a déjà été décrit dans un autre article dans un cas d’utilisation de NLP : Train LSTMs et LSTMs.
Supposons encore une fois les mêmes données :
tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) dataset = windowed_dataset(x_train, window_size, batch_size=128, shuffle_buffer=shuffle_buffer_size)
On met 2 LSTM à la suite (return_sequence=True) et ils sont bidirectionnels.
model = tf.keras.models.Sequential([ tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=-1), input_shape=[None]), tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32, return_sequences=True)), tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32)), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 100.0) ])
Cette fois loss=MSE mais ça ne change rien.
model.compile(loss="mse", optimizer=optimizer, metrics=["mae"])
model.fit(dataset,epochs=100,verbose=1)
Le résultat est MAE: 4.8631, ce qui est très bon