genderize — cora / ultra
Two models that take a personal name and return a gender (M/F) and a country (226 ISO-3166 alpha-2 codes), with probabilities. Byte-level, CPU-only, no tokenizer and no vocabulary file: you feed them a string.
Package contents
| File | What it is |
|---|---|
genderize_cora.pt |
weights, cora (3.1 MB) — CC BY-NC 4.0 |
genderize_ultra.pt |
weights, ultra (12.9 MB) — CC BY-NC 4.0 |
genderize_cora.config.json |
{"ch": 160} — CC BY-NC 4.0 |
genderize_ultra.config.json |
{"ch": 384} — CC BY-NC 4.0 |
genderize_cora.calibration.json |
per-head temperature — CC BY-NC 4.0 |
genderize_ultra.calibration.json |
per-head temperature (the file additionally records a calibration-error metric) — CC BY-NC 4.0 |
maps.json |
class ids: gender M/F, 226 countries — CC BY-NC 4.0 |
genderize_infer.py |
standalone inference code (needs torch and numpy) — MIT |
LICENSE |
full text of CC BY-NC 4.0 (weights) |
LICENSE-CODE |
full text of the MIT License (inference code) |
COMMERCIAL_USE.md |
what counts as non-commercial use, and how to obtain a commercial licence |
What the models do
| Variant | Gender | Country | Size |
|---|---|---|---|
genderize-cora |
2 classes (M/F) | 226 classes | ~0.77M parameters, ch=160 |
genderize-ultra |
2 classes (M/F) | 226 classes | ~3.21M parameters, ch=384 |
ultra is the larger, more accurate variant; cora is the lighter one. Both run
on CPU: on an Intel i3-6100T (2 cores, 2 threads used) the network alone processes
about 850 names/s (cora) and 160 names/s (ultra) in batches of 100.
How it works
1. Normalisation. The input name is normalised before anything else: Unicode NFC, lowercase, whitespace collapsed (internal runs and leading/trailing spaces become a single space). The normalised form is what the weights saw, so this step must not be changed or skipped:
" MARÍA GARCÍA " -> "maría garcía"
2. Encoding. The normalised text is encoded as UTF-8 and truncated to 48 bytes (not 48 characters: an accented or non-Latin character takes more than one byte, so the effective character budget is smaller). Each byte becomes an integer in 0–255; byte value 0 is the padding value. A name shorter than 48 bytes is padded with zeros.
3. Network. Byte-level, dual-head 1-D convolutional classifier:
- one embedding table, 256 entries (one per byte value) of width 64, padding index 0;
- a 1×1 convolution projecting 64 →
chchannels; - four residual 1-D convolution blocks, kernel sizes 3, 5, 7, 3, each with convolution + BatchNorm + GELU around a residual connection, padding to keep the length;
- the masked positions are pooled twice — mean-pooling and max-pooling — and the two vectors are concatenated;
- a shared layer of 512 units (GELU, plus a dropout layer that is inactive at inference);
- two linear heads read out from it: gender, 2 classes, and country, 226 classes.
cora uses ch = 160, ultra ch = 384; those are the only differences
between the two configs.
4. Probabilities. Because it is a convolutional stack, the model sees all
byte positions at once: character order matters through the convolution kernels,
not through a recurrent state. Logits are divided by a per-head temperature
before softmax — temp_gender and temp_country from the calibration file:
| Variant | temp_gender | temp_country |
|---|---|---|
| cora | 1.0096479654312134 | 1.0 |
| ultra | 1.0460342168807983 | 0.939997673034668 |
5. Decision rule. Gender is reported as male when P(M) ≥ 0.5, otherwise
female; the reported probability is the probability of the reported class.
Countries are returned as the top-k codes (default 5, --top) sorted by
probability.
6. Output. One record per input name:
{"name": "...", "gender": "male|female", "probability": 0.0-1.0,
"countries": [{"code": "IT", "probability": 0.0-1.0}, ...], "model": "cora|ultra"}
Loading and using the weights
All files sit flat in this repository. Download them (for example with
huggingface_hub.snapshot_download("textpie/genderize")) and run the reference
driver from that directory:
pip install torch numpy
python genderize_infer.py --variant ultra --top 3 "Andrea Rossi"
In Python:
from genderize_infer import Genderize
model = Genderize("ultra") # or Genderize("cora"); pass models_dir=... if the files live elsewhere
model.predict(["Andrea Rossi"], top=3)
Output for the five names published with the examples (--variant ultra),
reproduced locally against these exact weight files:
| Input | Gender | P | Country top-3 |
|---|---|---|---|
| Andrea Rossi | male | 0.849 | IT 0.84, FR 0.05, US 0.02 |
| Yuki Tanaka | female | 0.589 | JP 0.98, US 0.01, ID 0.00 |
| María García | female | 0.995 | ES 0.42, MX 0.13, AR 0.09 |
| Chen Wei | male | 0.692 | CN 0.56, TW 0.13, SG 0.09 |
| Fatima Al Sayed | female | 0.996 | AE 0.35, OM 0.19, SA 0.14 |
Benchmarks
Source: dbtool.it/benchmark. These are not new measurements taken for this card and no re-measurement was performed here.
- Bench: 25,000 names never seen in training, scored on the network alone.
- The dictionary layer used by the hosted API does not contribute on unseen names, so these figures describe the released weights.
| Metric | cora | ultra |
|---|---|---|
| Gender accuracy | 97.9 % | 98.2 % |
| Country top-1 accuracy | 82.6 % | 83.7 % |
Per-country figures for the full hosted system are on https://dbtool.it/academic; an independent open bench on public WGND 2.0 names (network alone, losses included) is on https://dbtool.it/benchmark.
What this release does NOT include
- The dictionary layer of the API. The hosted API combines the network with
a proprietary frequency layer (exact-name and per-country M/F frequencies)
derived from production data. That layer is not part of this release:
you get the network alone. Consequence: the public API can answer
differently from the weights you download, especially on very frequent
names. Documented example:
Yuki Tanakascores female 0.59 with the network alone (the value reproduced above), while the API with its dictionary layer answers male 0.58. - Everything about how the models were trained: no recipe, no epochs, no optimiser, no data split, no training hyperparameters, no data sources and no per-country counts. This release ships a usable model, not the process behind it, and no training example.
Training data — nature only
The models were trained on name–gender–country pairs covering 226 countries, tens of millions of examples. That is the whole description this release provides: the corpus is not redistributed, and its sources, composition and per-country sizes are not part of the package.
Limitations
- East Asian names: accuracy drops to roughly 82–88 %; Chinese, Korean and Japanese names are frequently confused with one another.
- Names ambiguous across countries: many names are plausible in several
countries, so a top-1 of ~83 % means roughly one name in six gets the wrong
country.
Andreais the classic case: male in Italy, female elsewhere. Treat the country score as a prior, and never as a single-country verdict. - 48-byte truncation: only the first 48 UTF-8 bytes reach the model. Long names and long compounds are silently cut, and multi-byte characters consume more of the budget than plain ASCII.
- Empty or near-empty input: an empty or fully-trimmed name is encoded as 48 padding bytes and still returns a prediction. Validate your input.
- Binary gender: the gender head has two classes; names that do not fit them are forced into the closer one.
- Transliteration: results depend on how the name was romanised upstream; different romanisations of the same name can disagree.
- Dictionary gap: the released weights are the network alone, so for very frequent names they can differ from the hosted API (see above).
Licence
Two licences, one per artefact:
- Weights —
genderize_cora.pt,genderize_ultra.pt, their.config.jsonand.calibration.json, andmaps.json— are released under Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0), full text inLICENSE. You may download, run, study, modify and redistribute them and their derivatives for non-commercial purposes, with attribution (dbtool.it, genderize cora/ultra, 2026) and an indication of changes. - Inference code —
genderize_infer.py— is released under the MIT License, full text inLICENSE-CODE. It can be reused freely; it carries no model knowledge.
Commercial use of the weights is not granted by this licence. The commercial
channel is dbtool.it (the hosted API and the on-premise licence), or a
separate written licence from the rights holder. What counts as commercial use,
and how to request a licence, is set out in COMMERCIAL_USE.md.
The training data is not part of this release, and neither is the dictionary layer of the hosted API (see above).
Contact
dbtool — https://dbtool.it — open-weights page: https://dbtool.it/open-models.html Domenico Gigante, d.gigante@tech-time.it