Scoring Probabilistic Forecasts: CRPS, PIT Calibration, and DeepAR
Бұл блог нүктелердің орнына үлестірімдерді болжаудың мәнін түсіндіріп қойған және интервал қолыңызда болғаннан кейін онымен не істеу керегін де көрсеткен: тәуекелді ескеретін позиция өлшеміне арналған конформды болжау үлестірімнен тәуелсіз интервалдар мен оларды пайдаланатын өлшем ережесін шығарады, ал Temporal Fusion Transformer мақаласы көп-квантильді шығыс қабатын ұсынады. Екеуі де оның сенімге лайық екенін анықтайтын ең маңызды бөлікті қамтымайды: болжамды үлестірімді қалай бағалау және онда көрсетілген белгісіздіктің шынайы екенін қалай тексеру керек.
Мақала осы туралы. Нақты үш мәселе:
- CRPS — ықтималдық болжамдарына арналған proper scoring rule және оның TFT мақаласында қолданылған pinball loss-пен дәл байланысы.
- PIT гистограммасы — модельдің калибрленбегенін ғана емес, қалай калибрленбегенін көрсететін диагностикалық құрал.
- DeepAR — авторегрессиялық sampling модельдер отбасы; бұл блогта ол әзірге тек benchmark ескертпесінде кездеседі және түсіндірілмеген.
Алдымен бір маңызды контекст: ол сізге қандай құралдар керегін анықтайды. Нүктелік болжам режимге қарай әртүрлі сәтсіздікке ұшырайды: төмен волатильді трендте шартты үлестірім тар әрі симметрияға жақын, сондықтан нүктелік болжам жақсы қысқаша сипаттама; жоспарланған оқиға алдында үлестірім бимодальды, ал шартты орташа баға қону ықтималдығы ең төмен жерде орналасады; дағдарыста сол жақ құйрық басым болып, орташа мән төмендеу тәуекелін қатты кемітеді. Толық үлестірімді қалпына келтірудің үш жолы бар — параметрлік (болжанған отбасы параметрлерін болжау: жылдам, бірақ қате спецификация қаупі бар), квантильдік (тұрақты торды болжау: болжамға тәуелсіз, дискретті) және sample-based (авторегрессиялық sampling, dropout немесе ансамбльдерден Monte Carlo жолдары: икемді, қымбат). Соңғы екі тәсіл қаржыда басым, себебі осы режимдерде үлестірімнің пішіні өзгереді.
Квантильдік болжамдар қысқаша

Pinball loss пен нақты квантиль торы TFT квантильдік шығыс қабаты мақаласында берілген, сондықтан формуланы қайталамаймын. Есте сақтайтын негізгі нәрсе — асимметриясы: кезінде артық және кем болжаудың құны бірдей, loss MAE-ге дейін түседі, бірақ кезінде кем болжау артық болжауға қарағанда қаттырақ жазаланады. қатынасы механизмнің өзі: ол fitted мәнді бақылаулардың тек 5%-ы ғана асып түсетін деңгейге тартады.
Жарияланған мақалада айтылмаған практикалық қиындық — квантильдердің қиылысуы: жеке квантильдік loss ештеңенің болуына жол бермейді. Дерек жеткілікті әрі ортақ backbone қолданылса, бұл сирек кездеседі, бірақ жұқа үлгілердегі шеткі квантильдерде болады және кейінгі CRPS немесе coverage есептеуін үнсіз бұзады. Арзан түзету — болжанған квантиль векторын post-hoc сұрыптау; принципті шешім — монотонды шығыс параметризациясы ( және нөлден кем емес өсімдерді болжау).
Forecasting framework қабылдамай-ақ квантильдік шығыстар қажет болса, нөлден жазылған минималды реализация:
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()
Интервал ені позиция өлшеміне айналады, біз бұл сәйкестікті — edge-ratio no-trade сүзгісін қоса — тәуекелді ескеретін позиция өлшеміне арналған конформды болжау мақаласында толық шығарамыз; -ның volatility targeting-пен эквиваленті GARCH болжамдарымен volatility targeting мақаласында берілген.
DeepAR: авторегрессиялық ықтималдық болжамы

DeepAR (Salinas және басқалар, 2020) sample-based жолын қолданады. Квантильдерді тікелей болжаудың орнына, ол әр қадамда likelihood-ті параметрлеу үшін авторегрессиялық RNN пайдаланады, содан кейін сол likelihood-ті алға жылжытып, толық болжамды үлестірімдерді sampling арқылы алады.
Архитектура. Әрбір қадамында желі алдыңғы бақылау -ді (әр серияға арналған фактор арқылы масштабталған), ковариаттар және қосымша static features тұтынады. LSTM күйді тасымалдайды:
Dense head -ті likelihood параметрлеріне түрлендіреді — Gaussian үшін , Student-t үшін . Оқыту барлық сериялар бойынша мәнін максималдайды. Inference кезінде -ден sample алып, оны келесі кіріс ретінде қайта бересіз және траектория алу үшін рет қайталайсыз.
Trading үшін екі салдар маңызды. Likelihood — модельдеу таңдауы, сондықтан майлы құйрықтар үміт емес, әдейі таңдалатын нәрсе: ауыр құйрықтарға Student-t немесе бимодальды оқиға үшін Gaussian mixture . Көпқадамды болжамдар үйлесімді: әр sample path сериялық корреляцияны сақтайтын ықтимал траектория, бұл бір қадамнан ұзын горизонт үшін қажет. (Үшінші артықшылық — көп серияға бір global model қолдану per-asset model-ден тиімді — TFT мақаласында келтірілген data-efficiency аргументімен бірдей; мұндағы static encoder рөлін per-series scaling және static covariates атқарады.)
GluonTS көмегімен DeepAR
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 Monte Carlo жолдарының санын басқарады. Live use үшін әдетте 100--200 жеткілікті; offline evaluation үшін 500--1000 қолданыңыз, себебі тым аз sample арқылы есептелген CRPS төмен қарай bias болады.
Predictive distribution алудың басқа жолдары
Үш балама тәсілді білген жөн, бірақ олар жеке бөлімдерге лайық емес. MC Dropout (Gal және Ghahramani, 2016) inference кезінде dropout-ты қосулы қалдырып, forward pass бойынша орташа мен дисперсияны алады; бұл жасырынған approximate variational inference және бұрын оқытылған модельге uncertainty қосудың ең жылдам жолы. Deep ensembles (Lakshminarayanan және басқалар, 2017) әртүрлі seed-терден көшірме оқытып, predictive distribution-ды mixture ретінде қарастырады — benchmark-терде тұрақты күшті, құны , сондықтан latency-sensitive горизонттарды жоққа шығарады, бірақ 4h немесе daily горизонттарды емес. Normalizing flows (Rasul және басқалар, 2021) параметрлік family таңдамастан multimodality және asymmetry-ді ұстап, қарапайым base density-ден ерікті target-ке invertible map үйренеді. Үшеуі де sample шығарады, сондықтан келесі бөлімдегі барлық нәрсе оларға өзгеріссіз қолданылады.
CRPS: Predictive distribution үшін дұрыс метрика

MSE және MAE point forecast-тарды бағалайды. Олар үлестірімнің жақсы екенін айта алмайды, себебі тек оның бір summary-іне қарайды. Стандартты алмастыру — Continuous Ranked Probability Score, бұл мақаладағы ең пайдалы жалғыз құрал.
CRPS — болжанған CDF пен барлық массаны нақты болған нәтижеге қоятын degenerate CDF арасындағы интегралданған квадраттық қашықтық:
Оны маңызды ететін үш қасиет бар:
- Бұл proper scoring rule (Gneiting және Raftery, 2007): күтілетін мән бойынша болжанған үлестірім шын үлестірімге тең болғанда ғана минимумға жетеді. Әдейі overconfidence те, жасанды кең үлестіріммен hedging те score-ды жақсартпайды. MSE-on-the-median-де мұндай қасиет жоқ, сондықтан CRPS міндетті.
- MAE-ді жалпылайды. Degenerate point forecast үшін ол absolute error-ге дейін түседі, сондықтан CRPS target бірліктерінде болады — hourly log return болжасаңыз, CRPS 0.004 unitless сан емес, 40 bps mean absolute error-мен тікелей салыстырылады.
- Calibration шартымен sharpness-ті марапаттайды. Бірдей калибрленген екі forecast арасында тар болғаны жақсырақ score алады. Сондықтан CRPS жалғыз жеткіліксіз: нашар score екі шарттың қайсысы бұзылғанын айтпайды, келесі бөлім осы үшін.
Quantile loss-пен байланыс
Бұл — блогтың қалған бөлігінде жетіспейтін байланыс. Толық CDF орнына quantile grid берілсе, CRPS pinball loss арқылы жуықталады:
Мұны тура мағынасында оқыңыз: TFT мақаласында reported "quantile loss" пен мұнда талқыланған CRPS 2 коэффициентіне дейін бір шаманың өзі, ал grid тығыздалған сайын жуықтау жақсарады. Олар бәсекелес метрикалар емес, екеуін де есеп беруге себеп жоқ.
Бірлікке қатысты ескерту. GluonTS mean_wQuantileLoss — сол averaged pinball loss, бірақ абсолют target мәндерінің қосындысына normalized, сондықтан dimensionless және масштабы әртүрлі asset-тер арасында салыстырмалы. Дұрыс CRPS normalized емес, return unit-терінде қалады. mean_wQuantileLoss-ті "CRPS" деген label астында шығармаңыз — бұл мақаланың draft нұсқасы дәл солай істеген, бір шкалада емес екі санды салыстырудың оңай жолы.
Sample-дерден CRPS
Monte Carlo sample-дері (DeepAR, ensembles, flows) болса, energy form-ды қолданыңыз:
Бірінші мүше дәлдікті марапаттайды, екіншісі overdispersion-ды жазалайды. Екінші мүшені naively жазсаңыз, ол болады және мыңдаған forecast бағалағанда bottleneck-ке айналады. Алдымен sort жасау оны қысқартады: өсу реттелген order statistics үшін double sum -ге тең, сондықтан толық есеп , оның басым бөлігі sort-қа кетеді.
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)
Екі форма да бірдей predictive distribution үшін жақын мән беруі керек; бермесе, quantile crossing немесе sample санының тым аз екенін күдіктеніңіз. Production-да properscoring.crps_ensemble(observation, samples) жақсы тексерілген drop-in.
Calibration: Көрсетілген uncertainty шынайы ма?

Жақсы CRPS интервалдар мәлімдеген мағынаны беретініне кепіл емес. Nominal 90% interval outcomes-тың 70%-ын ғана қамтитын модель overconfident; interval width-ті оқитын sizing rule-де ол қажет болмаған сәтте дәл сол кезде leverage-ті арттырады. TFT мақаласы ескертуді айтып, conformal prediction-ды түзету ретінде ұсынады; төменде бұл failure-ді қалай анықтап, диагноз қоятыныңыз көрсетіледі.
PIT гистограммасы
Әрбір бақылау үшін оны сол қадамның predicted CDF ішіндегі quantile ретінде есептеңіз:
Модель калибрленген болса, мәндері аралығында uniform болады. Диагностикалық күш ауытқудың пішіні failure mode-ты атауында:
- U-тәрізді: overconfident — құйрықтарға тым көп масса түседі, үлестірім тым тар.
- Төбелі: underconfident — бақылаулар орталық маңына жиналады, үлестірім тым кең.
- Солға қисайған: модель жүйелі түрде артық болжайды.
- Оңға қисайған: модель жүйелі түрде кем болжайды.
Joint risk үшін copula models мақаласындағы copula модельдерімен шатастырмау керек. Онда probability integral transform — GARCH-EVT marginal-дарын pseudo-uniform observation-дарға айналдырып, copula fit жасауға арналған marginal transform, яғни preprocessing қадамы. Мұнда transform out-of-sample forecast-тарға қолданылады, ал uniformity — тексерілетін нәтиже, жасалатын input емес. Математика бірдей, inference бағыты қарама-қарсы.
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
KS test туралы екі ескерту. Ол PIT мәндері тәуелсіз деп санайды, ал overlapping multi-horizon forecast-тар қатты autocorrelated — p-value-ді тек шамамен flag деп қабылдаңыз, нақты дәлел histogram shape болады. Сонымен бірге бақылау жеткілікті болса, тест маңызды емес калибрлеу ауытқуы үшін де uniformity-ді қабылдамайды; шешімді effect size анықтауы керек.
Coverage тексеруі
Қарапайым, бірақ тікелей қолданылатын диагностика: % интервал outcomes-тың %-ын қамти ма?
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
Ереже ретінде: typical sample size кезінде 2 percentage point-тан аз gap — noise, ал 3--5 point-тан үлкен gap — position sizing-те көрінетін нақты calibration problem. Оны pooled емес, әр horizon бойынша іске қосыңыз — horizon ұзарған сайын coverage дерлік әрқашан нашарлайды, pooling оны жасырады.
Calibration сәтсіз болғанда
Кепілдік деңгейі артатын ретпен үш стандартты түзету бар. Temperature scaling held-out data-да fit жасалған арқылы predicted scale parameter-ді бөледі — бір параметр, өте арзан; uniform over/underconfidence-ді түзетеді, бірақ shape-dependent қатені емес. Isotonic recalibration predicted quantile levels-ді observed frequencies-ке монотонды түрде mapping етеді, бұл shape distortion-ды өңдейді, бірақ calibration set жеткілікті үлкен болуы керек. Conformal prediction кез келген модельді орап, finite-sample coverage guarantees береді; толық split-conformal алгоритмі, нақты order-statistic rank және бір жолды summary жасыратын interpolation мен clamping тұзақтары trading үшін conformal prediction мақаласында бар.
Практикалық ескертулер

Non-stationarity. Volatility regime өзгерген сайын calibration drift болады, сондықтан тұрақты calibration set ескіреді. Дәл осы жағдайға арналған механизм — Adaptive Conformal Inference: ол online miscoverage level-ді жаңартып, adversarial sequence жағдайында да ұзақ мерзімді coverage кепілдігін сақтайды — trading үшін conformal prediction мақаласының ACI бөліміне қараңыз.
Computational cost. 500 sample path бар DeepAR шамамен бір forward pass-тен қымбат. Intraday жұмыс үшін quantile regression-ді (барлық quantile бір pass-та) немесе parametric head-ті ( бір рет болжайды) таңдаңыз; autoregressive sampling-ті 4h және daily горизонттарға қалдырыңыз.
Regime changes. Forecaster-ді regime detector-мен жұптап, әр regime үшін жеке calibration parameter-лерін сақтаңыз — HMM көмегімен regime detection мақаласында толық implementation және backtest бар.
Multivariate forecasts. Әр asset-тің marginal distribution-ы portfolio risk үшін жеткіліксіз; маңыздысы — joint tail, ал joint risk үшін copula models independence assumption-дары оны қаншалық төмен бағалайтынын санмен көрсетеді.
Downstream consumers. VaR және expected shortfall sample-based forecast-тен quantile және conditional tail mean ретінде тікелей шығады — анықтамалар мен Monte Carlo рецепті joint risk үшін copula models мақаласында. Kelly sizing тегін келмейтін жалғыз нәрсе: forecast interval-ден Kelly fraction шығару сол interval ішіндегі үлестірім туралы қосымша assumption талап етеді, ал conformal мақаласы interval ratio-ны -ке жай жалғауға тікелей қарсы. Fraction нақты нені талап ететінін стратегияларды өлшемдеуге арналған Kelly criterion мақаласынан қараңыз.
Қорытынды

Ықтималдық forecast үшін evaluation stack қысқа әрі міндетті: score үшін CRPS, себебі ол proper және target unit-терінде өлшенеді; диагноз үшін PIT histogram, себебі ол жай flag қоймай, failure mode-ты атайды; шешім үшін per-horizon coverage, өйткені позиция өлшеміне айналатын сан осы. "Quantile loss" ретінде reported кез келген нәрсе CRPS-ке 2 коэффициентіне дейін тең, сондықтан мұнда екі емес, бір метрика бар.
Ыңғайсыз шындық — мұның бәрі модельдің сіз күткеннен нашар екенін анықтауға арналған құрал. Дәл мақсат та осы. Білмеген кезде интервалын адал кеңейтетін модель өзіне сенімді түрде тар болып қалатын модельден әлдеқайда пайдалы; оларды ажыратудың жалғыз жолы — дұрыс бағалау.
Дереккөздер
- 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
Authors
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.