|
Download README.md from aildan/country-code-decide-small: direct link, hf CLI and curl.
- Browser
- Download file 15.8 kB
-
https://huggingface.co/aildan/country-code-decide-small/resolve/main/README.md
- Command line
-
hf download hf://aildan/country-code-decide-small/README.md
-
curl -L -o README.md https://huggingface.co/aildan/country-code-decide-small/resolve/main/README.md
15.8 kB
| license: apache-2.0 | |
| language: [multilingual] | |
| library_name: onnx | |
| pipeline_tag: text-classification | |
| tags: [country-codes, iso-3166, entity-normalization, selective-prediction, calibration, abstention, kyb, onnx] | |
| base_model: jhu-clsp/mmBERT-small | |
| # CountryCode-Decide (small, v1.2.1) | |
| Maps a country or US state written as free text — any language or script, misspellings, | |
| abbreviations, historical names, register junk — to ISO 3166-1 alpha-2 / ISO 3166-2:US codes with | |
| calibrated probabilities, and **abstains** when the requested error rate cannot be met. | |
| 75 MB, about 4 ms per string on a CPU. | |
| Code, evaluation and documentation: https://github.com/aliildan/country-code-decide · [Paper](https://doi.org/10.5281/zenodo.23289343) | |
| ## Intended use | |
| Normalising country / jurisdiction / nationality fields in company-register data, KYB onboarding | |
| and similar back-office pipelines, with a person reviewing abstentions and candidate lists. | |
| **Out of scope:** full postal addresses (the model reads one field), subdivisions other than US | |
| states, legal advice, any decision about a person without human review. | |
| ## How to use | |
| Serving needs only `onnxruntime`, `tokenizers` and `numpy` (no PyTorch). The decision logic | |
| (calibration, thresholds, candidate lists) lives in the repository's `CountryCoder`: | |
| ```bash | |
| git clone https://github.com/aliildan/country-code-decide && cd country-code-decide | |
| uv sync --no-default-groups --group serve && source .venv/bin/activate | |
| uvx --from huggingface_hub hf download aildan/country-code-decide-small --exclude model.onnx \ | |
| --local-dir models/country-code-decide-small | |
| ``` | |
| ```python | |
| from countrycode.infer.pipeline import CountryCoder | |
| coder = CountryCoder.load("models/country-code-decide-small") | |
| d = coder.code("Untted Klngdom", field="country", domain="register", alpha=0.05) | |
| d.target, d.candidates, d.abstain # ('GB', ('GB',), False); d.confidence ≈ 0.95 | |
| ``` | |
| Input: `text`, optional `field` (`country`, `jurisdiction`, `nationality`, `unknown`) and | |
| `register_country` (ISO alpha-2 of the register, or `US-XX` for a US state register). Output per | |
| string: `target` (alpha-2, `US-XX`, `NONE` or `None` = abstain), `country`, `us_state`, | |
| `confidence`, `candidates` (codes with calibrated probability ≥ 0.05, at most three), `abstain`, | |
| for an error-rate target `alpha` (certified targets — curated: 5 %; register: 5 %; `None` always answers). Pass | |
| `domain="register"` for register and form fields: the certified register calibration applies only | |
| then; without a domain the stricter of the two policies applies, which is conservative but not | |
| itself certified. | |
| ## Files | |
| | File | Content | | |
| |---|---| | |
| | `model.int8.onnx` | the model: encoder + two heads, 8-bit weights (served; the CPU runtime computes its matrix products with int8 activations) | | |
| | `model.onnx` | the same model in fp32 (reference) | | |
| | `tokenizer.json`, `tokenizer_config.json` | the trimmed mmBERT tokenizer | | |
| | `calibration.json` | temperatures, certified thresholds and candidate rule per domain (`register`, `curated`) | | |
| | `meta.json` | label space, code names, context format, training run, quality-gate status | | |
| ## Training data | |
| The model was trained on three kinds of strings. **About a third of the training examples are | |
| generated (synthetic)**; no generated string is used for calibration or evaluation. | |
| - **Lexicon** — 372,683 names in 1,274 languages (CLDR 47, Wikidata, codes, reviewed overrides); 86,406 distinct keys, 1,947 kept as genuinely ambiguous. | |
| - **Real register strings** of the training split (Colorado, Connecticut, UK Companies House, | |
| Brønnøysund), labelled by a person or by the register's own code; no normalised key shared with | |
| any evaluation part. | |
| - **Generated variants**, one release per generator, each made by code from a lexicon name with a | |
| known code (so the label is known by construction): | |
| - `keyboard` — typos on real keyboard layouts chosen by the name's language, with the number and | |
| kind of edits fitted to real register typos; | |
| - `format` — case, spacing, comma inversion, postcode noise, field truncation, abbreviation | |
| prefixes (labelled with every code whose name starts that way); | |
| - `translit` — romanisations and umlaut conventions; | |
| - `geo` — a country or US state written with one of its GeoNames regions or cities; | |
| - `pairs` — two countries in one string (labelled as the set of both); | |
| - `affix` — a country with a compass or region qualifier; | |
| - `caprov` — Canadian province codes in a US register's jurisdiction field (→ `CA`); | |
| - `junk`, `regjunk` — strings that are not places: placeholders, dates, numbers, generated | |
| company names, legal terms, street lines (→ `NONE`). | |
| Every release the model uses passed the quality gates before training: **G1** label correctness (each string | |
| checked against every name of every code; strings closer to another code are dropped) plus a | |
| human audit of 300 random items per release (every item when a release is smaller) with zero | |
| label errors required; **G2** realism (edit statistics compared with real typos — reported, no | |
| pass/fail threshold); **G3** | |
| usefulness (kept only if it improved held-out real strings; the OCR, LLM-paraphrase and | |
| region generators failed this and are not used); **G4** no overlap with any evaluation string; **G5** reproducibility | |
| (code, config, seed and source item recorded per string). | |
| | generator | training examples | human audit (errors / audited) | | |
| |---|---|---| | |
| | junk@2 | 1,375 | 0 / 300 | | |
| | keyboard@2 | 9,812 | 0 / 300 | | |
| | format@4 | 13,411 | 0 / 300 | | |
| | translit@4 | 4,396 | 0 / 300 | | |
| | geo@2 | 7,381 | 0 / 300 | | |
| | pairs@3 | 3,988 | 0 / 300 | | |
| | affix@2 | 785 | 0 / 300 | | |
| | caprov@1 | 78 | 0 / 39 | | |
| | regjunk@1 | 2,731 | 0 / 300 | | |
| - 142,925 training examples: 98,517 lexicon names and codes (68.9 %), 43,957 generated (30.8 %), 451 real register strings (0.3 %). | |
| - Real register strings are labelled by a person or by the register's own code; none shares a normalised key with any evaluation part. | |
| - No personal data: register sources were read with aggregate queries (distinct value and count), | |
| never records; generated company names come from word lists. | |
| ## Evaluation | |
| Fresh registers never used for training, calibration or model selection (New York, Oregon): every | |
| distinct string of their jurisdiction and country fields, labelled by exact lexicon match or by a | |
| human reviewer, read once after a frozen pre-registration (release 1.2). Release 1.2.1 keeps the | |
| weights and corrects the certificate after an external review, so its numbers below are post-hoc. | |
| | Fresh registers, 361 distinct strings | CountryCode-Decide small v1.2.1 | | |
| |---|---| | |
| | Accuracy, every string answered | 88.6 % (95 % CI 85.3–91.7) | | |
| | Right country (a US state counts as US) | 91.2 % | | |
| | At the certified 5 % error target | answers 88 % automatically, realised error 2.5 % | | |
| | Best non-LLM baseline (exact lexicon + context rules) | 89.5 % | | |
| | Zero-shot LLM (qwen3.5_35b-a3b-q4_K_M) | 85.0 %, ~29× slower (LLM on a GPU, this model on a CPU) | | |
| | Messy register strings (656): lexicon alone → lexicon, then model | answers 24.8 % → 79.0 % (error 4.8 %) | | |
| | Model file · CPU latency | 75 MB (int8 ONNX) · p50 4.1 ms per string | | |
| | Predictor | automatic labels (exact lexicon match, n = 261) | hand-labelled (n = 100) | hand-labelled answered | | |
| |---|---|---|---| | |
| | CountryCode-Decide (always answers) | 99.2 % | 61.0 % | 100 | | |
| | CountryCode-Decide at the certified 5 % target | 98.1 % | 56.0 % | 61 | | |
| | Zero-shot LLM (qwen3.5_35b-a3b-q4_K_M) | 94.3 % | 61.0 % | 97 | | |
| | Lexicon + context rules | 99.6 % | 63.0 % | 62 | | |
| | multilingual-e5-small nearest name | 100.0 % | 37.0 % | 100 | | |
| | exact lexicon match | 100.0 % | 24.0 % | 23 | | |
| Automatic labels come from an exact lexicon match, so a lexicon is right on them by construction; on | |
| the hand-labelled strings the model, the LLM and an exact lexicon with the training context rules are | |
| about level. On held-out strings of the training registers (656 strings, many misspelled or abbreviated; other strings of the same registers were in training) the model is right on 82.6 %, the best other predictor (qwen3.5_35b-a3b-q4_K_M zero-shot) on 73.8 %, the lexicon with context rules on 35.8 %; on the 472 of them with no exact lexicon match 81.1 % against 73.1 %. On GBIF's curated list the LLM is better: 69.6 % against 58.8 %. | |
| Lexicon first, then the model at its 5 % target: | |
| | Set | Lexicon + context rules alone (answered, error) | Lexicon, then the model (answered, error) | Model on what the lexicon cannot read (answered, wrong) | | |
| |---|---|---|---| | |
| | Fresh registers (New York, Oregon), 361 strings | 89.5 %, error 1.5 % | 91.7 %, error 3.0 % | 8 of 38, 5 wrong (62.5 %) | | |
| | Held-out strings of the training registers, 656 strings | 24.8 %, error 1.8 % | 79.0 %, error 4.8 % | 355 of 493, 22 wrong (6.2 %) | | |
| | GBIF curated list, 3,980 strings | 17.8 %, error 2.4 % | 39.6 %, error 4.8 % | 867 of 3,270, 59 wrong (6.8 %) | | |
| The model roughly triples what is answered automatically on messy register strings, but on what the | |
| lexicon cannot read its 5 % certificate does not hold (last column); treat those answers as | |
| suggestions for review until the residual has its own calibration. | |
| - Country-level accuracy (right country; a US state counts as US): 0.912 (highest: v1_model 0.921). | |
| - On the 33 strings not in the lexicon (no exact name match) the model's accuracy is 0.212. | |
| - At the certified 5 % error target the model answers 88% of the strings automatically with a realised error of 2.5 %; non-places get a code 0.0 % of the time (n = 4). The 5 % target is certified over 318 register units (one per register and normalised key); 1 % and 2 % are not. | |
| - The zero-shot LLM (qwen3.5_35b-a3b-q4_K_M) is less accurate when every string must be answered (0.850 vs the model's 0.886); its own confidence covers 71% of the strings at 5 % empirical risk, without a certificate. It is ~29× slower (the LLM on a GPU, this model on a CPU). | |
| The guarantee is an average over strings like the calibration strings, and the errors concentrate on hard ones: at the 5 % target 13.1 % of the 61 answered hand-labelled strings are wrong, 0.0 % of the 256 answered strings that an exact lexicon match labelled. | |
| **The certificate, corrected.** The pre-registered certificate treated the 592 register calibration strings as independent draws and certified 2 % and 5 %. The same string often appears in several fields of a register, so they are not independent: they form 318 register-and-key units. Counted per unit — a correction made after the test read, prompted by an external review — 2 % is not certified, and 5 % is, at λ = 0.762. This release (small-v1.2.1) ships that calibration; the weights are unchanged. | |
| | predictor | n | acc | acc (freq-weighted) | country acc | coverage @1 % risk | coverage @5 % risk | non-place false accept | ECE | ms / item | | |
| |---|---|---|---|---|---|---|---|---|---| | |
| | lexicon_context | 361 | 0.895 | 1.000 | 0.901 | 0.000 | 0.895 | 0.250 | 0.015 | 0.0 | | |
| | model | 361 | 0.886 | 1.000 | 0.912 | 0.856 | 0.911 | 0.750 | 0.058 | 4.2 | | |
| | v1_model | 361 | 0.884 | 1.000 | 0.921 | 0.197 | 0.864 | 1.000 | 0.253 | 3.9 | | |
| | model@0.05 | 361 | 0.864 | 1.000 | 0.873 | 0.856 | 0.878 | 0.000 | 0.033 | 4.2 | | |
| | llm_qwen3.5_35b-a3b-q4_K_M | 361 | 0.850 | 0.997 | 0.858 | 0.000 | 0.709 | 0.000 | 0.108 | cached | | |
| | e5_knn | 361 | 0.825 | 0.995 | 0.847 | 0.091 | 0.825 | 0.750 | 0.108 | 0.6 | | |
| | lexicon_fuzzy | 361 | 0.795 | 0.994 | 0.802 | 0.000 | 0.820 | 0.500 | 0.054 | 1.0 | | |
| | lexicon_exact | 361 | 0.789 | 0.994 | 0.793 | 0.000 | 0.787 | 0.250 | 0.014 | 0.0 | | |
| | ons_style | 361 | 0.787 | 0.994 | 0.793 | 0.000 | 0.792 | 0.500 | 0.044 | 0.3 | | |
| | countrynames | 361 | 0.634 | 0.761 | 0.745 | 0.000 | 0.000 | 0.000 | 0.226 | 2.7 | | |
| | pycountry | 361 | 0.634 | 0.761 | 0.776 | 0.000 | 0.000 | 0.250 | 0.184 | 1.5 | | |
| | coco | 361 | 0.596 | 0.761 | 0.601 | 0.000 | 0.000 | 0.250 | 0.120 | 0.5 | | |
| | model@0.01 | 361 | 0.011 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | – | 4.1 | | |
| | model@0.02 | 361 | 0.011 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | – | 4.1 | | |
| Pre-registered hypotheses of release 1.2, as read (P1 and P2 rest on the item-level certificate | |
| corrected above): | |
| | hypothesis | result | evidence | | |
| |---|---|---| | |
| | P1 | met | certified=True, lambda=0.827, answered_n=309, errors=3, p_value_risk_le_2pct=0.947, contradicted=False | | |
| | P2 | met | answered=0.856 | | |
| | P3 | met | size_mb=74.732, cpu_p50_ms=4.092, tokens_identical=1.000, select_accuracy_drop=0.003 | | |
| | P4 | met | model_accuracy=0.886, best_non_llm=e5_knn, best_non_llm_accuracy=0.825 | | |
| | A1 | reported (not judged) | certified=False, answered=0.000, risk_answered=nan | | |
| Gold labels: The register calibration strings were labelled a second time, blind (no proposal and no first label shown): agreement 99.2 % on 592 strings (5 disagreements); one-sided 95 % upper bound on disagreement 1.8 %. On the 218 hand-labelled strings the passes disagree on 5 (2.3 %); the 374 automatic or preset labels agree by construction. Test labels were labelled once. | |
| Latency and size: p50 4.1 ms, p95 5.1 ms per string on CPU (4 threads, ONNX Runtime, tokenisation included); model file 75 MB. | |
| ## Limitations and risks | |
| - For clean fields an exact lexicon with context rules is as accurate as this model; use the model | |
| for what a lexicon cannot read, but note that its certificate does not cover that residual (see | |
| the cascade table). Its advantage was measured on held-out strings of registers whose | |
| other strings were in training; on fresh registers, strings with no exact lexicon match are hard. | |
| - The certificate holds on average for strings drawn like the calibration strings (register | |
| fields), counted per register and normalised key, each target on its own; not on the hard strings | |
| a lexicon cannot read (with probability ≥ 90 % over the | |
| calibration draw); errors concentrate on hard strings. Recalibrate before relying on it for a | |
| different source. The curated GBIF domain is much harder than registers for this model, and there | |
| a 35B LLM is more accurate. | |
| - One human reviewer labelled the register strings; the calibration strings were labelled twice by | |
| that reviewer, the second time blind (consistency, not inter-annotator agreement); test labels | |
| were labelled once. | |
| - Of the 4,997 evaluation strings, 5 are not in Latin script, so accuracy on other scripts is not measured. | |
| - One training seed (13); the usefulness gate compared runs whose spread is about its margin. | |
| - Ambiguous names are returned as candidate lists; downstream systems must not silently pick one. | |
| - Known gaps: bare Canadian province codes in US jurisdiction fields (`ON`, `BC`) are not recognised | |
| (every real such string is an evaluation string, so the no-overlap gate removed them from the | |
| generated province variants); `Congo` without context is answered as `CG`. | |
| - Not legal advice; keep a person in the loop. | |
| ## Citation | |
| ```bibtex | |
| @misc{ildan2026countrycode, | |
| title = {From Messy Country Names to ISO 3166 Codes: A Small Model That Abstains, and When a Lexicon Is Enough}, | |
| author = {Ildan, Ali}, | |
| year = {2026}, | |
| publisher = {Zenodo}, | |
| doi = {10.5281/zenodo.23289343} | |
| } | |
| @software{ildan2026countrycode_software, | |
| title = {CountryCode-Decide: messy country names to ISO 3166 codes, with calibrated abstention}, | |
| author = {Ildan, Ali}, | |
| year = {2026}, | |
| url = {https://github.com/aliildan/country-code-decide}, | |
| doi = {10.5281/zenodo.23289336} | |
| } | |
| ``` | |
| ## Attribution | |
| Unicode CLDR (Unicode License v3) · Wikidata (CC0) · GeoNames (CC BY 4.0) · GBIF parsers | |
| (Apache-2.0) · Colorado, Connecticut and Oregon open data (public domain) · UK Companies House | |
| (OGL v3) · Brønnøysund Register Centre (NLOD) · New York open data (OPEN-NY terms, evaluation only) | |
| · mmBERT (MIT). | |