fzengin18 commited on
Commit
85d2709
·
verified ·
1 Parent(s): ed3d9aa

Update model card

Browse files
Files changed (1) hide show
  1. README.md +220 -214
README.md CHANGED
@@ -3,7 +3,7 @@ license: apache-2.0
3
  language:
4
  - tr
5
  - en
6
- library_name: transformers
7
  tags:
8
  - tokenizer
9
  - tokenizers
@@ -11,14 +11,12 @@ tags:
11
  - turkish
12
  - english
13
  - bilingual
14
- datasets:
15
- - wikimedia/wikipedia
16
- - Helsinki-NLP/opus-100
17
  ---
18
 
19
  # Multrenizer
20
 
21
- Multrenizer is a bilingual English-Turkish Unigram tokenizer built from scratch for Turkish morphology, Turkish-aware casing, and mixed TR-EN text.
22
 
23
  ## Links
24
 
@@ -27,139 +25,32 @@ Multrenizer is a bilingual English-Turkish Unigram tokenizer built from scratch
27
 
28
  ## Why Multrenizer?
29
 
30
- Standard multilingual tokenizers routinely break Turkish at poor boundaries, waste context on agglutinative suffixes, and mishandle the Turkish dotted/dotless `I/i` rule. Multrenizer is designed to fix those failure modes without discarding punctuation and chat-critical symbols.
31
 
32
- Core design goals:
33
 
34
- - Turkish-aware normalization: hardcoded `İ -> i` and `I -> ı` before Unicode normalization
35
- - Apostrophe preservation: forms like `feature'ı`, `merge'lemek`, `İstanbul'da`, and `can't` keep `'` as a real token
36
- - Compact vocabulary budget: `~26K` target vocab for a Turkish-first bilingual tokenizer
37
- - Fixed utility budget: dedicated punctuation, emoji, math, currency, and chat symbols
38
- - Code-switching support: trained on mixed TR-EN text instead of treating it as noise
39
 
40
- ## Benchmark Results
41
-
42
- Evaluated on `5,000` Turkish sentences, `5,000` English sentences, and `500` code-switching sentences from the prepared corpus against 5 reference tokenizers.
43
-
44
- Notes:
45
-
46
- - Multrenizer's shipped local artifact is auto-read from `multrenizer-tokenizer/tokenizer.json`; the current released artifact is `25,917` tokens.
47
- - Example token strings for byte-level models are shown as raw tokenizer pieces. Metrics are based on exact token counts, not prettified decoding.
48
-
49
- ### Compared Tokenizers
50
-
51
- | Tokenizer | Source | Vocab Size | Algorithm | Type |
52
- |---|---|---:|---|---|
53
- | **Multrenizer** | This project | **25,917** | Unigram | Bilingual EN-TR, purpose-built |
54
- | **Kumru-2B** | [vngrs-ai/Kumru-2B](https://huggingface.co/vngrs-ai/Kumru-2B) | 50,176 | BPE | Turkish LLM (VNGRS, Sep 2025, Mistral-based) |
55
- | **Turkcell-7B** | [TURKCELL/Turkcell-LLM-7b-v1](https://huggingface.co/TURKCELL/Turkcell-LLM-7b-v1) | 48,351 | BPE | Turkish LLM (Turkcell, Apr 2024, Mistral-based) |
56
- | **GPT-2** | [openai-community/gpt2](https://huggingface.co/openai-community/gpt2) | 50,257 | BPE | English-centric baseline (OpenAI, 2019) |
57
- | **Qwen-3** | [Qwen/Qwen3-0.6B](https://huggingface.co/Qwen/Qwen3-0.6B) | 151,643 | BPE | Multilingual (Alibaba, 2025) |
58
- | **Mistral-3.1** | [mistralai/Mistral-Small-3.1-24B-Base-2503](https://huggingface.co/mistralai/Mistral-Small-3.1-24B-Base-2503) | 131,072 | BPE/SP | Multilingual (Mistral AI, Mar 2025) |
59
-
60
- ### Fertility, Compression, and Token Count
61
-
62
- Lower fertility means fewer tokens per word. Higher compression means more characters carried per token.
63
 
64
- | Metric | Multrenizer | Kumru-2B | Turkcell-7B | GPT-2 | Qwen-3 | Mistral-3.1 |
65
- |---|:---:|:---:|:---:|:---:|:---:|:---:|
66
- | Vocab Size | **25,917** | 50,176 | 48,351 | 50,257 | 151,643 | 131,072 |
67
- | **TR Fertility** | **1.627** | 1.649 | 1.917 | 3.785 | 2.616 | 2.384 |
68
- | EN Fertility | 1.525 | 2.151 | 1.555 | **1.314** | 1.372 | 1.381 |
69
- | **CS Fertility** | **1.756** | 1.923 | 1.832 | 3.475 | 2.445 | 2.479 |
70
- | **TR Compression** | **4.783** | 4.719 | 4.060 | 2.056 | 2.976 | 3.265 |
71
- | EN Compression | 4.148 | 2.942 | 4.068 | **4.816** | 4.610 | 4.580 |
72
- | **TR Total Tokens (5K)** | **130,844** | 132,637 | 154,166 | 304,345 | 210,334 | 191,682 |
73
- | EN Total Tokens (5K) | 157,027 | 221,420 | 160,121 | **135,235** | 141,275 | 142,196 |
74
- | **CS Total Tokens (500)** | **5,525** | 6,050 | 5,762 | 10,933 | 7,693 | 7,799 |
75
-
76
- Current position:
77
-
78
- - Best Turkish efficiency in this comparison set: TR fertility, TR compression, TR total tokens
79
- - Best code-switching efficiency in this comparison set: CS fertility and CS total tokens
80
- - Competitive English coverage for a Turkish-first tokenizer, but not better than English-native GPT-2 on EN-only token count
81
- - Only tokenizer here that passes Turkish `I/i` normalization correctly
82
-
83
- ### Morphological Splitting
84
-
85
- Total tokens needed to represent 10 difficult Turkish words:
86
-
87
- | Tokenizer | Vocab Size | Total Tokens | Avg per Word |
88
- |---|---:|:---:|:---:|
89
- | **Multrenizer** | **25,917** | **32** | **3.2** |
90
- | Kumru-2B | 50,176 | 35 | 3.5 |
91
- | Turkcell-7B | 48,351 | 38 | 3.8 |
92
- | Mistral-3.1 | 131,072 | 71 | 7.1 |
93
- | Qwen-3 | 151,643 | 73 | 7.3 |
94
- | GPT-2 | 50,257 | 105 | 10.5 |
95
-
96
- Selected examples:
97
 
98
  ```text
99
- güzelleştirilmiş
100
- Multrenizer: güzel + leştirilmiş [2 tokens]
101
- Kumru-2B: güzel + leÅŁtirilmiÅŁ [2 tokens]
102
- Turkcell-7B: güzel + leştirilmiş [2 tokens]
103
- Qwen-3: g + üz + elle + ÅŁtir + ilmiÅŁ [5 tokens]
104
- Mistral-3.1: g + üz + elle + ÅŁtir + ilmiÅŁ [5 tokens]
105
- GPT-2: g + ü + z + elle + ÅŁ + t + ir + il + mi + ÅŁ [10 tokens]
106
-
107
- İstanbul'da
108
- Multrenizer: istanbul + ' + da [3 tokens]
109
- Kumru-2B: İstanbul + ' + da [3 tokens]
110
- Turkcell-7B: İstanbul + ' + da [3 tokens]
111
- Qwen-3: İ + stanbul + 'd + a [4 tokens]
112
- Mistral-3.1: İ + stanbul + 'd + a [4 tokens]
113
- GPT-2: Ä + ° + stanbul + 'd + a [5 tokens]
114
-
115
- Afyonkarahisarlılaştıramadıklarımızdan
116
- Multrenizer: afyonkarahisar + lı + laştı + r + ama + dıkları + mızda + n [8 tokens]
117
- Kumru-2B: Af + yonkarahisar + lı + laÅŁtır + ama + dık + larımız + dan [8 tokens]
118
- Turkcell-7B: Afyon + kar + ah + is + arlı + laştır + a + madık + larımızdan [9 tokens]
119
- Qwen-3: Af + yon + kar + ah + is + ar + lı + la + ÅŁt + ı + ram + ad + ıkl + ar + ımız + dan [16 tokens]
120
- Mistral-3.1: Af + yon + kar + ah + is + arl + ı + laÅŁt + ı + ram + ad + ıkları + m + ı + zd + an [16 tokens]
121
- GPT-2: Af + yon + kar + ah + is + arl + ı + la + ÅŁ + t + ı + ram + ad + ı + k + lar + ı + m + ı + z + dan [21 tokens]
122
  ```
123
 
124
- ### Turkish I/i Normalization
125
-
126
- This is the critical locale-sensitive test:
127
-
128
- - `İ` must lowercase to `i`
129
- - `I` must lowercase to `ı`
130
-
131
- | Input | Expected | Multrenizer | Kumru-2B | Turkcell-7B | GPT-2 | Qwen-3 | Mistral-3.1 |
132
- |---|---|:---:|:---:|:---:|:---:|:---:|:---:|
133
- | İstanbul | istanbul | **OK** | FAIL | FAIL | FAIL | FAIL | FAIL |
134
- | IŞIK | ışık | **OK** | FAIL | FAIL | FAIL | FAIL | FAIL |
135
- | SIR | sır | **OK** | FAIL | FAIL | FAIL | FAIL | FAIL |
136
- | İNSAN | insan | **OK** | FAIL | FAIL | FAIL | FAIL | FAIL |
137
- | ISITMAK | ısıtmak | **OK** | FAIL | FAIL | FAIL | FAIL | FAIL |
138
- | **Score** | | **8/8** | **0/8** | **0/8** | **0/8** | **0/8** | **0/8** |
139
-
140
- Multrenizer is the only tokenizer in this comparison that handles Turkish casing correctly.
141
-
142
- ### Code-Switching
143
-
144
- ```text
145
- "Bu feature'ı implement ederken edge case'leri handle etmeyi unutmayalım."
146
-
147
- Multrenizer [15 tok] bu | feature | ' | ı | implement | ederken | edge | case | ' | leri | handle | etmeyi | unutmaya | lım | .
148
- Kumru-2B [20 tok] Bu | fe | ature | ' | ı | imp | lement | ederken | ed | ge | cas | e | ' | leri | hand | le | etmeyi | unutma | yalım | .
149
- Turkcell-7B [15 tok] Bu | feature | ' | ı | implement | ederken | edge | case | ' | leri | handle | etmeyi | unut | mayalım | .
150
- GPT-2 [24 tok] Bu | feature | ' | ı | implement | ed | er | ken | edge | case | ' | ler | i | handle | et | me | yi | un | ut | may | al | ı | m | .
151
- Qwen-3 [22 tok] Bu | feature | ' | ı | implement | ed | er | ken | edge | case | ' | leri | handle | et | m | ey | i | un | ut | may | alım | .
152
- Mistral-3.1 [20 tok] Bu | feature | 'ı | implement | eder | ken | edge | case | ' | leri | handle | et | me | yi | un | ut | may | al | ım | .
153
-
154
- "merge'lemek istediğim branch conflict veriyor."
155
-
156
- Multrenizer [ 8 tok] merge | ' | lemek | istediğim | branch | conflict | veriyor | .
157
- Kumru-2B [14 tok] mer | ge | ' | lemek | istediÄŁim | b | ran | ch | con | f | lic | t | veriyor | .
158
- Turkcell-7B [ 8 tok] merge | ' | lemek | istediğim | branch | conflict | veriyor | .
159
- GPT-2 [16 tok] mer | ge | ' | lem | ek | is | ted | i | ÄŁ | im | branch | conflict | ver | iy | or | .
160
- Qwen-3 [11 tok] merge | ' | lem | ek | istediÄŁ | im | branch | conflict | ver | iyor | .
161
- Mistral-3.1 [13 tok] merge | ' | le | mek | ist | edi | ÄŁ | im | branch | conflict | ver | iyor | .
162
- ```
163
 
164
  ## Quick Start
165
 
@@ -180,45 +71,39 @@ from tokenizers import Tokenizer
180
 
181
  tok = Tokenizer.from_file("multrenizer-tokenizer/tokenizer.json")
182
 
183
- encoded = tok.encode("İstanbul'da güzel bir gün")
184
  print(encoded.tokens)
185
- # ['<s>', 'istanbul', "'", 'da', 'güzel', 'bir', 'gün', '</s>']
186
 
187
- print(tok.normalizer.normalize_str("IŞIK"))
188
- # 'ışık'
 
189
  ```
190
 
191
  ### Load from Hugging Face
192
 
193
- ```python
194
- from tokenizers import Tokenizer
195
-
196
- tok = Tokenizer.from_pretrained("fzengin18/multrenizer")
197
-
198
- encoded = tok.encode("İstanbul'da güzel bir gün")
199
- print(encoded.tokens)
200
- # ['<s>', 'istanbul', "'", 'da', 'güzel', 'bir', 'gün', '</s>']
201
- ```
202
-
203
- If you use `transformers`, this also works:
204
-
205
  ```python
206
  from transformers import AutoTokenizer
207
 
208
  tok = AutoTokenizer.from_pretrained("fzengin18/multrenizer")
209
- print(tok.tokenize("İstanbul'da güzel bir gün"))
 
 
 
 
 
210
  ```
211
 
212
  ### Train from scratch
213
 
214
  ```bash
215
- # 1. Download and prepare corpus
216
- python prepare_data.py --size medium
217
 
218
  # 2. Train tokenizer
219
  python train_tokenizer.py --data-dir data/
220
 
221
- # 3. Optional: push tokenizer files to Hugging Face Hub
222
  python train_tokenizer.py --data-dir data/ \
223
  --repo-id fzengin18/multrenizer \
224
  --hf-token "$HF_TOKEN"
@@ -232,82 +117,200 @@ python benchmark.py --tr-lines 5000 --en-lines 5000
232
 
233
  ## Architecture
234
 
235
- ### Pipeline
236
 
237
- ```text
238
- Raw text
239
- -> Turkish I/i normalizer (Replace: İ->i, I->ı, i̇->i)
240
- -> Quote canonicalization (’ ʼ -> ')
241
- -> NFKC normalization
242
- -> Lowercase
243
- -> Strip whitespace
244
- -> Pre-tokenizer (whitespace + apostrophe + punctuation split)
245
- -> Unigram model (~26K target vocab)
246
- -> Post-processor (<s> ... </s>)
247
- ```
248
 
249
- ### Data Mix
250
 
251
- The released artifact is trained with the default file-based interleave in `train_tokenizer.py`, which approximates:
 
 
 
 
 
 
252
 
253
- | Stream | Share | Purpose |
254
  |---|---|---|
255
- | Turkish | ~60% | Core Turkish morphology |
256
- | English | ~30% | English coverage |
257
- | Code-switching | ~10% | TR-EN boundary handling |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
- Corpus collection is Turkish-forward, and code-switching examples are generated from OPUS parallel pairs during data preparation.
260
 
261
- Exact source configs used during corpus preparation:
262
 
263
- - `wikimedia/wikipedia` with `20231101.tr`
264
- - `wikimedia/wikipedia` with `20231101.en`
265
- - `Helsinki-NLP/opus-100` with `en-tr`
 
 
266
 
267
- The synthetic code-switching stream is generated locally from OPUS-100 parallel pairs, so it does not appear as a separate Hugging Face dataset entry.
268
 
269
- ### Vocabulary Budget
270
 
271
- Multrenizer is designed around a `26,000` target vocabulary, with a fixed budget reserved for always-preserved tokens:
272
 
273
- - `32` named special tokens
274
- - `512` reserved tokens
275
- - `292` utility tokens
276
- - up to `25,164` learned subword tokens
 
 
 
277
 
278
- Current shipped artifact: `25,917` total tokens.
279
 
280
- ### Special Tokens
281
 
282
- | Category | IDs | Tokens | Purpose |
283
- |---|---|---|---|
284
- | **Core** | 0-3 | `<unk>` `<s>` `</s>` `<pad>` | Basic tokenizer operation |
285
- | **Chat** | 4-8 | `<\|system\|>` `<\|user\|>` `<\|assistant\|>` `<\|end\|>` `<\|sep\|>` | Instruction tuning and chat models |
286
- | **Reasoning** | 9-12 | `<think>` `</think>` `<\|step\|>` `<\|reflection\|>` | Reasoning traces and self-check markers |
287
- | **Tool Use** | 13-16 | `<tool_call>` `</tool_call>` `<tool_response>` `</tool_response>` | Tool and function calling |
288
- | **Code/FIM** | 17-20 | `<\|code\|>` `<\|fim_prefix\|>` `<\|fim_middle\|>` `<\|fim_suffix\|>` | Code and fill-in-middle workflows |
289
- | **Bilingual** | 21-22 | `<\|tr\|>` `<\|en\|>` | Language tags |
290
- | **RAG** | 23-24 | `<\|context\|>` `<\|/context\|>` | Retrieval boundaries |
291
- | **Multi-modal** | 25-28 | `<\|image\|>` `<\|audio\|>` `<\|video\|>` `<\|file\|>` | Placeholder tokens |
292
- | **Structured** | 29-31 | `<\|json\|>` `<\|table\|>` `<\|cite\|>` | Structured output markers |
293
- | **Reserved** | 32-543 | `<\|reserved_0\|>` ... `<\|reserved_511\|>` | Future growth without retraining |
294
- | **Utility** | 544+ | Punctuation, emoji, math, currency, typography | Critical text symbols kept intact |
295
-
296
- ### Utility Tokens
297
-
298
- | Category | Count | Examples |
299
- |---|---:|---|
300
- | Punctuation | 31 | `. , ! ? ; : - ( ) [ ] { } / \ " ' ...` |
301
- | Currency & Business | 15 | `₺ $ £ ¥ % @ # &` |
302
- | Math & Science | 25 | × ÷ π α β γ` |
303
- | Arrows & Symbols | 15 | `→ ✓ ✗ © ® ™` |
304
- | Typography | 10 | » ‚` |
305
- | Emoji (faces) | 70 | `😀 😂 🤣 😊 😍 🤔 😭 😡 💀 🤖` |
306
- | Emoji (hands) | 28 | `👋 👍 👎 👏 🙏 💪 ✌️` |
307
- | Emoji (hearts) | 18 | `❤️ 💛 💚 💙 💜 🖤 💔` |
308
- | Emoji (symbols) | 36 | `🔥 ⚠️ 💯 🚀` |
309
- | Emoji (objects) | 36 | `💻 📱 🎯 🏆 📊 ☕ 🔗 💰` |
310
- | Emoji (flags) | 8 | `🇹🇷 🇺🇸 🇬🇧 🇩🇪 🇫🇷 🇪🇸 🇮🇹 🇯🇵` |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
 
312
  ## Project Structure
313
 
@@ -317,11 +320,14 @@ multrenizer/
317
  │ ├── tokenizer.json
318
  │ ├── tokenizer_config.json
319
  │ └── special_tokens_map.json
320
- ├── prepare_data.py # Corpus download and preparation
321
  ├── train_tokenizer.py # Tokenizer training script
322
- ├── benchmark.py # Benchmark against 5 reference tokenizers
323
  ├── benchmark_results.json # Full benchmark output
324
- ├── tests/ # Regression tests for tokenizer behavior
 
 
 
325
  ├── requirements.txt
326
  └── pyproject.toml
327
  ```
 
3
  language:
4
  - tr
5
  - en
6
+ library_name: tokenizers
7
  tags:
8
  - tokenizer
9
  - tokenizers
 
11
  - turkish
12
  - english
13
  - bilingual
14
+ - sentencepiece
 
 
15
  ---
16
 
17
  # Multrenizer
18
 
19
+ A bilingual Turkish-English Unigram tokenizer with **lossless round-trip decode**, **case preservation**, and a **correct utility-token taxonomy** — punctuation, currency, math symbols, and emoji are kept atomic in the vocabulary AND preserved through `decode(skip_special_tokens=True)`.
20
 
21
  ## Links
22
 
 
25
 
26
  ## Why Multrenizer?
27
 
28
+ Standard multilingual tokenizers commonly fail on Turkish in three ways: (1) they break agglutinative morphemes at suboptimal boundaries, (2) they fold case (`İstanbul` → `istanbul`) so a downstream model can never produce proper nouns again, and (3) they misclassify punctuation and emoji as "special tokens", which means `decode(skip_special_tokens=True)` silently strips them and the model output ends up missing commas, apostrophes, and 😀.
29
 
30
+ Multrenizer is built to fix all three without giving up Turkish efficiency.
31
 
32
+ Core design:
 
 
 
 
33
 
34
+ - **Case preserving.** `İstanbul` stays `İstanbul`, `TÜRKİYE` stays `TÜRKİYE`. The model learns to produce real proper nouns, not lowercased shadows.
35
+ - **Lossless round-trip.** Metaspace pipeline with `prepend_scheme="first"` plus `AddedToken(normalized=True)` — `decode(encode(x)) == x` for case, punctuation, currency, math, and compound emoji.
36
+ - **Correct utility taxonomy.** Only model-control symbols (`<s>`, `<|user|>`, `<|reserved_*|>`, etc.) are flagged `special=True`. Punctuation, currency, math symbols, and emoji are atomic vocab entries with `special=False` they survive `skip_special_tokens=True`.
37
+ - **Compact vocabulary.** `~26K` total budget, Turkish-first morpheme coverage.
38
+ - **Code-switching aware.** Trained on a TR/EN/CS interleave; mixed text like `merge'lemek istediğim branch` segments cleanly.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ ## Pipeline
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  ```text
43
+ Raw text
44
+ -> Quote canonicalization (’ ‘ ʼ ' -> ')
45
+ -> NFKC normalization
46
+ -> Strip whitespace
47
+ -> Pre-tokenizer: Metaspace(replacement="▁", prepend_scheme="first", split=True)
48
+ -> Unigram model (~26K vocab)
49
+ -> Decoder: Metaspace(prepend_scheme="first") [mirror of pre-tokenizer]
50
+ -> Post-processor (<s> ... </s>)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  ```
52
 
53
+ No lowercase. No locale-specific I/i replacement (it would be a workaround for a bug Python introduces only when you call `.lower()`, which we don't).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  ## Quick Start
56
 
 
71
 
72
  tok = Tokenizer.from_file("multrenizer-tokenizer/tokenizer.json")
73
 
74
+ encoded = tok.encode("İstanbul'da %50 indirim 😀")
75
  print(encoded.tokens)
76
+ # ['<s>', "▁İstanbul'da", '▁%', '50', '▁indirim', '', '😀', '</s>']
77
 
78
+ # Round-trip is lossless — case, punctuation, emoji all survive.
79
+ print(tok.decode(encoded.ids))
80
+ # "İstanbul'da %50 indirim 😀"
81
  ```
82
 
83
  ### Load from Hugging Face
84
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  ```python
86
  from transformers import AutoTokenizer
87
 
88
  tok = AutoTokenizer.from_pretrained("fzengin18/multrenizer")
89
+ ids = tok.encode("Türkiye'de %50 indirim, 100₺ ❤️", add_special_tokens=False)
90
+
91
+ # `skip_special_tokens=True` removes ONLY <...>-formatted control tokens.
92
+ # Punctuation, currency, and emoji are preserved.
93
+ print(tok.decode(ids, skip_special_tokens=True))
94
+ # "Türkiye'de %50 indirim, 100₺ ❤️"
95
  ```
96
 
97
  ### Train from scratch
98
 
99
  ```bash
100
+ # 1. Download and prepare corpus (Wikipedia TR+EN, OPUS-100, synthetic CS)
101
+ python prepare_data.py --size large
102
 
103
  # 2. Train tokenizer
104
  python train_tokenizer.py --data-dir data/
105
 
106
+ # 3. Optional: push to Hugging Face Hub
107
  python train_tokenizer.py --data-dir data/ \
108
  --repo-id fzengin18/multrenizer \
109
  --hf-token "$HF_TOKEN"
 
117
 
118
  ## Architecture
119
 
120
+ ### Token Taxonomy
121
 
122
+ | Tier | Count | `special` flag | Behavior on `skip_special_tokens=True` |
123
+ |---|---:|:---:|---|
124
+ | Named specials (`<s>`, `<\|user\|>`, `<think>`, ...) | 32 | `true` | **Removed** |
125
+ | Reserved (`<\|reserved_0\|>` ... `<\|reserved_511\|>`) | 512 | `true` | **Removed** |
126
+ | Utility — punctuation/symbols (`,`, `!`, `'`, `%`, `$`, `→`) | 169 | `false` | **Preserved** |
127
+ | Utility — emoji (corpus-learned + curated fallback) | ~240 | `false` | **Preserved** |
128
+ | Learned subwords (Unigram) | ~24,700 | `false` | **Preserved** |
 
 
 
 
129
 
130
+ This is the central correctness fix vs the prior artifact, where 292 utility tokens were incorrectly flagged `special=true` and erased on every decode.
131
 
132
+ ### Vocabulary Budget
133
+
134
+ Target: `26,000`. Actual shipped artifact: `25,679` (Unigram convergence undershoot is normal).
135
+
136
+ The model trained downstream should set its `vocab_size` to **the tokenizer's actual size**, not the 26K target.
137
+
138
+ ### Special Tokens
139
 
140
+ | Category | IDs | Tokens |
141
  |---|---|---|
142
+ | **Core** | 0-3 | `<unk>` `<s>` `</s>` `<pad>` |
143
+ | **Chat** | 4-8 | `<\|system\|>` `<\|user\|>` `<\|assistant\|>` `<\|end\|>` `<\|sep\|>` |
144
+ | **Reasoning** | 9-12 | `<think>` `</think>` `<\|step\|>` `<\|reflection\|>` |
145
+ | **Tool Use** | 13-16 | `<tool_call>` `</tool_call>` `<tool_response>` `</tool_response>` |
146
+ | **Code/FIM** | 17-20 | `<\|code\|>` `<\|fim_prefix\|>` `<\|fim_middle\|>` `<\|fim_suffix\|>` |
147
+ | **Language** | 21-22 | `<\|tr\|>` `<\|en\|>` |
148
+ | **RAG** | 23-24 | `<\|context\|>` `<\|/context\|>` |
149
+ | **Multi-modal** | 25-28 | `<\|image\|>` `<\|audio\|>` `<\|video\|>` `<\|file\|>` |
150
+ | **Structured** | 29-31 | `<\|json\|>` `<\|table\|>` `<\|cite\|>` |
151
+ | **Reserved** | 32-543 | `<\|reserved_0\|>` ... `<\|reserved_511\|>` |
152
+
153
+ Look these up at runtime with `tokenizer.token_to_id("<...>")` — do not hardcode IDs in downstream code.
154
+
155
+ ### Utility Tokens (special=False, atomic)
156
+
157
+ | Category | Approx. count | Examples |
158
+ |---|---:|---|
159
+ | Punctuation | 38 | `. , ! ? ; : - ( ) [ ] { } / \ " ' ... – — … ‽ ‼ ⁇ ¿ ¡` |
160
+ | Currency & business | 23 | `₺ $ € £ ¥ ₹ ₽ ¢ ฿ ₪ ₸ ₣ ₮ ₩ % ‰ ° § ¶ № @ # &` |
161
+ | Math & science | 33 | `± × ÷ ≠ ≤ ≥ ≈ ∞ √ ∑ ∫ ∂ Δ π α β γ δ ε θ λ μ σ φ ω ∀ ∃ ∈ ⊂ ⊆ ∪ ∩ ⇔ ↦ ∝ ∇ ∮ ∧ ∨ ¬ Α Β Γ Σ Φ Ω` |
162
+ | Programming digraphs | 11 | `=> != == <= >= -> :: ** // && \|\|` |
163
+ | Arrows & symbols | 15 | `→ ← ↑ ↓ ↔ ⇒ • · ★ ☆ ✓ ✗ © ® ™` |
164
+ | Box drawing & UI | 17 | `─ │ ┌ ┐ └ ┘ ├ ┤ ┬ ┴ ┼ ▪ ▫ ◆ ▶ ◀ ▼ ▲` |
165
+ | Typography | 10 | `« » " " ' ' ‹ › „ ‚` |
166
+ | Emoji — faces | 70 | `😀 😂 🤣 😊 😍 🤔 😭 😡 💀 🤖` |
167
+ | Emoji — hands | 28 | `👋 👍 👎 👏 🙏 💪 ✊ ✌️` |
168
+ | Emoji — hearts | 18 | `❤️ 💛 💚 💙 💜 🖤 💔` |
169
+ | Emoji — symbols | 36 | `🔥 ✨ ⭐ ✅ ❌ ⚠️ 💯 🚀` |
170
+ | Emoji — objects | 36 | `💻 📱 🎯 🏆 📊 ☕ 🔗 💰` |
171
+ | Emoji — nature & common | 17 | `🌍 🎉 🎁 ⏰ 📅 📌 🚨 🛒 💳 📞 📧 🏻🏼🏽🏾🏿` |
172
+ | Emoji — flags | 26 | `🇹🇷 🇺🇸 🇬🇧 🇩🇪 🇫🇷 🇨🇳 🇰🇷 🇮🇳 🇸🇦 🇮🇱 🇷🇺 🇧🇷 🇨🇦 🇲🇽 🇦🇺 🇳🇱` |
173
+ | Emoji — compounds (ZWJ/VS16 sequences) | 12 | `❤️ 👨‍💻 👩‍💻 🏃‍♂️ 🏃‍♀️ 👨‍👩‍👧 👨‍🏫 👩‍🏫 👍🏻–👍🏿` |
174
+
175
+ ZWJ (`U+200D`) and VS16 (`U+FE0F`) are NOT added as standalone tokens; they exist only inside whole compound-emoji sequences.
176
 
177
+ ### Data Mix
178
 
179
+ The released artifact is trained on the corpus produced by `prepare_data.py --size large`:
180
 
181
+ | Stream | Approx. lines | Share | Source |
182
+ |---|---:|:---:|---|
183
+ | Turkish | 1.30M | ~59% | Wikipedia TR streaming |
184
+ | English | 0.71M | ~32% | Wikipedia EN streaming |
185
+ | Code-switching | 0.20M | ~9% | OPUS-100 EN-TR pairs + synthetic templates |
186
 
187
+ An additional 5K synthetic emoji-rich CS lines are appended to ensure curated emojis make it into the learned vocabulary instead of the post-train fallback list.
188
 
189
+ ## Correctness Tests
190
 
191
+ The repo ships a pytest suite (`tests/test_tokenizer_correctness.py`) covering:
192
 
193
+ 1. **Round-trip lossless decode** — `decode(encode(x)) == normalize(x)` over Turkish, English, and code-switching samples.
194
+ 2. **`skip_special_tokens=True` preserves utility** — punctuation, currency, emoji must survive; only `<...>`-formatted control tokens are removed.
195
+ 3. **Compound emoji round-trip** VS16, ZWJ, regional-indicator pairs decode back to the original sequence.
196
+ 4. **Turkish morpheme integrity** — words like `şehirdir`, `kitabımı`, `evlerinden` decode without internal whitespace.
197
+ 5. **UNK rate < 0.5%** on a bilingual bench (line-by-line).
198
+ 6. **Special token ID stability** — IDs 0-7 (`<unk>`, `<s>`, `</s>`, `<pad>`, `<|system|>`, `<|user|>`, `<|assistant|>`, `<|end|>`).
199
+ 7. **Case preservation** — `İstanbul`, `TÜRKİYE`, `ABD`, `AKP` all round-trip without folding.
200
 
201
+ Current artifact: **60/60 PASS**.
202
 
203
+ ## Benchmark Results
204
 
205
+ Evaluated on `5,000` Turkish sentences, `5,000` English sentences, and `500` code-switching sentences from the prepared corpus, against five reference tokenizers.
206
+
207
+ ### Compared Tokenizers
208
+
209
+ | Tokenizer | Source | Vocab Size | Algorithm | Type |
210
+ |---|---|---:|---|---|
211
+ | **Multrenizer** | This project | **25,679** | Unigram | Bilingual EN-TR, purpose-built |
212
+ | **Kumru-2B** | [vngrs-ai/Kumru-2B](https://huggingface.co/vngrs-ai/Kumru-2B) | 50,176 | BPE | Turkish LLM (VNGRS, Sep 2025, Mistral-based) |
213
+ | **Turkcell-7B** | [TURKCELL/Turkcell-LLM-7b-v1](https://huggingface.co/TURKCELL/Turkcell-LLM-7b-v1) | 48,351 | BPE | Turkish LLM (Turkcell, Apr 2024, Mistral-based) |
214
+ | **GPT-2** | [openai-community/gpt2](https://huggingface.co/openai-community/gpt2) | 50,257 | BPE | English-centric baseline (OpenAI, 2019) |
215
+ | **Qwen-3** | [Qwen/Qwen3-0.6B](https://huggingface.co/Qwen/Qwen3-0.6B) | 151,643 | BPE | Multilingual (Alibaba, 2025) |
216
+ | **Mistral-3.1** | [mistralai/Mistral-Small-3.1-24B-Base-2503](https://huggingface.co/mistralai/Mistral-Small-3.1-24B-Base-2503) | 131,072 | BPE/SP | Multilingual (Mistral AI, Mar 2025) |
217
+
218
+ ### Fertility, Compression, and Token Count
219
+
220
+ Lower fertility = fewer tokens per word. Higher compression = more characters per token.
221
+
222
+ | Metric | Multrenizer | Kumru-2B | Turkcell-7B | GPT-2 | Qwen-3 | Mistral-3.1 |
223
+ |---|:---:|:---:|:---:|:---:|:---:|:---:|
224
+ | Vocab Size | **25,679** | 50,176 | 48,351 | 50,257 | 151,643 | 131,072 |
225
+ | **TR Fertility** | **1.63** | 1.65 | 1.92 | 3.79 | 2.62 | 2.38 |
226
+ | EN Fertility | 1.51 | 2.15 | 1.55 | **1.31** | 1.37 | 1.38 |
227
+ | **TR Compression** | **4.79** | 4.72 | 4.06 | 2.06 | 2.98 | 3.27 |
228
+ | EN Compression | 4.18 | 2.94 | 4.07 | **4.82** | 4.61 | 4.58 |
229
+ | **TR Total Tokens (5K sent)** | **130,725** | 132,637 | 154,166 | 304,345 | 210,334 | 191,682 |
230
+ | EN Total Tokens (5K sent) | 155,848 | 221,420 | 160,121 | **135,235** | 141,275 | 142,196 |
231
+ | Round-trip Lossless | 11/11 | 11/11 | 11/11 | 11/11 | 11/11 | 11/11 |
232
+
233
+ Headline:
234
+
235
+ - **Best Turkish efficiency** in this set on every TR metric (fertility, compression, total tokens), with the smallest vocabulary by a wide margin.
236
+ - **Competitive English** — second only to GPT-2 on EN total tokens, and that's an English-native baseline trained on far more English text.
237
+ - **Round-trip lossless on all 11 cases.** This is a minimum-correctness bar; modern BPE tokenizers also pass it. The previous Multrenizer artifact did **not** (it dropped utility tokens via `skip_special_tokens=True`); fixing that is one of the main reasons this version exists.
238
+
239
+ ### Morphological Splitting
240
+
241
+ Total tokens needed to represent ten difficult Turkish words:
242
+
243
+ | Tokenizer | Vocab Size | Total Tokens | Avg per Word |
244
+ |---|---:|:---:|:---:|
245
+ | Kumru-2B | 50,176 | **35** | **3.5** |
246
+ | **Multrenizer** | **25,679** | **36** | **3.6** |
247
+ | Turkcell-7B | 48,351 | 38 | 3.8 |
248
+ | Mistral-3.1 | 131,072 | 71 | 7.1 |
249
+ | Qwen-3 | 151,643 | 73 | 7.3 |
250
+ | GPT-2 | 50,257 | 105 | 10.5 |
251
+
252
+ A few selected words:
253
+
254
+ ```text
255
+ İstanbul'da
256
+ Multrenizer [1 tok] İstanbul'da ← single token, atomic
257
+ Kumru-2B [3 tok] İstanbul + ' + da
258
+ Turkcell-7B [3 tok] İstanbul + ' + da
259
+ GPT-2 [5 tok] Ä + ° + stanbul + 'd + a
260
+ Qwen-3 [4 tok] İ + stanbul + 'd + a
261
+ Mistral-3.1 [4 tok] İ + stanbul + 'd + a
262
+
263
+ Afyonkarahisarlılaştıramadıklarımızdan
264
+ Multrenizer [8 tok] Afyonkarahisar + lı + laştı + ram + a + dıkları + mızda + n
265
+ Kumru-2B [8 tok] Af + yonkarahisar + lı + laştır + ama + dık + larımız + dan
266
+ Turkcell-7B [9 tok] Afyon + kar + ah + is + arlı + laştır + a + madık + larımızdan
267
+ Qwen-3 [16 tok] (16-piece byte-level fragmentation)
268
+ Mistral-3.1 [16 tok] (16-piece byte-level fragmentation)
269
+ GPT-2 [21 tok] (21-piece byte-level fragmentation)
270
+
271
+ düşünülebileceğini
272
+ Multrenizer [2 tok] düşünül + ebileceğini
273
+ Kumru-2B [3 tok] düşünül + ebil + eceğini
274
+ Turkcell-7B [4 tok] düş + ünü + le + bileceğini
275
+ GPT-2 [12 tok] d + ü + ş + ü + n + ü + le + b + ile + ce + ğ + ini
276
+ ```
277
+
278
+ ### Round-Trip Lossless Decode
279
+
280
+ 11 representative inputs covering case preservation, apostrophe handling, currency, math/measurement symbols, VS16 / ZWJ / regional-indicator emoji, and mixed-content sentences. Multrenizer passes 11/11. All compared modern tokenizers also pass — the value here is being on the right side of the bar (the previous Multrenizer artifact wasn't), not winning a race.
281
+
282
+ ```text
283
+ Input Multrenizer ...
284
+ İstanbul'da güzel bir gün geçirdim. OK
285
+ TÜRKİYE büyük harflerle yazılır. OK
286
+ It's a beautiful day, isn't it? OK
287
+ Fiyat: 100₺ ve $50, €30 ve £25. OK
288
+ Türkiye'de %50 indirim ve %25 KDV. OK
289
+ Hava 22°C, yağış %30, basınç 1013hPa. OK
290
+ ❤️ ile 💔 farklı duygular ifade eder. OK
291
+ 🇹🇷 ve 🇺🇸 bayrakları yan yana asıldı. OK
292
+ Geliştirici 👨‍💻 olarak çalışıyorum. OK
293
+ Selam 👋! Türkiye'de %50 indirim 100₺. OK
294
+ ```
295
+
296
+ ### Code-Switching Tokenization
297
+
298
+ ```text
299
+ "Bu feature'ı implement ederken edge case'leri handle etmeyi unutmayalım."
300
+
301
+ Multrenizer [12 tok] Bu | feature | 'ı | implement | ederken | edge | case | 'leri | handle | etmeyi | unutmay | alım.
302
+ Kumru-2B [20 tok] Bu | fe | ature | ' | ı | imp | lement | ederken | ed | ge | cas | e | ' | leri | hand | le | etmeyi | unutma | yalım | .
303
+ Turkcell-7B [15 tok] Bu | feature | ' | ı | implement | ederken | edge | case | ' | leri | handle | etmeyi | unut | mayalım | .
304
+
305
+ "merge'lemek istediğim branch conflict veriyor."
306
+
307
+ Multrenizer [ 7 tok] merge'le | mek | istediğim | branch | conflict | veriyor | .
308
+ Kumru-2B [14 tok] mer | ge | ' | lemek | istediğim | b | ran | ch | con | f | lic | t | veriyor | .
309
+ Turkcell-7B [ 8 tok] merge | ' | lemek | istediğim | branch | conflict | veriyor | .
310
+ GPT-2 [16 tok] mer | ge | ' | lem | ek | is | ted | i | ğ | im | branch | conflict | ver | iy | or | .
311
+ ```
312
+
313
+ > Full benchmark output (all sections, all sentences, machine-readable) is in `benchmark_results.json`. Regenerate with `python benchmark.py`.
314
 
315
  ## Project Structure
316
 
 
320
  │ ├── tokenizer.json
321
  │ ├── tokenizer_config.json
322
  │ └── special_tokens_map.json
323
+ ├── prepare_data.py # Corpus download (Wikipedia + OPUS-100 + synthetic CS)
324
  ├── train_tokenizer.py # Tokenizer training script
325
+ ├── benchmark.py # Benchmark vs 5 reference tokenizers
326
  ├── benchmark_results.json # Full benchmark output
327
+ ├── tests/
328
+ │ └── test_tokenizer_correctness.py # Round-trip + utility + ID-stability suite
329
+ ├── RETRAIN_PLAN.md # Design / pivot history (audit trail)
330
+ ├── MINDAI_INTEGRATION.md # Downstream integration recipe
331
  ├── requirements.txt
332
  └── pyproject.toml
333
  ```