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

# 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)
K.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
df.head(), df.tail()

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

# Set the random seed for reproducibility
seed_value = 42
np.random.seed(seed_value)
rn.seed(seed_value)
tf.random.set_seed(seed_value)
K.clear_session()

n_steps = 10
n_features = 1
n_seq = 1
# Define the total size for training + validation
train_val_size = int(len(passenger_count) * 0.8)
test_size = len(passenger_count) - train_val_size

# Define the initial training size and step size for sliding the window
initial_train_size = int(train_val_size * 0.75)
step_size = n_steps  # Assuming you want to step by one n_steps increment each time

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

def create_model():
  model = Sequential()

  # Convolutional Layer 1
  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.01)))  # Dropout layer

  # Convolutional Layer 2 - Added another conv layer
  #model.add(TimeDistributed(Conv1D(filters=16, kernel_size=3, activation='relu', kernel_initializer=initializer)))
  #model.add(TimeDistributed(Dropout(0.01)))  # Dropout layer

  model.add(TimeDistributed(Flatten()))

  # LSTM Layer 1 - Bidirectional
  model.add(Bidirectional(LSTM(20, activation='elu', kernel_initializer=initializer, recurrent_initializer=initializer2, return_sequences=True)))
  model.add(Dropout(0.01))  # Dropout layer

  # LSTM Layer 2 - Bidirectional - Added another LSTM layer
  model.add(Bidirectional(LSTM(10, activation='elu', kernel_initializer=initializer, recurrent_initializer=initializer2)))
  model.add(Dropout(0.01))  # Dropout layer


  # Dense Output Layer
  model.add(Dense(1, kernel_initializer=initializer))
  #model.add(Dropout(0.01))

  # Compile the model
  model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='mse')

  return model

# Ensure the loop leaves enough data for at least one validation sequence
for start_index in range(0, train_val_size - initial_train_size - n_steps, step_size):
    # Define the end of the training window
    end_train_index = start_index + initial_train_size
    
    # Ensure there's enough data for at least one validation sequence
    end_val_index = end_train_index + n_steps + 1
    
    # Split the data into training and validation sets
    train_data = passenger_count[start_index:end_train_index]
    val_data = passenger_count[end_train_index:end_val_index]
    
    # Scale the data
    scaler = MinMaxScaler(feature_range=(0, 1))
    train_data_scaled = scaler.fit_transform(train_data.reshape(-1, 1))
    val_data_scaled = scaler.transform(val_data.reshape(-1, 1))
    
    # Prepare the input and output sequences
    X_train, y_train = split_sequence(train_data_scaled, n_steps)
    print(X_train.shape, y_train.shape)  # Add this line to check the shapes
    X_val, y_val = split_sequence(val_data_scaled, n_steps)
    
    # Reshape the input sequences for the model
    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))

    print(f"Train data size: {len(train_data)}")
    print(f"Validation data size: {len(val_data)}")
    
    # Clear the previous model session and create a new model
    K.clear_session()
    model = create_model()
    
    # Train the model
    history = model.fit(
        X_train, y_train, 
        epochs=200, 
        verbose=1, 
        validation_data=(X_val, y_val)
    )

# After training with sliding windows, train a final model on the full train_val_size
# and test it on the remaining data
train_data = passenger_count[:train_val_size]
test_data = passenger_count[train_val_size:]

# Scale the data
scaler = MinMaxScaler(feature_range=(0, 1))
train_data_scaled = scaler.fit_transform(train_data.reshape(-1, 1))
test_data_scaled = scaler.transform(test_data.reshape(-1, 1))

# Prepare the input and output sequences for training and testing
X_train, y_train = split_sequence(train_data_scaled, n_steps)
X_test, y_test = split_sequence(test_data_scaled, n_steps)

# Reshape the input sequences for the model
X_train = X_train.reshape((X_train.shape[0], n_seq, n_steps, n_features))
X_test = X_test.reshape((X_test.shape[0], n_seq, n_steps, n_features))

# Clear session and create final model
K.clear_session()
model = create_model()

# Train the final model
final_history = model.fit(
    X_train, y_train, 
    epochs=200, 
    verbose=1
)



# Evaluate the final model and make predictions on the test dataset
y_pred = model.predict(X_test)

# Reshape the predictions to be 2D (samples, features) for inverse transform
y_pred_reshaped = y_pred.reshape(-1, 1)
y_pred_inverse = scaler.inverse_transform(y_pred_reshaped)

# Reshape y_test to be 2D if necessary
y_test_reshaped = y_test.reshape(-1, 1)
y_true_inverse = scaler.inverse_transform(y_test_reshaped)

# Assign the predicted values for the test set to y_test_pred_inverse
y_test_pred_inverse = y_pred_inverse

# Calculate the evaluation metrics for the test set
mse_test = mean_squared_error(y_true_inverse, y_test_pred_inverse)
mae_test = mean_absolute_error(y_true_inverse, y_test_pred_inverse)
rmse_test = np.sqrt(mse_test)
mape_test = mean_absolute_percentage_error(y_true_inverse, y_test_pred_inverse)  # Use your defined function

# Print the evaluation metrics for the test set
print(f"Test Mean Squared Error: {mse_test}")
print(f"Test Mean Absolute Error: {mae_test}")
print(f"Test Root Mean Squared Error: {rmse_test}")
print(f"Test Mean Absolute Percentage Error: {mape_test}")

# Plot the forecasting for the test set
plt.plot(y_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()
```

is this a good implementation of sliding window for my time series forecasting?