The problem in brief
You hear 19 consecutive notes of a piano piece (for each note: its onset time, duration, MIDI pitch and velocity) and you are told the composer. You must predict the pitch of the 20th note as a real number. There are 119 640 labelled training windows and 51 275 test windows, cut from recorded classical piano performances of the same kind as in the sister problem Haydn Seek. It is from the Swedish AI Olympiad final 2025 (problem by Arvid Kristoffersson). You print one number per test window.
The test answers are hidden, so all scores below come from 20 000 training windows held out from fitting.
How you are scored
The statement calls the sum of squared differences, but a sum over 51 275 windows would always exceed 200 and give 0, and the published scores (around 89) only make sense if is the mean squared error in semitones squared, so we read it that way:
Every unit of MSE is a point. Full marks need MSE , an RMSE of 10 semitones. That sounds lax, until you see how unpredictable the next note is.
What to aim for. The Kattis statistics show no full solve. The best score is 89.4 (MSE about 110.6), and the next ones are 80.8 and 79.7.
First look at the data
- The pitch of the next note has a variance of 195.7. Predicting a constant would therefore score about 4.
- The last note is a bad guess: MSE 255. Notes are listed by onset time, and piano music is two-handed and full of chords, so the next note often jumps to the other hand. The histogram below shows the jump from the last note: it is broad, with sharp peaks at and (octaves) and none at 0 (repeated notes are rare).
- The mean of the 19 pitches is much better: MSE 147.6, a score of 52. Under squared error, the best constant guess is the centre of the register the passage lives in.
- Inside chords, the order of notes with near-equal onsets is not sorted by pitch (going up 43 %, down 57 %), so there is no bookkeeping shortcut.
- In 61 % of windows, the answer is one of the 19 pitches already played.

A first baseline
MSE rewards predicting the conditional mean . For a bimodal situation ("the next note is either the bass or the melody"), the best answer under squared error is in between, even though no real note is ever played there.
Gradient boosting (HistGradientBoostingRegressor) on about 150 hand-made features: the 19 pitches relative to their mean, the same sorted, spread and range, durations, velocities, gaps between onsets, intervals, the pitch-class histogram, and the composer as a categorical variable. To help the trees, the target is the offset from the window's mean pitch. This model reaches MSE 124.6, score 75.4.
The key idea
Every window teaches 18 transitions for free, without any label. Inside a 19-note window, notes 1 to are followed by note , notes 2 to by note , and so on. Pooling all windows, training and test, gives a corpus of about 3 million "context of pitches, then the next pitch" examples. This uses no answer at all, so it is not leakage: it is unsupervised use of the test inputs, exactly as a language model is pre-trained on raw text.
Since many pieces appear in several performances, the last notes of a window have often been heard elsewhere, followed by a known note. For each window and each , we look up its last pitches and record three features: how often that context occurred, the average next pitch (relative to the last note) and its spread. We do the same with intervals instead of absolute pitches, which makes a melody match even in another key. This is a classical -gram language model, and we let the boosted trees learn the back-off: trust long contexts when they have been seen often, fall back to shorter ones otherwise.
The right panel of the figure shows why it works. When only a 2- or 3-note context has been seen elsewhere (27 % of windows), the error stays large. When a 6- to 10-note context has been seen (27 % of windows), the boosted model's error falls to 50 to 70, three times better than the register mean.
Adding these 39 features takes boosting from MSE 124.6 to 110.2.
The full solution
- Parse the JSON lists into an array of shape (windows, 19, 4).
- Corpus -gram statistics from all 170 915 windows, using pandas
groupbyon 64-bit hashes of the contexts (7 bits per pitch). It takes 12 s. - Gradient boosting on the -gram features plus the hand-made features, predicting the offset from the mean pitch (learning rate 0.05, 63 leaves, early stopping at about 400 trees).
- A second, different model: a 2-layer GRU (192 units) reads the 19 notes (a pitch embedding plus duration, velocity, gap and composer) and outputs a probability distribution over the 128 MIDI pitches. We train it with cross-entropy and predict the mean of the distribution, which is the MSE-optimal answer if the distribution is well calibrated. It overfits after about 5 epochs (validation MSE rises from 126.6 to 137.8 over 12 epochs), so we train it for 6 epochs with a one-cycle learning rate. Alone, it reaches 126.
- Blend: boosting GRU. We chose the weight on one half of the validation windows and report on the other half.
- For Kattis, the predictions are rounded to half a semitone (which adds less than 0.03 to the MSE), packed as bytes, compressed and embedded in a 49 KiB program that prints them.
Results
MSE on held-out training windows (official test answers are private). The final row is measured on the 10 000 windows that were not used to pick the blend weight.
| approach | MSE | score |
|---|---|---|
| last pitch | 255.1 | 0 |
| constant (mean pitch of the training set) | 195.7 | 4.3 |
| mean of the 19 pitches | 147.6 | 52.4 |
| corpus -gram back-off alone (longest context seen at least 3 times) | 144.5 | 55.5 |
| GRU, distribution over pitches, 12 epochs (overfits) | 137.8 | 62.2 |
| GRU, 6 epochs | 126.0 | 74.0 |
| boosting, hand-made features | 124.6 | 75.4 |
| boosting + corpus -gram features | 110.3 | 89.7 |
| blend 0.8 boosting + 0.2 GRU (final) | 108.7 | 91.3 |
That is slightly above the best Kattis score (89.4), but still 8.7 MSE short of full marks.
What to remember
- Under squared error, predict the conditional mean. A bimodal outcome is answered "in between", and the mean of a predicted distribution is a good way to get there.
- Unlabelled data, including the test inputs, can be mined for self-supervised statistics: here every window holds 18 free next-note examples.
- -gram counts with several context lengths, fed to boosted trees, give a learned back-off model in a few lines of pandas.
- Two models with different biases (trees on counts, a sequence network) blend for a free gain. Tune the weight on data you do not report on.
- Check the score's units: here "sum" has to mean "mean", or no published score is possible.