Scoring Probabilistic Forecasts: CRPS, PIT Calibration, and DeepAR
Blog ini telah menerangkan sebab kita perlu meramalkan taburan dan bukannya titik, serta cara menggunakan selang apabila sudah memilikinya: ramalan konformal untuk saiz posisi sedar risiko menerbitkan selang tanpa taburan dan peraturan saiz yang menggunakannya, manakala artikel Temporal Fusion Transformer menyediakan lapisan output berbilang kuantil. Namun kedua-duanya tidak membincangkan perkara yang menentukan sama ada semua itu wajar dipercayai: cara menilai taburan ramalan dan cara menyemak sama ada ketidakpastian yang dinyatakannya benar-benar jujur.
Itulah yang dibincangkan dalam artikel ini. Tiga perkara khususnya:
- CRPS — peraturan skor wajar untuk ramalan kebarangkalian dan hubungannya yang tepat dengan loss pinball yang telah dilaporkan dalam artikel TFT.
- Histogram PIT — diagnostik penentukuran yang menunjukkan bagaimana model tersalah penentukuran, bukan sekadar sama ada ia tersalah atau tidak.
- DeepAR — keluarga model pensampelan autoregresif yang sebelum ini hanya muncul sebagai nota kaki benchmark di blog ini dan tidak pernah diterangkan.
Satu kerangka dahulu, kerana ia menentukan peralatan yang diperlukan. Ramalan titik gagal dengan cara yang berbeza mengikut rejim: dalam trend volatiliti rendah, taburan bersyarat adalah sempit dan hampir simetri, jadi ramalan titik ialah ringkasan yang baik; sebelum peristiwa yang dijadualkan, taburan itu bimodal dan min bersyarat berada tepat di tempat harga paling kurang berkemungkinan mendarat; dalam krisis, ekor kiri mendominasi dan min meremehkan risiko penurunan dengan ketara. Tiga laluan boleh memulihkan taburan penuh — parametrik (ramalkan parameter keluarga yang diandaikan: pantas, tetapi berisiko tersalah spesifikasi), berasaskan kuantil (ramalkan grid tetap: bebas andaian, diskret), dan berasaskan sampel (laluan Monte Carlo daripada pensampelan autoregresif, dropout atau ensemble: fleksibel, mahal). Dua yang terakhir dominan dalam kewangan kerana bentuk taburan memang berubah merentas rejim tersebut.
Ramalan Kuantil secara Ringkas

Loss pinball dan grid kuantil yang konkrit telah diberikan dalam lapisan output kuantil TFT, jadi formula itu tidak akan diulang. Perkara yang perlu difahami ialah ketaksimetriannya: pada , ramalan berlebihan dan terkurang mempunyai kos yang sama dan loss menjadi MAE, tetapi pada , ramalan terkurang dikenakan penalti lebih besar daripada ramalan berlebihan. Nisbah itulah mekanismenya — nisbah ini menarik nilai yang dipadankan ke tahap yang hanya 5% pemerhatian patut melebihinya.
Masalah praktikal yang tidak disebut dalam artikel terbitan itu ialah persilangan kuantil: tiada apa-apa dalam loss setiap kuantil yang menghalang . Dengan data yang mencukupi dan backbone yang dikongsi, keadaan ini jarang berlaku, tetapi ia boleh muncul pada kuantil ekstrem apabila sampel sedikit dan secara senyap merosakkan pengiraan CRPS atau liputan seterusnya. Penyelesaian murah ialah mengisih vektor kuantil yang diramal selepas model dilatih; penyelesaian berprinsip ialah parameterisasi output monoton (ramalkan bersama kenaikan bukan negatif).
Berikut ialah implementasi minimum dari awal, berguna apabila anda mahukan output kuantil tanpa mengguna pakai framework ramalan:
import torch
import torch.nn as nn
class QuantileRegressionNet(nn.Module):
"""Multi-quantile forecasting network for financial returns."""
def __init__(self, input_dim: int, hidden_dim: int = 128,
quantiles: list[float] = [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99]):
super().__init__()
self.quantiles = quantiles
self.backbone = nn.Sequential(
nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.2),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.2),
)
self.heads = nn.ModuleList([nn.Linear(hidden_dim, 1) for _ in quantiles])
def forward(self, x: torch.Tensor) -> torch.Tensor:
h = self.backbone(x)
out = torch.cat([head(h) for head in self.heads], dim=-1)
return torch.sort(out, dim=-1).values
def pinball_loss(predictions: torch.Tensor, targets: torch.Tensor,
quantiles: list[float]) -> torch.Tensor:
"""predictions: (batch, n_quantiles); targets: (batch, 1)."""
errors = targets - predictions
tau = torch.tensor(quantiles, device=predictions.device).unsqueeze(0)
return torch.max(tau * errors, (tau - 1) * errors).mean()
Lebar selang dipetakan kepada saiz posisi, dan pemetaan itu dihuraikan sepenuhnya — termasuk penapis no-trade nisbah edge — dalam ramalan konformal untuk saiz posisi sedar risiko; kesetaraan dengan penyasaran volatiliti diterbitkan dalam penyasaran volatiliti dengan ramalan GARCH.
DeepAR: Ramalan Kebarangkalian Autoregresif

DeepAR (Salinas et al., 2020) menggunakan pendekatan berasaskan sampel. Daripada meramalkan kuantil secara langsung, ia menggunakan RNN autoregresif untuk memparameterkan likelihood pada setiap langkah, kemudian menghasilkan taburan ramalan penuh dengan menggerakkan likelihood itu ke hadapan.
Seni bina. Pada setiap langkah , rangkaian menerima pemerhatian sebelumnya (diskalakan dengan faktor setiap siri), kovariat dan ciri statik pilihan. LSTM membawa state:
Head dense memetakan kepada parameter likelihood — untuk Gaussian, untuk Student-t. Latihan memaksimumkan merentas semua siri. Semasa inferens, anda membuat sampel daripada , memasukkan sampel itu kembali sebagai input seterusnya, dan mengulang kali untuk mendapatkan trajektori.
Dua akibat penting untuk dagangan. Likelihood ialah pilihan pemodelan, jadi ekor gemuk perlu dipilih dan bukannya diharapkan — Student-t untuk ekor berat, atau campuran Gaussian untuk tingkah laku peristiwa bimodal. Ramalan berbilang langkah adalah koheren: setiap laluan sampel ialah trajektori munasabah yang mengekalkan korelasi bersiri, iaitu perkara yang diperlukan untuk horizon lebih panjang daripada satu langkah. (Hujah ketiga yang biasa digunakan — satu model global merentas banyak siri mengatasi model bagi setiap aset — ialah hujah kecekapan data yang sama dalam artikel TFT, dengan penskalaan setiap siri dan kovariat statik memainkan peranan pengekod statik di sana.)
DeepAR dengan GluonTS
import pandas as pd
from gluonts.dataset.pandas import PandasDataset
from gluonts.torch.model.deepar import DeepAREstimator
from gluonts.torch.distributions import StudentTOutput
from gluonts.evaluation import make_evaluation_predictions, Evaluator
def prepare_crypto_dataset(returns_df: pd.DataFrame, freq: str = "h"):
"""Wide return frame (index=datetime, columns=assets) -> GluonTS dataset.
from_long_dataframe wants LONG format: one row per (timestamp, item_id)
with the target in a column. Passing a DataFrame of dicts does not work.
"""
long_df = (
returns_df.stack()
.rename("target")
.rename_axis(index=["timestamp", "item_id"])
.reset_index()
.dropna(subset=["target"])
)
return PandasDataset.from_long_dataframe(
long_df, target="target", item_id="item_id",
timestamp="timestamp", freq=freq,
)
estimator = DeepAREstimator(
prediction_length=24, # 24 hours ahead
context_length=168, # one week of hourly data
freq="h",
num_layers=2,
hidden_size=64,
dropout_rate=0.1,
lr=1e-3,
batch_size=64,
trainer_kwargs={"max_epochs": 50},
distr_output=StudentTOutput(),
)
predictor = estimator.train(training_data=train_dataset)
forecast_it, ts_it = make_evaluation_predictions(
dataset=test_dataset, predictor=predictor, num_samples=500,
)
forecasts, actuals = list(forecast_it), list(ts_it)
evaluator = Evaluator(quantiles=[0.05, 0.25, 0.5, 0.75, 0.95])
agg_metrics, item_metrics = evaluator(actuals, forecasts)
print(f"mean_wQuantileLoss: {agg_metrics['mean_wQuantileLoss']:.4f}")
num_samples mengawal bilangan laluan Monte Carlo. Untuk penggunaan langsung, 100-200 biasanya mencukupi; untuk penilaian luar talian gunakan 500-1000 kerana CRPS yang dianggarkan daripada terlalu sedikit sampel mempunyai bias ke bawah.
Laluan Lain kepada Taburan Ramalan
Tiga alternatif wajar diketahui tetapi tidak memerlukan bahagian tersendiri. MC Dropout (Gal dan Ghahramani, 2016) mengekalkan dropout aktif semasa inferens dan mengambil min serta varians merentas forward pass; ia sebenarnya inferens variasi anggaran, dan merupakan cara terpantas untuk menambah ketidakpastian pada model yang telah dilatih. Ensemble mendalam (Lakshminarayanan et al., 2017) melatih salinan dengan seed berbeza dan menganggap taburan ramalan sebagai campuran — secara konsisten kuat dalam benchmark, pada kos , yang menyingkirkannya untuk horizon sensitif latensi tetapi bukan untuk 4 jam atau harian. Normalizing flow (Rasul et al., 2021) mempelajari pemetaan boleh songsang daripada ketumpatan asas mudah kepada sasaran sewenang-wenangnya, lalu menangkap multimodaliti dan ketaksimetrian tanpa memilih keluarga parametrik. Ketiga-tiganya menghasilkan sampel, jadi segala-galanya dalam bahagian seterusnya terpakai tanpa perubahan.
CRPS: Metrik yang Tepat untuk Taburan Ramalan

MSE dan MAE menilai ramalan titik. Kedua-duanya tidak dapat memberitahu sama ada sesuatu taburan itu baik kerana hanya melihat satu ringkasannya. Pengganti standard ialah Continuous Ranked Probability Score, dan inilah perkara paling berguna dalam artikel ini.
CRPS ialah jarak kuasa dua bersepadu antara CDF yang diramal dengan CDF terdegenerasi yang meletakkan semua jisim pada perkara yang sebenarnya berlaku:
Tiga sifat menjadikannya penting:
- Ia ialah peraturan skor wajar (Gneiting dan Raftery, 2007): diminimumkan secara jangkaan hanya apabila taburan yang diramal sama dengan taburan sebenar. Keyakinan berlebihan yang disengajakan atau lindung nilai dengan taburan yang dibuat terlalu lebar tidak meningkatkan skor. MSE pada median tiada sifat ini, sebab itu CRPS bukan pilihan tambahan.
- Ia menggeneralisasikan MAE. Untuk ramalan titik terdegenerasi, ia menjadi error mutlak, jadi CRPS berada dalam unit sasaran — bagi ramalan pulangan log setiap jam, CRPS 0.004 boleh dibandingkan terus dengan purata error mutlak 40 bps, bukannya nombor tanpa unit yang tidak boleh disemak kewajarannya.
- Ia memberi ganjaran kepada ketajaman tertakluk pada penentukuran. Antara dua ramalan yang sama-sama ditentukur, ramalan yang lebih sempit mendapat skor lebih baik. Sebab itu CRPS sahaja tidak mencukupi: skor buruk tidak memberitahu syarat mana yang gagal, dan itulah tujuan bahagian seterusnya.
Jambatan kepada Loss Kuantil
Inilah hubungan yang belum ada dalam bahagian blog yang lain. Dengan grid kuantil dan bukannya CDF penuh, CRPS dianggarkan melalui loss pinball:
Bacalah secara literal: "loss kuantil" yang dilaporkan dalam artikel TFT dan CRPS yang dibincangkan di sini ialah kuantiti yang sama sehingga faktor 2, dengan anggaran menjadi lebih tepat apabila grid dipadatkan. Kedua-duanya bukan metrik yang bersaing dan tiada sebab untuk melaporkan kedua-duanya.
Satu peringatan unit, kerana kesilapan ini mudah berlaku. mean_wQuantileLoss GluonTS ialah purata loss pinball yang sama tetapi dinormalkan dengan jumlah nilai mutlak sasaran, menjadikannya tanpa dimensi dan boleh dibandingkan merentas aset berskala berbeza. CRPS sebenar tidak dinormalkan dan kekal dalam unit pulangan. Jangan cetak mean_wQuantileLoss di bawah label "CRPS" — versi draf artikel ini pernah melakukan tepat kesilapan itu, dan ia cara mudah membandingkan dua nombor yang tidak berada pada skala sama.
CRPS daripada Sampel
Dengan sampel Monte Carlo (DeepAR, ensemble, flow), gunakan bentuk tenaga:
Suku pertama memberi ganjaran kepada ketepatan, manakala suku kedua mengenakan penalti terhadap penyebaran berlebihan. Jika ditulis secara naif, suku kedua ialah dan menjadi bottleneck apabila menilai ribuan ramalan. Pengisihan terlebih dahulu memudahkannya: untuk statistik susunan menaik , jumlah berganda bersamaan , maka keseluruhannya ialah yang didominasi oleh pengisihan.
import numpy as np
def crps_empirical(samples: np.ndarray, observation: float) -> float:
"""CRPS from Monte Carlo samples, O(n log n) via order statistics."""
n = len(samples)
mae = np.mean(np.abs(samples - observation))
x = np.sort(samples)
k = np.arange(1, n + 1)
dispersion = np.sum((2 * k - n - 1) * x) / n**2
return mae - dispersion
def crps_quantile(quantile_predictions: np.ndarray,
quantile_levels: np.ndarray,
observation: float) -> float:
"""CRPS approximation from a quantile grid (2x mean pinball loss)."""
errors = observation - quantile_predictions
pinball = np.where(errors >= 0,
quantile_levels * errors,
(quantile_levels - 1) * errors)
return 2.0 * np.mean(pinball)
Kedua-dua bentuk sepatutnya hampir sama pada taburan ramalan yang sama; jika tidak, syaki persilangan kuantil atau sampel yang terlalu sedikit. Dalam production, properscoring.crps_ensemble(observation, samples) ialah pengganti drop-in yang telah diuji dengan baik.
Penentukuran: Adakah Ketidakpastian yang Dinyatakan Jujur?

CRPS yang baik tidak menjamin bahawa selang membawa maksud yang dinyatakannya. Model yang selang nominal 90%-nya hanya merangkumi 70% hasil adalah terlalu yakin, dan dalam peraturan saiz yang membaca lebar selang, model itu akan menaikkan leverage tepat ketika sepatutnya tidak. Artikel TFT menyatakan amaran ini dan mencadangkan ramalan konformal sebagai pembaikan; bahagian berikut menunjukkan cara mengesan dan mendiagnosis kegagalan tersebut.
Histogram PIT
Untuk setiap pemerhatian , kira kuantilnya di bawah CDF yang diramal pada langkah tersebut:
Jika model ditentukur, adalah seragam pada . Kekuatan diagnostiknya ialah bentuk penyimpangan menamakan mod kegagalan:
- Berbentuk U: terlalu yakin — terlalu banyak jisim berada di ekor, taburan terlalu sempit.
- Berbentuk bonggol: kurang yakin — pemerhatian berkumpul dekat pusat, taburan terlalu lebar.
- Condong ke kiri: model secara sistematik meramalkan terlalu tinggi.
- Condong ke kanan: model secara sistematik meramalkan terlalu rendah.
Nota untuk mengelakkan kekeliruan dengan model kopula untuk risiko bersama, yang turut menggunakan transformasi kamiran kebarangkalian: di sana ia ialah transformasi marginal, iaitu langkah prapemprosesan yang menukar marginal GARCH-EVT kepada pemerhatian pseudo-seragam supaya kopula boleh dipadankan kepadanya. Di sini transformasi digunakan pada ramalan luar sampel dan keseragaman ialah hasil yang diuji, bukan input yang dihasilkan. Matematik sama, tetapi arah inferens berbeza.
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import kstest
def pit_calibration_check(forecasts, actuals, n_bins=20):
"""PIT histogram + KS test against uniform.
forecasts: list of SampleForecast objects (GluonTS)
"""
pit_values = []
for forecast, actual in zip(forecasts, actuals):
samples = forecast.samples # (n_samples, prediction_length)
h = forecast.prediction_length
for t in range(h):
obs = actual.values[-h + t]
pit_values.append(np.mean(samples[:, t] <= obs))
pit_values = np.array(pit_values)
ks_stat, p_value = kstest(pit_values, "uniform")
fig, ax = plt.subplots(figsize=(8, 4))
ax.hist(pit_values, bins=n_bins, density=True, alpha=0.7, edgecolor="black")
ax.axhline(y=1.0, color="red", linestyle="--", label="Perfect calibration")
ax.set_xlabel("PIT value")
ax.set_ylabel("Density")
ax.set_title(f"PIT Histogram (KS={ks_stat:.3f}, p={p_value:.3f})")
ax.legend()
plt.tight_layout()
return pit_values, ks_stat, p_value
Dua peringatan tentang ujian KS. Ujian ini menganggap nilai PIT bebas, sedangkan ramalan multi-horizon yang bertindih sangat autokorelasi — jadi anggap p-value sebagai petunjuk kasar dan bentuk histogram sebagai bukti sebenar. Dengan pemerhatian yang mencukupi, ujian juga menolak keseragaman bagi salah penentukuran yang terlalu kecil untuk penting; saiz kesanlah yang patut memandu keputusan.
Semakan Liputan
Diagnostik yang lebih kasar tetapi boleh diambil tindakan secara langsung: adakah selang % mengandungi % hasil?
import numpy as np
import pandas as pd
def coverage_table(forecasts, actuals, levels=(0.50, 0.80, 0.90, 0.95)):
"""Empirical coverage at multiple nominal levels."""
results = {}
for level in levels:
lower_q = (1 - level) / 2
upper_q = 1 - lower_q
covered = total = 0
for forecast, actual in zip(forecasts, actuals):
samples = forecast.samples
h = forecast.prediction_length
for t in range(h):
obs = actual.values[-h + t]
lo = np.quantile(samples[:, t], lower_q)
hi = np.quantile(samples[:, t], upper_q)
covered += int(lo <= obs <= hi)
total += 1
empirical = covered / total
results[f"{int(level*100)}% interval"] = {
"nominal": level, "empirical": empirical,
"gap": empirical - level,
}
return pd.DataFrame(results).T
Peraturan umum: jurang di bawah 2 mata peratusan ialah noise pada saiz sampel biasa, manakala jurang melebihi 3-5 mata ialah masalah penentukuran sebenar yang akan muncul dalam saiz posisi. Jalankan mengikut horizon, bukan secara terkumpul — liputan hampir sentiasa merosot apabila horizon memanjang dan pengumpulan akan menyembunyikannya.
Apabila Penentukuran Gagal
Tiga pembaikan standard, mengikut urutan jaminan yang meningkat. Penskalaan suhu membahagikan parameter skala yang diramal dengan yang dipelajari dan dipadankan pada data yang diketepikan — satu parameter, amat murah, membaiki keyakinan berlebihan/terkurang yang seragam tetapi tidak menangani apa-apa yang bergantung pada bentuk. Penentukuran semula isotoni memetakan aras kuantil yang diramal kepada frekuensi diperhatikan secara monoton, lalu menangani herotan bentuk tetapi memerlukan set penentukuran yang agak besar. Ramalan konformal membungkus sebarang model dan memberikan jaminan liputan sampel terhingga; algoritma split-conformal penuh, kedudukan statistik susunan tepat, serta perangkap interpolasi dan clamping yang ditimbulkan oleh ringkasan satu baris semuanya ada dalam ramalan konformal untuk dagangan.
Pertimbangan Praktikal

Ketidakpegunan. Penentukuran berubah apabila rejim volatiliti bertukar, jadi set penentukuran tetap semakin lapuk. Mekanisme yang direka khusus untuk ini ialah Adaptive Conformal Inference, yang mengemas kini aras salah liputan dalam talian dan membawa jaminan liputan jangka panjang walaupun di bawah jujukan adversarial — lihat bahagian ACI dalam ramalan konformal untuk dagangan.
Kos pengiraan. DeepAR dengan 500 laluan sampel berharga kira-kira satu forward pass. Untuk kerja intrahari, utamakan regresi kuantil (semua kuantil dalam satu pass) atau head parametrik (ramalkan sekali); simpan pensampelan autoregresif untuk horizon 4 jam dan harian.
Perubahan rejim. Padankan peramal dengan pengesan rejim dan simpan parameter penentukuran mengikut rejim — pengesanan rejim dengan HMM mengandungi implementasi dan backtest yang lengkap.
Ramalan multivariat. Taburan marginal setiap aset tidak mencukupi untuk risiko portfolio; ekor bersama yang penting, dan model kopula untuk risiko bersama mengukur sejauh mana andaian kebebasan memandang rendahnya.
Pengguna hiliran. VaR dan expected shortfall terus terhasil daripada ramalan berasaskan sampel sebagai kuantil dan min ekor bersyarat — definisi serta resipi Monte Carlo ada dalam model kopula untuk risiko bersama. Saiz Kelly ialah satu perkara yang tidak datang secara percuma: menerbitkan pecahan Kelly daripada selang ramalan memerlukan andaian tambahan tentang taburan dalam selang itu, dan artikel konformal secara jelas berhujah agar nisbah selang tidak dipasang pada . Lihat kriteria Kelly untuk strategi untuk perkara yang sebenarnya diperlukan oleh pecahan tersebut.
Kesimpulan

Susunan penilaian untuk ramalan kebarangkalian adalah ringkas dan tidak boleh dirunding: CRPS untuk skor, kerana ia wajar dan berada dalam unit sasaran; histogram PIT untuk diagnosis, kerana ia menamakan mod kegagalan dan bukan sekadar memberi bendera; liputan setiap horizon untuk keputusan, kerana nombor itulah yang dipetakan kepada saiz posisi. Apa-apa yang dilaporkan sebagai "loss kuantil" ialah CRPS sehingga faktor 2, jadi hanya ada satu metrik di sini, bukan dua.
Bahagian yang tidak selesa ialah semua ini merupakan peralatan untuk mengetahui bahawa model lebih teruk daripada yang diharapkan. Itulah tujuannya. Model yang secara jujur melebarkan selangnya apabila tidak tahu adalah jauh lebih berguna daripada model yang kekal sempit dengan penuh keyakinan, dan satu-satunya cara membezakan kedua-duanya ialah menilainya dengan betul.
Rujukan
- Salinas, D., Flunkert, V., Gasthaus, J., & Januschowski, T. (2020). "DeepAR: Probabilistic forecasting with autoregressive recurrent networks." International Journal of Forecasting, 36(3), 1181-1191.
- Koenker, R. & Bassett, G. (1978). "Regression Quantiles." Econometrica, 46(1), 33-50.
- Gneiting, T. & Raftery, A. E. (2007). "Strictly Proper Scoring Rules, Prediction, and Estimation." Journal of the American Statistical Association, 102(477), 359-378.
- Gneiting, T., Balabdaoui, F., & Raftery, A. E. (2007). "Probabilistic forecasts, calibration and sharpness." Journal of the Royal Statistical Society: Series B, 69(2), 243-268.
- Gal, Y. & Ghahramani, Z. (2016). "Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning." ICML.
- Lakshminarayanan, B., Pritzel, A., & Blundell, C. (2017). "Simple and Scalable Predictive Uncertainty Estimation using Deep Ensembles." NeurIPS.
- Rasul, K., Sheikh, A.-S., Schuster, I., Bergmann, U., & Vollgraf, R. (2021). "Multivariate Probabilistic Time Series Forecasting via Conditioned Normalizing Flows." ICLR.
- GluonTS: Probabilistic Time Series Modeling in Python. https://ts.gluon.ai
Pengarang
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.