Hukyl commited on
Commit
bbb6c09
ยท
0 Parent(s):

Initial release

Browse files
Files changed (4) hide show
  1. .gitattributes +35 -0
  2. README.md +244 -0
  3. best.pt +3 -0
  4. training_meta.jsonl +11 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - uk
4
+ library_name: pytorch
5
+ pipeline_tag: image-to-text
6
+ license: cc-by-nc-sa-4.0
7
+ base_model: Hukyl/parseq-b-cyrillic-handwritten
8
+ tags:
9
+ - ocr
10
+ - htr
11
+ - handwritten-text-recognition
12
+ - parseq
13
+ - ukrainian
14
+ - cyrillic
15
+ - rukopys
16
+ - image-to-text
17
+ datasets:
18
+ - UkrainianCatholicUniversity/rukopys
19
+ - annyhnatiuk/ukrainian-handwritten-text
20
+ - constantinwerner/cyrillic-handwriting-dataset
21
+ - ai-forever/school_notebooks_RU
22
+ - pumb-ai/synthetic-cyrillic-large
23
+ - nastyboget/synthetic_cyrillic_large
24
+ metrics:
25
+ - cer
26
+ - wer
27
+ model-index:
28
+ - name: parseq-b-rukopys
29
+ results:
30
+ - task:
31
+ type: image-to-text
32
+ name: Handwritten Text Recognition
33
+ dataset:
34
+ type: UkrainianCatholicUniversity/rukopys
35
+ name: Rukopys, page-level val
36
+ split: validation
37
+ metrics:
38
+ - type: cer
39
+ value: 0.0953
40
+ name: CER
41
+ - type: wer
42
+ value: 0.2796
43
+ name: WER
44
+ ---
45
+
46
+ # parseq-b-rukopys
47
+
48
+ A PARSeq-B (permuted autoregressive sequence) line recognizer for single-line
49
+ handwritten Ukrainian document text, fine-tuned on the
50
+ [Rukopys](https://huggingface.co/datasets/UkrainianCatholicUniversity/rukopys) dataset.
51
+
52
+ The training data included `handwritten`, `printed`, `annotation` and `table` region crops from Rukopys.
53
+
54
+ ## TL;DR
55
+
56
+ | | value |
57
+ |---|---|
58
+ | Architecture | PARSeq โ€” ViT-B encoder + 1-layer permutation-LM decoder (ECCV 2022) |
59
+ | Parameters | ~96M |
60
+ | Init from | [`Hukyl/parseq-b-cyrillic-handwritten`](https://huggingface.co/Hukyl/parseq-b-cyrillic-handwritten) (mixed pretrain) |
61
+ | Handles | `handwritten`, `printed`, `annotation` |
62
+ | Decode | AR greedy + 1 refinement iteration |
63
+ | Gold-val CER / WER | **0.0953 / 0.2796** |
64
+ | Input | a single cropped text line, BGR (numpy) โ€” resized to 48ร—512 |
65
+ | Output | the transcribed string (232-char frozen Cyrillic charset) |
66
+
67
+ ## Intended use
68
+
69
+ Recognizing single cropped lines/regions of handwritten Ukrainian documents, downstream
70
+ of a line/region detector or on already-segmented lines.
71
+
72
+ A companion ViT-S variant (~24M params) fine-tuned with the same recipe is available at
73
+ [`Hukyl/parseq-s-rukopys`](https://huggingface.co/Hukyl/parseq-s-rukopys). The two land
74
+ in a statistical tie on the same held-out split: the ViT-S is marginally ahead on the
75
+ dominant `handwritten` class, this model a little ahead on the low-support
76
+ `annotation`/`printed` classes.
77
+
78
+ ## How to use
79
+
80
+ Note: the checkpoint is a plain `torch.save` archive, not transformers-compatible. The
81
+ payload is a dict:
82
+
83
+ ```python
84
+ import torch
85
+ from huggingface_hub import hf_hub_download
86
+
87
+ path = hf_hub_download("Hukyl/parseq-b-rukopys", "best.pt")
88
+ payload = torch.load(path, map_location="cpu", weights_only=True)
89
+ # keys: "model_state" (state_dict), "charset" (str), "config" (dict),
90
+ # "metrics" (dict), "epoch" (int)
91
+ print(payload["config"])
92
+ # {'img_height': 48, 'img_width': 512, 'patch_size': (4, 8), 'embed_dim': 768,
93
+ # 'enc_num_heads': 12, 'enc_mlp_ratio': 4, 'enc_depth': 12, 'dec_num_heads': 12,
94
+ # 'dec_mlp_ratio': 4, 'dec_depth': 1, 'max_label_length': 100, 'dropout': 0.1,
95
+ # 'decode_ar': True, 'refine_iters': 1, 'drop_path_rate': 0.05}
96
+ ```
97
+
98
+ To run it: construct a PARSeq model (ViT encoder + 1-layer two-stream permutation
99
+ decoder; see [`baudm/parseq`](https://github.com/baudm/parseq), Apache-2.0) with the
100
+ embedded `config`, build a tokenizer over the embedded `charset` (token order is
101
+ `[E]` + the charset string + `[B]` + `[P]`; `[E]` is id 0), then
102
+ `model.load_state_dict(payload["model_state"])`.
103
+
104
+ The input geometry ships in the checkpoint's `config`: each crop is resized
105
+ unconditionally to 48ร—512 RGB (no aspect preservation; `INTER_AREA` on downscale โ€” an
106
+ `INTER_LINEAR` mismatch aliases on the heavy downscale and degrades accuracy) and
107
+ normalized `(x โˆ’ 0.5) / 0.5`. Output confidence is the mean per-step max-softmax over
108
+ the decoded sequence.
109
+
110
+ ## Architecture
111
+
112
+ PARSeq, after Bautista & Atienza, "Scene Text Recognition with Permuted Autoregressive
113
+ Sequence Models" (ECCV 2022, [arXiv:2207.06966](https://arxiv.org/abs/2207.06966)). Model
114
+ code is derived from [`baudm/parseq`](https://github.com/baudm/parseq) (Apache-2.0).
115
+ Geometry, decoder, and charset are identical to the ViT-S variant; only the encoder width
116
+ and head count are scaled up.
117
+
118
+ | component | value |
119
+ |---|---|
120
+ | encoder | ViT-B โ€” 12 layers, dim 768, 12 heads, MLP ratio 4, patch 4ร—8 |
121
+ | decoder | 1-layer two-stream permutation-LM, 12 heads, MLP ratio 4 |
122
+ | training | K=6 permutations (permuted AR sequence modeling) |
123
+ | decode | autoregressive greedy + 1 refinement iteration |
124
+ | dropout / drop-path | 0.1 / 0.05 |
125
+ | input | 48ร—512 RGB, unconditional resize, `(xโˆ’0.5)/0.5` |
126
+ | max label length | 100 |
127
+ | parameters | ~96M |
128
+
129
+ ### Charset
130
+
131
+ A frozen 232-character Cyrillic charset (`sha256 d1b9161eโ€ฆ3ff976`). Token IDs follow
132
+ string order; specials are `[E]` (id 0) first and `[B]`, `[P]` last. Text is not
133
+ NFKD-normalized. The set covers the full Ukrainian Cyrillic block (incl. ั– ั— ั” า‘ and
134
+ the apostrophe), the Russian-only capitals ะญ ะ and letters ัŠ ั‹ ั‘, digits,
135
+ punctuation, and a small set of Greek/math symbols. Latin lookalikes that are
136
+ visually identical to Cyrillic letters (`a c e i o y`; `A B C E H K M O P T X`) are
137
+ deliberately excluded โ€” the target output space is Cyrillic-normalized, so those are
138
+ folded to their Cyrillic counterparts.
139
+
140
+ ## Training curriculum
141
+
142
+ Two stages, seed 42. The trainer filters samples to the frozen charset and drops labels
143
+ longer than 100 characters before training.
144
+
145
+ 1. **Mixed pretrain** โ€”
146
+ [`Hukyl/parseq-b-cyrillic-handwritten`](https://huggingface.co/Hukyl/parseq-b-cyrillic-handwritten):
147
+ general Cyrillic handwriting reader trained from scratch on a real + synthetic mix
148
+ (~0.65M crops/epoch, synthetic:real โ‰ˆ 1.15:1). See the pretrain card for the full mix
149
+ and recipe.
150
+ 2. **Rukopys gold fine-tune** (15 ep, LR 5e-5 peak): warm-started from the stage-1
151
+ weights, fine-tuned on the human-labeled Rukopys gold data, with page-level
152
+ validation split โ€” 19,196 train / 3,202 val crops (`handwritten` 18,414 /
153
+ `annotation` 450 / `printed` 240 / `table` 122 in train, before charset/length
154
+ filtering). The published weights are the best-val-CER epoch (13 of 15).
155
+
156
+ ### Stage-2 hyperparameters (as launched)
157
+
158
+ | hyperparameter | value |
159
+ |---|---|
160
+ | epochs | 15 |
161
+ | effective batch size | 64 (micro-batch 64 ร— accum 1) |
162
+ | learning rate | 5e-5 peak |
163
+ | schedule | OneCycleLR, warmup 5% |
164
+ | optimizer | AdamW, weight_decay 0.05 |
165
+ | label smoothing | 0.1 |
166
+ | drop-path rate | 0.05 |
167
+ | permutations (K) | 6 |
168
+ | gradient clipping | 20 |
169
+ | precision | bf16 AMP |
170
+ | split | by-page (~185 held-out pages) |
171
+ | seed | 42 |
172
+
173
+ ### Online data augmentation
174
+
175
+ Online augmentation was applied during training (`default` profile). Only training
176
+ crops were augmented, and every train crop was augmented (`identity_prob = 0.0`), at
177
+ the 48ร—512 working resolution (downscaled before augmenting).
178
+
179
+ Each crop was transformed once per epoch by _one geometric_ + _one or two photometric_ operations at random. Each class received a separate augmentation profile that was selected to minimize the distribution shift.
180
+
181
+ | pool | handwritten / annotation | printed |
182
+ |---|---|---|
183
+ | geometric (pick 1) | margin pad 0.02โ€“0.15 / trim 0.01โ€“0.05, rotation ยฑ1โ€“5ยฐ, elastic distortion (ฮฑ25 ฯƒ5), baseline warp (amp 2โ€“8 px, freq 0.5โ€“2) | margin pad 0.02โ€“0.15 / trim 0.01โ€“0.05, rotation ยฑ1โ€“3ยฐ |
184
+ | photometric (pick 1โ€“2) | paper-colour shift, Gaussian noise ฯƒ5โ€“15, JPEG q30โ€“65, contrast 0.7โ€“1.3 / gamma 0.6โ€“1.5, morphological op (k=2) | paper-colour shift, Gaussian noise ฯƒ3โ€“10, JPEG q40โ€“70, contrast 0.8โ€“1.2 / gamma 0.8โ€“1.3, morphological op (k=2) |
185
+
186
+ ## Results
187
+
188
+ Protocol: AR greedy + 1 refinement iteration, page-level gold validation split
189
+ (val crops come from pages the model never saw in training), seed 42.
190
+
191
+ ### Overall
192
+
193
+ | metric | training-time best | standalone predictor eval (n=3,202) |
194
+ |---|---|---|
195
+ | CER | 0.0953 | 0.0958 |
196
+ | WER | 0.2796 | 0.2802 |
197
+ | exact-match accuracy | โ€” | 0.4316 |
198
+
199
+ ### Per class
200
+
201
+ | class | n | CER | WER | accuracy |
202
+ |---|---:|---:|---:|---:|
203
+ | handwritten | 3,067 | 0.0849 | 0.2664 | 0.4356 |
204
+ | annotation | 73 | 0.4222 | 0.5882 | 0.4932 |
205
+ | printed | 48 | 0.3279 | 0.6527 | 0.2083 |
206
+ | table | 14 | 1.1667 | 1.4706 | 0.0000 |
207
+
208
+ We also acknowledge that `printed`/`annotation`/`table` n is quite small, so measuring
209
+ CER against them is quite noisy. `table` (multi-line pipe-separated serialization) is
210
+ not a target of this model; its CER above 1 is expected.
211
+
212
+ ## Limitations & biases
213
+
214
+ - Handwritten Ukrainian archival document material only; expect degradation on other
215
+ scripts, languages, or modern born-digital text.
216
+ - The character set and normalization are tuned to the document-OCR metric โ€” outputs are
217
+ normalized text, not faithful transcription. Latin lookalikes are folded to their
218
+ Cyrillic counterparts, so the model never emits the Latin forms.
219
+ - `printed` and `annotation` have small training support and high CER; `table` and
220
+ `formula` are unsupported.
221
+ - Single seed and validation split โ€” no across-run variance estimate.
222
+
223
+ ## Training data & attribution
224
+
225
+ | dataset | source | license | role |
226
+ |---|---|---|---|
227
+ | Rukopys | [`UkrainianCatholicUniversity/rukopys`](https://huggingface.co/datasets/UkrainianCatholicUniversity/rukopys) | CC BY-NC-SA 4.0 (subject to change, verify with the source repository) | gold fine-tune |
228
+ | UkrHandwritten | Kaggle [`annyhnatiuk/ukrainian-handwritten-text`](https://www.kaggle.com/datasets/annyhnatiuk/ukrainian-handwritten-text) | CC BY-SA 4.0 | pretrain (lineage) |
229
+ | Cyrillic Handwriting Dataset | Kaggle [`constantinwerner/cyrillic-handwriting-dataset`](https://www.kaggle.com/datasets/constantinwerner/cyrillic-handwriting-dataset) | CC0 | pretrain (lineage) |
230
+ | school_notebooks_RU | HF [`ai-forever/school_notebooks_RU`](https://huggingface.co/datasets/ai-forever/school_notebooks_RU) | MIT | pretrain (lineage) |
231
+ | pumb-ai/synthetic-cyrillic-large | HF [`pumb-ai/synthetic-cyrillic-large`](https://huggingface.co/datasets/pumb-ai/synthetic-cyrillic-large) | Apache-2.0 | pretrain (lineage) |
232
+ | nastyboget/synthetic_cyrillic_large | HF [`nastyboget/synthetic_cyrillic_large`](https://huggingface.co/datasets/nastyboget/synthetic_cyrillic_large) | MIT | pretrain (lineage) |
233
+
234
+ **Attribution is mandatory** for UkrHandwritten (CC BY-SA 4.0, share-alike) โ€” please keep
235
+ this credit if you redistribute or build on this model.
236
+
237
+ ## License & lineage
238
+
239
+ Model weights are released under CC BY-NC-SA 4.0 โ€” the most restrictive of the training
240
+ inputs (Rukopys is non-commercial as of time of publishing this model; UkrHandwritten is
241
+ share-alike). The PARSeq model code is Apache-2.0
242
+ ([`baudm/parseq`](https://github.com/baudm/parseq)). The stage-1
243
+ [pretrain checkpoint](https://huggingface.co/Hukyl/parseq-b-cyrillic-handwritten), which
244
+ did not train on Rukopys, is released under CC BY-SA 4.0.
best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:02ff30dc409494afdd5c7b0305243ce2c02ca0dd2502dfb6c48e3ec2c6650afd
3
+ size 382495362
training_meta.jsonl ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"event": "run_start", "at": "2026-06-29 17:37:12 UTC", "recipe": {"epochs": 15, "batch_size": 64, "accum_steps": 1, "effective_batch": 64, "learning_rate": 5e-05, "warmup_pct": 0.05, "weight_decay": 0.05, "label_smoothing": 0.1, "drop_path_rate": 0.05, "swa_epoch_start": 0.75, "grad_clip": 20.0, "perm_num": 6, "augment": true, "augment_profile": "default", "augment_identity_prob": 0.0, "seed": 42}, "data": {"n_train": 19196, "n_val": 3202, "dropped_unencodable_train": 30, "dropped_unencodable_val": 7}, "pretrained": "models/parseq_pretrain_b_main/best.pt"}
2
+ {"event": "best", "at": "2026-06-29 17:40:29 UTC", "epoch": 0, "swa": false, "metrics": {"cer": 0.1484086642903845, "wer": 0.3925978281822271, "accuracy": 0.32104934415990005, "n_samples": 3202.0}}
3
+ {"event": "best", "at": "2026-06-29 17:42:39 UTC", "epoch": 1, "swa": false, "metrics": {"cer": 0.1254719169847932, "wer": 0.3521172010537814, "accuracy": 0.3644597126795753, "n_samples": 3202.0}}
4
+ {"event": "best", "at": "2026-06-29 17:47:13 UTC", "epoch": 3, "swa": false, "metrics": {"cer": 0.11891253453694134, "wer": 0.32243140782625457, "accuracy": 0.40037476577139286, "n_samples": 3202.0}}
5
+ {"event": "best", "at": "2026-06-29 17:49:29 UTC", "epoch": 4, "swa": false, "metrics": {"cer": 0.11165714044671293, "wer": 0.3153633618197006, "accuracy": 0.40349781386633354, "n_samples": 3202.0}}
6
+ {"event": "best", "at": "2026-06-29 17:58:09 UTC", "epoch": 6, "swa": false, "metrics": {"cer": 0.10672178516440639, "wer": 0.2990426010409304, "accuracy": 0.41817613991255465, "n_samples": 3202.0}}
7
+ {"event": "best", "at": "2026-06-29 18:03:26 UTC", "epoch": 7, "swa": false, "metrics": {"cer": 0.10239807647691561, "wer": 0.2926813596350318, "accuracy": 0.42192379762648347, "n_samples": 3202.0}}
8
+ {"event": "best", "at": "2026-06-29 18:08:36 UTC", "epoch": 8, "swa": false, "metrics": {"cer": 0.10132242212051547, "wer": 0.28940435648653856, "accuracy": 0.4287945034353529, "n_samples": 3202.0}}
9
+ {"event": "best", "at": "2026-06-29 18:13:36 UTC", "epoch": 9, "swa": false, "metrics": {"cer": 0.09831691730116213, "wer": 0.28554905866478186, "accuracy": 0.4306683322923173, "n_samples": 3202.0}}
10
+ {"event": "best", "at": "2026-06-29 18:22:27 UTC", "epoch": 11, "swa": false, "metrics": {"cer": 0.09736781051610317, "wer": 0.2812439760971535, "accuracy": 0.4306683322923173, "n_samples": 3202.0}}
11
+ {"event": "best", "at": "2026-06-29 18:32:35 UTC", "epoch": 13, "swa": false, "metrics": {"cer": 0.09525868432708329, "wer": 0.2795733470410589, "accuracy": 0.43191755153029354, "n_samples": 3202.0}}