NBA game prediction
Predicts every NBA game before tip-off with a PyTorch model built from each team's players, keeps the regression that ran last season beside it as a benchmark, and records every pick against the result and the bookmaker's price.
Pre-gamepredicted before the game and storedBacktestscored afterwards, on games the model was not trained on
Python, PyTorch, FastAPI, Docker, Fly.io, Cloudflare R2
Tonight
last night's results and the next games
Loading the games.
Season
2026-27, pre-game
Loading the season.
Teams
2026-27, the same switch as above
Loading the season.
How it works
for a closer look
- 15 players × 23 numbers
Each player
Up to 15 players per team, from the team's previous game; the running model also drops anyone the injury report lists as out. Each player is 23 numbers: last-10-game form (minutes, points, rebounds, assists, steals, blocks, turnovers, plus-minus), season averages, shooting and usage rates per 36 minutes, and how many games and days of rest are behind him.
- 23 → 32 → 16, per player
One small network for every player
The same two-layer network reads each player and turns his 23 numbers into 16 that describe what he adds to a team. It is the same weights for every player, 1,296 of them, so it learns what kind of player matters rather than who he is.
- →15 × 16 → 16
A team is its players, weighted by minutes
The 16-number player vectors are averaged, weighted by the minutes each player has averaged over his last 10 games. The order of the players does not matter, which is what the name Deep Sets refers to: the network sees a set, not a list.
- 54 → 32 → 1
The matchup
Home team, away team and their difference (48 numbers), plus each side's rest days, back-to-back flag and games into the season (6 more) go through a second small network to one score. It runs a second time with the teams swapped and takes the difference, so swapping home and away flips the answer exactly. A learned home-court term is added.
- →62%for example5 copies → 1 probability
Five copies, averaged
The whole network is trained five times from different random starting weights. Their five scores are averaged, divided by a temperature fitted so that a 70% means 70%, and turned into the home team's win probability. Averaging five copies removes most of the luck of any single training run.
Which model runs. The set: all five copies, every day. They were trained in PyTorch on 2020-21 to 2024-25, each stopped at the epoch where its 2025-26 log loss was lowest (between 9 and 16 epochs), and retrained on 16 October 2026. On the server the same weights run in NumPy, which keeps PyTorch out of the 1 GB container; the two agree to within 0.0000002.
Why the backtest is still clean. The 2025-26 backtest on this page comes from an earlier copy, trained only up to 2023-24 with 2024-25 for stopping, so it never saw a 2025-26 game. The copy running now has seen 2025-26, so it can only be judged on 2026-27.
Size. About 3,100 weights per copy, small on purpose: there are only about 6,000 games to learn from. A deeper network with game sequences scored slightly better, but within noise, so the simpler one runs.
Ask the model
the player model, now
The code
live from GitHub
Finnish leagues
a side project
The same idea for Finnish men's basketball: win chances for every Korisliiga, I divisioona and southern II divisioona game, from team ratings that update after each result.
Finnish predictions