Spaces:
Sleeping
Sleeping
| 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 | |