"import os
import numpy as np
import random as rn
import tensorflow as tf
import pandas as pd
from keras.models import Sequential
from keras.layers import Dense, LSTM, TimeDistributed, Flatten, Dropout, Conv1D, Bidirectional, ELU
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error, mean_absolute_error
import matplotlib.pyplot as plt

# 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)

def root_mean_squared_error(y_true, y_pred):
    return np.sqrt(mean_squared_error(y_true, y_pred))

def mean_absolute_percentage_error(y_true, y_pred):
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

# 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  # 20% of the 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:]

# Create the input and target sequences for the train data
n_steps = 35
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)

# Reshape the input data for the training set
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)

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

def create_model():
    model = Sequential()
    model.add(TimeDistributed(Conv1D(filters=8, kernel_size=3, activation='relu', kernel_initializer=initializer), input_shape=(None, n_steps, n_features)))
    model.add(TimeDistributed(Dropout(0.3)))
    model.add(TimeDistributed(Flatten()))
    model.add(Bidirectional(LSTM(20, activation=ELU(), kernel_initializer=initializer, recurrent_initializer=initializer2, return_sequences=True)))
    model.add(Dense(1, kernel_initializer=initializer)) 
    model.compile(optimizer='adam', loss='mse')
    return model

model = create_model()
model.summary()

# Fit the model on the training set and evaluate it on the validation set
model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=150, verbose=1) 


# Evaluate the model and make predictions on the training dataset
y_train_pred = model.predict(X_train)
y_train_pred_2d = np.reshape(y_train_pred, (y_train_pred.shape[0]*y_train_pred.shape[1], y_train_pred.shape[2]))
y_train_pred_inverse = scaler.inverse_transform(y_train_pred_2d)
y_train_true_inverse = scaler.inverse_transform(y_train)

# Calculate metrics for the training set
mse = mean_squared_error(y_train_true_inverse, y_train_pred_inverse)
mae = mean_absolute_error(y_train_true_inverse, y_train_pred_inverse)
rmse = root_mean_squared_error(y_train_true_inverse, y_train_pred_inverse)
mape = mean_absolute_percentage_error(y_train_true_inverse, y_train_pred_inverse)
print(f"Training Mean Squared Error: {mse}")
print(f"Training Mean Absolute Error: {mae}")
print(f"Training Root Mean Squared Error: {rmse}")
print(f"Training Mean Absolute Percentage Error: {mape}")

# Plot the forecasting for the training set
plt.plot(y_train_true_inverse, label='True values', color='blue')
plt.plot(y_train_pred_inverse, label='Forecast', color='orange')
plt.legend()
plt.title("Forecast vs True values on training set using CNN LSTM")
plt.show()

# Evaluate the model and make predictions on the validation set
y_val_pred = model.predict(X_val)
y_val_pred_2d = np.reshape(y_val_pred, (y_val_pred.shape[0]*y_val_pred.shape[1], y_val_pred.shape[2]))
y_val_pred_inverse = scaler.inverse_transform(y_val_pred_2d)
y_val_true_inverse = scaler.inverse_transform(y_val)

# Calculate metrics for the validation set
mse = mean_squared_error(y_val_true_inverse, y_val_pred_inverse)
mae = mean_absolute_error(y_val_true_inverse, y_val_pred_inverse)
rmse = root_mean_squared_error(y_val_true_inverse, y_val_pred_inverse)
mape = mean_absolute_percentage_error(y_val_true_inverse, y_val_pred_inverse)
print(f"Validation Mean Squared Error: {mse}")
print(f"Validation Mean Absolute Error: {mae}")
print(f"Validation Root Mean Squared Error: {rmse}")
print(f"Validation Mean Absolute Percentage Error: {mape}")

# Plot the forecasting for the validation set
plt.plot(y_val_true_inverse, label='True values', color='blue')
plt.plot(y_val_pred_inverse, label='Forecast', color='orange')
plt.legend()
plt.title("Forecast vs True values on validation set using CNN LSTM")
plt.show()

# Evaluate the model and make predictions on the test set
y_test_pred = model.predict(X_test)
y_test_pred_2d = np.reshape(y_test_pred, (y_test_pred.shape[0]*y_test_pred.shape[1], y_test_pred.shape[2]))
y_test_pred_inverse = scaler.inverse_transform(y_test_pred_2d)
y_test_true_inverse = scaler.inverse_transform(y_test)

# Calculate metrics for the test set
mse = mean_squared_error(y_test_true_inverse, y_test_pred_inverse)
mae = mean_absolute_error(y_test_true_inverse, y_test_pred_inverse)
rmse = root_mean_squared_error(y_test_true_inverse, y_test_pred_inverse)
mape = mean_absolute_percentage_error(y_test_true_inverse, y_test_pred_inverse)
print(f"Test Mean Squared Error: {mse}")
print(f"Test Mean Absolute Error: {mae}")
print(f"Test Root Mean Squared Error: {rmse}")
print(f"Test Mean Absolute Percentage Error: {mape}")

# Plot the forecasting for the test set
plt.plot(y_test_true_inverse, label='True values', color='blue')
plt.plot(y_test_pred_inverse, label='Forecast', color='orange')
plt.legend()
plt.title("Forecast vs True values on test set using CNN LSTM")
plt.show()

# Get the date indices for train, validation, and test sets
train_dates = df.index[n_steps:train_size]
val_dates = df.index[train_size + n_steps:train_size + val_size]
test_dates = df.index[train_size + val_size + n_steps:]

# Adjust the lengths of the date indices to match the lengths of the prediction arrays
train_dates = train_dates[:len(y_train_pred_inverse)]
val_dates = val_dates[:len(y_val_pred_inverse)]
test_dates = test_dates[:len(y_test_pred_inverse)]

# Plot the original data
plt.figure(figsize=(15, 6))
plt.plot(df.index, passenger_count, label="Original Data", color="#6BD7FF")

# Plot the predictions for the training set
plt.plot(train_dates, y_train_pred_inverse.flatten(), label="Training Set Predictions", color="green")

# Plot the predictions for the validation set
plt.plot(val_dates, y_val_pred_inverse.flatten(), label="Validation Set Predictions", color="purple")

# Plot the predictions for the test set
plt.plot(test_dates, y_test_pred_inverse.flatten(), label="Test Set Predictions", color="orange")

# Customize the plot
plt.xlabel("Date")
plt.ylabel("Passenger Count")
plt.title("Passenger Count Forecast")
plt.legend()
plt.show()"

is this a good implementation of cnn bidirectional lstm for time series forecasting? is it possible to apply attention mechanism?