The problem in brief
You get five podcast recordings (17 to 31 minutes each) and, for each, the number of people who speak in it (2, 3 or 5). For every recording you must say, second by second, who is speaking: one line per speaker listing that speaker's turns as mm:ss-mm:ss intervals. You do not know who the speakers are, only how many; the judge will match your speaker lines to the real people in the most favourable way. The training data are six recordings of a single person speaking each (about 22 minutes in total), with no labels inside them. This task is known as speaker diarisation.
How you are scored
For every second and every speaker line, claiming that this person speaks earns if they do and if they do not; seconds you leave unclaimed earn nothing. With the total over all files (after the best matching of your lines to the true speakers),
The five test files last 8028 seconds in total, so a perfect diarisation (one speaker per second) would give : the cap of 100 is reached at about half of that, for example by being right on 75 % of the seconds and wrong on 25 % ( net point per second). There are no labels for the test files at all, so we must build our own validation.
First look at the data
- The test files are long and mostly continuous speech (their loudness rarely drops to silence), so almost every second belongs to someone.
- Turns are long: in the output of our final model, a typical turn lasts from 10 seconds to several minutes. This means that neighbouring seconds almost always have the same speaker, a strong prior we should use.
- The training recordings come from different sessions, so each one has its own "sound" (microphone, room): a classifier can tell them apart using the recording channel as much as the voice. That will matter when we interpret our validation.
Validation design. We cut the six training recordings into turns of 3 to 25 seconds and glue turns of different people together, giving fake 10-minute podcasts with known truth (15 of them, with ). We score them with the official rule, matching predicted to true speakers with the Hungarian algorithm (scipy.optimize.linear_sum_assignment), and report the net points per second (1 is perfect, 0.5 reaches the cap). Because channel differences make the fake podcasts too easy, we also build a harder version where each recording's average cepstrum (its channel signature) is subtracted first.
A first baseline
Claim that the first speaker speaks all the time, and leave the others empty. You get for that person's seconds and for everybody else's, so you only gain when one person talks more than half of the time. On the fake podcasts this gives net point per second (and 0 points on Kattis). Saying everybody speaks all the time is even worse. We need to separate the voices.
The key idea
Represent each second by a fingerprint of the voice, then cluster. The classic fingerprint is the MFCC: cut the audio into 25 ms frames every 10 ms, take the power spectrum, pool it into 40 bands spaced on the mel scale (which mimics the ear's resolution), take the logarithm and a discrete cosine transform, and keep the first 20 coefficients. They describe the shape of the spectral envelope, that is the shape of the speaker's vocal tract, largely independently of the words being said. We summarise each second by the mean and the standard deviation of its 100 MFCC frames: 40 numbers per second.
Since we know the number of speakers , we cluster the seconds of each file into groups with k-means, after standardising and keeping 20 PCA directions. Each cluster is one speaker.
The second idea is temporal smoothing. A single second of speech is a noisy fingerprint (a laugh, a breath, a whispered word), so per-second clustering produces short false switches. Because turns last many seconds, we replace each label by the majority label of a 5-second window centred on it. In probability terms, this is a cheap version of a hidden Markov model in which the speaker rarely changes from one second to the next.

The full solution
- Decode each mp3 to 16 kHz mono with ffmpeg.
- Compute MFCCs with NumPy (pre-emphasis, Hamming window, 512-point FFT, 40 mel bands, DCT), then the per-second mean and standard deviation.
- For each test file: standardise, PCA to 20 dimensions, k-means with the given (10 restarts).
- Smooth the labels by a majority vote over 5 seconds.
- Turn consecutive seconds of each label into intervals and print one bracketed list per speaker, with no spaces.
Z = PCA(20).fit_transform(StandardScaler().fit_transform(F)) # F: one row per second
lab = KMeans(K, n_init=10).fit_predict(Z)
oh = np.eye(K)[lab] # one-hot labels
lab = uniform_filter1d(oh, 5, axis=0, mode="nearest").argmax(1) # 5-second majority vote
Results
Net points per second on the 15 synthetic podcasts (1 = perfect, 0.5 = enough for the cap):
| approach | raw recordings | channel removed |
|---|---|---|
| first speaker speaks all the time | ||
| k-means per second, no smoothing | 0.89 | 0.15 |
| k-means + 5 s majority vote (final) | 0.94 | 0.24 |
| k-means + 9 s majority vote | 0.91 | 0.24 |
| agglomerative (Ward) + 5 s vote | 0.94 | 0.25 |
| Gaussian mixture + 5 s vote | 0.84 | |
| failed idea: abstain when the cluster posterior is below 0.6 | 0.65 | 0.14 |
Rerunning the final solution.py on a fresh set of synthetic podcasts gives 0.885 and 0.328 net point per second. Scaled to the 8028 test seconds, that is (score 100, capped) in the easy setting and (score about 81) in the hard one. The truth for real podcasts probably lies in between: speakers recorded on separate microphones are easy to separate, speakers sharing one room are not, and real conversations also contain overlaps and laughter that our fake podcasts lack. These are estimates from synthetic data, not test scores.
Two lessons from the table. Smoothing helps a lot when features are weak; a window that is too long starts to erase short turns. And abstaining on "uncertain" seconds hurt, although leaving a second unclaimed costs 0 instead of : k-means posteriors are not calibrated probabilities, and the costly mistakes are confident ones (two voices merged into one cluster), not uncertain ones.
What to remember
- No labels for the test? Build a labelled proxy from what you have (here, fake podcasts glued from single-speaker recordings), and score it with the official rule.
- Check what your proxy makes too easy. Different recording channels made the fake podcasts much easier than real ones; removing the channel signature gave a more honest lower estimate.
- MFCC statistics + clustering is the classic diarisation baseline: the mel spectrum captures the voice, clustering finds the speakers when you know how many there are.
- Use temporal structure. Labels of neighbouring seconds are strongly correlated; a majority vote (or an HMM) removes most isolated errors.
- Abstention only pays with calibrated confidence. A score that rewards silence needs probabilities you can trust.