# Set the random seed for reproducibility
seed_value = 42
os.environ['PYTHONHASHSEED'] = str(seed_value)
np.random.seed(seed_value)
rn.seed(seed_value)
tf.random.set_seed(seed_value)
tf.keras.backend.clear_session()

def parse_date(date_string):
    return pd.to_datetime(date_string, format='%d/%m/%Y')

df = pd.read_csv('daily2023.csv', parse_dates=['Date'], date_parser=parse_date, index_col='Date')
passenger_count = df['passenger count'].values

def split_sequence(sequence, n_steps):
    X, y = list(), list()
    for i in range(len(sequence)):
        end_ix = i + n_steps
        if end_ix > len(sequence) - 1:
            break
        seq_x, seq_y = sequence[i:end_ix], sequence[end_ix]
        X.append(seq_x)
        y.append(seq_y)
    return np.array(X), np.array(y)


# Fit the scaler on the entire dataset
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(passenger_count.reshape(-1, 1))

train_size = int(len(scaled_data) * 0.6)  # 60% of the data
val_size = int(len(scaled_data) * 0.2)  # 20% of the data
test_size = len(scaled_data) - train_size - val_size  # Remaining data

scaled_data_train = scaled_data[0:train_size]
scaled_data_val = scaled_data[train_size:train_size+val_size]
scaled_data_test = scaled_data[train_size+val_size:]


initializer=tf.keras.initializers.GlorotNormal(seed=42)
initializer2=tf.keras.initializers.Orthogonal(seed=42)

class Attention(Layer):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self, input_shape):
        self.W = self.add_weight(shape=(input_shape[-1], input_shape[-1],),
                                 initializer='random_normal',
                                 trainable=True)
        self.b = self.add_weight(shape=(input_shape[-1],),
                                 initializer='zeros',
                                 trainable=True)
        super().build(input_shape)

    def call(self, inputs):
        q = K.dot(inputs, self.W) + self.b
        q = K.tanh(q)
        a = K.softmax(q, axis=1)
        return a * inputs

    def compute_output_shape(self, input_shape):
        return input_shape

def create_model(filters, kernel_size, lstm_units, dropout_rate):
    model = Sequential()
    model.add(TimeDistributed(Conv1D(filters=filters, kernel_size=kernel_size, activation='relu',kernel_initializer=initializer), input_shape=(None, n_steps, n_features)))
    model.add(TimeDistributed(Dropout(dropout_rate)))
    model.add(TimeDistributed(Flatten()))
    model.add(Bidirectional(LSTM(lstm_units, return_sequences=True, activation=ELU(),kernel_initializer=initializer,recurrent_initializer=initializer2)))
    model.add(Attention())
    model.add(GlobalAveragePooling1D())
    model.add(Dense(1,kernel_initializer=initializer))
    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='mse')
    return model

def evaluate_model(model, X_train, y_train, X_val, y_val, X_test, y_test, scaler):
    y_train_pred = scaler.inverse_transform(model.predict(X_train))
    y_val_pred = scaler.inverse_transform(model.predict(X_val))  # Add this
    y_test_pred = scaler.inverse_transform(model.predict(X_test))
    y_train_true = scaler.inverse_transform(y_train.reshape(-1, 1))
    y_val_true = scaler.inverse_transform(y_val.reshape(-1, 1))  # And this
    y_test_true = scaler.inverse_transform(y_test.reshape(-1, 1))

    # Calculate metrics for the training set
    mse_train = mean_squared_error(y_train_true, y_train_pred)
    mae_train = mean_absolute_error(y_train_true, y_train_pred)
    rmse_train = np.sqrt(mse_train)
    mape_train = np.mean(np.abs((y_train_true - y_train_pred)/ y_train_true)) * 100

    # Calculate metrics for the validation set
    mse_val = mean_squared_error(y_val_true, y_val_pred)  # Add this
    mae_val = mean_absolute_error(y_val_true, y_val_pred)  # And this
    rmse_val = np.sqrt(mse_val)  # And this
    mape_val = np.mean(np.abs((y_val_true - y_val_pred) / y_val_true)) * 100  # And this

    # Calculate metrics for the test set
    mse_test = mean_squared_error(y_test_true, y_test_pred)
    mae_test = mean_absolute_error(y_test_true, y_test_pred)
    rmse_test = np.sqrt(mse_test)
    mape_test = np.mean(np.abs((y_test_true - y_test_pred) / y_test_true)) * 100

    return mse_train, mae_train, rmse_train, mape_train, mse_val, mae_val, rmse_val, mape_val, mse_test, mae_test, rmse_test, mape_test  # Update this

filters_options = [4, 8, 16]
kernel_size_options = [3, 5, 7]
lstm_units_options = [5, 10, 20]
dropout_rate_options = [0.1, 0.2, 0.3]
n_steps_options = [30,35,40,45,50,55,60]
epochs = 150

results = []

for n_steps in n_steps_options:
    X_train, y_train = split_sequence(scaled_data_train, n_steps)
    X_val, y_val = split_sequence(scaled_data_val, n_steps)
    X_test, y_test = split_sequence(scaled_data_test, n_steps)

    n_features = 1
    n_seq = 1
    X_train = X_train.reshape(X_train.shape[0], n_seq, n_steps, n_features)
    X_val = X_val.reshape(X_val.shape[0], n_seq, n_steps, n_features)
    X_test = X_test.reshape(X_test.shape[0], n_seq, n_steps, n_features)

    for filters, kernel_size, lstm_units, dropout_rate in itertools.product(filters_options, kernel_size_options, lstm_units_options, dropout_rate_options):
        model = create_model(filters=filters, kernel_size=kernel_size, lstm_units=lstm_units, dropout_rate=dropout_rate)
        model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=epochs, verbose=1)
        mse_train, mae_train, rmse_train, mape_train, mse_val, mae_val, rmse_val, mape_val, mse_test, mae_test, rmse_test, mape_test = evaluate_model(model, X_train, y_train, X_val, y_val, X_test, y_test, scaler)
        results.append({
            'n_steps': n_steps,
            'filters': filters,
            'kernel_size': kernel_size,
            'lstm_units': lstm_units,
            'dropout_rate': dropout_rate,
            'mse_train': mse_train,
            'mae_train': mae_train,
            'rmse_train': rmse_train,
            'mape_train': mape_train,
            'mse_val': mse_val,
            'mae_val': mae_val,
            'rmse_val': rmse_val,
            'mape_val': mape_val,
            'mse_test': mse_test,
            'mae_test': mae_test,
            'rmse_test': rmse_test,
            'mape_test': mape_test,
        })

results_df = pd.DataFrame(results)
results_df.to_csv('30-60steps_BDLSTM_150_ELU_ATTENTION.csv', index=False)

best_results = results_df.loc[results_df.groupby('n_steps')['mape_test'].idxmin()]

print("Best parameters for each n_steps:")
print(best_results)


is my application of the attention mechanism right? because the results are the same with the model that doesn't have attention mechanism included.
this is a gridsearch for the best parameters for a cnn bidirectional lstm time series forecasting model