Skip to content
Tanishq Jain
Resume

Probabilistic forecasting

DeepAR was the most accurate model I tested — and the most overconfident.

Role
Solo
Timeline
May 2026
  • Python 3.12
  • GluonTS (PyTorch)
  • statsmodels
  • pmdarima
  • properscoring
  • scikit-learn
  • MLflow
  • DVC
  • pytest

TL;DR

  • Problem

    A forecast's intervals matter as much as its point estimate. If "90%" intervals catch the truth only 74% of the time, every decision built on them is miscalibrated.

  • What I built

    A reproduction of DeepAR on four years of hourly Beijing PM2.5 data, benchmarked against seasonal-naive, ARIMA and ETS with proper scoring rules, plus a calibration diagnosis and an attempted fix.

  • Result

    DeepAR led on accuracy (CRPS 43.6 vs ARIMA's 47.4). But its 90% intervals covered only 74% of outcomes, and post-hoc recalibration didn't fix that under the test period's shift.

DeepAR seed
000.50.511Nominal coverageObserved coverageOverconfident
  • Seasonal naive
  • ARIMA
  • ETS
  • DeepAR, mean of 5 (computed)

DeepAR · mean ± sd over 5 seeds

CRPS
43.62 ± 1.75
ARIMA 47.418
RMSE
99.52 ± 2.26
ARIMA 103.454
90% coverage
74% ± 4%
ARIMA 80.4%
ECE
0.13 ± 0.03
ARIMA 0.072

DeepAR, mean of 5: CRPS 43.62 ± 1.75, RMSE 99.52 ± 2.26, 90% coverage 74% ± 4%, ECE 0.13 ± 0.03.

ECE by forecast horizon · best seed (1337)

1 h ahead
0.032
6 h ahead
0.060
12 h ahead
0.116
24 h ahead
0.167

ECE here is the average gap between nominal and observed interval coverage across nine levels.

Observed against nominal coverage of the forecast intervals on the test set, at nine levels. Lines below the diagonal are overconfident: their intervals are too narrow.Source
View data
Observed interval coverage at each nominal level
NominalSeasonal naiveARIMAETSDeepAR, seed 42DeepAR, seed 123DeepAR, seed 456DeepAR, seed 789DeepAR, seed 1337DeepAR, mean of 5 (computed)
0.10.0970.1870.1730.0650.0690.0660.0750.0880.073
0.20.1690.320.340.1180.1310.1410.1360.1670.139
0.30.2440.4050.4720.1770.2010.210.2070.2380.207
0.40.3260.480.580.250.2610.2910.2790.3110.278
0.50.40.5540.6560.3130.340.3760.3520.3920.355
0.60.4720.6240.720.3830.4090.470.4290.4780.434
0.70.5710.6770.7810.4740.5040.5580.5120.5710.524
0.80.6790.7410.8390.5710.5920.6630.6150.6770.624
0.90.7860.8040.8790.6790.7220.7680.7390.7970.741

Setup

  • Data: UCI Beijing air-quality data from the Aotizhongxin station — hourly PM2.5 from March 2013 to February 2017, 35,064 rows.
  • Splits:
    • train to June 2016;
    • validate July–December 2016;
    • test January–February 2017, on 59 non-overlapping 24-hour windows. Each test window is forecast from the training data and the earlier test hours; the validation months aren't in its history (the same for every model).
  • DeepAR: a one-week context, a 24-hour horizon and Student-t outputs, trained over 5 seeds.
  • Scoring: every model produces 100 sample paths, and the same code scores them all.

Results

Test set; DeepAR is mean ± sd over 5 seeds.

ModelCRPSRMSE90% coverageECE
Seasonal naive68.4137.578.6%0.084
ARIMA47.4103.580.4%0.072
ETS47.5104.987.9%0.109
DeepAR43.6 ± 1.799.5 ± 2.374.1% ± 4.00.125 ± 0.027

ECE here is the average gap between nominal and observed interval coverage across nine levels.

Where it breaks

Miscalibration grows with the horizon. For the best seed, the coverage gap is 0.03 one hour ahead and 0.17 at 24 hours.

The fix that didn't work

  • The method: isotonic recalibration (Kuleshov et al.), fitted on the validation period.
  • Validation: DeepAR was already well calibrated there (gap 0.014), so there was nothing to learn.
  • Test: on the shifted winter test period, the gap went from 0.090 to 0.098.
  • The diagnostic: a fit on the test period itself reaches 0.020. The method works; the shift is the problem.

Lessons

  • Measure calibration explicitly.
  • Report results across seeds; CRPS ranged from 41.4 to 46.2 across them.
  • A validation set has to look like the future.

Limitations and what's next

  • Scope: a single station, PM2.5 history only (no weather covariates), and untuned hyperparameters.
  • Next: shift-aware and conditional recalibration, more stations, and newer models.
How this was built

I designed the experiments and the evaluation, ran them, and wrote up the findings; Claude Code helped write the code.