Spaces:
Sleeping
Sleeping
| """ | |
| model_utils.py β LSTM model utilities for inference with MC Dropout | |
| Provides: | |
| - load_model_and_scalers() | |
| - prepare_input_sequence() | |
| - predict_with_mc_dropout() | |
| """ | |
| import os | |
| import numpy as np | |
| import torch | |
| import joblib | |
| import pandas as pd | |
| from retrain import LSTMModel, enable_mc_dropout, DEVICE | |
| # --------------------------------------------------------------------- | |
| # π₯ LOAD MODEL + SCALERS | |
| # --------------------------------------------------------------------- | |
| def load_model_and_scalers(ticker_name, horizon_name): | |
| """ | |
| Load trained LSTM model and associated scalers for a given ticker/horizon. | |
| """ | |
| base_dir = os.path.dirname(os.path.dirname(__file__)) | |
| model_dir = os.path.join(base_dir, "models", ticker_name) | |
| model_path = os.path.join(model_dir, f"{ticker_name}_{horizon_name}_model.pth") | |
| xscaler_path = os.path.join(model_dir, f"{ticker_name}_{horizon_name}_scaler.pkl") | |
| yscaler_path = os.path.join(model_dir, f"{ticker_name}_{horizon_name}_y_scaler.pkl") | |
| if not all(os.path.exists(p) for p in [model_path, xscaler_path, yscaler_path]): | |
| raise FileNotFoundError(f"β Missing model/scaler files for {ticker_name} ({horizon_name})") | |
| # Load scalers | |
| x_scaler = joblib.load(xscaler_path) | |
| y_scaler = joblib.load(yscaler_path) | |
| # Determine input size dynamically | |
| sample_input_size = len(x_scaler.mean_) | |
| model = LSTMModel(input_size=sample_input_size, | |
| hidden_size=256, num_layers=3, dropout=0.2).to(DEVICE) | |
| model.load_state_dict(torch.load(model_path, map_location=DEVICE, weights_only=True)) | |
| model.eval() | |
| return model, x_scaler, y_scaler | |
| # --------------------------------------------------------------------- | |
| # π PREPARE INPUT SEQUENCE | |
| # --------------------------------------------------------------------- | |
| def prepare_input_sequence(df, x_scaler, seq_len=90): | |
| """ | |
| Prepares the most recent sequence for inference. | |
| Args: | |
| df: DataFrame containing at least columns ["Open","High","Low","Close","Volume"] | |
| x_scaler: fitted StandardScaler | |
| seq_len: number of timesteps expected by model | |
| Returns: | |
| Tensor of shape [1, seq_len, features] | |
| """ | |
| df = df.dropna(subset=["Open","High","Low","Close","Volume"]) | |
| feats = df[["Open","High","Low","Close","Volume"]].values[-seq_len:] | |
| X_scaled = x_scaler.transform(feats) | |
| X_tensor = torch.tensor(X_scaled, dtype=torch.float32).unsqueeze(0).to(DEVICE) | |
| return X_tensor | |
| # --------------------------------------------------------------------- | |
| # π² MONTE CARLO DROPOUT INFERENCE | |
| # --------------------------------------------------------------------- | |
| def predict_with_mc_dropout(model, X_tensor, y_scaler, n_samples=100): | |
| """ | |
| Perform MC Dropout predictions to estimate uncertainty. | |
| Returns: | |
| { | |
| "predicted_price": float, | |
| "lower_bound": float, | |
| "upper_bound": float, | |
| "confidence_percent": float, | |
| "all_preds": np.ndarray | |
| } | |
| """ | |
| model = enable_mc_dropout(model) | |
| preds = [] | |
| with torch.no_grad(): | |
| for _ in range(n_samples): | |
| preds.append(model(X_tensor).item()) | |
| preds = np.array(preds) | |
| mean_pred = preds.mean() | |
| std_pred = preds.std() | |
| # Inverse scale to get price prediction | |
| mean_price = y_scaler.inverse_transform([[mean_pred]])[0][0] | |
| std_price = y_scaler.inverse_transform([[std_pred]])[0][0] - y_scaler.inverse_transform([[0]])[0][0] | |
| lower = mean_price - 2 * std_price | |
| upper = mean_price + 2 * std_price | |
| confidence_range = (upper - lower) / mean_price | |
| confidence_percent = max(0.0, 100 * (1 - confidence_range)) | |
| return { | |
| "predicted_price": float(mean_price), | |
| "lower_bound": float(lower), | |
| "upper_bound": float(upper), | |
| "confidence_percent": round(confidence_percent, 2), | |
| "all_preds": preds | |
| } | |