Under the hood

Six models compete. The most honest one wins.

Every model trains on the same three seasons (720 games) and is tested on 180 games it has never seen. The champion is chosen by probability quality, not by accuracy or complexity. In plain terms: when it says 70%, does the team actually win 70% of the time?

Leaderboard

#ModelBrier โ†“Log loss โ†“ROC-AUC โ†‘AccuracyCalibration (ECE)
1PyTorch Neural Network Champion0.17440.51620.81573.0%0.0508overconfident
2Random Forest 0.17460.51320.81572.9%0.0468overconfident
3GRU Sequence Model 0.17610.51700.81172.5%0.0480overconfident
4Gradient Boosting 0.17840.53380.81272.7%0.0551overconfident
5Logistic Regression 0.18020.52770.80571.9%0.0508overconfident
6Baseline Rule Model 0.18370.56080.81072.5%0.0845overconfident

Note the gradient boosting row: strong accuracy, weak Brier score. A model can pick winners well while its percentages run too hot, and that is exactly why accuracy alone doesn't decide the champion.

Does 70% mean 70%?

Each dot buckets thousands of held-out predictions. On the dashed line, predicted probability matches reality. Below the line at the right edge means overconfidence.

0%0%25%25%50%50%75%75%100%100%predicted home win probability

How it's built

The full lifecycle, from raw play-by-play to the page you are reading, is one codebase.

900
games across 3 seasons
437,270
game states learned from
85
engineered features
6
models competing
The pipeline

A Python pipeline (pandas, scikit-learn, PyTorch) turns every NBA play into a game state, engineers 85 features covering time, score, momentum, possession, and team strength priors from the previous season, then trains all 6 models on identical data. Games are split at the game level so no moment from a test game ever leaks into training. A scheduled job repeats the whole cycle weekly during the season: download, retrain, re-select the champion, redeploy.

The model runs in your browser

This site has no Python server. The champion network's weights are exported to JSON and executed in TypeScript, along with a full port of the feature pipeline. Every deploy runs a parity check proving the TypeScript output matches the original PyTorch model to eight decimal places. During live games, your browser fetches the play-by-play and computes win probability locally every ten seconds.

Engineering note

We also fit an isotonic calibration layer for the champion. On held-out games it did not improve probability quality (Brier 0.1750 raw vs 0.1771 calibrated), so it is deliberately not applied. The champion is already well calibrated.