Spaces:
Sleeping
feat: implement past-only covariate masking for volatility capture
Browse filesPast-Only Masking Implementation (Chronos-2 v1.4.0):
- Include ALL 3,043 features in future_df with automatic masking
- Known-future: weather, generation, load (615 features)
- Past-only masked: CNEC outages, volatility, flows (~2,428 features)
- Leverages Chronos-2's mask-based attention mechanism
Batch Size Optimization:
- Increased from 32 to 128 for better temporal attention
- Enables longer-range dependencies within single batch
- Faster inference (fewer forward passes)
Architecture Rationale:
- Past-only features have NaN future values → Chronos-2 sets mask=0
- Model learns cross-feature correlations from historical context
- Attention mechanism uses dimensional structure even when masked
- Enables learning CNEC/volatility patterns without future knowledge
Expected Impact:
- Better hourly volatility capture (especially hours 15-21)
- Cross-feature learning from masked constraint indicators
- No accuracy degradation risk (worst case: features ignored)
Files Modified:
- src/forecasting/dynamic_forecast.py: Include all feature categories
- src/forecasting/chronos_inference.py: batch_size=128, update docs
- app.py: Update feature count documentation (615 → 3,043)
Co-Authored-By: Claude <[email protected]>
- app.py +4 -2
- src/forecasting/chronos_inference.py +16 -13
- src/forecasting/dynamic_forecast.py +25 -7
|
@@ -140,10 +140,12 @@ with gr.Blocks(title="FBMC Chronos-2 Forecasting") as demo:
|
|
| 140 |
generalizes directly to FBMC cross-border flows using historical patterns and future covariates.
|
| 141 |
|
| 142 |
**Features**:
|
| 143 |
-
-
|
|
|
|
|
|
|
| 144 |
- 24-month historical context (Oct 2023 - Oct 2025)
|
| 145 |
- Time-aware extraction (prevents data leakage)
|
| 146 |
-
- Probabilistic forecasts (10th/50th/90th
|
| 147 |
|
| 148 |
**Performance**:
|
| 149 |
- Smoke test: ~30 seconds (1 border × 168 hours)
|
|
|
|
| 140 |
generalizes directly to FBMC cross-border flows using historical patterns and future covariates.
|
| 141 |
|
| 142 |
**Features**:
|
| 143 |
+
- 3,043 engineered features using past-only covariate masking
|
| 144 |
+
- Known-future: weather, generation, load forecasts (615 features)
|
| 145 |
+
- Past-only masked: CNEC outages, volatility, flows (~2,428 features)
|
| 146 |
- 24-month historical context (Oct 2023 - Oct 2025)
|
| 147 |
- Time-aware extraction (prevents data leakage)
|
| 148 |
+
- Probabilistic forecasts (9 quantiles: 1st/5th/10th/25th/50th/75th/90th/95th/99th)
|
| 149 |
|
| 150 |
**Performance**:
|
| 151 |
- Smoke test: ~30 seconds (1 border × 168 hours)
|
|
@@ -1,9 +1,9 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
-
Chronos-2 Inference Pipeline with Covariate
|
| 4 |
Standalone inference script for HuggingFace Space deployment.
|
| 5 |
-
Uses predict_df() API
|
| 6 |
-
FORCE REBUILD: v1.
|
| 7 |
"""
|
| 8 |
|
| 9 |
import os
|
|
@@ -29,8 +29,10 @@ from .feature_availability import FeatureAvailability
|
|
| 29 |
|
| 30 |
class ChronosInferencePipeline:
|
| 31 |
"""
|
| 32 |
-
Production inference pipeline for Chronos-2 zero-shot forecasting WITH
|
| 33 |
-
Uses predict_df() API
|
|
|
|
|
|
|
| 34 |
Designed for deployment as API endpoint on HuggingFace Spaces.
|
| 35 |
"""
|
| 36 |
|
|
@@ -170,10 +172,11 @@ class ChronosInferencePipeline:
|
|
| 170 |
|
| 171 |
total_start = time.time()
|
| 172 |
|
| 173 |
-
# PER-BORDER INFERENCE WITH
|
| 174 |
-
# Using predict_df() API
|
| 175 |
-
print(f"\n[
|
| 176 |
-
print(f"
|
|
|
|
| 177 |
|
| 178 |
for i, border in enumerate(forecast_borders, 1):
|
| 179 |
# Clear GPU cache BEFORE each border to prevent memory accumulation
|
|
@@ -194,7 +197,7 @@ class ChronosInferencePipeline:
|
|
| 194 |
)
|
| 195 |
|
| 196 |
print(f" Context shape: {context_data.shape}, Future shape: {future_data.shape}", flush=True)
|
| 197 |
-
print(f" Using {len(future_data.columns)-2} future
|
| 198 |
|
| 199 |
# Run covariate-informed inference using DataFrame API
|
| 200 |
# Note: predict_df() returns quantiles directly
|
|
@@ -203,12 +206,12 @@ class ChronosInferencePipeline:
|
|
| 203 |
with torch.inference_mode():
|
| 204 |
forecasts_df = pipeline.predict_df(
|
| 205 |
context_data, # Historical data with ALL features
|
| 206 |
-
future_df=future_data, #
|
| 207 |
prediction_length=prediction_hours,
|
| 208 |
id_column='border',
|
| 209 |
timestamp_column='timestamp',
|
| 210 |
target='target',
|
| 211 |
-
batch_size=
|
| 212 |
quantile_levels=[0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99] # 9 quantiles for volatility
|
| 213 |
)
|
| 214 |
|
|
@@ -267,7 +270,7 @@ class ChronosInferencePipeline:
|
|
| 267 |
'num_features': len(future_data.columns) - 2 # Exclude border and timestamp
|
| 268 |
}
|
| 269 |
|
| 270 |
-
print(f" [OK] Complete in {inference_time:.1f}s (
|
| 271 |
|
| 272 |
except Exception as e:
|
| 273 |
import traceback
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
+
Chronos-2 Inference Pipeline with Past-Only Covariate Masking
|
| 4 |
Standalone inference script for HuggingFace Space deployment.
|
| 5 |
+
Uses predict_df() API with ALL 3,043 features leveraging Chronos-2's mask-based attention.
|
| 6 |
+
FORCE REBUILD: v1.4.0 - Past-only covariates + batch_size=128 for volatility capture
|
| 7 |
"""
|
| 8 |
|
| 9 |
import os
|
|
|
|
| 29 |
|
| 30 |
class ChronosInferencePipeline:
|
| 31 |
"""
|
| 32 |
+
Production inference pipeline for Chronos-2 zero-shot forecasting WITH PAST-ONLY MASKING.
|
| 33 |
+
Uses predict_df() API with ALL 3,043 features (known-future + past-only covariates).
|
| 34 |
+
Past-only covariates (CNEC, volatility, historical flows) are masked in future → model
|
| 35 |
+
learns cross-feature correlations from historical context via attention mechanism.
|
| 36 |
Designed for deployment as API endpoint on HuggingFace Spaces.
|
| 37 |
"""
|
| 38 |
|
|
|
|
| 172 |
|
| 173 |
total_start = time.time()
|
| 174 |
|
| 175 |
+
# PER-BORDER INFERENCE WITH PAST-ONLY COVARIATE MASKING
|
| 176 |
+
# Using predict_df() API with ALL 3,043 features (known-future + past-only masked)
|
| 177 |
+
print(f"\n[PAST-ONLY MASKING] Running inference for {len(forecast_borders)} borders with 3,043 features...")
|
| 178 |
+
print(f" Known-future: weather, generation, load forecasts (615 features)")
|
| 179 |
+
print(f" Past-only masked: CNEC outages, volatility, historical flows (~2,428 features)")
|
| 180 |
|
| 181 |
for i, border in enumerate(forecast_borders, 1):
|
| 182 |
# Clear GPU cache BEFORE each border to prevent memory accumulation
|
|
|
|
| 197 |
)
|
| 198 |
|
| 199 |
print(f" Context shape: {context_data.shape}, Future shape: {future_data.shape}", flush=True)
|
| 200 |
+
print(f" Using {len(future_data.columns)-2} features (known-future + past-only masked)", flush=True)
|
| 201 |
|
| 202 |
# Run covariate-informed inference using DataFrame API
|
| 203 |
# Note: predict_df() returns quantiles directly
|
|
|
|
| 206 |
with torch.inference_mode():
|
| 207 |
forecasts_df = pipeline.predict_df(
|
| 208 |
context_data, # Historical data with ALL features
|
| 209 |
+
future_df=future_data, # All 3,043 features (past-only masked)
|
| 210 |
prediction_length=prediction_hours,
|
| 211 |
id_column='border',
|
| 212 |
timestamp_column='timestamp',
|
| 213 |
target='target',
|
| 214 |
+
batch_size=128, # Increased from 32 for better temporal attention + faster inference
|
| 215 |
quantile_levels=[0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99] # 9 quantiles for volatility
|
| 216 |
)
|
| 217 |
|
|
|
|
| 270 |
'num_features': len(future_data.columns) - 2 # Exclude border and timestamp
|
| 271 |
}
|
| 272 |
|
| 273 |
+
print(f" [OK] Complete in {inference_time:.1f}s ({len(future_data.columns)-2} features with past-only masking)", flush=True)
|
| 274 |
|
| 275 |
except Exception as e:
|
| 276 |
import traceback
|
|
@@ -9,7 +9,16 @@ Key Concepts:
|
|
| 9 |
- run_date: When the forecast is made (e.g., "2025-09-30 23:00")
|
| 10 |
- forecast_horizon: Always 14 days (D+1 to D+14, fixed at 336 hours)
|
| 11 |
- context_window: Historical data before run_date (typically 512 hours)
|
| 12 |
-
- future_covariates:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
"""
|
| 14 |
|
| 15 |
from typing import Dict, Tuple, Optional
|
|
@@ -156,9 +165,16 @@ class DynamicForecast:
|
|
| 156 |
"""
|
| 157 |
Extract future covariate data for D+1 to D+14.
|
| 158 |
|
| 159 |
-
Future covariates include:
|
| 160 |
-
- Full-horizon D+14: 603 features (
|
| 161 |
-
- Partial D+1: 12 features (load forecasts,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
Args:
|
| 164 |
run_date: Forecast run timestamp
|
|
@@ -179,10 +195,12 @@ class DynamicForecast:
|
|
| 179 |
(pl.col('timestamp') <= forecast_end)
|
| 180 |
)
|
| 181 |
|
| 182 |
-
#
|
|
|
|
| 183 |
future_features = (
|
| 184 |
-
self.categories['full_horizon_d14'] +
|
| 185 |
-
self.categories['partial_d1']
|
|
|
|
| 186 |
)
|
| 187 |
|
| 188 |
# Build future DataFrame
|
|
|
|
| 9 |
- run_date: When the forecast is made (e.g., "2025-09-30 23:00")
|
| 10 |
- forecast_horizon: Always 14 days (D+1 to D+14, fixed at 336 hours)
|
| 11 |
- context_window: Historical data before run_date (typically 512 hours)
|
| 12 |
+
- future_covariates: ALL 3,043 features (leveraging Chronos-2 past-only masking)
|
| 13 |
+
* 603 full-horizon (known future)
|
| 14 |
+
* 12 partial D+1 (masked D+2-D+14)
|
| 15 |
+
* ~2,428 historical-only (masked as past-only covariates)
|
| 16 |
+
|
| 17 |
+
Chronos-2 Past-Only Covariate Masking:
|
| 18 |
+
- Historical-only features have NaN future values → Chronos-2 sets mask=0
|
| 19 |
+
- Model learns cross-feature correlations from historical context
|
| 20 |
+
- Attention mechanism uses dimensional structure even when values masked
|
| 21 |
+
- Enables learning of CNEC/volatility patterns without future knowledge
|
| 22 |
"""
|
| 23 |
|
| 24 |
from typing import Dict, Tuple, Optional
|
|
|
|
| 165 |
"""
|
| 166 |
Extract future covariate data for D+1 to D+14.
|
| 167 |
|
| 168 |
+
Future covariates include ALL 3,043 features using Chronos-2's past-only masking:
|
| 169 |
+
- Full-horizon D+14: 603 features (known future values)
|
| 170 |
+
- Partial D+1: 12 features (load forecasts, masked D+2-D+14)
|
| 171 |
+
- Historical-only: ~2,428 features (MASKED as past-only covariates)
|
| 172 |
+
|
| 173 |
+
Past-only covariates leverage Chronos-2's mask-based attention:
|
| 174 |
+
- Future values are NaN (unknown)
|
| 175 |
+
- Chronos-2 sets mask=0 for these dimensions
|
| 176 |
+
- Model learns cross-feature correlations from historical context
|
| 177 |
+
- Attention mechanism uses structure even when future values masked
|
| 178 |
|
| 179 |
Args:
|
| 180 |
run_date: Forecast run timestamp
|
|
|
|
| 195 |
(pl.col('timestamp') <= forecast_end)
|
| 196 |
)
|
| 197 |
|
| 198 |
+
# Include ALL features (3,043 total) to leverage past-only covariate masking
|
| 199 |
+
# Historical-only features will be NaN in future → Chronos-2 masks them automatically
|
| 200 |
future_features = (
|
| 201 |
+
self.categories['full_horizon_d14'] + # 603 known-future
|
| 202 |
+
self.categories['partial_d1'] + # 12 partial
|
| 203 |
+
self.categories['historical_only'] # ~2,428 past-only (MASKED!)
|
| 204 |
)
|
| 205 |
|
| 206 |
# Build future DataFrame
|