Use RNNs and CNNs for time series, sequence and forecasting models

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

Recurrent Neural Networks | MIT 6.S191

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

Recurrent Neural Networks | MIT 6.S191