Time Series Weather Prediction Using LSTM and Self-Attention Mechanisms

The following implementation demonstrates a deep learning approach to forecasting meteorological data. It utilizes a hybrid architecture combining Long Short-Term Memory (LSTM) networks with a Multi-Head Self-Attention mechanism to capture temporal dependencies effectively.

Library Imports and Configuration

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from sklearn.preprocessing import StandardScaler
from torch.utils.data import DataLoader, TensorDataset

# Hyperparameter Configuration
CONFIG = {
    "seq_len": 1,          # Length of the input sequence window
    "batch_sz": 16,        # Number of samples per gradient update
    "n_features": 14,      # Number of input features per timestep
    "hidden_dim": 64,      # Number of neurons in the hidden layer
    "output_dim": 1,       # Dimension of the output (scalar for regression)
    "n_layers": 3,         # Number of stacked LSTM layers
    "epochs": 10,          # Total number of training epochs
    "learning_rate": 0.01, # Step size for optimizer
    "model_path": "weather_attn_lstm.pth"
}

Data Loading and Preprocessing

# Load dataset
df = pd.read_csv('data/weather_data.csv', index_col=0)

# Initialize scalers
feature_scaler = StandardScaler()
target_scaler = StandardScaler()

# Fit and transform the entire dataset
scaled_data = feature_scaler.fit_transform(df.values)

# Fit target scaler specifically on the temperature column (assumed 'T (degC)')
target_scaler.fit(df['T (degC)'].values.reshape(-1, 1))

def prepare_sequences(dataset, window_size, n_vars):
    """Creates sequences for supervised learning."""
    X, y = [], []
    total_len = len(dataset)
    
    for i in range(total_len - window_size):
        # Extract features for the current window
        X.append(dataset[i : i + window_size])
        # Extract the target (temperature at the next step)
        y.append(dataset[i + window_size][1]) 
    
    X = np.array(X)
    y = np.array(y)
    
    # Split into training and testing sets (80/20 split)
    split_idx = int(0.8 * X.shape[0])
    
    x_train = X[:split_idx].reshape(-1, window_size, n_vars)
    y_train = y[:split_idx]
    x_test = X[split_idx:].reshape(-1, window_size, n_vars)
    y_test = y[split_idx:]
    
    return x_train, y_train, x_test, y_test

# Generate datasets
x_train, y_train, x_test, y_test = prepare_sequences(
    scaled_data, 
    CONFIG["seq_len"], 
    CONFIG["n_features"]
)

# Convert to PyTorch Tensors
train_dataset = TensorDataset(
    torch.from_numpy(x_train).float(), 
    torch.from_numpy(y_train).float()
)
test_dataset = TensorDataset(
    torch.from_numpy(x_test).float(), 
    torch.from_numpy(y_test).float()
)

# Create DataLoaders
train_loader = DataLoader(train_dataset, batch_size=CONFIG["batch_sz"], shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=CONFIG["batch_sz"], shuffle=False)

Model Architecture Definition

class AttnLSTMNet(nn.Module):
    def __init__(self, input_sz, hidden_sz, num_layers, out_sz):
        super(AttnLSTMNet, self).__init__()
        self.hidden_sz = hidden_sz
        self.num_layers = num_layers
        
        # Multi-Head Attention layer
        self.attention_layer = nn.MultiheadAttention(
            embed_dim=input_sz, 
            num_heads=2
        )
        
        # LSTM layer for sequence processing
        self.lstm_layer = nn.LSTM(
            input_sz, 
            hidden_sz, 
            num_layers, 
            batch_first=True
        )
        
        # Fully connected layer for final prediction
        self.fc_layer = nn.Linear(hidden_sz, out_sz)

    def forward(self, x):
        # Input shape: (batch_size, seq_len, input_dim)
        
        # Self-Attention mechanism
        attn_output, attn_weights = self.attention_layer(x, x, x)
        
        # LSTM processing
        lstm_out, (h_n, c_n) = self.lstm_layer(attn_output)
        
        # Reshape for fully connected layer
        batch_size, seq_len, hidden_dim = lstm_out.shape
        lstm_flat = lstm_out.reshape(-1, hidden_dim)
        
        # Apply linear transformation
        predictions = self.fc_layer(lstm_flat)
        
        # Restore sequence dimensions and return last timestep
        predictions = predictions.reshape(seq_len, batch_size, -1)
        return predictions[-1]

# Initialize model, loss, and optimizer
model = AttnLSTMNet(
    CONFIG["n_features"], 
    CONFIG["hidden_dim"], 
    CONFIG["n_layers"], 
    CONFIG["output_dim"]
)
loss_criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=CONFIG["learning_rate"])

Model Training and Validation

min_val_loss = float('inf')

for epoch in range(CONFIG["epochs"]):
    model.train()
    running_loss = 0.0
    
    # Training phase
    for inputs, targets in train_loader:
        optimizer.zero_grad()
        
        # Forward pass
        outputs = model(inputs)
        
        # Calculate loss
        loss = loss_criterion(outputs, targets.unsqueeze(1))
        
        # Backward pass and optimization
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
    
    # Validation phase
    model.eval()
    val_loss = 0.0
    with torch.no_grad():
        for inputs, targets in test_loader:
            outputs = model(inputs)
            loss = loss_criterion(outputs, targets.unsqueeze(1))
            val_loss += loss.item()
    
    avg_val_loss = val_loss / len(test_loader)
    print(f"Epoch [{epoch+1}/{CONFIG["epochs"]}] Train Loss: {running_loss/len(train_loader):.4f} | Val Loss: {avg_val_loss:.4f}")
    
    # Save best model
    if avg_val_loss < min_val_loss:
        min_val_loss = avg_val_loss
        torch.save(model.state_dict(), CONFIG["model_path"])

Result Visualization

model.eval()
with torch.no_grad():
    # Predict on first 10,000 training samples
    sample_input = torch.tensor(x_train[:10000], dtype=torch.float32)
    predictions = model(sample_input).numpy()

plt.figure(figsize=(12, 8))

# Inverse transform to original scale
plt.plot(
    target_scaler.inverse_transform(predictions.reshape(-1, 1)), 
    "b", 
    label="Predicted Values"
)
plt.plot(
    target_scaler.inverse_transform(y_train[:10000].reshape(-1, 1)), 
    "r", 
    label="Actual Values"
)

plt.legend()
plt.show()

Tags: Deep Learning pytorch Time Series Forecasting LSTM Self-Attention

Posted on Mon, 20 Jul 2026 17:28:08 +0000 by deadoralive