# What parameters or hyper-parameters of my model for time-series should I change to improve the MAE?

The following time series exercise is about writing the best possible model, minimizing the MAE. Helper functions normalize_series, windowed_dataset are given and not to be changed as well as BATCH_SIZE, N_PAST, N_FUTURE or SHIFT. We use a window of the past 10 observations of 1 feature, and train the model to predict the next 10 observations of that feature.

import pandas as pd
import tensorflow as tf

def normalize_series(data, min, max):
data = data - min
data = data / max
return data

def windowed_dataset(series, batch_size, n_past=10, n_future=10, shift=1):
ds = tf.data.Dataset.from_tensor_slices(series)
ds = ds.window(size=n_past + n_future, shift=shift, drop_remainder=True)
ds = ds.flat_map(lambda w: w.batch(n_past + n_future))
ds = ds.map(lambda w: (w[:n_past], w[n_past:]))
return ds.batch(batch_size).prefetch(1)

def solution_model():

# Number of features in the dataset. We use all features as predictors to
# predict all features of future time steps.
N_FEATURES = len(df.columns) # =1

# Normalizes the data
data = df.values
data = normalize_series(data, data.min(axis=0), data.max(axis=0))

# Splits the data into training and validation sets.
SPLIT_TIME = int(len(data) * 0.8)
x_train = data[:SPLIT_TIME]
x_valid = data[SPLIT_TIME:]

tf.keras.backend.clear_session()
tf.random.set_seed(42)

BATCH_SIZE = 32

# Number of past time steps based on which future observations should be
# predicted
N_PAST = 10

# Number of future time steps which are to be predicted.
N_FUTURE = 10

# By how many positions the window slides to create a new window
# of observations.
SHIFT = 1

# Code to create windowed train and validation datasets.
train_set = windowed_dataset(series=x_train, batch_size=BATCH_SIZE,
n_past=N_PAST, n_future=N_FUTURE,
shift=SHIFT)
valid_set = windowed_dataset(series=x_valid, batch_size=BATCH_SIZE,
n_past=N_PAST, n_future=N_FUTURE,
shift=SHIFT)

model = tf.keras.models.Sequential([

tf.keras.layers.Conv1D(filters=1,
kernel_size=5,
strides=1,
activation="relu",
input_shape=[None, 1]
),

tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(60, return_sequences=True)),
tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(60, return_sequences=True)),
#tf.keras.layers.Dense(30, activation="relu"),
#tf.keras.layers.Dense(10, activation="relu"),
tf.keras.layers.Dense(N_FEATURES)
])

# Code to train and compile the model
lr_schedule = tf.keras.callbacks.LearningRateScheduler(
lambda epoch: 1e-8 * 10 ** (epoch / 20)
)

optimizer = tf.keras.optimizers.SGD(learning_rate=1e-5, momentum=0.9)

model.compile(
loss=tf.keras.losses.Huber(),
optimizer=optimizer,
metrics=["mae"]
)

model.fit(
train_set, validation_data=valid_set, epochs=100, callbacks=[lr_schedule]
)

model.summary()

return model


With main

if __name__ == '__main__':
model = solution_model()
model.save("model.h5")


I tried adding several dense layers, replacing the Bidirectional LSTM layers with LSTM/GRU/Bidirectional GRU layers. MAE obtained stays around 0.2 at best.

Is there any obvious (ex. input or output size) mistake in the approach, and how to improve to lower mae?