ainouche-abderahmane commited on
Commit
085b9be
·
verified ·
1 Parent(s): 9265968

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - kab
4
+ license: apache-2.0
5
+ tags:
6
+ - kabyle
7
+ - taqbaylit
8
+ - berber
9
+ - amazigh
10
+ - ocr
11
+ - image-to-text
12
+ - tifinagh
13
+ - document-understanding
14
+ - low-resource
15
+ - ctc
16
+ pipeline_tag: image-to-text
17
+ metrics:
18
+ - cer
19
+ - wer
20
+ - exact_match
21
+ model-index:
22
+ - name: Feraoun-Pro
23
+ results:
24
+ - task:
25
+ type: image-to-text
26
+ name: Line-level document OCR, Kabyle Latin
27
+ dataset:
28
+ type: agbalu/KabTifinagh
29
+ name: 25,484 held-out rendered lines, Latin
30
+ metrics:
31
+ - type: cer
32
+ value: 0.0734
33
+ name: Character error rate
34
+ - task:
35
+ type: image-to-text
36
+ name: Line-level document OCR, Kabyle Neo-Tifinagh
37
+ dataset:
38
+ type: agbalu/KabTifinagh
39
+ name: 4,516 held-out rendered lines, Neo-Tifinagh
40
+ metrics:
41
+ - type: cer
42
+ value: 0.0091
43
+ name: Character error rate
44
+ ---
45
+
46
+ # Feraoun-Pro
47
+
48
+ A 51M-parameter CTC recogniser that reads a **line of printed Kabyle** (Taqbaylit, `kab`)
49
+ from a pixel strip and writes it out as text — in the Berber Latin orthography and in
50
+ Neo-Tifinagh, from one checkpoint, without being told which script it is looking at, in a
51
+ single forward pass.
52
+
53
+ It exists because the Kabyle written record is on paper. Novels, grammars, periodicals and
54
+ the archive scans that carry a century of prose are images, and general-purpose OCR was
55
+ never trained on the language: the emphatic consonants `ḍ ḥ ṛ ṣ ṭ ẓ` are a base letter plus
56
+ a sub-dot that other engines drop or normalise away, and dropping the dot changes the word.
57
+ `ţ` — Boulifa's original transcription convention — has a slot in this vocabulary. It has
58
+ never had one before.
59
+
60
+ **What it adds over [`agbalu/Feraoun-36M`](https://huggingface.co/agbalu/Feraoun-36M),
61
+ which also reads both scripts.** A 173-symbol table against 171 — `ţ` and `Ţ`, Boulifa's
62
+ convention, and 33 Neo-Tifinagh codepoints against 31. A single CTC forward pass in place of
63
+ one autoregressive decoder step per character. A variable-width canvas up to 1,024 px, so a
64
+ long line is no longer squeezed to fit. 600,000 training lines against 80,000. The two
65
+ models are separate artifacts on the same task and both stay published; Results says how to
66
+ choose.
67
+
68
+ ## Results
69
+
70
+ **30,000 held-out lines, rendered**, drawn at a fixed seed over the full 600,000-line pool —
71
+ 85% Latin, 15% Neo-Tifinagh, matching the training mix. The model saw none of these
72
+ sentences during training.
73
+
74
+ | | Latin (`n` = 25,484) | Neo-Tifinagh (`n` = 4,516) |
75
+ |---|---|---|
76
+ | Character error rate | **7.34%** | **0.91%** |
77
+
78
+ **Line exact match over all 30,000 lines: 81.77%.**
79
+
80
+ Three things the table does not say on its own.
81
+
82
+ **`Feraoun-36M` publishes 2.85% Latin CER, 1.64% Neo-Tifinagh CER and 70.20% exact match,
83
+ and none of those is comparable with the figures above.** The two models were scored on
84
+ different held-out draws from different line sets, and neither has been run on the other's —
85
+ so the pair is two measurements, not a ranking, and this card does not turn it into one.
86
+ Choose by page: this model takes a line up to 1,024 px without squeezing it, has `ţ` in its
87
+ alphabet, and decodes in one pass; the 36M model is smaller and is the one whose font
88
+ generalisation has been tested.
89
+
90
+ **The Tifinagh number is a retention result, not a difficulty result.** Neo-Tifinagh writes
91
+ no sub-dots and no capitals and its lines are shorter, so sub-1% there is not a better score
92
+ on the same task — it is an easier task, and the figure's value is that it proves the script
93
+ survived training alongside Latin rather than fading.
94
+
95
+ **Checkpoint selection was gated on both scripts at once.** The combined error
96
+ `0.5 · Latin CER + 0.5 · Tifinagh CER` had to improve while neither script regressed past
97
+ its baseline. A checkpoint that reads Tifinagh better by forgetting Latin is not selected,
98
+ which is the failure this model's whole training design is built around.
99
+
100
+ ## What the numbers cover, and what they do not
101
+
102
+ Every figure above is measured on rendered lines. What that leaves open, stated so you can
103
+ plan around it rather than discover it:
104
+
105
+ - **Real scanned pages are not in the evaluation.** Training and validation are both
106
+ synthetic renders, as is `Feraoun-36M`'s 2.85%. Performance on Adlis book scans — genuine
107
+ century-old print — is open for both models.
108
+ - **The head-to-head against `Feraoun-36M` has not been run**, for the reason above.
109
+ - **`ţ`/`Ţ` have vocabulary slots and no per-glyph score.** The validation pool holds too
110
+ few `ţ` lines to report one. The slots are what make the glyph representable at all; no
111
+ other Kabyle OCR system has them.
112
+ - **Font generalisation is not quantified.** Training and evaluation draw from the same set
113
+ of container faces, so a typeface the model has never seen is outside what these numbers
114
+ describe. Expect it to be the axis that moves most.
115
+
116
+ ## Architecture
117
+
118
+ | | |
119
+ |---|---|
120
+ | Parameters | **51,440,589**, plus 132,036 elements of buffer — the sinusoidal position table and the stem's batch-norm statistics — for 51,572,625 tensor elements in the weights file. The training checkpoint holds 60,158,014: the difference is an 8,585,389-parameter auxiliary decoder that supplies gradients and is not exported |
121
+ | Stem | 5-layer convolutional front end, 64 px fixed height, variable width to 1,024 px |
122
+ | Encoder | 16-layer pre-norm Transformer, 512 hidden, 8 heads |
123
+ | Head | CTC over 173 classes, greedy decode |
124
+ | Input | `(1, 3, 64, W)`, pixel range `[−1, 1]`, aspect ratio preserved |
125
+ | Vocabulary | 173 classes — 4 special, Latin, Kabyle extended and sub-dot (`ḍ ḥ ṛ ṣ ṭ ẓ`), accented Latin, `ţ`/`Ţ`, digits, punctuation, 33 Neo-Tifinagh |
126
+
127
+ **No decoder steps, no beam search, no key-value cache.** A line is one forward pass:
128
+ convolutional stem, Transformer encoder, linear projection, CTC argmax. The auxiliary
129
+ autoregressive decoder existed only to keep gradients reaching the early convolutional
130
+ layers, and it is not in the published weights.
131
+
132
+ **173 classes, not subwords.** A subword tokeniser completes vocabulary words where the ink
133
+ is broken, which is precisely the failure an OCR system must not have: it produces fluent
134
+ Kabyle that is not what the page says.
135
+
136
+ ## Intended use
137
+
138
+ Turning printed Kabyle into text: scanned novels, grammars, periodicals and archive
139
+ material, in the Latin orthography or in Neo-Tifinagh, from the same model.
140
+
141
+ **Not suitable for**: handwriting, absent from the training data; multi-column layout, which
142
+ `transcribe_page` reads straight across; any decision about a person; or any language other
143
+ than Kabyle. Tarifit, Tashelhit and Central Atlas Tamazight share part of this orthography
144
+ and are not evaluated here.
145
+
146
+ ## Usage
147
+
148
+ `transformers`, `torch`, `pillow` and `numpy`, nothing else. The architecture is not one of
149
+ the library's own, so the modelling code travels in this repository and
150
+ `trust_remote_code=True` is what loads it.
151
+
152
+ ```python
153
+ from PIL import Image
154
+ from transformers import AutoModel
155
+
156
+ model = AutoModel.from_pretrained("agbalu/Feraoun-Pro", trust_remote_code=True).eval()
157
+
158
+ print(model.transcribe(Image.open("line_crop.png")))
159
+ # 'Taqbaylit d tutlayt tayemmat nneɣ.'
160
+
161
+ print(model.transcribe(Image.open("tifinagh_line.png")))
162
+ # 'ⵜⴰⵇⴱⴰⵢⵍⵉⵜ ⴷ ⵜⵓⵜⵍⴰⵢⵜ ⵜⴰⵢⴻⵎⵎⴰⵜ ⵏⵏⴻⵖ.'
163
+ ```
164
+
165
+ `transcribe` does the preprocessing as well as the decoding, and that matters: the model was
166
+ fitted on lines scaled to 64 px of height with aspect ratio preserved, so a caller who builds
167
+ the tensor themselves at another scale is measuring a different model.
168
+
169
+ For a full page, `transcribe_page` segments on the horizontal ink profile first. That
170
+ segmentation assumes a single column of roughly horizontal lines; it has not been evaluated
171
+ on multi-column layouts, marginalia or skewed scans.
172
+
173
+ ## Training data
174
+
175
+ Lines are **rendered, not scanned.** There is no page of real Kabyle print with
176
+ character-level ground truth to train against.
177
+
178
+ | | |
179
+ |---|---|
180
+ | Latin text | AƔBALU-Text v1, not publicly released |
181
+ | Neo-Tifinagh text | [`agbalu/KabTifinagh`](https://huggingface.co/datasets/agbalu/KabTifinagh) `script_conversion` train split, CC-BY-2.0 |
182
+ | Objective | CTC, plus an auxiliary autoregressive decoder whose gradients reach the encoder and which is **not** exported |
183
+
184
+ The renderer composites text set at 24–38 pt in the faces available in the training container
185
+ onto generated paper grain, then degrades by blur, exposure, JPEG artefacting and a slight
186
+ skew. Height is fixed at 64 px; width follows the line up to 1,024 px, so glyphs are never
187
+ squashed to a square canvas.
188
+
189
+ **The degradation is deliberately modest**, and the model has not been shown genuinely hard
190
+ inputs: no bleed-through from the reverse of the page, no ink starvation, no page-edge
191
+ shadow, no show-through from facing pages. Expect real archival scans to be harder than
192
+ anything in training.
193
+
194
+ **The corpus composition is uneven.** AƔBALU-Text v1 is a 42-source mixture and the sources
195
+ are not weighted equally. The line set is a seeded draw over the full pool rather than a
196
+ prefix, which is what keeps it from being one source, but it does not flatten the source
197
+ distribution: a novel's long sentences and a newspaper's headlines are both in the pool and
198
+ both outside what a 30,000-line validation draw can fully represent.
199
+
200
+ ## Training recipe
201
+
202
+ One A10G throughout, 600,000 rendered lines per stage, 570,000 train / 30,000 validation on
203
+ a fixed seed, batch 64, one epoch per stage, cosine schedule with 5% warmup.
204
+
205
+ | stage | mix | peak lr | what it does |
206
+ |---|---|---|---|
207
+ | 1 | Latin only | `2e-5` | builds the Latin recogniser and the sub-dot distinctions |
208
+ | 2 | 60% Latin / 40% Tifinagh | `2e-5` | introduces the 33 Neo-Tifinagh codepoints |
209
+ | 3 | 85% Latin / 15% Tifinagh | `5e-6` → `2.5e-7` | settles both scripts; 8,907 steps |
210
+
211
+ **The staging is the design, not a schedule that happened to be split up.** Introducing
212
+ Neo-Tifinagh at 40% costs Latin accuracy — that is where this model's 7.34% Latin CER comes
213
+ from, and it is why stage 3 drops the Tifinagh share to a rehearsal fraction and the learning
214
+ rate by 4×. The 15% rehearsal is enough to keep the Tifinagh codepoints from fading while the
215
+ Latin weights settle. Every stage's checkpoint is gated on the combined two-script error with
216
+ a per-script regression ceiling, so no stage can trade one script for the other.
217
+
218
+ ## Limitations
219
+
220
+ **7.34% Latin CER on synthetic renders is not 7.34% on your documents.** The validation set
221
+ is drawn from the same rendered distribution as training. Real scanned pages have ink bleed,
222
+ page curvature, show-through and aging that the renderer approximates at modest levels.
223
+ Whether the gap is one point or ten is not known.
224
+
225
+ **Latin character accuracy is the weak axis.** It is the price of dual-script capacity in a
226
+ model this size, and it is stated in Results rather than buried here.
227
+
228
+ **Punctuation after dense glyph clusters is a consistent weakness.** In Neo-Tifinagh,
229
+ `ⴰⵣⵓⵍ ⴼⵍⵍ-ⴰⵡⴻⵏ, ⴰⵎⴻⴽ ⵜⴻⵍⵍⴰⵎ?` came back as `ⴰⵣⵓⵍ ⴼⵍⵍ,ⴰⵡⴻⵏ, ⴰⵎⴻⴽ ⵜⴻⵍⵍⴰⵎ!` — the hyphen read
230
+ as a comma and the closing `?` as `!`, both at marks that follow glyphs without a full
231
+ inter-word space.
232
+
233
+ **Whitespace boundaries are not reliable.** The validation ground truth is stripped, so
234
+ trailing-space errors introduced by the renderer's post-processing do not appear in the CER.
235
+ They will appear in your output if the downstream consumer is whitespace-sensitive.
236
+
237
+ **Page layout is a projection profile, nothing more.** `transcribe_page` finds horizontal ink
238
+ bands and crops them. There is no column detection, no reading-order model and no table or
239
+ figure handling, so a two-column periodical is read straight across.
240
+
241
+ ## Files
242
+
243
+ | file | contents |
244
+ |---|---|
245
+ | `model.safetensors` | the convolutional stem, the 16-layer encoder and the CTC head — 51,440,589 parameters and 132,036 elements of buffer, 206 MB fp32. `from_pretrained` reports no missing and no unexpected keys |
246
+ | `config.json` | architecture and the `auto_map` that points at the code below |
247
+ | `configuration_feraoun_pro.py`, `modeling_feraoun_pro.py` | the architecture and the preprocessing, importing only `torch`, `transformers`, `pillow` and `numpy` |
248
+ | `tokenizer.json`, `tokenizer_config.json` | the 173-symbol table, id for id with the one the model was trained on |
249
+
250
+ Training cannot be resumed from the published files: the optimizer, scheduler, step and epoch
251
+ stay in the source checkpoint on the training volume and are not exported.
252
+
253
+ ## Reproduction
254
+
255
+ ```bash
256
+ make modal-ocr-pro RUN=latin EPOCHS=1 LR=2e-5 RATIO=0.0 LINES=600000
257
+ make modal-ocr-pro RUN=dual EPOCHS=1 LR=2e-5 RATIO=0.40 RESUME=latin
258
+ make modal-ocr-pro RUN=feraoun-pro EPOCHS=1 LR=5e-6 RATIO=0.15 RESUME=dual
259
+ make modal-ocr-pro-pull RUN=feraoun-pro
260
+ make push REPO=feraoun-pro
261
+ ```
262
+
263
+ ## The name
264
+
265
+ **Mouloud Feraoun** (1913–1962), the schoolteacher from Tizi Hibel who wrote *Le Fils du
266
+ pauvre* and put Si Mohand's oral poetry onto the printed page — the direction this model
267
+ reverses. He was assassinated by the OAS on 15 March 1962, three days before the Évian
268
+ Accords were signed. The naming is homage and carries no endorsement.
269
+
270
+ ## Citation
271
+
272
+ ```bibtex
273
+ @software{agbalu_feraoun_pro_2026,
274
+ title = {Feraoun-Pro: dual-script CTC document OCR for Kabyle},
275
+ author = {AƔBALU},
276
+ year = {2026},
277
+ url = {https://huggingface.co/agbalu/Feraoun-Pro},
278
+ note = {51,572,625 parameters; Latin CER 7.34\%, Neo-Tifinagh CER 0.91\%,
279
+ line exact match 81.77\%, over 30,000 held-out rendered lines}
280
+ }
281
+ ```
282
+
283
+ ## Licence
284
+
285
+ **Apache-2.0** on the weights and the code. The training text is CC-BY-2.0 on the Tifinagh
286
+ side and a mixed corpus of uneven provenance on the Latin side. A permissive grant on the
287
+ weights makes no claim about the text behind them, so the corpus composition is stated above
288
+ rather than assumed.
289
+
290
+ Part of [AƔBALU](https://huggingface.co/agbalu), a Kabyle NLP collection.
__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """Feraoun-Pro standalone hub package."""
2
+
3
+ from .configuration_feraoun_pro import FeraounProConfig
4
+ from .modeling_feraoun_pro import FeraounProForCTC, prepare_line_image, segment_page_into_lines
5
+
6
+ __all__ = ["FeraounProConfig", "FeraounProForCTC", "prepare_line_image", "segment_page_into_lines"]
config.json ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "FeraounProForCTC"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_feraoun_pro.FeraounProConfig",
7
+ "AutoModel": "modeling_feraoun_pro.FeraounProForCTC"
8
+ },
9
+ "bos_token_id": 1,
10
+ "d_model": 512,
11
+ "dim_feedforward": 2048,
12
+ "dropout": 0.1,
13
+ "eos_token_id": 2,
14
+ "max_width_frames": 256,
15
+ "model_type": "feraoun_pro",
16
+ "num_encoder_layers": 16,
17
+ "num_heads": 8,
18
+ "pad_token_id": 0,
19
+ "transformers_version": "5.12.1",
20
+ "unk_token_id": 3,
21
+ "vocab_size": 173,
22
+ "vocabulary": [
23
+ "<pad>",
24
+ "<s>",
25
+ "</s>",
26
+ "<unk>",
27
+ "a",
28
+ "b",
29
+ "c",
30
+ "d",
31
+ "e",
32
+ "f",
33
+ "g",
34
+ "h",
35
+ "i",
36
+ "j",
37
+ "k",
38
+ "l",
39
+ "m",
40
+ "n",
41
+ "o",
42
+ "p",
43
+ "q",
44
+ "r",
45
+ "s",
46
+ "t",
47
+ "u",
48
+ "v",
49
+ "w",
50
+ "x",
51
+ "y",
52
+ "z",
53
+ "A",
54
+ "B",
55
+ "C",
56
+ "D",
57
+ "E",
58
+ "F",
59
+ "G",
60
+ "H",
61
+ "I",
62
+ "J",
63
+ "K",
64
+ "L",
65
+ "M",
66
+ "N",
67
+ "O",
68
+ "P",
69
+ "Q",
70
+ "R",
71
+ "S",
72
+ "T",
73
+ "U",
74
+ "V",
75
+ "W",
76
+ "X",
77
+ "Y",
78
+ "Z",
79
+ "\u0263",
80
+ "\u0194",
81
+ "\u025b",
82
+ "\u0190",
83
+ "\u010d",
84
+ "\u010c",
85
+ "\u01e7",
86
+ "\u01e6",
87
+ "\u0163",
88
+ "\u0162",
89
+ "\u1e0d",
90
+ "\u1e0c",
91
+ "\u1e25",
92
+ "\u1e24",
93
+ "\u1e5b",
94
+ "\u1e5a",
95
+ "\u1e63",
96
+ "\u1e62",
97
+ "\u1e6d",
98
+ "\u1e6c",
99
+ "\u1e93",
100
+ "\u1e92",
101
+ "\u00e9",
102
+ "\u00e8",
103
+ "\u00ea",
104
+ "\u00eb",
105
+ "\u00e0",
106
+ "\u00e2",
107
+ "\u00ee",
108
+ "\u00ef",
109
+ "\u00f4",
110
+ "\u00f9",
111
+ "\u00fb",
112
+ "\u00e7",
113
+ "\u00c9",
114
+ "\u00c8",
115
+ "\u00ca",
116
+ "\u00cb",
117
+ "\u00c0",
118
+ "\u00c2",
119
+ "\u00ce",
120
+ "\u00cf",
121
+ "\u00d4",
122
+ "\u00d9",
123
+ "\u00db",
124
+ "\u00c7",
125
+ "0",
126
+ "1",
127
+ "2",
128
+ "3",
129
+ "4",
130
+ "5",
131
+ "6",
132
+ "7",
133
+ "8",
134
+ "9",
135
+ " ",
136
+ ".",
137
+ ",",
138
+ "?",
139
+ "!",
140
+ "\u00ab",
141
+ "\u00bb",
142
+ "-",
143
+ "\u2014",
144
+ ":",
145
+ ";",
146
+ "'",
147
+ "\"",
148
+ "/",
149
+ "(",
150
+ ")",
151
+ "[",
152
+ "]",
153
+ "{",
154
+ "}",
155
+ "%",
156
+ "*",
157
+ "+",
158
+ "=",
159
+ "_",
160
+ "~",
161
+ "\u2013",
162
+ "\u2026",
163
+ "\n",
164
+ "\t",
165
+ "\u2d30",
166
+ "\u2d31",
167
+ "\u2d33",
168
+ "\u2d37",
169
+ "\u2d39",
170
+ "\u2d3b",
171
+ "\u2d3c",
172
+ "\u2d3d",
173
+ "\u2d40",
174
+ "\u2d43",
175
+ "\u2d44",
176
+ "\u2d45",
177
+ "\u2d47",
178
+ "\u2d49",
179
+ "\u2d4a",
180
+ "\u2d4d",
181
+ "\u2d4e",
182
+ "\u2d4f",
183
+ "\u2d53",
184
+ "\u2d54",
185
+ "\u2d55",
186
+ "\u2d59",
187
+ "\u2d5a",
188
+ "\u2d5b",
189
+ "\u2d5c",
190
+ "\u2d5f",
191
+ "\u2d61",
192
+ "\u2d62",
193
+ "\u2d63",
194
+ "\u2d65",
195
+ "\u2d56"
196
+ ]
197
+ }
configuration_feraoun_pro.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration for the Feraoun-Pro document OCR model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Final
6
+
7
+ from transformers import PreTrainedConfig
8
+
9
+ SYMBOLS: Final[str] = (
10
+ "abcdefghijklmnopqrstuvwxyz"
11
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
12
+ "ɣƔɛƐčČǧǦţŢ"
13
+ "ḍḌḥḤṛṚṣṢṭṬẓẒ"
14
+ "éèêëàâîïôùûçÉÈÊËÀÂÎÏÔÙÛÇ"
15
+ "0123456789"
16
+ " .,?!«»-—:;'\"/()[]{}%*+=_~–…\n\t"
17
+ "ⴰⴱⴳⴷⴹⴻⴼⴽⵀⵃⵄⵅⵇⵉⵊⵍⵎⵏⵓⵔⵕⵙⵚⵛⵜⵟⵡⵢⵣⵥⵖ"
18
+ )
19
+ """The 169 printable classes after the four special tokens, spelled out rather than
20
+ imported: nothing under `hub/` may import `agbalu`, because these modules are loaded from
21
+ the published repository where this package does not exist.
22
+ `tests/unit/test_ocr_pro.py` pins them against `agbalu.ocr.vocabulary_pro`, so the two
23
+ copies cannot drift apart silently."""
24
+
25
+ DEFAULT_VOCABULARY: Final[tuple[str, ...]] = ("<pad>", "<s>", "</s>", "<unk>", *SYMBOLS)
26
+
27
+
28
+ class FeraounProConfig(PreTrainedConfig):
29
+ model_type = "feraoun_pro"
30
+
31
+ def __init__(
32
+ self,
33
+ vocabulary: list[str] | tuple[str, ...] | None = None,
34
+ vocab_size: int = 173,
35
+ d_model: int = 512,
36
+ num_encoder_layers: int = 16,
37
+ num_heads: int = 8,
38
+ dim_feedforward: int = 2048,
39
+ max_width_frames: int = 256,
40
+ dropout: float = 0.1,
41
+ **kwargs: Any,
42
+ ) -> None:
43
+ # The symbol table ships in `config.json`: a CTC id means nothing without it, and a
44
+ # downloader who has to reconstruct it from this repository does not have a usable
45
+ # artifact. `vocab_size` stays a separate field because it is the width of
46
+ # `ctc_head` and a saved config carries it.
47
+ self.vocabulary = list(vocabulary) if vocabulary is not None else list(DEFAULT_VOCABULARY)
48
+ self.vocab_size = vocab_size
49
+ self.d_model = d_model
50
+ self.num_encoder_layers = num_encoder_layers
51
+ self.num_heads = num_heads
52
+ self.dim_feedforward = dim_feedforward
53
+ self.max_width_frames = max_width_frames
54
+ self.dropout = dropout
55
+
56
+ kwargs.setdefault("pad_token_id", 0)
57
+ kwargs.setdefault("bos_token_id", 1)
58
+ kwargs.setdefault("eos_token_id", 2)
59
+ kwargs.setdefault("unk_token_id", 3)
60
+ super().__init__(**kwargs)
61
+
62
+
63
+ __all__ = ["DEFAULT_VOCABULARY", "SYMBOLS", "FeraounProConfig"]
export.stats.json ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source": "artifacts/runs/feraoun-pro/best.pt",
3
+ "source_contents": [
4
+ "model",
5
+ "optimizer",
6
+ "rng",
7
+ "state"
8
+ ],
9
+ "dropped_state": [
10
+ "optimizer",
11
+ "rng"
12
+ ],
13
+ "dropped_tied": [],
14
+ "dropped_derived": [],
15
+ "dropped_training_only": [
16
+ "aux_decoder.decoder.layers.0.linear1.bias",
17
+ "aux_decoder.decoder.layers.0.linear1.weight",
18
+ "aux_decoder.decoder.layers.0.linear2.bias",
19
+ "aux_decoder.decoder.layers.0.linear2.weight",
20
+ "aux_decoder.decoder.layers.0.multihead_attn.in_proj_bias",
21
+ "aux_decoder.decoder.layers.0.multihead_attn.in_proj_weight",
22
+ "aux_decoder.decoder.layers.0.multihead_attn.out_proj.bias",
23
+ "aux_decoder.decoder.layers.0.multihead_attn.out_proj.weight",
24
+ "aux_decoder.decoder.layers.0.norm1.bias",
25
+ "aux_decoder.decoder.layers.0.norm1.weight",
26
+ "aux_decoder.decoder.layers.0.norm2.bias",
27
+ "aux_decoder.decoder.layers.0.norm2.weight",
28
+ "aux_decoder.decoder.layers.0.norm3.bias",
29
+ "aux_decoder.decoder.layers.0.norm3.weight",
30
+ "aux_decoder.decoder.layers.0.self_attn.in_proj_bias",
31
+ "aux_decoder.decoder.layers.0.self_attn.in_proj_weight",
32
+ "aux_decoder.decoder.layers.0.self_attn.out_proj.bias",
33
+ "aux_decoder.decoder.layers.0.self_attn.out_proj.weight",
34
+ "aux_decoder.decoder.layers.1.linear1.bias",
35
+ "aux_decoder.decoder.layers.1.linear1.weight",
36
+ "aux_decoder.decoder.layers.1.linear2.bias",
37
+ "aux_decoder.decoder.layers.1.linear2.weight",
38
+ "aux_decoder.decoder.layers.1.multihead_attn.in_proj_bias",
39
+ "aux_decoder.decoder.layers.1.multihead_attn.in_proj_weight",
40
+ "aux_decoder.decoder.layers.1.multihead_attn.out_proj.bias",
41
+ "aux_decoder.decoder.layers.1.multihead_attn.out_proj.weight",
42
+ "aux_decoder.decoder.layers.1.norm1.bias",
43
+ "aux_decoder.decoder.layers.1.norm1.weight",
44
+ "aux_decoder.decoder.layers.1.norm2.bias",
45
+ "aux_decoder.decoder.layers.1.norm2.weight",
46
+ "aux_decoder.decoder.layers.1.norm3.bias",
47
+ "aux_decoder.decoder.layers.1.norm3.weight",
48
+ "aux_decoder.decoder.layers.1.self_attn.in_proj_bias",
49
+ "aux_decoder.decoder.layers.1.self_attn.in_proj_weight",
50
+ "aux_decoder.decoder.layers.1.self_attn.out_proj.bias",
51
+ "aux_decoder.decoder.layers.1.self_attn.out_proj.weight",
52
+ "aux_decoder.embedding.weight",
53
+ "aux_decoder.lm_head.bias",
54
+ "aux_decoder.lm_head.weight"
55
+ ],
56
+ "tensors": 221,
57
+ "parameters": 51572625,
58
+ "files": [
59
+ {
60
+ "name": "README.md",
61
+ "bytes": 13969,
62
+ "sha256": "39d5914cdb53dcfd14aefcb7838e870cbd981600ac21ac3734934a0534b2007f"
63
+ },
64
+ {
65
+ "name": "__init__.py",
66
+ "bytes": 296,
67
+ "sha256": "9ac3b94df93a57b0dac01996acc6e5ef217e016f117fbc97fd05c473d7dbfc84"
68
+ },
69
+ {
70
+ "name": "config.json",
71
+ "bytes": 2507,
72
+ "sha256": "746ce438be8325339c8e5a0b0caddcbd20d1cab56386f13e0157d147ce3f65ab"
73
+ },
74
+ {
75
+ "name": "configuration_feraoun_pro.py",
76
+ "bytes": 2423,
77
+ "sha256": "32020487761b9375e0a6931cfdfd39551bccd8c50939a39108bdc845036b8b8f"
78
+ },
79
+ {
80
+ "name": "model.safetensors",
81
+ "bytes": 206313076,
82
+ "sha256": "dfc55395ed15b9d727eae16594ec7f87c8ef28fdc2e6d5f7bd6e88d7bbafe165"
83
+ },
84
+ {
85
+ "name": "modeling_feraoun_pro.py",
86
+ "bytes": 7590,
87
+ "sha256": "7c8018a171eb4647453140103bf919f98d2860339a74d819ef1869865a239563"
88
+ },
89
+ {
90
+ "name": "tokenizer.json",
91
+ "bytes": 4764,
92
+ "sha256": "350100049e4f193563065147c35fce2dbcc344cf98420e0fa209c4b668864c37"
93
+ },
94
+ {
95
+ "name": "tokenizer_config.json",
96
+ "bytes": 193,
97
+ "sha256": "444cfded3d0102faf5c8bb4706f540becd8103ce7344ddbbad8df2da0928fa52"
98
+ }
99
+ ]
100
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dfc55395ed15b9d727eae16594ec7f87c8ef28fdc2e6d5f7bd6e88d7bbafe165
3
+ size 206313076
modeling_feraoun_pro.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Feraoun-Pro: Hybrid CNN-Transformer CTC Document OCR Model for Kabyle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from typing import TYPE_CHECKING, Final
7
+
8
+ import torch
9
+ from torch import Tensor, nn
10
+ from transformers import PreTrainedModel
11
+
12
+ from .configuration_feraoun_pro import FeraounProConfig
13
+
14
+ if TYPE_CHECKING:
15
+ from PIL import Image
16
+
17
+ TARGET_HEIGHT: Final[int] = 64
18
+ MAX_WIDTH: Final[int] = 1024
19
+ CROP_INK_THRESHOLD: Final[int] = 225
20
+ LINE_INK_THRESHOLD: Final[int] = 200
21
+ LINE_MARGIN: Final[int] = 4
22
+
23
+
24
+ def crop_text_bbox(image: Image.Image, padding: int = LINE_MARGIN) -> Image.Image:
25
+ """Crop to the bounding box of the ink."""
26
+ import numpy as np
27
+
28
+ array = np.array(image.convert("L"), dtype=np.uint8)
29
+ ink = array < CROP_INK_THRESHOLD
30
+ if not ink.any():
31
+ return image
32
+
33
+ rows, columns = np.where(ink)
34
+ left = max(0, int(columns.min()) - padding)
35
+ right = min(image.width, int(columns.max()) + padding)
36
+ top = max(0, int(rows.min()) - padding)
37
+ bottom = min(image.height, int(rows.max()) + padding)
38
+ if right <= left or bottom <= top:
39
+ return image
40
+ return image.crop((left, top, right, bottom))
41
+
42
+
43
+ def prepare_line_image(image: Image.Image) -> Tensor:
44
+ """Crop to the ink, scale the height to 64 px, normalise, and clamp the width."""
45
+ import numpy as np
46
+ from PIL import Image as PILImage
47
+
48
+ cropped = crop_text_bbox(image).convert("RGB")
49
+ width, height = cropped.size
50
+
51
+ scale = float(TARGET_HEIGHT) / max(height, 1)
52
+ new_width = max(16, min(MAX_WIDTH, int(width * scale)))
53
+ # Ensure divisible by 4 for CNN stem
54
+ new_width = int(math.ceil(new_width / 4.0) * 4)
55
+
56
+ resized = cropped.resize((new_width, TARGET_HEIGHT), resample=PILImage.Resampling.LANCZOS)
57
+ arr = np.array(resized, dtype=np.float32) / 255.0
58
+ arr = (arr - 0.5) / 0.5
59
+ return torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0) # (1, 3, 64, W)
60
+
61
+
62
+ def segment_page_into_lines(page: Image.Image, min_line_height: int = 15) -> list[Image.Image]:
63
+ """Segment page into line strips on horizontal ink profile."""
64
+ import numpy as np
65
+
66
+ array = np.array(page.convert("L"), dtype=np.uint8)
67
+ ink = (array < LINE_INK_THRESHOLD).astype(np.float32).sum(axis=1)
68
+ active = ink > 0.015 * array.shape[1]
69
+
70
+ lines: list[Image.Image] = []
71
+ start = 0
72
+ inside = False
73
+
74
+ for idx, is_ink in enumerate(active):
75
+ if is_ink and not inside:
76
+ start = idx
77
+ inside = True
78
+ elif not is_ink and inside:
79
+ inside = False
80
+ if idx - start >= min_line_height:
81
+ lines.append(
82
+ page.crop(
83
+ (0, max(0, start - 2), page.width, min(page.height, idx + 2))
84
+ ).convert("RGB")
85
+ )
86
+
87
+ if inside and len(active) - start >= min_line_height:
88
+ lines.append(page.crop((0, start, page.width, len(active))).convert("RGB"))
89
+
90
+ return lines
91
+
92
+
93
+ class CNNStem(nn.Module):
94
+ def __init__(self, d_model: int) -> None:
95
+ super().__init__()
96
+ self.block1 = nn.Sequential(
97
+ nn.Conv2d(3, 32, kernel_size=3, stride=(2, 1), padding=1, bias=False),
98
+ nn.BatchNorm2d(32),
99
+ nn.GELU(),
100
+ )
101
+ self.block2 = nn.Sequential(
102
+ nn.Conv2d(32, 64, kernel_size=3, stride=(2, 1), padding=1, bias=False),
103
+ nn.BatchNorm2d(64),
104
+ nn.GELU(),
105
+ )
106
+ self.block3 = nn.Sequential(
107
+ nn.Conv2d(64, 128, kernel_size=3, stride=(2, 2), padding=1, bias=False),
108
+ nn.BatchNorm2d(128),
109
+ nn.GELU(),
110
+ )
111
+ self.block4 = nn.Sequential(
112
+ nn.Conv2d(128, 256, kernel_size=3, stride=(2, 2), padding=1, bias=False),
113
+ nn.BatchNorm2d(256),
114
+ nn.GELU(),
115
+ )
116
+ self.proj = nn.Linear(256 * 4, d_model)
117
+
118
+ def forward(self, x: Tensor) -> Tensor:
119
+ x = self.block1(x)
120
+ x = self.block2(x)
121
+ x = self.block3(x)
122
+ x = self.block4(x)
123
+ b, c, h, w = x.shape
124
+ x = x.permute(0, 3, 1, 2).contiguous().view(b, w, c * h)
125
+ out: Tensor = self.proj(x)
126
+ return out
127
+
128
+
129
+ class FeraounProForCTC(PreTrainedModel):
130
+ config_class = FeraounProConfig
131
+ base_model_prefix = "feraoun_pro"
132
+
133
+ def __init__(self, config: FeraounProConfig) -> None:
134
+ super().__init__(config)
135
+ self.config = config
136
+ self.stem = CNNStem(d_model=config.d_model)
137
+
138
+ # Build sinusoidal positional encodings
139
+ pe = torch.zeros(config.max_width_frames, config.d_model)
140
+ position = torch.arange(config.max_width_frames).unsqueeze(1)
141
+ div_term = torch.exp(
142
+ torch.arange(0, config.d_model, 2) * (-math.log(10000.0) / config.d_model)
143
+ )
144
+ pe[:, 0::2] = torch.sin(position * div_term)
145
+ pe[:, 1::2] = torch.cos(position * div_term)
146
+ self.register_buffer("pos_encoding", pe)
147
+
148
+ encoder_layer = nn.TransformerEncoderLayer(
149
+ d_model=config.d_model,
150
+ nhead=config.num_heads,
151
+ dim_feedforward=config.dim_feedforward,
152
+ dropout=config.dropout,
153
+ activation="gelu",
154
+ batch_first=True,
155
+ norm_first=True,
156
+ )
157
+ # The nested-tensor fast path does not apply to a pre-norm layer; torch infers that
158
+ # and warns. Declaring it keeps a downloader's console clean.
159
+ self.encoder = nn.TransformerEncoder(
160
+ encoder_layer, num_layers=config.num_encoder_layers, enable_nested_tensor=False
161
+ )
162
+ self.ctc_head = nn.Linear(config.d_model, config.vocab_size)
163
+ self.post_init()
164
+
165
+ def forward(self, pixel_values: Tensor) -> Tensor:
166
+ features = self.stem(pixel_values)
167
+ seq_len = features.size(1)
168
+ # `get_buffer` rather than the attribute: `nn.Module.__getattr__` is typed as
169
+ # `Tensor | Module`, and a registered buffer is always the former.
170
+ pe = self.get_buffer("pos_encoding")[:seq_len, :].unsqueeze(0).to(features.device)
171
+ encoded = self.encoder(features + pe)
172
+ logits: Tensor = self.ctc_head(encoded)
173
+ return logits
174
+
175
+ def decode_ctc(self, token_ids: list[int]) -> str:
176
+ """Collapse repeats, then drop blanks — the CTC rule, in that order.
177
+
178
+ Dropping blanks first merges the two characters a blank was separating, so a
179
+ geminate consonant comes back single.
180
+ """
181
+ vocabulary = self.config.vocabulary
182
+ blank = self.config.pad_token_id
183
+ special = {blank, self.config.bos_token_id, self.config.eos_token_id}
184
+ collapsed: list[int] = []
185
+ previous = -1
186
+ for token_id in token_ids:
187
+ if token_id != previous:
188
+ if token_id not in special:
189
+ collapsed.append(token_id)
190
+ previous = token_id
191
+ return "".join(vocabulary[token_id] for token_id in collapsed if token_id < len(vocabulary))
192
+
193
+ @torch.no_grad()
194
+ def transcribe(self, image: Image.Image) -> str:
195
+ """Read one line crop. Preprocessing included, because it is half of this model."""
196
+ self.eval()
197
+ pixel_values = prepare_line_image(image).to(self.device)
198
+ logits = self(pixel_values=pixel_values)
199
+ return self.decode_ctc(torch.argmax(logits, dim=-1)[0].tolist())
200
+
201
+ @torch.no_grad()
202
+ def transcribe_page(self, page: Image.Image) -> list[str]:
203
+ """Segment a page into lines and read each one."""
204
+ return [self.transcribe(line) for line in segment_page_into_lines(page)]
205
+
206
+
207
+ __all__ = ["FeraounProForCTC", "prepare_line_image", "segment_page_into_lines"]
tokenizer.json ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.0",
3
+ "truncation": null,
4
+ "padding": null,
5
+ "added_tokens": [
6
+ {
7
+ "id": 0,
8
+ "content": "<pad>",
9
+ "single_word": false,
10
+ "lstrip": false,
11
+ "rstrip": false,
12
+ "normalized": false,
13
+ "special": true
14
+ },
15
+ {
16
+ "id": 1,
17
+ "content": "<s>",
18
+ "single_word": false,
19
+ "lstrip": false,
20
+ "rstrip": false,
21
+ "normalized": false,
22
+ "special": true
23
+ },
24
+ {
25
+ "id": 2,
26
+ "content": "</s>",
27
+ "single_word": false,
28
+ "lstrip": false,
29
+ "rstrip": false,
30
+ "normalized": false,
31
+ "special": true
32
+ },
33
+ {
34
+ "id": 3,
35
+ "content": "<unk>",
36
+ "single_word": false,
37
+ "lstrip": false,
38
+ "rstrip": false,
39
+ "normalized": false,
40
+ "special": true
41
+ }
42
+ ],
43
+ "normalizer": null,
44
+ "pre_tokenizer": {
45
+ "type": "Split",
46
+ "pattern": {
47
+ "Regex": "[\\s\\S]"
48
+ },
49
+ "behavior": "Isolated",
50
+ "invert": false
51
+ },
52
+ "post_processor": {
53
+ "type": "TemplateProcessing",
54
+ "single": [
55
+ {
56
+ "SpecialToken": {
57
+ "id": "<s>",
58
+ "type_id": 0
59
+ }
60
+ },
61
+ {
62
+ "Sequence": {
63
+ "id": "A",
64
+ "type_id": 0
65
+ }
66
+ },
67
+ {
68
+ "SpecialToken": {
69
+ "id": "</s>",
70
+ "type_id": 0
71
+ }
72
+ }
73
+ ],
74
+ "pair": [
75
+ {
76
+ "Sequence": {
77
+ "id": "A",
78
+ "type_id": 0
79
+ }
80
+ },
81
+ {
82
+ "Sequence": {
83
+ "id": "B",
84
+ "type_id": 1
85
+ }
86
+ }
87
+ ],
88
+ "special_tokens": {
89
+ "</s>": {
90
+ "id": "</s>",
91
+ "ids": [
92
+ 2
93
+ ],
94
+ "tokens": [
95
+ "</s>"
96
+ ]
97
+ },
98
+ "<s>": {
99
+ "id": "<s>",
100
+ "ids": [
101
+ 1
102
+ ],
103
+ "tokens": [
104
+ "<s>"
105
+ ]
106
+ }
107
+ }
108
+ },
109
+ "decoder": {
110
+ "type": "Fuse"
111
+ },
112
+ "model": {
113
+ "type": "WordLevel",
114
+ "vocab": {
115
+ "<pad>": 0,
116
+ "<s>": 1,
117
+ "</s>": 2,
118
+ "<unk>": 3,
119
+ "a": 4,
120
+ "b": 5,
121
+ "c": 6,
122
+ "d": 7,
123
+ "e": 8,
124
+ "f": 9,
125
+ "g": 10,
126
+ "h": 11,
127
+ "i": 12,
128
+ "j": 13,
129
+ "k": 14,
130
+ "l": 15,
131
+ "m": 16,
132
+ "n": 17,
133
+ "o": 18,
134
+ "p": 19,
135
+ "q": 20,
136
+ "r": 21,
137
+ "s": 22,
138
+ "t": 23,
139
+ "u": 24,
140
+ "v": 25,
141
+ "w": 26,
142
+ "x": 27,
143
+ "y": 28,
144
+ "z": 29,
145
+ "A": 30,
146
+ "B": 31,
147
+ "C": 32,
148
+ "D": 33,
149
+ "E": 34,
150
+ "F": 35,
151
+ "G": 36,
152
+ "H": 37,
153
+ "I": 38,
154
+ "J": 39,
155
+ "K": 40,
156
+ "L": 41,
157
+ "M": 42,
158
+ "N": 43,
159
+ "O": 44,
160
+ "P": 45,
161
+ "Q": 46,
162
+ "R": 47,
163
+ "S": 48,
164
+ "T": 49,
165
+ "U": 50,
166
+ "V": 51,
167
+ "W": 52,
168
+ "X": 53,
169
+ "Y": 54,
170
+ "Z": 55,
171
+ "ɣ": 56,
172
+ "Ɣ": 57,
173
+ "ɛ": 58,
174
+ "Ɛ": 59,
175
+ "č": 60,
176
+ "Č": 61,
177
+ "ǧ": 62,
178
+ "Ǧ": 63,
179
+ "ţ": 64,
180
+ "Ţ": 65,
181
+ "ḍ": 66,
182
+ "Ḍ": 67,
183
+ "ḥ": 68,
184
+ "Ḥ": 69,
185
+ "ṛ": 70,
186
+ "Ṛ": 71,
187
+ "ṣ": 72,
188
+ "Ṣ": 73,
189
+ "ṭ": 74,
190
+ "Ṭ": 75,
191
+ "ẓ": 76,
192
+ "Ẓ": 77,
193
+ "é": 78,
194
+ "è": 79,
195
+ "ê": 80,
196
+ "ë": 81,
197
+ "à": 82,
198
+ "â": 83,
199
+ "î": 84,
200
+ "ï": 85,
201
+ "ô": 86,
202
+ "ù": 87,
203
+ "û": 88,
204
+ "ç": 89,
205
+ "É": 90,
206
+ "È": 91,
207
+ "Ê": 92,
208
+ "Ë": 93,
209
+ "À": 94,
210
+ "Â": 95,
211
+ "Î": 96,
212
+ "Ï": 97,
213
+ "Ô": 98,
214
+ "Ù": 99,
215
+ "Û": 100,
216
+ "Ç": 101,
217
+ "0": 102,
218
+ "1": 103,
219
+ "2": 104,
220
+ "3": 105,
221
+ "4": 106,
222
+ "5": 107,
223
+ "6": 108,
224
+ "7": 109,
225
+ "8": 110,
226
+ "9": 111,
227
+ " ": 112,
228
+ ".": 113,
229
+ ",": 114,
230
+ "?": 115,
231
+ "!": 116,
232
+ "«": 117,
233
+ "»": 118,
234
+ "-": 119,
235
+ "—": 120,
236
+ ":": 121,
237
+ ";": 122,
238
+ "'": 123,
239
+ "\"": 124,
240
+ "/": 125,
241
+ "(": 126,
242
+ ")": 127,
243
+ "[": 128,
244
+ "]": 129,
245
+ "{": 130,
246
+ "}": 131,
247
+ "%": 132,
248
+ "*": 133,
249
+ "+": 134,
250
+ "=": 135,
251
+ "_": 136,
252
+ "~": 137,
253
+ "–": 138,
254
+ "…": 139,
255
+ "\n": 140,
256
+ "\t": 141,
257
+ "ⴰ": 142,
258
+ "ⴱ": 143,
259
+ "ⴳ": 144,
260
+ "ⴷ": 145,
261
+ "ⴹ": 146,
262
+ "ⴻ": 147,
263
+ "ⴼ": 148,
264
+ "ⴽ": 149,
265
+ "ⵀ": 150,
266
+ "ⵃ": 151,
267
+ "ⵄ": 152,
268
+ "ⵅ": 153,
269
+ "ⵇ": 154,
270
+ "ⵉ": 155,
271
+ "ⵊ": 156,
272
+ "ⵍ": 157,
273
+ "ⵎ": 158,
274
+ "ⵏ": 159,
275
+ "ⵓ": 160,
276
+ "ⵔ": 161,
277
+ "ⵕ": 162,
278
+ "ⵙ": 163,
279
+ "ⵚ": 164,
280
+ "ⵛ": 165,
281
+ "ⵜ": 166,
282
+ "ⵟ": 167,
283
+ "ⵡ": 168,
284
+ "ⵢ": 169,
285
+ "ⵣ": 170,
286
+ "ⵥ": 171,
287
+ "ⵖ": 172
288
+ },
289
+ "unk_token": "<unk>"
290
+ }
291
+ }
tokenizer_config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<s>",
4
+ "eos_token": "</s>",
5
+ "model_max_length": 1024,
6
+ "pad_token": "<pad>",
7
+ "tokenizer_class": "TokenizersBackend",
8
+ "unk_token": "<unk>"
9
+ }