Match each of 1.7M businesses to every record of the same business among 10M records from two other sources. Names arrive with typos, abbreviations, reordered addresses and Indian scripts, and one test country (France) never appears in training. The metric is macro F0.5, so a false match costs twice as much as a missed one.
Nine versions, each one measured
Held-out macro F0.5 by pipeline version. v1 (hand-tuned rules) scored 0.799, below this axis. *v9 trained, test run not finished.
A learned filter before the expensive model
Candidate pairs on the test set
13× fewer pairs, about 4.2 per business, with no loss in F0.5.
Fitting 10M records into 8 GB
Peak memory of the blocking stage
Dashed line: the laptop's 8 GB of RAM. Scale 0 to 10 GB.
What moved the score
- Rarity-based blocking over a packed index. Keys come from each record's rarest words. A 40-bit key hash and a 24-bit record ID share one sorted
uint64array per country, so every lookup is a binary search. - Bidirectional blocking with a hub cap. Searching backwards from every S2/S3 record lifted candidate recall from 0.953 to 0.971. The cap stopped one business from collecting 142,645 candidates through the word "com".
- A fine-tuned transformer as one feature. A 22.7M-parameter MiniLM cross-encoder, fine-tuned in folds split by entity so it never scores entities it trained on. Alone it scored 0.961; as a LightGBM feature it added +0.005.
- Learned transliteration. A 1,305-word Indian-script to Latin dictionary learned only from training pairs. Median name similarity on true pairs went from 0.10 to 1.00.
- An expected-F0.5 decision layer. Each record goes to at most one business, then each business keeps the candidate subset that maximises expected F0.5, including predicting nothing.
I logged what failed too. A three-model LightGBM ensemble scored 0.9660 against 0.9661 for one model. Half precision on the Apple GPU was slower (435 vs 548 pairs/s). Error analysis on v7 showed 41.5% of remaining errors were true matches that never reached the candidate set, which is why later versions went after recall in blocking.
All nine versions
| Ver. | Main change | Cand. / business | Val F0.5 | Held-out F0.5 |
|---|