pvar_dashboard / src /models /lstm_model.py
F1nnSBK's picture
Deploy Photovoltaik Risiko-Manager dashboard to Hugging Face Spaces
5841846
Raw
History Blame Contribute Delete
4.56 kB
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from sklearn.preprocessing import StandardScaler
class PriceLSTM(nn.Module):
"""
PyTorch LSTM model for day-ahead electricity price mean forecasting.
"""
def __init__(self, input_dim: int, hidden_dim: int, num_layers: int, output_dim: int = 1):
super().__init__()
self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True)
self.linear = nn.Linear(hidden_dim, output_dim)
def forward(self, x: torch.Tensor) -> torch.Tensor:
lstm_out, _ = self.lstm(x)
return self.linear(lstm_out[:, -1, :])
class LSTMSequenceModel:
"""
Wrapper class managing standard-scaling, sequencing, PyTorch training,
and out-of-sample prediction.
"""
def __init__(self, seq_length: int = 24, hidden_dim: int = 64, num_layers: int = 2,
lr: float = 0.001, epochs: int = 15, batch_size: int = 64):
self.seq_length = seq_length
self.hidden_dim = hidden_dim
self.num_layers = num_layers
self.lr = lr
self.epochs = epochs
self.batch_size = batch_size
self.scaler_X = StandardScaler()
self.scaler_y = StandardScaler()
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model = None
def _create_sequences(self, X: np.ndarray, y: np.ndarray = None):
xs, ys = [], []
limit = len(X) - self.seq_length
for i in range(limit):
xs.append(X[i:(i + self.seq_length)])
if y is not None:
ys.append(y[i + self.seq_length])
if y is not None:
return np.array(xs), np.array(ys)
return np.array(xs)
def fit(self, X: pd.DataFrame, y: pd.Series):
"""Fits the LSTM model on X and y by scaling data and creating sequences."""
# Standard scale
X_scaled = self.scaler_X.fit_transform(X)
y_scaled = self.scaler_y.fit_transform(y.values.reshape(-1, 1))
# Create input sequences
X_seq, y_seq = self._create_sequences(X_scaled, y_scaled)
# Build DataLoader
dataset = TensorDataset(torch.Tensor(X_seq), torch.Tensor(y_seq))
loader = DataLoader(dataset, batch_size=self.batch_size, shuffle=True)
# Initialize network
self.model = PriceLSTM(
input_dim=X.shape[1],
hidden_dim=self.hidden_dim,
num_layers=self.num_layers
).to(self.device)
optimizer = torch.optim.Adam(self.model.parameters(), lr=self.lr)
criterion = nn.MSELoss()
# Training loop
self.model.train()
for epoch in range(self.epochs):
for batch_X, batch_y in loader:
batch_X = batch_X.to(self.device)
batch_y = batch_y.to(self.device)
optimizer.zero_grad()
outputs = self.model(batch_X)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
return self
def predict(self, X: pd.DataFrame, y: pd.Series = None):
"""
Generates predictions for the given features X.
If y is provided, aligns and returns (y_pred, y_aligned) for evaluation metrics.
If y is None, returns y_pred alone.
"""
if self.model is None:
raise ValueError("Model is not fitted yet. Call fit() first.")
# Scale X using trained scaler
X_scaled = self.scaler_X.transform(X)
self.model.eval()
with torch.no_grad():
if y is not None:
y_scaled = self.scaler_y.transform(y.values.reshape(-1, 1))
X_seq, y_seq = self._create_sequences(X_scaled, y_scaled)
X_tensor = torch.Tensor(X_seq).to(self.device)
preds_scaled = self.model(X_tensor).cpu().numpy()
y_pred = self.scaler_y.inverse_transform(preds_scaled).flatten()
y_aligned = self.scaler_y.inverse_transform(y_seq).flatten()
return y_pred, y_aligned
else:
X_seq = self._create_sequences(X_scaled)
X_tensor = torch.Tensor(X_seq).to(self.device)
preds_scaled = self.model(X_tensor).cpu().numpy()
y_pred = self.scaler_y.inverse_transform(preds_scaled).flatten()
return y_pred