Model card: run it without a server, in Python or JavaScript

#1
Files changed (3) hide show
  1. ARCHITECTURE.md +1 -1
  2. README.md +114 -52
  3. millid.py +1 -1
ARCHITECTURE.md CHANGED
@@ -204,7 +204,7 @@ flowchart LR
204
  is (α = 1), where the largest languages fill every batch, and uniform (α = 0), where a
205
  100-passage language is seen until memorised. XLM-R and mT5 sample their languages the same way.
206
  - **How long.** A quarter of passages are cut to a 20 to 80 character snippet, on word boundaries
207
- where the script has them: short text is the hard case, and what a document pipeline meets in
208
  headers, captions and table cells.
209
  - **Why macro F1 chooses.** It weighs every language equally, the score a thousand-language model
210
  should be judged on. Top-1 accuracy, which the largest languages dominate, is reported beside it.
 
204
  is (α = 1), where the largest languages fill every batch, and uniform (α = 0), where a
205
  100-passage language is seen until memorised. XLM-R and mT5 sample their languages the same way.
206
  - **How long.** A quarter of passages are cut to a 20 to 80 character snippet, on word boundaries
207
+ where the script has them: short text is the hard case, and what a document holds in its
208
  headers, captions and table cells.
209
  - **Why macro F1 chooses.** It weighs every language equally, the score a thousand-language model
210
  should be judged on. Top-1 accuracy, which the largest languages dominate, is reported beside it.
README.md CHANGED
@@ -1,44 +1,105 @@
1
  ---
 
2
  library_name: pytorch
3
  pipeline_tag: text-classification
4
  tags:
5
  - language-identification
 
 
6
  datasets:
7
  - baobabtech/millid-dataset
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
10
- # MillID medium: MultiLingual Language IDentifier
11
 
12
- A byte-level, encoder-only transformer that names the language of a passage: **1641 language-script
13
- labels** (ISO 639-3 + script, e.g. `eng_Latn`, `amh_Ethi`). 115.0M parameters, trained from
14
- scratch on [baobabtech/millid-dataset](https://huggingface.co/datasets/baobabtech/millid-dataset).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  ```python
17
- from millid import MillID # millid.py, in this repository
 
 
 
 
 
 
 
18
 
19
- model = MillID.from_pretrained("baobabtech/millid-medium")
20
- model.predict(["Lave men ou ak savon anvan ou manje."], k=3)
21
  ```
22
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  ## Architecture
24
 
25
- UTF-8 bytes (after Unicode NFKC), each embedded as byte + Unicode script + byte-in-character + hashed character
26
- 2-, 3- and 4-grams (32,768 buckets) -> depthwise convolution stem (kernel 5) -> strided
27
- convolution, 2 bytes per token -> 12 pre-norm blocks (RMSNorm, attention with RoPE,
28
- 12 heads, SwiGLU; d=768) -> masked mean pooling -> language head, each language's vector the sum of
29
- its own, its language's and its script's. Up to 256 bytes per passage. See `ARCHITECTURE.md`.
30
 
31
  ## Evaluation against GlotLID and fastText
32
 
33
- `validation` chose the checkpoint; `test` is split by document from training; `udhr` is the Universal Declaration
34
- of Human Rights, labelled by its translators, over the languages MillID knows, and `udhr-all` over all of them.
35
- `glotlid` is [GlotLID](https://huggingface.co/cis-lmu/glotlid) as it is; `glotlid@millid` is GlotLID restricted to
36
- MillID's labels, the like-for-like comparison. GlotLID's own labels came from training on data that includes UDHR
37
- and labelled FinePDFs and FineWeb-2, so its scores on `udhr` and on those sources are optimistic. `fasttext` is
38
- Meta's [fastText language identifier](https://huggingface.co/facebook/fasttext-language-identification) (NLLB,
39
- 218 labels), its answers mapped onto MillID's spelling (Simplified and Traditional Han as `Hani`, `pes`/`prs` as
40
- `fas`, `est` as `ekk`). It cannot answer most of the 1,600 languages here, so `test-common` and `udhr-common`
41
- score every model on the passages whose language all of them know: the head-to-head on equal terms.
42
 
43
  | Set | Model | Top-1 | Top-3 | Top-5 | Macro P | Macro R | Macro F1 | Median F1 | Languages |
44
  |---|---|---|---|---|---|---|---|---|---|
@@ -87,8 +148,7 @@ Top-1 accuracy by passage length:
87
 
88
  ### Leaderboard: test
89
 
90
- MillID's F1 against the others', language by language: against GlotLID restricted to MillID's labels, MillID is better on **110** languages, worse on **776** and within 0.01 on 735; against fastText restricted to MillID's labels, MillID is better on **1616** languages, worse on **0** and within 0.01 on 5. The full table, with precision and recall, is
91
- `reports/test/leaderboard.csv`.
92
 
93
  The 20 languages with the most passages:
94
 
@@ -96,23 +156,23 @@ The 20 languages with the most passages:
96
  |---|---|---|---|---|---|---|---|
97
  | `fra_Latn` | French | 1652 | 0.952 | 0.928 | 0.928 | 0.808 | 0.808 |
98
  | `eng_Latn` | English | 1444 | 0.916 | 0.858 | 0.859 | 0.653 | 0.648 |
99
- | `spa_Latn` | Spanish | 1136 | 0.941 | 0.950 | 0.950 | 0.574 | 0.574 |
100
  | `tam_Taml` | Tamil | 754 | 0.997 | 0.993 | 0.993 | 0.965 | 0.965 |
101
  | `arb_Arab` | Arabic (Modern Standard) | 745 | 0.829 | 0.785 | 0.785 | 0.513 | 0.512 |
102
  | `por_Latn` | Portuguese | 657 | 0.955 | 0.947 | 0.949 | 0.756 | 0.755 |
103
  | `kan_Knda` | Kannada | 601 | 0.982 | 0.993 | 0.993 | 0.922 | 0.922 |
104
  | `zul_Latn` | Zulu | 594 | 0.833 | 0.896 | 0.896 | 0.539 | 0.539 |
105
- | `swh_Latn` | Swahili | 592 | 0.858 | 0.918 | 0.916 | 0.346 | 0.346 |
106
  | `mar_Deva` | Marathi | 589 | 0.981 | 0.989 | 0.990 | 0.926 | 0.926 |
107
- | `tso_Latn` | Tsonga | 581 | 0.970 | 0.956 | 0.956 | 0.426 | 0.426 |
108
  | `hun_Latn` | Hungarian | 576 | 0.990 | 0.999 | 0.999 | 0.232 | 0.232 |
109
- | `afr_Latn` | Afrikaans | 569 | 0.973 | 0.985 | 0.985 | 0.861 | 0.860 |
110
  | `tel_Telu` | Telugu | 555 | 0.994 | 0.994 | 0.994 | 0.951 | 0.951 |
111
  | `nbl_Latn` | South Ndebele | 528 | 0.895 | 0.948 | 0.949 | 0.000 | 0.000 |
112
  | `rus_Cyrl` | Russian | 528 | 0.925 | 0.949 | 0.949 | 0.462 | 0.462 |
113
  | `ory_Orya` | Odia (individual language) | 511 | 0.993 | 0.991 | 0.994 | 0.976 | 0.976 |
114
  | `sot_Latn` | Southern Sotho | 491 | 0.966 | 0.977 | 0.978 | 0.660 | 0.660 |
115
- | `nso_Latn` | Northern Sotho | 479 | 0.960 | 0.968 | 0.968 | 0.915 | 0.914 |
116
  | `kua_Latn` | Kuanyama | 476 | 0.979 | 0.986 | 0.986 | 0.000 | 0.000 |
117
 
118
  Where MillID gains most over GlotLID:
@@ -140,7 +200,7 @@ Where MillID loses most to GlotLID:
140
  | Label | Language | Passages | MillID F1 | GlotLID F1 | GlotLID F1, restricted | fastText F1 | fastText F1, restricted |
141
  |---|---|---|---|---|---|---|---|
142
  | `ccp_Latn` | Chakma | 3 | 0.333 | 1.000 | 1.000 | 0.000 | 0.000 |
143
- | `ttj_Latn` | Tooro | 28 | 0.490 | 0.945 | 0.945 | 0.000 | 0.000 |
144
  | `hak_Hani` | Hakka Chinese | 29 | 0.415 | 0.824 | 0.824 | 0.000 | 0.000 |
145
  | `mus_Latn` | Muscogee | 2 | 0.667 | 1.000 | 1.000 | 0.000 | 0.000 |
146
  | `biu_Latn` | Biete | 7 | 0.667 | 1.000 | 1.000 | 0.000 | 0.000 |
@@ -149,23 +209,22 @@ Where MillID loses most to GlotLID:
149
  | `esi_Latn` | North Alaskan Inupiatun | 9 | 0.500 | 0.783 | 0.783 | 0.000 | 0.000 |
150
  | `und_Ethi` | und | 14 | 0.741 | 1.000 | 1.000 | 0.000 | 0.000 |
151
  | `hrv_Latn` | Croatian | 168 | 0.687 | 0.944 | 0.944 | 0.178 | 0.178 |
152
- | `dks_Latn` | Southeastern Dinka | 3 | 0.750 | 1.000 | 1.000 | 0.000 | 0.000 |
153
- | `nse_Latn` | Nsenga | 12 | 0.500 | 0.750 | 0.750 | 0.000 | 0.000 |
154
  | `tui_Latn` | Tupuri | 4 | 0.750 | 0.667 | 1.000 | 0.000 | 0.000 |
 
 
155
  | `mbd_Latn` | Dibabawon Manobo | 14 | 0.757 | 1.000 | 1.000 | 0.000 | 0.000 |
156
  | `lnd_Latn` | Lundayeh | 5 | 0.769 | 1.000 | 1.000 | 0.000 | 0.000 |
157
 
158
  ### Leaderboard: udhr
159
 
160
- MillID's F1 against the others', language by language: against GlotLID restricted to MillID's labels, MillID is better on **63** languages, worse on **95** and within 0.01 on 200; against fastText restricted to MillID's labels, MillID is better on **304** languages, worse on **13** and within 0.01 on 41. The full table, with precision and recall, is
161
- `reports/udhr/leaderboard.csv`.
162
 
163
  The 20 languages with the most passages:
164
 
165
  | Label | Language | Passages | MillID F1 | GlotLID F1 | GlotLID F1, restricted | fastText F1 | fastText F1, restricted |
166
  |---|---|---|---|---|---|---|---|
167
- | `roh_Latn` | Romansh | 399 | 0.991 | 0.999 | 0.999 | 0.945 | 0.945 |
168
- | `oci_Latn` | Occitan | 398 | 0.331 | 0.376 | 0.376 | 0.436 | 0.436 |
169
  | `cmn_Hani` | Chinese (Mandarin) | 319 | 0.886 | 0.868 | 0.868 | 0.468 | 0.468 |
170
  | `krl_Latn` | Karelian | 183 | 0.000 | 0.011 | 0.011 | 0.000 | 0.000 |
171
  | `qug_Latn` | Chimborazo Highland Quichua | 182 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 |
@@ -177,7 +236,7 @@ The 20 languages with the most passages:
177
  | `jpn_Jpan` | Japanese | 140 | 1.000 | 1.000 | 1.000 | 0.944 | 0.944 |
178
  | `snn_Latn` | Siona | 136 | 0.773 | 0.514 | 0.514 | 0.000 | 0.000 |
179
  | `ktu_Latn` | Kituba (Democratic Republic of Congo) | 118 | 0.769 | 0.680 | 0.680 | 0.000 | 0.000 |
180
- | `lat_Latn` | Latin | 118 | 0.996 | 0.991 | 0.991 | 0.000 | 0.000 |
181
  | `hat_Latn` | Haitian Creole | 117 | 0.991 | 0.979 | 0.979 | 0.699 | 0.699 |
182
  | `twi_Latn` | Twi | 116 | 0.987 | 0.996 | 0.996 | 0.399 | 0.396 |
183
  | `rmn_Latn` | Balkan Romani | 116 | 0.667 | 0.857 | 0.857 | 0.000 | 0.000 |
@@ -192,7 +251,7 @@ Where MillID gains most over GlotLID:
192
  | `mic_Latn` | Mi'kmaw | 59 | 1.000 | 0.427 | 0.427 | 0.000 | 0.000 |
193
  | `tet_Latn` | Tetum | 58 | 0.699 | 0.167 | 0.167 | 0.000 | 0.000 |
194
  | `skr_Arab` | Saraiki | 63 | 0.923 | 0.500 | 0.500 | 0.000 | 0.000 |
195
- | `sag_Latn` | Sango | 59 | 0.991 | 0.578 | 0.578 | 0.189 | 0.189 |
196
  | `zgh_Tfng` | Standard Moroccan Tamazight | 60 | 0.824 | 0.462 | 0.462 | 0.000 | 0.000 |
197
  | `fuf_Latn` | Pular | 58 | 0.816 | 0.483 | 0.483 | 0.000 | 0.000 |
198
  | `tzm_Tfng` | Central Atlas Tamazight | 1 | 0.333 | 0.065 | 0.065 | 0.033 | 0.033 |
@@ -201,8 +260,8 @@ Where MillID gains most over GlotLID:
201
  | `mxv_Latn` | Metlatónoc Mixtec | 58 | 0.215 | 0.000 | 0.000 | 0.000 | 0.000 |
202
  | `pnb_Arab` | Western Panjabi | 61 | 0.931 | 0.739 | 0.739 | 0.000 | 0.000 |
203
  | `lld_Latn` | Ladin | 59 | 0.899 | 0.731 | 0.731 | 0.000 | 0.000 |
204
- | `tah_Latn` | Tahitian | 57 | 0.973 | 0.813 | 0.813 | 0.966 | 0.966 |
205
- | `pbb_Latn` | Páez | 56 | 0.848 | 0.713 | 0.713 | 0.000 | 0.000 |
206
  | `eve_Cyrl` | Even | 49 | 0.990 | 0.886 | 0.886 | 0.000 | 0.000 |
207
 
208
  Where MillID loses most to GlotLID:
@@ -219,16 +278,15 @@ Where MillID loses most to GlotLID:
219
  | `ell_Grek` | Greek | 115 | 0.655 | 1.000 | 1.000 | 1.000 | 1.000 |
220
  | `bho_Deva` | Bhojpuri | 56 | 0.643 | 0.933 | 0.933 | 0.832 | 0.832 |
221
  | `fuv_Latn` | Nigerian Fulfulde | 108 | 0.405 | 0.681 | 0.689 | 0.475 | 0.475 |
222
- | `cni_Latn` | Asháninka | 52 | 0.707 | 0.963 | 0.963 | 0.000 | 0.000 |
223
- | `qvn_Latn` | North Junín Quechua | 57 | 0.219 | 0.459 | 0.459 | 0.000 | 0.000 |
224
  | `ind_Latn` | Indonesian | 60 | 0.674 | 0.903 | 0.903 | 0.565 | 0.565 |
225
  | `maz_Latn` | Central Mazahua | 59 | 0.742 | 0.939 | 0.939 | 0.000 | 0.000 |
226
  | `rmn_Latn` | Balkan Romani | 116 | 0.667 | 0.857 | 0.857 | 0.000 | 0.000 |
227
 
228
  ### Leaderboard: test-common
229
 
230
- MillID's F1 against the others', language by language: against GlotLID restricted to MillID's labels, MillID is better on **6** languages, worse on **142** and within 0.01 on 65; against fastText restricted to MillID's labels, MillID is better on **93** languages, worse on **47** and within 0.01 on 73. The full table, with precision and recall, is
231
- `reports/test-common/leaderboard.csv`.
232
 
233
  The 20 languages with the most passages:
234
 
@@ -250,7 +308,7 @@ The 20 languages with the most passages:
250
  | `tel_Telu` | Telugu | 555 | 1.000 | 0.998 | 0.998 | 0.999 | 0.999 |
251
  | `rus_Cyrl` | Russian | 528 | 0.929 | 0.985 | 0.985 | 0.927 | 0.927 |
252
  | `ory_Orya` | Odia (individual language) | 511 | 0.996 | 0.997 | 1.000 | 0.996 | 0.996 |
253
- | `sot_Latn` | Southern Sotho | 491 | 0.966 | 0.977 | 0.978 | 0.856 | 0.856 |
254
  | `nso_Latn` | Northern Sotho | 479 | 0.961 | 0.970 | 0.970 | 0.967 | 0.967 |
255
  | `asm_Beng` | Assamese | 467 | 0.995 | 0.999 | 0.999 | 0.997 | 0.997 |
256
  | `ben_Beng` | Bangla | 466 | 0.987 | 0.997 | 0.997 | 0.989 | 0.989 |
@@ -267,9 +325,9 @@ Where MillID gains most over GlotLID:
267
  | `arb_Arab` | Arabic (Modern Standard) | 745 | 0.905 | 0.886 | 0.886 | 0.957 | 0.954 |
268
  | `knc_Latn` | Central Kanuri | 293 | 0.959 | 0.952 | 0.952 | 0.981 | 0.981 |
269
  | `por_Latn` | Portuguese | 657 | 0.963 | 0.954 | 0.956 | 0.950 | 0.949 |
270
- | `san_Deva` | Sanskrit | 239 | 0.996 | 0.989 | 0.989 | 0.970 | 0.970 |
271
- | `kam_Latn` | Kamba | 272 | 0.980 | 0.972 | 0.974 | 0.862 | 0.862 |
272
  | `bug_Latn` | Buginese | 78 | 0.987 | 0.981 | 0.981 | 0.938 | 0.938 |
 
273
  | `mlt_Latn` | Maltese | 291 | 0.997 | 0.993 | 0.993 | 0.961 | 0.961 |
274
  | `tel_Telu` | Telugu | 555 | 1.000 | 0.998 | 0.998 | 0.999 | 0.999 |
275
  | `sin_Sinh` | Sinhala | 232 | 1.000 | 1.000 | 1.000 | 0.998 | 0.998 |
@@ -280,18 +338,18 @@ Where MillID loses most to GlotLID:
280
  | Label | Language | Passages | MillID F1 | GlotLID F1 | GlotLID F1, restricted | fastText F1 | fastText F1, restricted |
281
  |---|---|---|---|---|---|---|---|
282
  | `knc_Arab` | Central Kanuri | 68 | 0.562 | 0.857 | 0.860 | 0.029 | 0.029 |
283
- | `tzm_Tfng` | Central Atlas Tamazight | 157 | 0.645 | 0.935 | 0.935 | 0.751 | 0.751 |
284
  | `hrv_Latn` | Croatian | 168 | 0.717 | 0.994 | 0.994 | 0.856 | 0.856 |
285
  | `bos_Latn` | Bosnian | 198 | 0.727 | 1.000 | 1.000 | 0.897 | 0.897 |
286
  | `zsm_Latn` | Standard Malay | 244 | 0.773 | 0.998 | 0.998 | 0.908 | 0.908 |
287
  | `taq_Tfng` | Tamasheq | 104 | 0.613 | 0.805 | 0.805 | 0.000 | 0.000 |
288
  | `ind_Latn` | Indonesian | 335 | 0.807 | 0.991 | 0.991 | 0.887 | 0.887 |
289
  | `kin_Latn` | Kinyarwanda | 210 | 0.818 | 0.981 | 0.981 | 0.854 | 0.854 |
290
- | `run_Latn` | Rundi | 291 | 0.843 | 0.985 | 0.985 | 0.854 | 0.854 |
291
  | `nob_Latn` | Norwegian Bokmål | 265 | 0.820 | 0.961 | 0.959 | 0.894 | 0.894 |
292
  | `fuv_Latn` | Nigerian Fulfulde | 164 | 0.797 | 0.877 | 0.892 | 0.859 | 0.859 |
293
  | `mag_Deva` | Magahi | 97 | 0.905 | 1.000 | 1.000 | 0.772 | 0.772 |
294
- | `vec_Latn` | Venetian | 49 | 0.766 | 0.860 | 0.860 | 0.620 | 0.620 |
295
  | `cjk_Latn` | Chokwe | 24 | 0.909 | 1.000 | 1.000 | 0.807 | 0.807 |
296
  | `taq_Latn` | Tamasheq | 106 | 0.909 | 1.000 | 1.000 | 0.838 | 0.838 |
297
 
@@ -299,6 +357,10 @@ Per-language precision, recall (sensitivity), F1 and specificity for every model
299
 
300
  ## Training
301
 
302
- 1,944,095 passages, best validation macro F1 at step 56000, 3.4 hours.
303
- Languages sampled in proportion to n^0.3; 25% of passages cut to a 20 to 80 character
304
- snippet; batch 512, AdamW at 0.0003, cosine schedule, label smoothing 0.1.
 
 
 
 
 
1
  ---
2
+ license: apache-2.0
3
  library_name: pytorch
4
  pipeline_tag: text-classification
5
  tags:
6
  - language-identification
7
+ - onnx
8
+ - byte-level
9
  datasets:
10
  - baobabtech/millid-dataset
11
+ metrics:
12
+ - f1
13
+ - accuracy
14
+ model-index:
15
+ - name: millid-medium-v1
16
+ results:
17
+ - task:
18
+ type: text-classification
19
+ name: Language identification
20
+ dataset:
21
+ name: MillID test split (the full build)
22
+ type: baobabtech/millid-dataset
23
+ split: test
24
+ metrics:
25
+ - type: f1
26
+ name: Macro F1
27
+ value: 0.9576
28
+ - type: accuracy
29
+ name: Top-1 accuracy
30
+ value: 0.9616
31
+ - task:
32
+ type: text-classification
33
+ name: Language identification
34
+ dataset:
35
+ name: UDHR-LID, the languages MillID knows
36
+ type: baobabtech/millid-dataset
37
+ config: udhr
38
+ split: test
39
+ metrics:
40
+ - type: f1
41
+ name: Macro F1
42
+ value: 0.8780
43
+ - type: accuracy
44
+ name: Top-1 accuracy
45
+ value: 0.8524
46
  ---
47
 
48
+ # MillID medium v1: MultiLingual Language IDentifier
49
 
50
+ A byte-level, encoder-only transformer that names the language of a passage, from a table cell to a paragraph: **1,641 language-script labels** (ISO 639-3 + script, e.g. `eng_Latn`, `amh_Ethi`). 115.0M parameters, trained from scratch. Apache-2.0.
51
+
52
+ ## The MillID models
53
+
54
+ | Model | Size | Macro F1, `test` / `udhr` | Runs best on |
55
+ |---|---|---|---|
56
+ | [MillID tiny v1.5](https://huggingface.co/baobabtech/millid-tiny-v1.5) | 2.9M parameters, ONNX 11 MB | 0.9554 / 0.8764 | a CPU, or a web page |
57
+ | **MillID medium v1** (this model) | 115M parameters, ONNX 455 MB | 0.9576 / 0.8780 | a GPU |
58
+ | [MillID large v1.5](https://huggingface.co/baobabtech/millid-large-v1.5) | 193M parameters, ONNX 764 MB | 0.9646 / 0.8812 | a GPU: the most accurate |
59
+
60
+ All three, language by language, against GlotLID and Meta's fastText: the [leaderboard](https://huggingface.co/baobabtech/millid). GlotLID, restricted to MillID's labels, scores 0.9755 / 0.8873 on the same sets.
61
+
62
+ ## Use it
63
+
64
+ No server and no PyTorch: the model runs in your process from `onnx/model.onnx`, in Python or JavaScript, and the two give the same answers (one encoder, ported line for line and tested against the same fixtures in [baobab-tech/millid](https://github.com/baobab-tech/millid)).
65
+
66
+ ```bash
67
+ pip install "millid[cpu] @ git+https://github.com/baobab-tech/millid.git" # [gpu] for CUDA
68
+ ```
69
 
70
  ```python
71
+ from millid import MillID
72
+
73
+ model = MillID.from_pretrained("baobabtech/millid-medium-v1")
74
+ model.predict("Lave men ou ak savon anvan ou manje.", k=3)
75
+ # [Prediction(label='hat_Latn', language='Haitian Creole', probability=...), ...]
76
+ model.predict(["one text", "another"], k=3) # one list of answers per text
77
+ model.identify_document(long_text) # any length: its languages and segments
78
+ ```
79
 
80
+ ```bash
81
+ millid predict "Lave men ou ak savon anvan ou manje." --model baobabtech/millid-medium-v1 -k 3
82
  ```
83
 
84
+ JavaScript, in Node or a browser (`js/` in the same repository; not on npm yet):
85
+
86
+ ```js
87
+ import { MillID } from "@baobabtech/millid"; // "@baobabtech/millid/web" in a browser, with { ort }
88
+ const model = await MillID.fromPretrained("baobabtech/millid-medium-v1");
89
+ await model.predict("Lave men ou ak savon anvan ou manje.", 3);
90
+ ```
91
+
92
+ `millid.py` in this repository is the PyTorch training code, which reads `model.safetensors` to fine-tune or export.
93
+
94
+ The ONNX graph takes, for each UTF-8 byte of a passage cut to 255 bytes after a CLS token, `ids`, `script` and `charpos` (int64, `[batch, bytes]`), `ngrams` (int64, `[batch, bytes, 3]`) and `mask` (bool, `[batch, bytes]`), and returns `logits` (float32, `[batch, labels]`). `millid.encode_batch` and the JavaScript `encodeBatch` build them; script ids come from a frozen table (Unicode 15.0), so every runtime gives the same ones.
95
+
96
  ## Architecture
97
 
98
+ UTF-8 bytes (after Unicode NFKC), each embedded as byte + Unicode script + byte-in-character + hashed character 2-, 3- and 4-grams (32,768 buckets) -> depthwise convolution stem (kernel 5) -> strided convolution, 2 bytes per token -> 12 pre-norm blocks (RMSNorm, attention with RoPE, 12 heads, SwiGLU; d=768) -> masked mean pooling -> language head, each language's vector the sum of its own, its language's and its script's. Up to 256 bytes per passage. See `ARCHITECTURE.md`.
 
 
 
 
99
 
100
  ## Evaluation against GlotLID and fastText
101
 
102
+ `validation` chose the checkpoint; `test` is split by document from training; `udhr` is the Universal Declaration of Human Rights, labelled by its translators, over the languages MillID knows, and `udhr-all` over all of them. `glotlid` is [GlotLID](https://huggingface.co/cis-lmu/glotlid) as it is; `glotlid@millid` is GlotLID restricted to MillID's labels, the like-for-like comparison. GlotLID's own labels came from training on data that includes UDHR and labelled FinePDFs and FineWeb-2, so its scores on `udhr` and on those sources are optimistic. `fasttext` is Meta's [fastText language identifier](https://huggingface.co/facebook/fasttext-language-identification) (NLLB, 218 labels), its answers mapped onto MillID's spelling (Simplified and Traditional Han as `Hani`, `pes`/`prs` as `fas`, `est` as `ekk`). It cannot answer most of the 1,600 languages here, so `test-common` and `udhr-common` score every model on the passages whose language all of them know: the head-to-head on equal terms.
 
 
 
 
 
 
 
 
103
 
104
  | Set | Model | Top-1 | Top-3 | Top-5 | Macro P | Macro R | Macro F1 | Median F1 | Languages |
105
  |---|---|---|---|---|---|---|---|---|---|
 
148
 
149
  ### Leaderboard: test
150
 
151
+ MillID's F1 against the others', language by language: against GlotLID restricted to MillID's labels, MillID is better on **110** languages, worse on **776** and within 0.01 on 735; against fastText restricted to MillID's labels, MillID is better on **1616** languages, worse on **0** and within 0.01 on 5. The full table, with precision and recall, is `reports/test/leaderboard.csv`.
 
152
 
153
  The 20 languages with the most passages:
154
 
 
156
  |---|---|---|---|---|---|---|---|
157
  | `fra_Latn` | French | 1652 | 0.952 | 0.928 | 0.928 | 0.808 | 0.808 |
158
  | `eng_Latn` | English | 1444 | 0.916 | 0.858 | 0.859 | 0.653 | 0.648 |
159
+ | `spa_Latn` | Spanish | 1136 | 0.942 | 0.951 | 0.951 | 0.574 | 0.574 |
160
  | `tam_Taml` | Tamil | 754 | 0.997 | 0.993 | 0.993 | 0.965 | 0.965 |
161
  | `arb_Arab` | Arabic (Modern Standard) | 745 | 0.829 | 0.785 | 0.785 | 0.513 | 0.512 |
162
  | `por_Latn` | Portuguese | 657 | 0.955 | 0.947 | 0.949 | 0.756 | 0.755 |
163
  | `kan_Knda` | Kannada | 601 | 0.982 | 0.993 | 0.993 | 0.922 | 0.922 |
164
  | `zul_Latn` | Zulu | 594 | 0.833 | 0.896 | 0.896 | 0.539 | 0.539 |
165
+ | `swh_Latn` | Swahili | 592 | 0.858 | 0.917 | 0.916 | 0.346 | 0.346 |
166
  | `mar_Deva` | Marathi | 589 | 0.981 | 0.989 | 0.990 | 0.926 | 0.926 |
167
+ | `tso_Latn` | Tsonga | 581 | 0.970 | 0.956 | 0.956 | 0.425 | 0.425 |
168
  | `hun_Latn` | Hungarian | 576 | 0.990 | 0.999 | 0.999 | 0.232 | 0.232 |
169
+ | `afr_Latn` | Afrikaans | 569 | 0.974 | 0.985 | 0.985 | 0.861 | 0.860 |
170
  | `tel_Telu` | Telugu | 555 | 0.994 | 0.994 | 0.994 | 0.951 | 0.951 |
171
  | `nbl_Latn` | South Ndebele | 528 | 0.895 | 0.948 | 0.949 | 0.000 | 0.000 |
172
  | `rus_Cyrl` | Russian | 528 | 0.925 | 0.949 | 0.949 | 0.462 | 0.462 |
173
  | `ory_Orya` | Odia (individual language) | 511 | 0.993 | 0.991 | 0.994 | 0.976 | 0.976 |
174
  | `sot_Latn` | Southern Sotho | 491 | 0.966 | 0.977 | 0.978 | 0.660 | 0.660 |
175
+ | `nso_Latn` | Northern Sotho | 479 | 0.960 | 0.969 | 0.969 | 0.915 | 0.914 |
176
  | `kua_Latn` | Kuanyama | 476 | 0.979 | 0.986 | 0.986 | 0.000 | 0.000 |
177
 
178
  Where MillID gains most over GlotLID:
 
200
  | Label | Language | Passages | MillID F1 | GlotLID F1 | GlotLID F1, restricted | fastText F1 | fastText F1, restricted |
201
  |---|---|---|---|---|---|---|---|
202
  | `ccp_Latn` | Chakma | 3 | 0.333 | 1.000 | 1.000 | 0.000 | 0.000 |
203
+ | `ttj_Latn` | Tooro | 28 | 0.490 | 0.946 | 0.946 | 0.000 | 0.000 |
204
  | `hak_Hani` | Hakka Chinese | 29 | 0.415 | 0.824 | 0.824 | 0.000 | 0.000 |
205
  | `mus_Latn` | Muscogee | 2 | 0.667 | 1.000 | 1.000 | 0.000 | 0.000 |
206
  | `biu_Latn` | Biete | 7 | 0.667 | 1.000 | 1.000 | 0.000 | 0.000 |
 
209
  | `esi_Latn` | North Alaskan Inupiatun | 9 | 0.500 | 0.783 | 0.783 | 0.000 | 0.000 |
210
  | `und_Ethi` | und | 14 | 0.741 | 1.000 | 1.000 | 0.000 | 0.000 |
211
  | `hrv_Latn` | Croatian | 168 | 0.687 | 0.944 | 0.944 | 0.178 | 0.178 |
 
 
212
  | `tui_Latn` | Tupuri | 4 | 0.750 | 0.667 | 1.000 | 0.000 | 0.000 |
213
+ | `nse_Latn` | Nsenga | 12 | 0.500 | 0.750 | 0.750 | 0.000 | 0.000 |
214
+ | `dks_Latn` | Southeastern Dinka | 3 | 0.750 | 1.000 | 1.000 | 0.000 | 0.000 |
215
  | `mbd_Latn` | Dibabawon Manobo | 14 | 0.757 | 1.000 | 1.000 | 0.000 | 0.000 |
216
  | `lnd_Latn` | Lundayeh | 5 | 0.769 | 1.000 | 1.000 | 0.000 | 0.000 |
217
 
218
  ### Leaderboard: udhr
219
 
220
+ MillID's F1 against the others', language by language: against GlotLID restricted to MillID's labels, MillID is better on **63** languages, worse on **95** and within 0.01 on 200; against fastText restricted to MillID's labels, MillID is better on **304** languages, worse on **13** and within 0.01 on 41. The full table, with precision and recall, is `reports/udhr/leaderboard.csv`.
 
221
 
222
  The 20 languages with the most passages:
223
 
224
  | Label | Language | Passages | MillID F1 | GlotLID F1 | GlotLID F1, restricted | fastText F1 | fastText F1, restricted |
225
  |---|---|---|---|---|---|---|---|
226
+ | `roh_Latn` | Romansh | 399 | 0.991 | 0.999 | 0.999 | 0.946 | 0.946 |
227
+ | `oci_Latn` | Occitan | 398 | 0.331 | 0.376 | 0.376 | 0.435 | 0.435 |
228
  | `cmn_Hani` | Chinese (Mandarin) | 319 | 0.886 | 0.868 | 0.868 | 0.468 | 0.468 |
229
  | `krl_Latn` | Karelian | 183 | 0.000 | 0.011 | 0.011 | 0.000 | 0.000 |
230
  | `qug_Latn` | Chimborazo Highland Quichua | 182 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 |
 
236
  | `jpn_Jpan` | Japanese | 140 | 1.000 | 1.000 | 1.000 | 0.944 | 0.944 |
237
  | `snn_Latn` | Siona | 136 | 0.773 | 0.514 | 0.514 | 0.000 | 0.000 |
238
  | `ktu_Latn` | Kituba (Democratic Republic of Congo) | 118 | 0.769 | 0.680 | 0.680 | 0.000 | 0.000 |
239
+ | `lat_Latn` | Latin | 118 | 0.996 | 0.992 | 0.992 | 0.000 | 0.000 |
240
  | `hat_Latn` | Haitian Creole | 117 | 0.991 | 0.979 | 0.979 | 0.699 | 0.699 |
241
  | `twi_Latn` | Twi | 116 | 0.987 | 0.996 | 0.996 | 0.399 | 0.396 |
242
  | `rmn_Latn` | Balkan Romani | 116 | 0.667 | 0.857 | 0.857 | 0.000 | 0.000 |
 
251
  | `mic_Latn` | Mi'kmaw | 59 | 1.000 | 0.427 | 0.427 | 0.000 | 0.000 |
252
  | `tet_Latn` | Tetum | 58 | 0.699 | 0.167 | 0.167 | 0.000 | 0.000 |
253
  | `skr_Arab` | Saraiki | 63 | 0.923 | 0.500 | 0.500 | 0.000 | 0.000 |
254
+ | `sag_Latn` | Sango | 59 | 0.992 | 0.578 | 0.578 | 0.189 | 0.189 |
255
  | `zgh_Tfng` | Standard Moroccan Tamazight | 60 | 0.824 | 0.462 | 0.462 | 0.000 | 0.000 |
256
  | `fuf_Latn` | Pular | 58 | 0.816 | 0.483 | 0.483 | 0.000 | 0.000 |
257
  | `tzm_Tfng` | Central Atlas Tamazight | 1 | 0.333 | 0.065 | 0.065 | 0.033 | 0.033 |
 
260
  | `mxv_Latn` | Metlatónoc Mixtec | 58 | 0.215 | 0.000 | 0.000 | 0.000 | 0.000 |
261
  | `pnb_Arab` | Western Panjabi | 61 | 0.931 | 0.739 | 0.739 | 0.000 | 0.000 |
262
  | `lld_Latn` | Ladin | 59 | 0.899 | 0.731 | 0.731 | 0.000 | 0.000 |
263
+ | `tah_Latn` | Tahitian | 57 | 0.973 | 0.812 | 0.812 | 0.966 | 0.966 |
264
+ | `pbb_Latn` | Páez | 56 | 0.849 | 0.713 | 0.713 | 0.000 | 0.000 |
265
  | `eve_Cyrl` | Even | 49 | 0.990 | 0.886 | 0.886 | 0.000 | 0.000 |
266
 
267
  Where MillID loses most to GlotLID:
 
278
  | `ell_Grek` | Greek | 115 | 0.655 | 1.000 | 1.000 | 1.000 | 1.000 |
279
  | `bho_Deva` | Bhojpuri | 56 | 0.643 | 0.933 | 0.933 | 0.832 | 0.832 |
280
  | `fuv_Latn` | Nigerian Fulfulde | 108 | 0.405 | 0.681 | 0.689 | 0.475 | 0.475 |
281
+ | `cni_Latn` | Asháninka | 52 | 0.708 | 0.963 | 0.963 | 0.000 | 0.000 |
282
+ | `qvn_Latn` | North Junín Quechua | 57 | 0.219 | 0.460 | 0.460 | 0.000 | 0.000 |
283
  | `ind_Latn` | Indonesian | 60 | 0.674 | 0.903 | 0.903 | 0.565 | 0.565 |
284
  | `maz_Latn` | Central Mazahua | 59 | 0.742 | 0.939 | 0.939 | 0.000 | 0.000 |
285
  | `rmn_Latn` | Balkan Romani | 116 | 0.667 | 0.857 | 0.857 | 0.000 | 0.000 |
286
 
287
  ### Leaderboard: test-common
288
 
289
+ MillID's F1 against the others', language by language: against GlotLID restricted to MillID's labels, MillID is better on **6** languages, worse on **142** and within 0.01 on 65; against fastText restricted to MillID's labels, MillID is better on **93** languages, worse on **47** and within 0.01 on 73. The full table, with precision and recall, is `reports/test-common/leaderboard.csv`.
 
290
 
291
  The 20 languages with the most passages:
292
 
 
308
  | `tel_Telu` | Telugu | 555 | 1.000 | 0.998 | 0.998 | 0.999 | 0.999 |
309
  | `rus_Cyrl` | Russian | 528 | 0.929 | 0.985 | 0.985 | 0.927 | 0.927 |
310
  | `ory_Orya` | Odia (individual language) | 511 | 0.996 | 0.997 | 1.000 | 0.996 | 0.996 |
311
+ | `sot_Latn` | Southern Sotho | 491 | 0.967 | 0.977 | 0.978 | 0.856 | 0.856 |
312
  | `nso_Latn` | Northern Sotho | 479 | 0.961 | 0.970 | 0.970 | 0.967 | 0.967 |
313
  | `asm_Beng` | Assamese | 467 | 0.995 | 0.999 | 0.999 | 0.997 | 0.997 |
314
  | `ben_Beng` | Bangla | 466 | 0.987 | 0.997 | 0.997 | 0.989 | 0.989 |
 
325
  | `arb_Arab` | Arabic (Modern Standard) | 745 | 0.905 | 0.886 | 0.886 | 0.957 | 0.954 |
326
  | `knc_Latn` | Central Kanuri | 293 | 0.959 | 0.952 | 0.952 | 0.981 | 0.981 |
327
  | `por_Latn` | Portuguese | 657 | 0.963 | 0.954 | 0.956 | 0.950 | 0.949 |
328
+ | `san_Deva` | Sanskrit | 239 | 0.996 | 0.990 | 0.990 | 0.971 | 0.971 |
 
329
  | `bug_Latn` | Buginese | 78 | 0.987 | 0.981 | 0.981 | 0.938 | 0.938 |
330
+ | `kam_Latn` | Kamba | 272 | 0.980 | 0.972 | 0.974 | 0.863 | 0.863 |
331
  | `mlt_Latn` | Maltese | 291 | 0.997 | 0.993 | 0.993 | 0.961 | 0.961 |
332
  | `tel_Telu` | Telugu | 555 | 1.000 | 0.998 | 0.998 | 0.999 | 0.999 |
333
  | `sin_Sinh` | Sinhala | 232 | 1.000 | 1.000 | 1.000 | 0.998 | 0.998 |
 
338
  | Label | Language | Passages | MillID F1 | GlotLID F1 | GlotLID F1, restricted | fastText F1 | fastText F1, restricted |
339
  |---|---|---|---|---|---|---|---|
340
  | `knc_Arab` | Central Kanuri | 68 | 0.562 | 0.857 | 0.860 | 0.029 | 0.029 |
341
+ | `tzm_Tfng` | Central Atlas Tamazight | 157 | 0.645 | 0.934 | 0.934 | 0.751 | 0.751 |
342
  | `hrv_Latn` | Croatian | 168 | 0.717 | 0.994 | 0.994 | 0.856 | 0.856 |
343
  | `bos_Latn` | Bosnian | 198 | 0.727 | 1.000 | 1.000 | 0.897 | 0.897 |
344
  | `zsm_Latn` | Standard Malay | 244 | 0.773 | 0.998 | 0.998 | 0.908 | 0.908 |
345
  | `taq_Tfng` | Tamasheq | 104 | 0.613 | 0.805 | 0.805 | 0.000 | 0.000 |
346
  | `ind_Latn` | Indonesian | 335 | 0.807 | 0.991 | 0.991 | 0.887 | 0.887 |
347
  | `kin_Latn` | Kinyarwanda | 210 | 0.818 | 0.981 | 0.981 | 0.854 | 0.854 |
348
+ | `run_Latn` | Rundi | 291 | 0.843 | 0.985 | 0.985 | 0.855 | 0.855 |
349
  | `nob_Latn` | Norwegian Bokmål | 265 | 0.820 | 0.961 | 0.959 | 0.894 | 0.894 |
350
  | `fuv_Latn` | Nigerian Fulfulde | 164 | 0.797 | 0.877 | 0.892 | 0.859 | 0.859 |
351
  | `mag_Deva` | Magahi | 97 | 0.905 | 1.000 | 1.000 | 0.772 | 0.772 |
352
+ | `vec_Latn` | Venetian | 49 | 0.766 | 0.861 | 0.861 | 0.620 | 0.620 |
353
  | `cjk_Latn` | Chokwe | 24 | 0.909 | 1.000 | 1.000 | 0.807 | 0.807 |
354
  | `taq_Latn` | Tamasheq | 106 | 0.909 | 1.000 | 1.000 | 0.838 | 0.838 |
355
 
 
357
 
358
  ## Training
359
 
360
+ 1,944,095 passages; the best validation macro F1 at step 56,000, after 3.4 hours on 1 GPU. Languages sampled in proportion to n^0.3; 25% of passages cut to a 20 to 80 character snippet; batch 512, AdamW at 0.0003, cosine schedule, label smoothing 0.1.
361
+
362
+ The training data is the full MillID build. The public [baobabtech/millid-dataset](https://huggingface.co/datasets/baobabtech/millid-dataset) is that build without Save the Children's and Nal'ibali's text (1.6% of its passages), which is not ours to redistribute. Its splits are the same, so its `test` split is the one scored above, without those passages.
363
+
364
+ ## Licence
365
+
366
+ The weights and the code are Apache-2.0. The training data carries its own licence row by row (see the [dataset card](https://huggingface.co/datasets/baobabtech/millid-dataset#licensing)), including text licensed for non-commercial use only (CC BY-NC, CC BY-NC-SA).
millid.py CHANGED
@@ -654,7 +654,7 @@ class Sampler:
654
  A language is drawn with probability proportional to n^alpha, n being its passage count:
655
  alpha 1 is the data as it is, alpha 0 every language equally; 0.3 in between. Then a
656
  passage of it is drawn, and sometimes cut short, because a short snippet is where
657
- language identification is hardest and what a document pipeline sees in table cells.
658
  """
659
 
660
  def __init__(self, texts: list[str], label_ids: np.ndarray, alpha: float, short_p: float, seed: int):
 
654
  A language is drawn with probability proportional to n^alpha, n being its passage count:
655
  alpha 1 is the data as it is, alpha 0 every language equally; 0.3 in between. Then a
656
  passage of it is drawn, and sometimes cut short, because a short snippet is where
657
+ language identification is hardest and what a page holds in headers and table cells.
658
  """
659
 
660
  def __init__(self, texts: list[str], label_ids: np.ndarray, alpha: float, short_p: float, seed: int):