Skip to content
Selected work

Competition write-up

DaT Parkinson's Challenge

A DrivenData competition from the French Society of Nuclear Medicine: read a dopamine-transporter SPECT brain scan and give the probability that it is abnormal. I finished 11th of 1,009 entrants on the private leaderboard. This write-up walks through the most complete system I built — four tabular views of every scan and a 3D convolutional network, fused without a single fitted weight — and what 20 submissions taught me about trusting the public leaderboard.

Competition
DaT Parkinson's Challenge · DrivenData
Domain
Nuclear medicine · DaT SPECT
Core stack
TabICL · 3D CNN · 3D DenseNet121 · 2D/2.5D CNNs · ANTs · PyTorch · LightGBM
11 / 1,009
private leaderboard
0.2763
private log loss
0.9494
private AUROC
20
scored submissions
363
experiments logged

The problem

A DaT scan images where a radioactive tracer binds to dopamine transporters. In a healthy brain it collects in the striatum as two bright commas. In parkinsonian syndromes the tail of each comma — the putamen — fades first, usually on one side, until the striata look like dots.

The training set was 1,362 scans from many cameras and protocols: spacing from 2.3 to 3.9 mm, fields of view differing 3.6-fold, intensities from 3 to 65,466. Submissions were code, run offline on hidden test scans with a three-hour limit. One rule shaped everything: each test scan had to be scored on its own, with no statistics taken across the test set.

The leaderboard ranked by log loss, with AUROC reported beside it. The two measure different things: AUROC only asks whether abnormal scans are ranked above normal ones, while log loss also charges for how confident each probability is.

Normal

Abnormal

Stylised axial slice, not a patient scan. In a normal study the putamen extends back from each caudate head as a comma; in parkinsonian syndromes it fades from the back, usually on one side first, until each striatum reads as a dot.

Log loss — the ranking metric

LogLoss = −1NNi=1[ yi log pi + (1 − yi) log(1 − pi) ]

y is 1 for an abnormal scan, p the predicted probability that it is abnormal. It scores confidence as well as correctness: calling an abnormal scan abnormal at 0.99 costs 0.01; calling it normal at 0.99 costs 4.61. One confident mistake outweighs hundreds of confident correct answers, which is why calibration decided places here.

AUROC — reported alongside

AUROC = 1N+ N i∈+ j∈−[ 𝟙(pi > pj) + 12𝟙(pi = pj) ]

Take every pair of one abnormal scan (i) and one normal scan (j) and count how often the abnormal one gets the higher probability; ties count half. It depends only on the ranking, so rescaling the probabilities cannot change it — but it can change the log loss.

The architecture

Each scan is resampled, centred on the striatum and cropped, then seen three ways: as it is, registered to a template of normal scans, and registered to a template of severely abnormal scans. Four pillars read those views — three TabICL models on 116 to 132 hand-built striatal measurements, and a 3D convolutional network trained from scratch on the striatal box.

The pillars are averaged in logit space with weights fixed in advance, shifted once to the test set's prevalence, and floored when a scan looks strongly abnormal. Training ran on 6 cloud GPUs in parallel, and every prediction depends on its own scan only.

NIfTI scan · any camera, any spacingShared preprocessingresample to 2 mm isotropic · head mask · striatal centre÷ robust reference-region mean · 96 × 96 × 64 crop (192 × 192 × 128 mm)Native cropno registrationNormal-template spaceANTs affine, seededtemplate of normal scansSevere-template spaceANTs affine, seededabnormal scans, SBR < 1.1① Two-view TabICLnative ÷ p75 of own body maskregistered ÷ p75 of brain maskmirror: weaker putamen right116 features per viewTabICL × 2 → logit mean② Severe-space TabICLregistered to severe template116 features+ 4 denervation gradients+ 12 bilateral box features132 features → TabICL③ 3D CNN48 × 32 × 32 striatal box4 stages · 16→32→64→128 ch879,474 parameters · 5 seedsnormal box 0.6 + severe 0.4from scratch, no pretraining④ Occipital TabICLregistered crop÷ occipital reference116 featuresTabICLFit-free logit fusionz = 0.30·z① + 0.30·z② + 0.30·z③ + 0.10·z④+ prevalence shift logit(272/524) − logit(747/1362) = −0.118weights fixed in advance, none fitted to the scoresConditional risk floorp = σ(z); if putamen asymmetry or caudate elongationon the registered crop exceeds z > 0.95, then p ≥ 0.20P(abnormal) for this scanscored alone · never uses other test scans
The four-pillar model as packaged for the competition container. Each TabICL model reads the 1,362-row training table in context (8 estimators, Latin feature shuffling).
Log lossAUROC
Four-pillar model · cross-validation0.22080.9691
Four-pillar model · without the 3D CNN0.22400.9674
Four-pillar model · floor on random scans (control)0.23530.9633
Four-pillar model · public leaderboard0.24590.9596
Four-pillar model · private leaderboard0.2816
Best private submission · private leaderboard0.27630.9494
Cross-validation is out-of-fold on site-disjoint folds. The last row is a different, simpler submission — the one that set my final rank — shown for reference; AUROC is not published for every private submission

Every submission

I scored 20 submissions. The first eight came from an earlier phase — classical models, 3D DenseNets, a hybrid, then TabICL. The next twelve came from a four-week second phase built on top of it.

Every one of those twelve that has a local score beat the TabICL model locally. None of the twelve beat it on the private leaderboard. Over all 19 working submissions, public and private scores still ranked models similarly (Spearman +0.82), but each model lost between 0.034 and 0.068 crossing from one to the other.

Every scored submission: local CV, public and private leaderboard

Log loss, lower is better. The shaded half predates the second phase. The coordinate-bug submission (1.06) is left off the scale.

  • public leaderboard
  • private leaderboard
  • local CV (● site-grouped, ■ stratified)
  • local CV fitted in-sample
first phasesecond phase0.200.240.280.320.3612345678910111214151617181920best private 0.2763submission, in order
Show data table
#SubmissionLocal CVCV kindPublicPrivate
1Classical LightGBM + logistic regression0.2853stratified0.27080.3085
23D DenseNet121, one model + temperature0.2745stratified0.30630.3503
33D DenseNet121, five seeds0.2649stratified0.28840.3356
4Hybrid: deep + classical0.2207stratified0.26010.2968
5Classical v20.25810.2924
6Classical v3, atlas features0.26300.3020
7Classical T5, LightGBM + logistic regression0.2677grouped0.25070.2878
8TabICL on 96 features0.2554grouped0.24240.2763
9TabICL on 116 features0.2422site-disjoint0.24250.2870
10Brain-based denominator, both arms0.2363site-disjoint0.23910.2824
11Plus worse-side mirror0.2334site-disjoint0.23960.2834
12Rank-mean of nine legs0.2349site-disjoint0.22860.2810
13Eleven legs, coordinate bug1.06191.0666
14Table legs + 3D CNN0.2257site-disjoint0.23610.2854
15Eleven legs incl. 3D CNN + TTA0.2260site-disjoint0.24090.2856
16Many-leg ensemble, fitted weights0.2179in-sample0.24280.3011
1721 fitted parameters0.2090in-sample0.24660.3145
18Compound blend 30/700.2260site-disjoint0.24530.2858
19Four pillars incl. 3D CNN + risk floor0.2208site-disjoint0.24590.2816
20Last upload, unidentified0.23890.2840
SubmissionLocal CVPublicPrivate
TabICL, 96 features0.25540.24240.2763
TabICL, 116 features0.24220.24250.2870
Brain-based denominator0.23630.23910.2824
Rank-mean of nine legs (rule breach)0.23490.22860.2810
Table legs + 3D CNN0.22570.23610.2854
Four pillars + 3D CNN + floor0.22080.24590.2816
Many-leg ensemble0.2179 in-sample0.24280.3011
21 fitted parameters0.2090 in-sample0.24660.3145
Selected submissions. Local CV on site-grouped or site-disjoint folds unless marked; in-sample means weights were fitted on the scored predictions

What the second phase tried

Four weeks and 363 logged experiments: better reference regions, mirroring the weaker side, a from-scratch 3D CNN as an extra model, symmetry averaging at test time, many-leg ensembles, risk floors and temperature fits. Several gave real local gains. The chart sets those local gains beside what the public board returned.

One entry deserves its own line. My best public score, 0.2286, came from ranking each scan against the other test scans — which the rules forbid. A legal reconstruction later measured that step's gain at +0.0002 AUROC: noise that happened to land well.

Offline wins, board results

Change in log loss against the model each change was built on. Negative is better. The last offline row is a control: ten label-free noise arms.

  • better
  • worse
-0.008-0.0040+0.004+0.008OFFLINE (LOCAL CV)Brain-based denominator-0.00593D CNN added to the table model-0.0040Worse-side mirror-0.0029Three-way equal mean-0.0022Ten noise arms, weights fitted in-sample-0.0077PUBLIC LEADERBOARDBrain-based denominator vs base-0.0033Four equal pillars vs brain-based denominator-0.0012Worse-side mirror+0.0005Risk floor on four pillars+0.0048Four pillars + 3D CNN+0.0068Table + 3D CNN vs rank-mean+0.0075
Show data table
ChangeMeasured onΔ log loss
Brain-based denominatoroffline-0.0059
3D CNN added to the table modeloffline-0.0040
Worse-side mirroroffline-0.0029
Three-way equal meanoffline-0.0022
Ten noise arms, weights fitted in-sampleoffline-0.0077
Brain-based denominator vs basepublic-0.0033
Four equal pillars vs brain-based denominatorpublic-0.0012
Worse-side mirrorpublic+0.0005
Risk floor on four pillarspublic+0.0048
Four pillars + 3D CNNpublic+0.0068
Table + 3D CNN vs rank-meanpublic+0.0075

Why local wins did not reach the leaderboard

The honest answer is that I spent most of the second phase measuring rather than building. I pre-registered experiments, built nested scorers, audited folds and chased the gap between local and leaderboard scores. That discipline caught real errors — but it also meant I kept refining one family of models instead of trying genuinely different ones.

The mistakes, in order of cost. I tuned ensemble weights on the same predictions I scored, which manufactures gains out of noise. My early folds let acquisition sites leak between training and validation, so some ideas were closed on flawed evidence. I optimised a pooled metric that ranked submissions in the wrong order. And the code I shipped did not always compute exactly what I had measured offline.

Two teams that finished ahead of me on the private board published their solutions, and both built different representations early — 2D projections of the striatum, synthetic lesions, deliberately softer calibration — and let the leaderboard test them. Next time I would build first, verify what survives, and keep a simple baseline protected throughout.

A better local score did not buy a better leaderboard

Second-phase submissions with a site-disjoint local CV. Better local CV is to the right. Hollow diamonds had weights fitted on the scored predictions.

0.220.260.30local CV → public leaderboard0.240.230.220.21ROUTE-20Championlocal CV → private leaderboard0.240.230.220.21ROUTE-20Champion
Show data table
SubmissionLocal CVPublicPrivate
TabICL on 116 features0.24220.24250.2870
Brain-based denominator, both arms0.23630.23910.2824
Plus worse-side mirror0.23340.23960.2834
Rank-mean of nine legs0.23490.22860.2810
Table legs + 3D CNN0.22570.23610.2854
Eleven legs incl. 3D CNN + TTA0.22600.24090.2856
Many-leg ensemble, fitted weights0.2179 (in-sample)0.24280.3011
21 fitted parameters0.2090 (in-sample)0.24660.3145
Compound blend 30/700.22600.24530.2858
Four pillars incl. 3D CNN + risk floor0.22080.24590.2816

Public vs private: every model lost 0.034 to 0.068

Each point is one submission. The dashed line is private = public + 0.035.

  • second phase
  • first phase
  • best private
0.260.280.300.320.340.360.220.240.260.280.30TabICL · 96 featurespublic log loss
Show data table
SubmissionPublicPrivateGap
Classical LightGBM + logistic regression0.27080.3085+0.0377
3D DenseNet121, one model + temperature0.30630.3503+0.0440
3D DenseNet121, five seeds0.28840.3356+0.0472
Hybrid: deep + classical0.26010.2968+0.0367
Classical v20.25810.2924+0.0343
Classical v3, atlas features0.26300.3020+0.0390
Classical T5, LightGBM + logistic regression0.25070.2878+0.0371
TabICL on 96 features0.24240.2763+0.0339
TabICL on 116 features0.24250.2870+0.0445
Brain-based denominator, both arms0.23910.2824+0.0433
Plus worse-side mirror0.23960.2834+0.0438
Rank-mean of nine legs0.22860.2810+0.0524
Table legs + 3D CNN0.23610.2854+0.0493
Eleven legs incl. 3D CNN + TTA0.24090.2856+0.0447
Many-leg ensemble, fitted weights0.24280.3011+0.0583
21 fitted parameters0.24660.3145+0.0679
Compound blend 30/700.24530.2858+0.0405
Four pillars incl. 3D CNN + risk floor0.24590.2816+0.0357
Last upload, unidentified0.23890.2840+0.0451

The shake-up

The private set was harder for everyone. The public leader finished 28th, the tenth-placed team 38th, the twelfth 2nd. I went from 4th to 11th.

The private leaderboard reshuffled the public top twelve

  • me (ghost_sas), and two teams ahead of me in public
publicranks below #12 compressedprivateMarc-Dvci #1#28TheAvengers #2#1South-Wing #3#17ghost_sas (me) #4#11Shatatarka #5#7Shivom #6#3paulonium #7#6Tensla #8#9AAF Team #9#8venkt #10#38Suyash92 #11#4Tigertech #12#2
Show data table
TeamPublic rankPrivate rank
Marc-Dvci128
TheAvengers21
South-Wing317
ghost_sas (me)411
Shatatarka57
Shivom63
paulonium76
Tensla89
AAF Team98
venkt1038
Suyash92114
Tigertech122

What I would do differently

My biggest mistake was treating the public leaderboard as the truth. Every idea went through the same loop: build it, submit it, read the public score, and drop it if the number went the wrong way. The public set was small and easier than the private one, so that loop was steering me by noise.

The four-pillar model is the clearest example. On the public board it looked like a step backwards, so I moved on. On the private board it turned out to be one of my strongest second-phase submissions. Other ideas were abandoned the same way, on a single public reading, without ever finding out what they were really worth.

Next time the leaderboard becomes one signal among several, not the goal. I would judge a model by how it was built and how it holds up on honest validation — site-disjoint folds, a protected simple baseline, the shipped code checked against what was measured — and use the public score to catch broken submissions, not to decide which ideas live.

More projects

The rest of the work.

01

Automated Leukocyte Classification

I worked with the R&D arm of one of the largest medical-biology groups in Alsace — more than 40 labs — building an in-house model that classifies white blood cells from blood-smear images. Trained on 39,037 clinician-validated cells across 12 classes, it outperformed a re-implemented top-3 competition solution on the same test split, and beat vision backbones ten times its size while running several times faster.

  • PyTorch
  • YOLO-cls
  • SigLIP2 · DINOv2
  • Medical imaging
Read the case study
95.8%
macro F1 across 12 cell classes
03

The Link

I worked with one of the largest residential landlords in Alsace, where every department held its own data, used it for its own tasks, and could not see past its own boundary — with basic reporting rented from outside vendors. Before any modelling was possible, I spent weeks mapping what each department held and what it needed, and joining those sources into one base covering the whole estate.

  • Python
  • pandas
  • SQL
  • Data integration
Read the case study
13
variables catalogued into one base
04

The Value

Chapter two of the same engagement: with the data finally joined, modelling what actually sets rent per square metre.

  • scikit-learn
  • SciPy
  • Web scraping
  • Forecasting
Project complete — write-up in progress
60%
of predictive weight in one financing rule
05

AI Assistant for Cross-Continental Freight

An AI chatbot for a large startup moving cargo between Europe and the Gulf states, built around the way freight actually gets booked, tracked and chased across two regions and several time zones.

  • LLMs
  • Conversational AI
  • Logistics
In progress
Report coming soon — the work is still running
Back to the homepage