Final Project¶

Prediction & Forecasting the stock market price of Microsoft with LSTM¶

Section 1: Introduction¶

The primary objective of this analysis is to predict the stock market price of Microsoft using deep learning methods. Leveraging historical stock market data from March 13, 1986, to December 29, 2023, the aim is to develop a robust predictive model that assists in forecasting Microsoft's stock prices.

In [25]:
# Importing necessary library
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')

# Reading the dataset 'MSFT.csv' into a DataFrame
df = pd.read_csv('MSFT.csv', index_col=False)  # 'MSFT.csv' file contains the historical data of Microsoft stock prices.
df
Out[25]:
Date Open High Low Close Volume
0 1986-03-13 00:00:00-05:00 0.054485 0.062498 0.054485 0.059827 1031788800
1 1986-03-14 00:00:00-05:00 0.059827 0.063032 0.059827 0.061963 308160000
2 1986-03-17 00:00:00-05:00 0.061963 0.063566 0.061963 0.063032 133171200
3 1986-03-18 00:00:00-05:00 0.063032 0.063566 0.060895 0.061429 67766400
4 1986-03-19 00:00:00-05:00 0.061429 0.061963 0.059827 0.060361 47894400
... ... ... ... ... ... ...
9753 2024-11-22 00:00:00-05:00 411.369995 417.399994 411.059998 417.000000 24814600
9754 2024-11-25 00:00:00-05:00 418.380005 421.079987 414.850006 418.790008 27691100
9755 2024-11-26 00:00:00-05:00 419.589996 429.040008 418.850006 427.989990 23458900
9756 2024-11-27 00:00:00-05:00 425.109985 427.230011 422.019989 422.989990 18332400
9757 2024-11-29 00:00:00-05:00 420.089996 424.880005 417.799988 423.459992 16271900

9758 rows × 6 columns

Section 2: Data Exploration¶

Data Overview¶

The initial exploration involved loading the dataset, containing attributes such as 'Date,' 'Open,' 'High,' 'Low,' 'Close,' 'Adjusted Close,' and 'Volume.' Subsequently, the DataFrame was refined to retain only the 'Date' and 'Close' columns, essential for modeling purposes.

In [2]:
# Subsetting the DataFrame to retain only the 'Date' and 'Close' columns
df = df[['Date','Close']]
df
Out[2]:
Date Close
0 1986-03-13 00:00:00-05:00 0.059827
1 1986-03-14 00:00:00-05:00 0.061963
2 1986-03-17 00:00:00-05:00 0.063032
3 1986-03-18 00:00:00-05:00 0.061429
4 1986-03-19 00:00:00-05:00 0.060361
... ... ...
9753 2024-11-22 00:00:00-05:00 417.000000
9754 2024-11-25 00:00:00-05:00 418.790008
9755 2024-11-26 00:00:00-05:00 427.989990
9756 2024-11-27 00:00:00-05:00 422.989990
9757 2024-11-29 00:00:00-05:00 423.459992

9758 rows × 2 columns

Date Type Conversion¶

Data Type Conversion for 'Date' Column , The provided code segment aims to convert the 'Date' column, initially of type 'object', into a datetime type for enhanced temporal analysis.

  • The code initializes a function str_to_datetime that accepts a date string in 'YYYY-MM-DD' format and converts it into a datetime object.
  • str_to_datetime uses the datetime.datetime constructor to create a datetime object from the split year, month, and day values of the input string.
  • An example conversion is demonstrated for the date '1986-03-13' to showcase the conversion process.
In [3]:
# Displaying the 'Date' column from the DataFrame
df['Date']
Out[3]:
0       1986-03-13 00:00:00-05:00
1       1986-03-14 00:00:00-05:00
2       1986-03-17 00:00:00-05:00
3       1986-03-18 00:00:00-05:00
4       1986-03-19 00:00:00-05:00
                  ...            
9753    2024-11-22 00:00:00-05:00
9754    2024-11-25 00:00:00-05:00
9755    2024-11-26 00:00:00-05:00
9756    2024-11-27 00:00:00-05:00
9757    2024-11-29 00:00:00-05:00
Name: Date, Length: 9758, dtype: object
In [4]:
#show exactly what column names are available
print("Column names:")
print(df.columns.tolist())
Column names:
['Date', 'Close']
In [5]:
print("Current columns:", df.columns.tolist())
print("Index name:", df.index.name)
print("DataFrame shape:", df.shape)
print("\nFirst few rows:")
print(df.head())
Current columns: ['Date', 'Close']
Index name: None
DataFrame shape: (9758, 2)

First few rows:
                        Date     Close
0  1986-03-13 00:00:00-05:00  0.059827
1  1986-03-14 00:00:00-05:00  0.061963
2  1986-03-17 00:00:00-05:00  0.063032
3  1986-03-18 00:00:00-05:00  0.061429
4  1986-03-19 00:00:00-05:00  0.060361
In [6]:
# Reset index to turn 'Date' back into a column
df = df.reset_index()

# Now convert the 'Date' column
df['Date'] = pd.to_datetime(df['Date'], errors='coerce', utc=True)

# Set it back as index
df.set_index('Date', inplace=True)

# Optional: Check for failed conversions
if df.index.isna().sum() > 0:
    print("\n⚠️ Warning: Some dates could not be parsed:")
    print(df[df.index.isna()])
In [7]:
# this confirm the index is datetime
print("Index dtype:", df.index.dtype)
print("Is index tz-aware?", df.index.tz is not None)
Index dtype: datetime64[ns, UTC]
Is index tz-aware? True
In [9]:
print("Current columns:", df.columns.tolist())
print("Index name:", df.index.name)
print("Index type:", type(df.index))
print("First few index values:")
print(df.index[:5])
Current columns: ['index', 'Close']
Index name: Date
Index type: <class 'pandas.core.indexes.datetimes.DatetimeIndex'>
First few index values:
DatetimeIndex(['1986-03-13 05:00:00+00:00', '1986-03-14 05:00:00+00:00',
               '1986-03-17 05:00:00+00:00', '1986-03-18 05:00:00+00:00',
               '1986-03-19 05:00:00+00:00'],
              dtype='datetime64[ns, UTC]', name='Date', freq=None)
In [11]:
# Clean It Up
# Drop the 'index' column if it's not needed
if 'index' in df.columns:
    df = df.drop(columns=['index'])

# Optional: Verify
print("Final columns:", df.columns.tolist())
print("Index name:", df.index.name)
Final columns: ['Close']
Index name: Date
In [13]:
# This to confirm everything is perfect
print("✅ DataFrame Info:")
print(df.info())

print("\n✅ First few rows:")
print(df.head())

print("\n✅ Index type:")
print(type(df.index))
print("Index dtype:", df.index.dtype)
print("Is index tz-aware?", df.index.tz is not None)
✅ DataFrame Info:
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 9758 entries, 1986-03-13 05:00:00+00:00 to 2024-11-29 05:00:00+00:00
Data columns (total 1 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Close   9758 non-null   float64
dtypes: float64(1)
memory usage: 152.5 KB
None

✅ First few rows:
                              Close
Date                               
1986-03-13 05:00:00+00:00  0.059827
1986-03-14 05:00:00+00:00  0.061963
1986-03-17 05:00:00+00:00  0.063032
1986-03-18 05:00:00+00:00  0.061429
1986-03-19 05:00:00+00:00  0.060361

✅ Index type:
<class 'pandas.core.indexes.datetimes.DatetimeIndex'>
Index dtype: datetime64[ns, UTC]
Is index tz-aware? True
In [15]:
import matplotlib.pyplot as plt

# Plotting the 'Close' values against the DataFrame index
plt.plot(df.index, df['Close'])
Out[15]:
[<matplotlib.lines.Line2D at 0x1e32097afc0>]
No description has been provided for this image

Section 3: Data Preparation for Modeling¶

Windowed DataFrame Generation¶

This section involves the creation of a windowed DataFrame, leveraging the function df_to_windowed_df, to structure the existing dataset for analysis. The generated windowed DataFrame aims to organize data into specific windows, setting the groundwork for subsequent time-based analysis or modeling tasks.

Purpose:¶

  • Windowed DataFrame Creation: Focuses on segmenting the original dataset into distinct windows, allowing for structured representation suitable for time series or sequential analysis.
  • Data Transformation for Analysis: Following the windowed DataFrame creation, on transforming this structured data into organized components—dates, input features (X), and output values (y)—essential for predictive modeling tasks or time-based analysis.

Code Complexity and Development Effort:¶

  • Code Complexity: Moderate-to-high due to date handling, DataFrame manipulation, and iterative processes involved.

  • Development Effort: Significant time and effort invested in comprehending, developing, and refining code functionalities, requiring thorough investigation and testing.

Overview:¶

The combined sections involve the creation of a windowed DataFrame and its conversion into structured data components—dates, input features (X), and output values (y). This process significantly contributes to the preparation of data suitable for predictive modeling, emphasizing the importance of organized data for time series analysis and forecasting tasks.

In [26]:
# Define helper function to convert string dates to datetime objects
def str_to_datetime(s):
    """
    Convert string in format 'YYYY-MM-DD' to datetime object
    
    Args:
        s (str): Date string in format 'YYYY-MM-DD'
    
    Returns:
        datetime: Converted datetime object
    """
    return datetime.strptime(s, '%Y-%m-%d')

print("✅ str_to_datetime function defined")
print("Example usage:", str_to_datetime('2020-01-01'))
✅ str_to_datetime function defined
Example usage: 2020-01-01 00:00:00
In [27]:
def df_to_windowed_df(dataframe, first_date_str, last_date_str, n=3):
    """
    Create a windowed DataFrame for supervised learning.
    
    This function transforms sequential time series data into a format where:
    - Input (X): Previous n days' closing prices
    - Output (y): Next day's closing price
    
    Args:
        dataframe (pd.DataFrame): DataFrame with DatetimeIndex and 'Close' column
        first_date_str (str): Start date as 'YYYY-MM-DD'
        last_date_str (str): End date as 'YYYY-MM-DD'
        n (int): Number of previous days to use for prediction (default: 3)
    
    Returns:
        pd.DataFrame: Windowed DataFrame with columns:
                     ['Target Date', 'Target-3', 'Target-2', 'Target-1', 'Target']
    """
    # Convert string dates to datetime objects
    start_date = str_to_datetime(first_date_str)
    end_date = str_to_datetime(last_date_str)
    
    # Filter data within the specified date range
    filtered_df = dataframe[(dataframe.index >= start_date) & 
                           (dataframe.index <= end_date)]
    
    # Check if we have any data in the specified range
    if filtered_df.empty:
        print(f"⚠️ Warning: No data found between {first_date_str} and {last_date_str}")
        return pd.DataFrame()
    
    # Initialize lists to store the results
    dates = []
    X, Y = [], []
    
    # Loop through the data to create sliding windows
    for i in range(n, len(filtered_df)):
        # Get the target date (current row)
        target_date = filtered_df.index[i]
        
        # Get the n+1 previous values (for input and target)
        window_values = filtered_df['Close'].iloc[i-n:i+1].values
        
        # Split into input (X) and output (y)
        x = window_values[:-1]  # First n values (previous days)
        y = window_values[-1]   # Last value (target day)
        
        # Store the results
        dates.append(target_date)
        X.append(x)
        Y.append(y)
    
    # Create result DataFrame if we have data
    if not dates:
        print(f"⚠️ Warning: Not enough data for window size n={n}")
        return pd.DataFrame()
    
    # Create the result DataFrame
    result_df = pd.DataFrame({'Target Date': dates})
    
    # Add input features (X values)
    X_array = np.array(X)
    for i in range(n):
        result_df[f'Target-{n-i}'] = X_array[:, i]
    
    # Add target values (y)
    result_df['Target'] = Y
    
    return result_df

print("✅ df_to_windowed_df function defined")
print("Function docstring:", df_to_windowed_df.__doc__[:200] + "...")
✅ df_to_windowed_df function defined
Function docstring: 
    Create a windowed DataFrame for supervised learning.
    
    This function transforms sequential time series data into a format where:
    - Input (X): Previous n days' closing prices
    - Outp...
In [30]:
# After setting the index, ensure it's a proper DatetimeIndex
df.index = pd.to_datetime(df.index, errors='coerce', utc=True)

# Verify
print("Index type after fix:", type(df.index))
print("Index dtype after fix:", df.index.dtype)
Index type after fix: <class 'pandas.core.indexes.datetimes.DatetimeIndex'>
Index dtype after fix: datetime64[ns, UTC]
In [33]:
# Define helper function
def str_to_datetime(s):
    """Convert string to timezone-aware datetime (UTC)."""
    return pd.to_datetime(s, utc=True)

# Create windowed data
print("🔄 Creating windowed DataFrame...")
windowed_df = df_to_windowed_df(
    dataframe=df,
    first_date_str='2018-12-29',
    last_date_str='2023-12-29',
    n=3
)

if not windowed_df.empty:
    print(f"✅ Windowed data created: {windowed_df.shape[0]} samples")
    print("\nFirst few rows:")
    print(windowed_df.head())
else:
    print("❌ Failed to create windowed data")
🔄 Creating windowed DataFrame...
⚠️ Warning: No data found between 2018-12-29 and 2023-12-29
❌ Failed to create windowed data
In [35]:
print("📊 Actual Date Range in Your Data:")
print(f"Min date: {df.index.min()}")
print(f"Max date: {df.index.max()}")

# Show first few and last few dates
print("\n📅 First 5 dates:")
print(df.index[:5])

print("\n📅 Last 5 dates:")
print(df.index[-5:])
📊 Actual Date Range in Your Data:
Min date: 1970-01-01 00:00:00+00:00
Max date: 1970-01-01 00:00:00.000009757+00:00

📅 First 5 dates:
DatetimeIndex([          '1970-01-01 00:00:00+00:00',
               '1970-01-01 00:00:00.000000001+00:00',
               '1970-01-01 00:00:00.000000002+00:00',
               '1970-01-01 00:00:00.000000003+00:00',
               '1970-01-01 00:00:00.000000004+00:00'],
              dtype='datetime64[ns, UTC]', freq=None)

📅 Last 5 dates:
DatetimeIndex(['1970-01-01 00:00:00.000009753+00:00',
               '1970-01-01 00:00:00.000009754+00:00',
               '1970-01-01 00:00:00.000009755+00:00',
               '1970-01-01 00:00:00.000009756+00:00',
               '1970-01-01 00:00:00.000009757+00:00'],
              dtype='datetime64[ns, UTC]', freq=None)
In [36]:
actual_start = df.index.min().strftime('%Y-%m-%d')
actual_end = df.index.max().strftime('%Y-%m-%d')

print(f"✅ Using actual date range: {actual_start} to {actual_end}")

windowed_df = df_to_windowed_df(
    dataframe=df,
    first_date_str=actual_start,
    last_date_str=actual_end,
    n=3
)
✅ Using actual date range: 1970-01-01 to 1970-01-01
⚠️ Warning: Not enough data for window size n=3
In [37]:
print("🔍 First 20 raw Date values:")
print(df['Date'].head(20).tolist())

print("\n📊 Unique Date Values (Top 10):")
print(df['Date'].value_counts().head(10))

print("\n❌ How many NaT (Not a Time) values?")
print(df['Date'].isna().sum())
🔍 First 20 raw Date values:
['1986-03-13 00:00:00-05:00', '1986-03-14 00:00:00-05:00', '1986-03-17 00:00:00-05:00', '1986-03-18 00:00:00-05:00', '1986-03-19 00:00:00-05:00', '1986-03-20 00:00:00-05:00', '1986-03-21 00:00:00-05:00', '1986-03-24 00:00:00-05:00', '1986-03-25 00:00:00-05:00', '1986-03-26 00:00:00-05:00', '1986-03-27 00:00:00-05:00', '1986-03-31 00:00:00-05:00', '1986-04-01 00:00:00-05:00', '1986-04-02 00:00:00-05:00', '1986-04-03 00:00:00-05:00', '1986-04-04 00:00:00-05:00', '1986-04-07 00:00:00-05:00', '1986-04-08 00:00:00-05:00', '1986-04-09 00:00:00-05:00', '1986-04-10 00:00:00-05:00']

📊 Unique Date Values (Top 10):
Date
1986-03-13 00:00:00-05:00    1
2011-12-30 00:00:00-05:00    1
2011-12-20 00:00:00-05:00    1
2011-12-21 00:00:00-05:00    1
2011-12-22 00:00:00-05:00    1
2011-12-23 00:00:00-05:00    1
2011-12-27 00:00:00-05:00    1
2011-12-28 00:00:00-05:00    1
2011-12-29 00:00:00-05:00    1
2012-01-03 00:00:00-05:00    1
Name: count, dtype: int64

❌ How many NaT (Not a Time) values?
0
In [38]:
# Try common formats
df['Date'] = pd.to_datetime(df['Date'], errors='coerce', utc=True, format='%b %d, %Y')

# If that doesn't work, try without format (let pandas guess)
df['Date'] = pd.to_datetime(df['Date'], errors='coerce', utc=True)

# Or try day-first format (common outside US)
df['Date'] = pd.to_datetime(df['Date'], errors='coerce', utc=True, dayfirst=True)
In [41]:
# Convert to string first
df['Date'] = df['Date'].astype(str)

# Now strip whitespace
df['Date'] = df['Date'].str.strip()

# Then convert back to datetime
df['Date'] = pd.to_datetime(df['Date'], errors='coerce', utc=True)
In [42]:
# Keep only Date and Close columns
df = df[['Date', 'Close']].copy()

# Step 1: Convert to string (to handle any type)
df['Date'] = df['Date'].astype(str)

# Step 2: Clean whitespace
df['Date'] = df['Date'].str.strip()

# Step 3: Optional - Remove extra characters if needed
# df['Date'] = df['Date'].str.replace(r'[^a-zA-Z0-9\-/:\s]', '', regex=True)

# Step 4: Try parsing with multiple formats
formats = [
    '%Y-%m-%d',
    '%b %d, %Y',
    '%B %d, %Y',
    '%d/%m/%Y',
    '%m/%d/%Y',
    '%Y/%m/%d'
]

for fmt in formats:
    print(f"Trying format: {fmt}")
    temp = pd.to_datetime(df['Date'], errors='coerce', utc=True, format=fmt)
    if temp.notna().sum() > 0:
        print(f"✅ Found {temp.notna().sum()} valid dates using {fmt}")
        df['Date'] = temp
        break

# If no format worked, try without format (pandas will guess)
if df['Date'].isna().sum() == len(df):
    print("⚠️ No format worked. Trying pandas auto-parse...")
    df['Date'] = pd.to_datetime(df['Date'], errors='coerce', utc=True)

# Set index
df.set_index('Date', inplace=True)
df.sort_index(inplace=True)
df.dropna(inplace=True)

# Verify
print(f"\n✅ Final Data Range: {df.index.min()} to {df.index.max()}")
print(f"Number of rows: {len(df)}")

# Show first few dates
print("\n📅 First 5 dates:")
print(df.index[:5])
Trying format: %Y-%m-%d
Trying format: %b %d, %Y
Trying format: %B %d, %Y
Trying format: %d/%m/%Y
Trying format: %m/%d/%Y
Trying format: %Y/%m/%d

✅ Final Data Range: NaT to NaT
Number of rows: 9758

📅 First 5 dates:
Index(['NaT', 'NaT', 'NaT', 'NaT', 'NaT'], dtype='object', name='Date')
In [42]:
# Keep only Date and Close columns
df = df[['Date', 'Close']].copy()

# Step 1: Convert to string (to handle any type)
df['Date'] = df['Date'].astype(str)

# Step 2: Clean whitespace
df['Date'] = df['Date'].str.strip()

# Step 3: Optional - Remove extra characters if needed
# df['Date'] = df['Date'].str.replace(r'[^a-zA-Z0-9\-/:\s]', '', regex=True)

# Step 4: Try parsing with multiple formats
formats = [
    '%Y-%m-%d',
    '%b %d, %Y',
    '%B %d, %Y',
    '%d/%m/%Y',
    '%m/%d/%Y',
    '%Y/%m/%d'
]

for fmt in formats:
    print(f"Trying format: {fmt}")
    temp = pd.to_datetime(df['Date'], errors='coerce', utc=True, format=fmt)
    if temp.notna().sum() > 0:
        print(f"✅ Found {temp.notna().sum()} valid dates using {fmt}")
        df['Date'] = temp
        break

# If no format worked, try without format (pandas will guess)
if df['Date'].isna().sum() == len(df):
    print("⚠️ No format worked. Trying pandas auto-parse...")
    df['Date'] = pd.to_datetime(df['Date'], errors='coerce', utc=True)

# Set index
df.set_index('Date', inplace=True)
df.sort_index(inplace=True)
df.dropna(inplace=True)

# Verify
print(f"\n✅ Final Data Range: {df.index.min()} to {df.index.max()}")
print(f"Number of rows: {len(df)}")

# Show first few dates
print("\n📅 First 5 dates:")
print(df.index[:5])
Trying format: %Y-%m-%d
Trying format: %b %d, %Y
Trying format: %B %d, %Y
Trying format: %d/%m/%Y
Trying format: %m/%d/%Y
Trying format: %Y/%m/%d

✅ Final Data Range: NaT to NaT
Number of rows: 9758

📅 First 5 dates:
Index(['NaT', 'NaT', 'NaT', 'NaT', 'NaT'], dtype='object', name='Date')

In this final part of this section, the code finalizes the dataset preparation by dividing it into distinct subsets: training, validation, and test sets. The dataset is segmented into these subsets based on defined proportions: 80% for training, 10% for validation, and 10% for testing. This partitioning is vital for evaluating the model's performance on unseen data after training and validation. The code then uses Matplotlib to create a visual representation, plotting each set—training, validation, and test—across time, enabling a clear comparison between these partitions. This step ensures that the model is trained on a substantial portion of the data, validated on a separate portion, and ultimately tested on completely unseen data to gauge its real-world predictive capacity.

In [43]:
# Splitting dataset into training, validation, and test sets
q_80 = int(len(dates) * .8)  # Index for 80% of the dataset
q_90 = int(len(dates) * .9)  # Index for 90% of the dataset

dates_train, X_train, y_train = dates[:q_80], X[:q_80], y[:q_80]  # Training set

dates_val, X_val, y_val = dates[q_80:q_90], X[q_80:q_90], y[q_80:q_90]  # Validation set
dates_test, X_test, y_test = dates[q_90:], X[q_90:], y[q_90:]      # Test set

# Plotting the subsets
plt.figure(figsize=(10,6))
plt.plot(dates_train, y_train)
plt.plot(dates_val, y_val)
plt.plot(dates_test, y_test)

plt.legend(['Train', 'Validation', 'Test'])
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[43], line 2
      1 # Splitting dataset into training, validation, and test sets
----> 2 q_80 = int(len(dates) * .8)  # Index for 80% of the dataset
      3 q_90 = int(len(dates) * .9)  # Index for 90% of the dataset
      5 dates_train, X_train, y_train = dates[:q_80], X[:q_80], y[:q_80]  # Training set

NameError: name 'dates' is not defined

Section 4 : Section 4: LSTM Model Building¶

Overview:¶

This section focuses on the construction and evaluation of a Long Short-Term Memory (LSTM) model using TensorFlow and Keras. The LSTM architecture is a type of recurrent neural network (RNN) known for its ability to process sequences of data and capture long-term dependencies within the data.

Purpose:¶

  • Model Construction: The code segment initiates the construction of the LSTM model using TensorFlow and Keras. The model consists of an input layer, LSTM layer, and several densely connected layers.
  • Model Compilation: It compiles the model with mean squared error (MSE) as the loss function and the Adam optimizer with a specific learning rate.
  • Model Training: The model is trained on the training set (X_train, y_train) for a specified number of epochs, and its performance is evaluated on the validation set (X_val, y_val).
  • Prediction Visualization: Subsequent code segments generate visualizations showcasing the model's predictions against the observed values for the validation and test datasets, providing insights into the model's performance.
  • Recursive Predictions: Another part of the code generates recursive predictions where the model forecasts the next value based on its previous prediction, iterating over the validation and test sets.

Methodology:¶

  • Model Architecture: Sequential model with an LSTM layer followed by densely connected layers with ReLU activation.
  • Model Training: Trained using the training data with a defined optimizer and loss function.
  • Prediction Evaluation: Compares model predictions with observed values for validation and test datasets, assessing model performance.
  • Recursive Forecasting: Generates predictions recursively, where each prediction feeds into the model for the next prediction iteration. This methodology aims to showcase the LSTM model's ability to forecast time series data and assess its performance against different subsets of the dataset.
In [92]:
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import layers

# Create a sequential model
model = Sequential([layers.Input((3, 1)),    # Input layer with shape (3, 1)
                    layers.LSTM(64),          # LSTM layer with 64 units
                    layers.Dense(32, activation='relu'),   # Dense layer with 32 units and ReLU activation
                    layers.Dense(32, activation='relu'),   # Another dense layer with 32 units and ReLU activation
                    layers.Dense(1)])               # Output layer with single unit

# Compile the model with mean squared error loss, Adam optimizer with a specific learning rate,
# and mean absolute error as a metric
model.compile(loss='mse', 
              optimizer=Adam(learning_rate=0.001),
              metrics=['mean_absolute_error'])

# Train the model on the training set (X_train, y_train) using validation data (X_val, y_val) for 100 epochs
model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=100)
Epoch 1/100
32/32 [==============================] - 5s 31ms/step - loss: 50726.1445 - mean_absolute_error: 215.5414 - val_loss: 79808.7656 - val_mean_absolute_error: 280.3969
Epoch 2/100
32/32 [==============================] - 0s 8ms/step - loss: 48372.3711 - mean_absolute_error: 210.0076 - val_loss: 74484.0781 - val_mean_absolute_error: 270.7208
Epoch 3/100
32/32 [==============================] - 0s 7ms/step - loss: 39159.3711 - mean_absolute_error: 186.0914 - val_loss: 52218.4219 - val_mean_absolute_error: 225.8048
Epoch 4/100
32/32 [==============================] - 0s 8ms/step - loss: 17394.5254 - mean_absolute_error: 113.5933 - val_loss: 16910.2520 - val_mean_absolute_error: 125.3984
Epoch 5/100
32/32 [==============================] - 0s 7ms/step - loss: 5030.3726 - mean_absolute_error: 60.2486 - val_loss: 5049.3706 - val_mean_absolute_error: 62.1609
Epoch 6/100
32/32 [==============================] - 0s 11ms/step - loss: 3496.0933 - mean_absolute_error: 50.2873 - val_loss: 4686.9941 - val_mean_absolute_error: 60.4328
Epoch 7/100
32/32 [==============================] - 0s 8ms/step - loss: 1377.8937 - mean_absolute_error: 27.9828 - val_loss: 1587.2222 - val_mean_absolute_error: 30.0090
Epoch 8/100
32/32 [==============================] - 0s 9ms/step - loss: 286.8610 - mean_absolute_error: 9.7137 - val_loss: 501.8519 - val_mean_absolute_error: 16.0639
Epoch 9/100
32/32 [==============================] - 0s 8ms/step - loss: 103.9734 - mean_absolute_error: 6.0067 - val_loss: 239.8957 - val_mean_absolute_error: 10.9306
Epoch 10/100
32/32 [==============================] - 0s 13ms/step - loss: 60.3380 - mean_absolute_error: 4.7646 - val_loss: 152.8372 - val_mean_absolute_error: 8.9447
Epoch 11/100
32/32 [==============================] - 0s 12ms/step - loss: 43.9838 - mean_absolute_error: 4.1641 - val_loss: 105.4178 - val_mean_absolute_error: 7.5323
Epoch 12/100
32/32 [==============================] - 0s 12ms/step - loss: 35.2579 - mean_absolute_error: 3.8796 - val_loss: 87.3697 - val_mean_absolute_error: 6.9575
Epoch 13/100
32/32 [==============================] - 0s 11ms/step - loss: 36.0701 - mean_absolute_error: 4.2106 - val_loss: 79.3274 - val_mean_absolute_error: 6.6942
Epoch 14/100
32/32 [==============================] - 0s 12ms/step - loss: 32.7236 - mean_absolute_error: 4.1058 - val_loss: 59.1139 - val_mean_absolute_error: 6.2949
Epoch 15/100
32/32 [==============================] - 0s 7ms/step - loss: 29.1068 - mean_absolute_error: 3.8779 - val_loss: 56.1612 - val_mean_absolute_error: 5.6483
Epoch 16/100
32/32 [==============================] - 0s 8ms/step - loss: 26.7665 - mean_absolute_error: 3.7200 - val_loss: 50.4490 - val_mean_absolute_error: 5.3375
Epoch 17/100
32/32 [==============================] - 0s 8ms/step - loss: 24.5873 - mean_absolute_error: 3.5160 - val_loss: 60.8637 - val_mean_absolute_error: 6.1548
Epoch 18/100
32/32 [==============================] - 0s 8ms/step - loss: 31.4589 - mean_absolute_error: 4.1101 - val_loss: 36.7707 - val_mean_absolute_error: 4.9632
Epoch 19/100
32/32 [==============================] - 0s 8ms/step - loss: 26.8563 - mean_absolute_error: 3.7649 - val_loss: 34.9859 - val_mean_absolute_error: 4.6048
Epoch 20/100
32/32 [==============================] - 0s 11ms/step - loss: 24.2428 - mean_absolute_error: 3.5081 - val_loss: 34.8649 - val_mean_absolute_error: 4.5156
Epoch 21/100
32/32 [==============================] - 0s 8ms/step - loss: 24.1515 - mean_absolute_error: 3.6006 - val_loss: 30.3779 - val_mean_absolute_error: 4.5209
Epoch 22/100
32/32 [==============================] - 0s 7ms/step - loss: 23.9884 - mean_absolute_error: 3.6063 - val_loss: 41.7536 - val_mean_absolute_error: 5.3572
Epoch 23/100
32/32 [==============================] - 0s 8ms/step - loss: 28.9343 - mean_absolute_error: 4.0460 - val_loss: 35.1988 - val_mean_absolute_error: 4.5060
Epoch 24/100
32/32 [==============================] - 0s 9ms/step - loss: 22.1466 - mean_absolute_error: 3.4944 - val_loss: 36.5249 - val_mean_absolute_error: 4.6173
Epoch 25/100
32/32 [==============================] - 0s 8ms/step - loss: 21.0343 - mean_absolute_error: 3.3313 - val_loss: 28.9334 - val_mean_absolute_error: 4.4262
Epoch 26/100
32/32 [==============================] - 0s 11ms/step - loss: 21.9365 - mean_absolute_error: 3.4103 - val_loss: 60.4685 - val_mean_absolute_error: 6.3462
Epoch 27/100
32/32 [==============================] - 0s 7ms/step - loss: 25.0985 - mean_absolute_error: 3.7659 - val_loss: 34.3633 - val_mean_absolute_error: 4.4097
Epoch 28/100
32/32 [==============================] - 0s 8ms/step - loss: 22.4752 - mean_absolute_error: 3.5568 - val_loss: 33.2390 - val_mean_absolute_error: 4.4080
Epoch 29/100
32/32 [==============================] - 0s 7ms/step - loss: 21.5835 - mean_absolute_error: 3.3964 - val_loss: 26.8974 - val_mean_absolute_error: 4.1572
Epoch 30/100
32/32 [==============================] - 0s 10ms/step - loss: 25.6173 - mean_absolute_error: 3.7936 - val_loss: 26.5916 - val_mean_absolute_error: 3.9785
Epoch 31/100
32/32 [==============================] - 0s 8ms/step - loss: 25.5473 - mean_absolute_error: 3.7232 - val_loss: 81.9715 - val_mean_absolute_error: 7.6601
Epoch 32/100
32/32 [==============================] - 0s 7ms/step - loss: 25.8138 - mean_absolute_error: 3.7710 - val_loss: 30.1247 - val_mean_absolute_error: 4.5052
Epoch 33/100
32/32 [==============================] - 0s 7ms/step - loss: 20.3365 - mean_absolute_error: 3.3070 - val_loss: 36.0484 - val_mean_absolute_error: 4.6115
Epoch 34/100
32/32 [==============================] - 0s 7ms/step - loss: 21.2953 - mean_absolute_error: 3.3291 - val_loss: 33.9067 - val_mean_absolute_error: 4.4085
Epoch 35/100
32/32 [==============================] - 0s 11ms/step - loss: 22.6468 - mean_absolute_error: 3.5000 - val_loss: 25.2795 - val_mean_absolute_error: 4.0287
Epoch 36/100
32/32 [==============================] - 0s 9ms/step - loss: 22.0695 - mean_absolute_error: 3.4399 - val_loss: 25.1860 - val_mean_absolute_error: 3.9477
Epoch 37/100
32/32 [==============================] - 0s 7ms/step - loss: 22.0614 - mean_absolute_error: 3.4256 - val_loss: 31.8564 - val_mean_absolute_error: 4.2846
Epoch 38/100
32/32 [==============================] - 0s 7ms/step - loss: 22.7449 - mean_absolute_error: 3.4957 - val_loss: 25.2479 - val_mean_absolute_error: 3.8817
Epoch 39/100
32/32 [==============================] - 0s 8ms/step - loss: 25.0833 - mean_absolute_error: 3.6898 - val_loss: 38.5298 - val_mean_absolute_error: 4.7794
Epoch 40/100
32/32 [==============================] - 0s 11ms/step - loss: 21.0166 - mean_absolute_error: 3.3372 - val_loss: 24.5818 - val_mean_absolute_error: 3.8404
Epoch 41/100
32/32 [==============================] - 0s 8ms/step - loss: 19.3731 - mean_absolute_error: 3.1792 - val_loss: 27.0325 - val_mean_absolute_error: 4.0314
Epoch 42/100
32/32 [==============================] - 0s 7ms/step - loss: 26.0746 - mean_absolute_error: 3.7549 - val_loss: 27.3762 - val_mean_absolute_error: 3.8948
Epoch 43/100
32/32 [==============================] - 0s 9ms/step - loss: 20.2978 - mean_absolute_error: 3.2653 - val_loss: 26.6908 - val_mean_absolute_error: 3.8598
Epoch 44/100
32/32 [==============================] - 0s 10ms/step - loss: 21.7916 - mean_absolute_error: 3.4047 - val_loss: 43.4781 - val_mean_absolute_error: 5.2178
Epoch 45/100
32/32 [==============================] - 0s 7ms/step - loss: 22.8816 - mean_absolute_error: 3.5740 - val_loss: 46.8919 - val_mean_absolute_error: 5.4479
Epoch 46/100
32/32 [==============================] - 0s 7ms/step - loss: 20.1738 - mean_absolute_error: 3.2714 - val_loss: 25.8548 - val_mean_absolute_error: 4.0512
Epoch 47/100
32/32 [==============================] - 0s 7ms/step - loss: 22.9297 - mean_absolute_error: 3.4697 - val_loss: 36.2870 - val_mean_absolute_error: 4.9797
Epoch 48/100
32/32 [==============================] - 0s 9ms/step - loss: 23.3037 - mean_absolute_error: 3.5083 - val_loss: 25.6105 - val_mean_absolute_error: 3.9776
Epoch 49/100
32/32 [==============================] - 0s 10ms/step - loss: 22.2615 - mean_absolute_error: 3.4498 - val_loss: 24.8393 - val_mean_absolute_error: 3.8651
Epoch 50/100
32/32 [==============================] - 0s 7ms/step - loss: 21.8947 - mean_absolute_error: 3.4305 - val_loss: 27.8521 - val_mean_absolute_error: 3.9416
Epoch 51/100
32/32 [==============================] - 0s 7ms/step - loss: 20.6147 - mean_absolute_error: 3.3327 - val_loss: 37.7302 - val_mean_absolute_error: 4.7183
Epoch 52/100
32/32 [==============================] - 0s 7ms/step - loss: 31.5291 - mean_absolute_error: 4.2654 - val_loss: 32.8833 - val_mean_absolute_error: 4.6925
Epoch 53/100
32/32 [==============================] - 0s 7ms/step - loss: 22.3771 - mean_absolute_error: 3.4268 - val_loss: 39.3379 - val_mean_absolute_error: 4.8987
Epoch 54/100
32/32 [==============================] - 0s 7ms/step - loss: 21.0260 - mean_absolute_error: 3.3026 - val_loss: 29.5800 - val_mean_absolute_error: 4.4387
Epoch 55/100
32/32 [==============================] - 0s 12ms/step - loss: 30.9052 - mean_absolute_error: 4.1463 - val_loss: 35.7195 - val_mean_absolute_error: 4.8670
Epoch 56/100
32/32 [==============================] - 0s 7ms/step - loss: 26.6116 - mean_absolute_error: 3.8209 - val_loss: 32.5536 - val_mean_absolute_error: 4.3137
Epoch 57/100
32/32 [==============================] - 0s 8ms/step - loss: 20.6008 - mean_absolute_error: 3.2420 - val_loss: 27.0348 - val_mean_absolute_error: 4.2155
Epoch 58/100
32/32 [==============================] - 0s 7ms/step - loss: 23.4402 - mean_absolute_error: 3.5081 - val_loss: 24.6843 - val_mean_absolute_error: 3.8621
Epoch 59/100
32/32 [==============================] - 0s 7ms/step - loss: 21.6499 - mean_absolute_error: 3.3737 - val_loss: 41.0533 - val_mean_absolute_error: 4.9862
Epoch 60/100
32/32 [==============================] - 0s 12ms/step - loss: 23.2434 - mean_absolute_error: 3.5644 - val_loss: 30.1804 - val_mean_absolute_error: 4.1528
Epoch 61/100
32/32 [==============================] - 0s 7ms/step - loss: 21.8650 - mean_absolute_error: 3.3928 - val_loss: 64.9241 - val_mean_absolute_error: 6.5724
Epoch 62/100
32/32 [==============================] - 0s 9ms/step - loss: 25.0806 - mean_absolute_error: 3.7527 - val_loss: 24.5863 - val_mean_absolute_error: 3.9597
Epoch 63/100
32/32 [==============================] - 0s 8ms/step - loss: 23.7072 - mean_absolute_error: 3.5963 - val_loss: 29.9752 - val_mean_absolute_error: 4.4513
Epoch 64/100
32/32 [==============================] - 0s 7ms/step - loss: 20.7408 - mean_absolute_error: 3.3504 - val_loss: 30.3059 - val_mean_absolute_error: 4.1228
Epoch 65/100
32/32 [==============================] - 0s 11ms/step - loss: 21.0484 - mean_absolute_error: 3.3621 - val_loss: 24.0271 - val_mean_absolute_error: 3.8282
Epoch 66/100
32/32 [==============================] - 0s 7ms/step - loss: 23.6899 - mean_absolute_error: 3.6150 - val_loss: 26.0410 - val_mean_absolute_error: 4.0932
Epoch 67/100
32/32 [==============================] - 0s 8ms/step - loss: 20.7963 - mean_absolute_error: 3.3253 - val_loss: 38.3111 - val_mean_absolute_error: 4.8907
Epoch 68/100
32/32 [==============================] - 0s 8ms/step - loss: 20.9105 - mean_absolute_error: 3.3548 - val_loss: 27.9666 - val_mean_absolute_error: 4.1168
Epoch 69/100
32/32 [==============================] - 0s 8ms/step - loss: 20.1888 - mean_absolute_error: 3.2586 - val_loss: 25.7007 - val_mean_absolute_error: 3.7996
Epoch 70/100
32/32 [==============================] - 0s 12ms/step - loss: 21.9967 - mean_absolute_error: 3.3751 - val_loss: 24.3632 - val_mean_absolute_error: 3.9003
Epoch 71/100
32/32 [==============================] - 0s 7ms/step - loss: 20.7171 - mean_absolute_error: 3.2270 - val_loss: 34.3956 - val_mean_absolute_error: 4.7486
Epoch 72/100
32/32 [==============================] - 0s 7ms/step - loss: 22.5949 - mean_absolute_error: 3.4522 - val_loss: 24.0795 - val_mean_absolute_error: 3.8568
Epoch 73/100
32/32 [==============================] - 0s 7ms/step - loss: 19.2761 - mean_absolute_error: 3.1225 - val_loss: 24.2182 - val_mean_absolute_error: 3.8945
Epoch 74/100
32/32 [==============================] - 0s 13ms/step - loss: 20.0854 - mean_absolute_error: 3.2272 - val_loss: 34.6308 - val_mean_absolute_error: 4.5167
Epoch 75/100
32/32 [==============================] - 0s 7ms/step - loss: 21.7760 - mean_absolute_error: 3.3865 - val_loss: 31.8813 - val_mean_absolute_error: 4.2365
Epoch 76/100
32/32 [==============================] - 0s 8ms/step - loss: 23.3229 - mean_absolute_error: 3.5881 - val_loss: 51.5505 - val_mean_absolute_error: 5.8026
Epoch 77/100
32/32 [==============================] - 0s 8ms/step - loss: 23.9316 - mean_absolute_error: 3.6062 - val_loss: 26.8458 - val_mean_absolute_error: 3.8551
Epoch 78/100
32/32 [==============================] - 0s 8ms/step - loss: 21.9814 - mean_absolute_error: 3.4719 - val_loss: 25.3911 - val_mean_absolute_error: 3.8101
Epoch 79/100
32/32 [==============================] - 0s 11ms/step - loss: 20.9901 - mean_absolute_error: 3.3594 - val_loss: 32.8351 - val_mean_absolute_error: 4.7251
Epoch 80/100
32/32 [==============================] - 0s 7ms/step - loss: 24.1758 - mean_absolute_error: 3.7161 - val_loss: 27.6141 - val_mean_absolute_error: 3.9292
Epoch 81/100
32/32 [==============================] - 0s 9ms/step - loss: 21.5106 - mean_absolute_error: 3.3727 - val_loss: 30.2790 - val_mean_absolute_error: 4.1270
Epoch 82/100
32/32 [==============================] - 0s 7ms/step - loss: 20.1135 - mean_absolute_error: 3.2504 - val_loss: 25.3960 - val_mean_absolute_error: 3.7765
Epoch 83/100
32/32 [==============================] - 0s 9ms/step - loss: 23.5658 - mean_absolute_error: 3.5773 - val_loss: 43.8989 - val_mean_absolute_error: 5.2368
Epoch 84/100
32/32 [==============================] - 0s 11ms/step - loss: 22.6244 - mean_absolute_error: 3.3774 - val_loss: 48.7853 - val_mean_absolute_error: 5.6219
Epoch 85/100
32/32 [==============================] - 0s 7ms/step - loss: 20.3692 - mean_absolute_error: 3.3059 - val_loss: 45.0758 - val_mean_absolute_error: 5.2920
Epoch 86/100
32/32 [==============================] - 0s 8ms/step - loss: 20.0955 - mean_absolute_error: 3.3121 - val_loss: 32.1239 - val_mean_absolute_error: 4.2728
Epoch 87/100
32/32 [==============================] - 0s 9ms/step - loss: 18.7950 - mean_absolute_error: 3.0712 - val_loss: 41.2694 - val_mean_absolute_error: 5.0852
Epoch 88/100
32/32 [==============================] - 0s 12ms/step - loss: 22.5343 - mean_absolute_error: 3.4513 - val_loss: 45.0232 - val_mean_absolute_error: 5.3158
Epoch 89/100
32/32 [==============================] - 0s 10ms/step - loss: 24.7313 - mean_absolute_error: 3.6302 - val_loss: 29.6013 - val_mean_absolute_error: 4.4197
Epoch 90/100
32/32 [==============================] - 0s 7ms/step - loss: 19.8725 - mean_absolute_error: 3.1976 - val_loss: 24.7524 - val_mean_absolute_error: 3.7740
Epoch 91/100
32/32 [==============================] - 0s 8ms/step - loss: 22.2237 - mean_absolute_error: 3.4613 - val_loss: 31.3193 - val_mean_absolute_error: 4.2246
Epoch 92/100
32/32 [==============================] - 0s 8ms/step - loss: 21.2648 - mean_absolute_error: 3.3238 - val_loss: 25.9929 - val_mean_absolute_error: 3.9420
Epoch 93/100
32/32 [==============================] - 0s 11ms/step - loss: 19.6476 - mean_absolute_error: 3.1882 - val_loss: 25.7189 - val_mean_absolute_error: 3.8255
Epoch 94/100
32/32 [==============================] - 0s 10ms/step - loss: 20.5151 - mean_absolute_error: 3.3408 - val_loss: 23.9001 - val_mean_absolute_error: 3.8074
Epoch 95/100
32/32 [==============================] - 0s 6ms/step - loss: 18.9152 - mean_absolute_error: 3.0737 - val_loss: 34.7337 - val_mean_absolute_error: 4.8874
Epoch 96/100
32/32 [==============================] - 0s 9ms/step - loss: 26.1909 - mean_absolute_error: 3.7697 - val_loss: 40.1694 - val_mean_absolute_error: 4.9520
Epoch 97/100
32/32 [==============================] - 0s 8ms/step - loss: 20.3085 - mean_absolute_error: 3.2980 - val_loss: 45.7694 - val_mean_absolute_error: 5.3313
Epoch 98/100
32/32 [==============================] - 0s 7ms/step - loss: 20.9214 - mean_absolute_error: 3.4237 - val_loss: 27.2224 - val_mean_absolute_error: 3.9263
Epoch 99/100
32/32 [==============================] - 0s 6ms/step - loss: 19.9604 - mean_absolute_error: 3.2042 - val_loss: 30.6290 - val_mean_absolute_error: 4.1616
Epoch 100/100
32/32 [==============================] - 0s 6ms/step - loss: 22.9296 - mean_absolute_error: 3.5538 - val_loss: 25.9603 - val_mean_absolute_error: 4.0136
Out[92]:
<keras.src.callbacks.History at 0x2db1bbdc250>

The Output :¶

The output represents the training progress of an LSTM model over 100 epochs, displaying both training and validation metrics after each epoch:

  • Epochs 1-100: For each epoch, the model's performance metrics are shown, including loss (mean squared error) and mean absolute error, both for the training dataset and the validation dataset.
  • Loss: The loss values represent the error between the predicted values and the actual values in the respective datasets (training and validation). Lower values indicate better performance.
  • Mean Absolute Error (MAE): This metric measures the average absolute difference between predicted and actual values. Like loss, lower values are indicative of better performance.
Observations:¶
  • The loss and MAE metrics for both training and validation datasets are reported after each epoch.
  • Initially, both training and validation loss and MAE are quite high but gradually decrease over epochs, indicating that the model is learning and improving its performance.
  • There seems to be a gap between the training and validation metrics, suggesting a potential overfitting issue if the validation loss starts increasing while the training loss continues to decrease significantly.

Visualization of Training Data Predictions¶

The provided code below segment is responsible for generating a visual comparison between the predictions made by a machine learning model and the actual observations within the training dataset. This visualization aims to offer insights into the model's performance on the training data.

  • Training Predictions Line: This line represents the values predicted by the machine learning model for the training dataset. Each point on this line indicates the model's prediction for a specific instance within the training data.
  • Training Observations Line: This line illustrates the actual observed values from the training dataset. It represents the real data points or ground truth values.

ideally, the two lines should align closely or overlap each other. A close fit between the 'Training Predictions' and 'Training Observations' lines signifies that the model has learned well from the training data. If there's a significant deviation or gap between these lines, it might indicate:

  1. Model Accuracy: A close alignment between the lines indicates that the model has learned to predict the training data accurately.
  2. Overfitting or Underfitting:
  • Overfitting: If the 'Training Predictions' line closely follows the training data but deviates significantly from the 'Training Observations' line, it could indicate the model has overfit the training data, capturing noise or specific patterns that don't generalize well.
  • Underfitting: A large gap between both lines suggests the model hasn't captured the underlying patterns in the training data adequately.
  1. Prediction Errors: Differences between the lines could also indicate areas where the model struggles to make accurate predictions, pointing to instances where the model requires improvement or where the data might be inherently noisy or complex.
In [82]:
train_predictions = model.predict(X_train).flatten()

plt.figure(figsize=(10, 6))
plt.plot(dates_train, train_predictions)
plt.plot(dates_train, y_train)
plt.legend(['Training Predictions', 'Training Observations'])
32/32 [==============================] - 1s 3ms/step
Out[82]:
<matplotlib.legend.Legend at 0x2db16fc9a50>
No description has been provided for this image
In [83]:
val_predictions = model.predict(X_val).flatten()

plt.figure(figsize=(10,4))
plt.plot(dates_val, val_predictions)
plt.plot(dates_val, y_val)
plt.legend(['Validation Predictions', 'Validation Observations'])
4/4 [==============================] - 0s 4ms/step
Out[83]:
<matplotlib.legend.Legend at 0x2db14fffa50>
No description has been provided for this image
In [84]:
test_predictions = model.predict(X_test).flatten()

plt.figure(figsize=(10,4))
plt.plot(dates_test, test_predictions)
plt.plot(dates_test, y_test)
plt.legend(['Testing Predictions', 'Testing Observations'])
4/4 [==============================] - 0s 4ms/step
Out[84]:
<matplotlib.legend.Legend at 0x2db17c9fa50>
No description has been provided for this image
By plotting these six lines together, this visualization allows for a holistic view of how well the model performs across different datasets:¶
  • Model Performance Assessment: Comparing predictions with actual observations across all datasets.
  • Generalization Check: Analyzing if the model's performance remains consistent across unseen data (validation and testing datasets).
  • Overfitting/Underfitting Evaluation: Observing potential disparities between predictions and observations across datasets. The legend provided corresponds to each line in the graph, enabling clear identification of which line represents which dataset's predictions and observations.
  1. Model Consistency Across Datasets: all six lines closely align and follow a similar pattern across the training, validation, and testing datasets, it indicates that the model is consistent in its predictions across different datasets. This consistency suggests that the model has learned patterns that generalize well to unseen data.
  2. Generalization and Robustness: Consistent alignment between the prediction and observation lines across the three datasets (training, validation, testing) suggests that the model generalizes well. It indicates that the model has not overfit to the training data and is performing similarly on unseen validation and testing datasets.
  3. Performance Evaluation: A close fit between the prediction and observation lines on all datasets implies that the model is making accurate predictions, capturing the underlying patterns in the data across different subsets.
  4. Prediction Accuracy Assessment: The closer the lines fit together, the higher the accuracy of the model's predictions across the datasets, indicating a strong performance and reliability in forecasting or modeling the observed data.
In [85]:
plt.plot(dates_train, train_predictions)
plt.plot(dates_train, y_train)
plt.plot(dates_val, val_predictions)
plt.plot(dates_val, y_val)
plt.plot(dates_test, test_predictions)
plt.plot(dates_test, y_test)
plt.legend(['Training Predictions', 
            'Training Observations',
            'Validation Predictions', 
            'Validation Observations',
            'Testing Predictions', 
            'Testing Observations'])
Out[85]:
<matplotlib.legend.Legend at 0x2db1ab6ac50>
No description has been provided for this image

Using a trained model to generate recursive predictions. This process use a recursive approach where the model generates predictions iteratively, using its own predictions as inputs for subsequent predictions. This can be useful for forecasting multiple steps ahead or simulating real-time prediction scenarios.

However, it's important to handle the recursive predictions with care, as errors might accumulate over each step, potentially affecting the accuracy of long-term predictions. Evaluation and validation on such recursive predictions are crucial to assess the model's performance in this scenario.

In [93]:
from copy import deepcopy

recursive_predictions = []  # Initialize an empty list to store the predicted values
recursive_dates = np.concatenate([dates_val, dates_test]) # Concatenate validation and test dates to create a combined date sequence

# Iterate through each date in the combined validation and test set
for target_date in recursive_dates:
  last_window = deepcopy(X_train[-1])     # Create a copy of the last window in the training data
  next_prediction = model.predict(np.array([last_window])).flatten()      # Predict the next value based on the last window and flatten the result
  recursive_predictions.append(next_prediction)     # Append the predicted value to the list of recursive predictions
  last_window[-1] = next_prediction       # Update the last element in the window with the predicted value for the next iteration
1/1 [==============================] - 1s 862ms/step
1/1 [==============================] - 0s 27ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 50ms/step
1/1 [==============================] - 0s 19ms/step
1/1 [==============================] - 0s 22ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 17ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 24ms/step
1/1 [==============================] - 0s 41ms/step
1/1 [==============================] - 0s 40ms/step
1/1 [==============================] - 0s 42ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 42ms/step
1/1 [==============================] - 0s 53ms/step
1/1 [==============================] - 0s 49ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 45ms/step
1/1 [==============================] - 0s 48ms/step
1/1 [==============================] - 0s 43ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 39ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 37ms/step
1/1 [==============================] - 0s 21ms/step
1/1 [==============================] - 0s 21ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 38ms/step
1/1 [==============================] - 0s 22ms/step
1/1 [==============================] - 0s 103ms/step
1/1 [==============================] - 0s 51ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 48ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 37ms/step
1/1 [==============================] - 0s 23ms/step
1/1 [==============================] - 0s 37ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 48ms/step
1/1 [==============================] - 0s 46ms/step
1/1 [==============================] - 0s 165ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 30ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 51ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 38ms/step
1/1 [==============================] - 0s 65ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 62ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 38ms/step
1/1 [==============================] - 0s 29ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 37ms/step
1/1 [==============================] - 0s 38ms/step
1/1 [==============================] - 0s 38ms/step
1/1 [==============================] - 0s 18ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 51ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 97ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 50ms/step
1/1 [==============================] - 0s 49ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 51ms/step
1/1 [==============================] - 0s 46ms/step
1/1 [==============================] - 0s 37ms/step
1/1 [==============================] - 0s 37ms/step
1/1 [==============================] - 0s 154ms/step
1/1 [==============================] - 0s 49ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 46ms/step
1/1 [==============================] - 0s 55ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 52ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 69ms/step
1/1 [==============================] - 0s 53ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 55ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 38ms/step
1/1 [==============================] - 0s 49ms/step
1/1 [==============================] - 0s 48ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 106ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 56ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 57ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 16ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 123ms/step
1/1 [==============================] - 0s 102ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 78ms/step
1/1 [==============================] - 0s 41ms/step
1/1 [==============================] - 0s 38ms/step
1/1 [==============================] - 0s 29ms/step
1/1 [==============================] - 0s 48ms/step
1/1 [==============================] - 0s 92ms/step
1/1 [==============================] - 0s 65ms/step
1/1 [==============================] - 0s 164ms/step
1/1 [==============================] - 0s 56ms/step
1/1 [==============================] - 0s 51ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 39ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 35ms/step
1/1 [==============================] - 0s 40ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 60ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 49ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 48ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 51ms/step
1/1 [==============================] - 0s 46ms/step
1/1 [==============================] - 0s 36ms/step
1/1 [==============================] - 0s 54ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 61ms/step
1/1 [==============================] - 0s 40ms/step
1/1 [==============================] - 0s 45ms/step
1/1 [==============================] - 0s 45ms/step
1/1 [==============================] - 0s 31ms/step
1/1 [==============================] - 0s 40ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 33ms/step
1/1 [==============================] - 0s 52ms/step
1/1 [==============================] - 0s 41ms/step
1/1 [==============================] - 0s 42ms/step
1/1 [==============================] - 0s 34ms/step
1/1 [==============================] - 0s 138ms/step
1/1 [==============================] - 0s 46ms/step
1/1 [==============================] - 0s 32ms/step
1/1 [==============================] - 0s 47ms/step
1/1 [==============================] - 0s 35ms/step
In [87]:
plt.figure(figsize=(10,6))
plt.plot(dates_train, train_predictions)
plt.plot(dates_train, y_train)
plt.plot(dates_val, val_predictions)
plt.plot(dates_val, y_val)
plt.plot(dates_test, test_predictions)
plt.plot(dates_test, y_test)
plt.plot(recursive_dates, recursive_predictions)
plt.legend(['Training Predictions', 
            'Training Observations',
            'Validation Predictions', 
            'Validation Observations',
            'Testing Predictions', 
            'Testing Observations',
            'Recursive Predictions'])
Out[87]:
<matplotlib.legend.Legend at 0x2db19b7e2d0>
No description has been provided for this image