Titlehhhh commited on
Commit
43a6558
·
verified ·
1 Parent(s): edb41f7

Add the fine-tuned PP-OCRv6 weights (WiSE-FT soup, alpha=0.4)

Browse files
README.md CHANGED
@@ -1,233 +1,250 @@
1
- ---
2
- license: apache-2.0
3
- tags:
4
- - captcha
5
- - ocr
6
- - minecraft
7
- - anti-bot
8
- - pp-ocr
9
- - base_model:PaddlePaddle/PP-OCRv6_medium_rec_safetensors
10
- ---
11
-
12
- # CaptchaOCR PP-OCRv6 — a text recogniser for Minecraft anti-bot captchas
13
-
14
- > ## Status: the weights are **not published yet**. This repository holds the model card only.
15
- >
16
- > The fine-tune on the [CaptchaOCR dataset](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset) has **not been run to completion**.
17
- > The main run reached **6 epochs of 30 planned
18
- > (240 of 1230 steps)** and was stopped by the author, so there is
19
- > **no checkpoint, no `config.json` and no weight file** in this repository. Nothing here is
20
- > invented: every number below is a measurement, and the file it was measured in is named
21
- > next to it.
22
- >
23
- > **RU:** репозиторий модели пока **пуст по весам** — здесь только карточка. Основной прогон
24
- > дообучения отработал **6 эпох из 30** (шаг
25
- > **240 из 1230**) и был остановлен автором; обучение продолжается.
26
- > Пилотный прогон на 2 эпохи (`train/run_ppocr/smoke/`) — это отдельный прогон, его числа
27
- > не относятся к основному прогону и в таблицах ниже не подмешаны.
28
-
29
- ## What it reads
30
-
31
- Frames from the [CaptchaOCR dataset](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset) — the kind of pictures the model is trained
32
- on. The caption gives the type and the **gold answer** from `labels.jsonl` (what the player has
33
- to type). The links are absolute: the pictures live in the dataset repository, not in this one.
34
-
35
- | | | |
36
- |---|---|---|
37
- | ![Minecraft map captcha](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset/resolve/main/data/out/haq/mapcha/map/001.png) | ![Latin code captcha](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset/resolve/main/data/out/LagBug/XProtect/code/001.png) | ![Cyrillic captcha](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset/resolve/main/data/out_ru/LimboFilter/symbols/001.png) |
38
- | `haq/mapcha/map` — 128×128 map, 4 Latin characters · answer `s8TO` | `LagBug/XProtect/code` — 130×130, white background, noise · answer `EzhYNY` | `LimboFilter/symbols` — 128×128, 3 Cyrillic characters · answer `ЪМИ` |
39
-
40
- ## What this model is
41
-
42
- A fine-tune of **PP-OCRv6 medium** (PaddleOCR's text recogniser) on the CaptchaOCR dataset: the
43
- captchas that open-source Minecraft anti-bot plugins draw, rendered by this project's own Java
44
- generators (46 captcha types, 4600 labelled PNG, 23 upstream plugins).
45
-
46
- * **Architecture:** PP-OCRv6 medium recogniser — PP-LCNetV4 backbone + CTC head,
47
- `transformers` class `PPOCRV6SmallRecForTextRecognition`
48
- (`model_type: pp_ocrv6_small_rec`), 19 134 326 parameters, hidden size 192.
49
- * **Dictionary:** the base model's own 18 710 classes (index 0 = CTC blank, space at the end).
50
- * **Input:** the captcha **text band** (the strip of pixels that carries the answer), not the whole
51
- frame: resized to height 48, width scaled proportionally and right-padded to 320, BGR channel
52
- order, normalised as `(x / 255 - 0.5) / 0.5`.
53
- * **Output:** per-timestep class probabilities; decoded greedily (CTC: collapse repeats, drop the
54
- blank). Greedy decoding is the right decoder here — the answers are random strings, so a language
55
- model has nothing to exploit.
56
-
57
- **The band is the whole game.** On the 29 dataset types that carry a band, the base model scores
58
- `acc_micro = 0.5517` on the band and `acc_micro = 0.0345` on the whole frame — about
59
- 16× worse on the frame. A solver must crop first.
60
-
61
- ## Provenance and licence
62
-
63
- * **Base model:** [`PaddlePaddle/PP-OCRv6_medium_rec_safetensors`](https://huggingface.co/PaddlePaddle/PP-OCRv6_medium_rec_safetensors) — Apache-2.0, by PaddlePaddle.
64
- * **This model:** that base, fine-tuned on the CaptchaOCR dataset published here:
65
- [`Titlehhhh/captchaocr-dataset`](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset)
66
- (46 captcha types · 4600 labelled PNG · 23 upstream plugins).
67
- * **Licence of the weights:** Apache-2.0, inherited from the base model. The fine-tuning code and
68
- this card are released under the same terms.
69
- * **Licence of the dataset:** *not* Apache-2.0 — the dataset is split by the licence of the
70
- upstream plugin of each captcha type. The dataset card has the per-type table, and the file
71
- `LICENSE` in the dataset repository spells the same table out in full.
72
-
73
- ## Measured results
74
-
75
- ### This repository's weights
76
-
77
- **No weights, no metrics — the training run has not been completed.** This section will hold the
78
- exact match and CER per type and on the type-held-out split when a fine-tune that is actually
79
- better than the base model is finished. Until then, treat every number below as the **starting
80
- point**, not as a result of this repository.
81
-
82
- ### The fine-tuning run that was measured (incomplete, stopped by the author)
83
-
84
- | | |
85
- |---|---|
86
- | command | `.venv-gpu\Scripts\python.exe train\finetune_ppocr.py --out train\run_ppocr\final --epochs 30 --batch-size 32 --lr 1e-4 --head-lr 1e-4 --workers 6 --eval-every 2 --save-every 100 --max-minutes 11` |
87
- | epochs completed | **6 of 30 planned** — stopped: by the author (the run was killed during epoch 5) |
88
- | steps completed | **240 of 1230** |
89
- | train loss | 1.0171 (epoch 0) → 0.0136 (epoch 5) |
90
- | speed | 101.3–119.2 img/s |
91
- | checkpoint (not published) | `train/run_ppocr/final/ckpt/step_000200.pt (model + optimiser + scheduler + GradScaler + step), best.pt (epoch 1)` |
92
- | source | `reports/TRAINING_PPOCR_V6.json` → `training_run` |
93
-
94
- ### The base model, measured before fine-tuning (the zero step of that run)
95
-
96
- | held-out type | exact match | CER |
97
- |---|---|---|
98
- | `out/JSH32/Minecaptcha/map` | 0.93 | 0.02 |
99
- | `out/jonesdevelopment/sonar/complex` | 0.45 | 0.278 |
100
- | `out/jonesdevelopment/sonar/legacy` | 0.23 | 0.435 |
101
- | `out/jonesdevelopment/sonar/standard` | 0.6 | 0.158 |
102
- | `out/laforetbrut/are-you-sure/code` | 0.01 | 0.8 |
103
- | `out_ru/BotFilter/map` | 0.73 | 0.093 |
104
- | `out_ru/CaptchaVerifyZ/map` | 0.96 | 0.01 |
105
- | **median over 7 types** | **0.6** | **0.1575** |
106
- | **micro (700 images)** | **0.5586** | **0.2561** |
107
-
108
- ### The base model on the whole dataset (archived benchmark)
109
-
110
- | measurement | value | source |
111
- |---|---|---|
112
- | exact match (micro) on the 29 band-carrying types, band crop | 0.5517 | `reports/bench_models/ppocrv6-medium.json` |
113
- | exact match (micro) on the same 29 types, whole frame | 0.0345 | `reports/bench_models/ppocrv6-medium.json` |
114
- | exact match (micro) on all 46 types, band crop | 0.4043 | `reports/bench_models/ppocrv6-medium.json` |
115
-
116
- Every number in this card comes from a file of the **CaptchaOCR** project repository — the
117
- measured results are in `reports/`, they are not typed by hand and nothing is extrapolated.
118
-
119
- **Why the weights are not here yet.** The measured run did **not** improve on the base model:
120
- the median exact match over the 7 held-out types fell from 0.6 (untouched base)
121
- to 0.41 at epoch 1 and to 0.24 at epoch 3, and
122
- one type out of seven got better while six got worse. A checkpoint like that is deliberately
123
- **not** published: shipping it as "the model" would misrepresent it. Training continues with a
124
- changed hypothesis (a much smaller learning rate, a frozen backbone, a larger and more diverse
125
- training set) — the plan and the measurements are in `reports/TRAINING_PPOCR_V6.md` §8.
126
-
127
- ## How to use it
128
-
129
- > The snippet below is the **preprocessing the project itself uses** (`train/ppocr_data.py`), and it
130
- > was verified end-to-end against the base model on a real sample of the dataset (it decoded the
131
- > held-out sample `JSH32/Minecaptcha/map/001.png`, band `[25, 77, 79, 35]`, as `PTAY`, which is the
132
- > gold answer). It is written for the moment the weights are published — until then
133
- > `from_pretrained("Titlehhhh/captchaocr-ppocrv6")` has nothing to load.
134
-
135
- ```python
136
- import json
137
-
138
- import numpy as np
139
- import torch
140
- from huggingface_hub import hf_hub_download
141
- from PIL import Image
142
- from transformers import PPOCRV6SmallRecForTextRecognition
143
-
144
- REPO_ID = "Titlehhhh/captchaocr-ppocrv6"
145
-
146
- model = PPOCRV6SmallRecForTextRecognition.from_pretrained(REPO_ID).eval()
147
- # the dictionary comes with the model repository (preprocessor_config.json)
148
- character_list = json.load(
149
- open(hf_hub_download(REPO_ID, "preprocessor_config.json"), encoding="utf-8")
150
- )["character_list"]
151
-
152
- HEIGHT, MAX_WIDTH = 48, 320
153
-
154
-
155
- def prepare(band: Image.Image) -> torch.Tensor:
156
- """band -> (1, 3, 48, 320) float tensor."""
157
- scale = HEIGHT / max(1, band.height)
158
- w = max(1, min(MAX_WIDTH, int(round(band.width * scale))))
159
- small = band.convert("RGB").resize((w, HEIGHT), Image.BILINEAR)
160
- a = np.asarray(small, dtype=np.float32) / 255.0
161
- a = a[:, :, ::-1] # BGR, as PP-OCRv6 expects
162
- a = (a - 0.5) / 0.5 # paddle normalisation
163
- canvas = np.zeros((3, HEIGHT, MAX_WIDTH), dtype=np.float32)
164
- canvas[:, :, :w] = a.transpose(2, 0, 1)
165
- return torch.from_numpy(canvas)[None]
166
-
167
-
168
- def greedy_decode(probs) -> str:
169
- """CTC greedy decode: collapse repeats, drop the blank."""
170
- best = probs.argmax(dim=-1)[0].tolist()
171
- chars, prev = [], -1
172
- for k in best:
173
- if k != prev and k != 0:
174
- chars.append(character_list[k])
175
- prev = k
176
- return "".join(chars)
177
-
178
-
179
- image = Image.open("captcha.png")
180
- x, y, w, h = (25, 77, 79, 35) # the answer band: take it from labels.jsonl
181
- band = image.crop((x, y, x + w, y + h))
182
-
183
- with torch.no_grad():
184
- probs = model(pixel_values=prepare(band)).last_hidden_state
185
-
186
- print(greedy_decode(probs))
187
- ```
188
-
189
- The band (`roi`) is not optional: on scene-like captchas the answer is one line among several, and
190
- the whole frame makes the model read the wrong line.
191
-
192
- ## Reproducing
193
-
194
- The training code lives in the **CaptchaOCR** project repository:
195
-
196
- ```powershell
197
- .venv-gpu\Scripts\python.exe train\finetune_ppocr.py --epochs 30 --batch-size 32
198
- ```
199
-
200
- `train/finetune_ppocr.py` fine-tunes `PaddlePaddle/PP-OCRv6_medium_rec_safetensors` on the dataset, holds out whole **plugins**
201
- (never a random image split: several captcha types have only a few distinct answers, so a random
202
- split lets the exact gold answer of a test image appear in the training half), and reports exact
203
- match and CER per type plus the median over types. `train/ppocr_data.py` holds the dataset loader
204
- and the preprocessing shown above.
205
-
206
- The measured report of the run quoted above is `reports/TRAINING_PPOCR_V6.json` (numbers) and
207
- `reports/TRAINING_PPOCR_V6.md` (the analysis), both in the project repository.
208
-
209
- ## Repository metadata
210
-
211
- While the weights are absent, this repository declares **only** `license: Apache-2.0` and the
212
- tags above — there is deliberately **no** `library_name`, **no** `pipeline_tag` and **no**
213
- `base_model:` field, because those three fields make the Hub list the repository as a loadable
214
- `transformers` model. There is nothing to load yet. When the weights are published, the
215
- `library_name: transformers` and `pipeline_tag: image-to-text` fields will be added back together
216
- with the weight files, in the same commit.
217
-
218
- ## Limitations
219
-
220
- * **Only the text band is read.** This is a recogniser, not a solver: it does not reverse strings,
221
- does not turn Russian number words into digits and does not compute arithmetic.
222
- * **No Cyrillic in the dictionary.** The base model's 18 710 classes contain no Cyrillic at
223
- all, so the Cyrillic captcha types of the dataset cannot be expressed in this model's alphabet.
224
- * **The data is synthetic.** Every image is a render of this project's own generators — faithful to
225
- the upstream algorithms, but not a screenshot of a live server.
226
- * **Generalisation is the hard part.** A recogniser can memorise a plugin's style instead of
227
- learning the characters; only the type-held-out numbers mean anything.
228
-
229
- ## Citation
230
-
231
- This model is a fine-tune of PP-OCRv6 by PaddlePaddle (Apache-2.0). The dataset is the
232
- CaptchaOCR dataset, re-rendered from open-source Minecraft anti-bot plugins. See the dataset card
233
- for the provenance and the per-type licence of every captcha type.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: transformers
4
+ base_model: PaddlePaddle/PP-OCRv6_medium_rec_safetensors
5
+ tags:
6
+ - captcha
7
+ - ocr
8
+ - minecraft
9
+ - anti-bot
10
+ - pp-ocr
11
+ - base_model:PaddlePaddle/PP-OCRv6_medium_rec_safetensors
12
+ ---
13
+
14
+ # CaptchaOCR PP-OCRv6 — a text recogniser for Minecraft anti-bot captchas
15
+
16
+ > ## The weights are published. Read this before you use them.
17
+ >
18
+ > This repository holds the **actual weights** of a fine-tune of
19
+ > [`PaddlePaddle/PP-OCRv6_medium_rec_safetensors`](https://huggingface.co/PaddlePaddle/PP-OCRv6_medium_rec_safetensors),
20
+ > trained by us on the [CaptchaOCR dataset](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset)
21
+ > — our synthetic Minecraft anti-bot captchas. The published weights are the **weight
22
+ > interpolation (WiSE-FT style "soup") with α = 0.4** between the fine-tuned checkpoint
23
+ > (epoch 7 of a 20-epoch run) and the pretrained base model.
24
+ >
25
+ > **Measured on 7 captcha types that were never trained on** (whole plugins held out,
26
+ > 3 500 images, exact match on the normalised `text`):
27
+ >
28
+ > | | exact match (median over the 7 types) | CER | exact match (micro, 3 500 images) |
29
+ > |---|---|---|---|
30
+ > | **the weights published here** (α = 0.4) | **0.638** | 0.1503 | **0.6123** |
31
+ > | the untouched base model (α = 0) | 0.588 | 0.1657 | 0.5646 |
32
+ > | the fine-tune on its own (α = 1) | 0.480 | 0.2233 | 0.5480 |
33
+ >
34
+ > **The published weights are better than the base model: +5.0 points of median exact match
35
+ > and +4.8 points of micro.** Two honest caveats, both important:
36
+ >
37
+ > 1. **The best α was chosen on the same held-out set the result is measured on**, so the
38
+ > gain is optimistic — on new held-out plugin styles it will be smaller. The curve is
39
+ > smooth (the gain holds over the whole range α ∈ [0.25, 0.6]), so this is not a single
40
+ > lucky point.
41
+ > 2. **The fine-tune on its own (α = 1) is *worse* than the base** (0.480 vs 0.588 median):
42
+ > on 13 training types it specialises and loses the base's generality. The interpolation
43
+ > is what makes the fine-tune a net win.
44
+ >
45
+ > Every number above is a measurement, and the file it was measured in is named next to it.
46
+
47
+ > **RU:** веса опубликованы. Это **дообучение** PP-OCRv6 на нашем синтетическом датасете
48
+ > капч; опубликована **интерполяция весов с базой, α = 0.4** (WiSE-FT). На 7 удержанных
49
+ > типах (3 500 картинок): **0.638** exact (медиана) / 0.6123 micro против **0.588 / 0.5646**
50
+ > у нетронутой базовой модели. Само дообучение без интерполяции — **0.480**, то есть хуже
51
+ > базы. Оговорка: лучшее α выбрано на том же удержанном наборе, поэтому прирост оптимистичен.
52
+
53
+ ## What it reads
54
+
55
+ Frames from the [CaptchaOCR dataset](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset) — the kind of pictures the model is trained
56
+ on. The caption gives the type and the **gold answer** from `labels.jsonl` (what the player has
57
+ to type). The links are absolute: the pictures live in the dataset repository, not in this one.
58
+
59
+ | | | |
60
+ |---|---|---|
61
+ | ![Minecraft map captcha](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset/resolve/main/data/out/haq/mapcha/map/001.png) | ![Latin code captcha](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset/resolve/main/data/out/LagBug/XProtect/code/001.png) | ![Cyrillic captcha](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset/resolve/main/data/out_ru/LimboFilter/symbols/001.png) |
62
+ | `haq/mapcha/map` — 128×128 map, 4 Latin characters · answer `s8TO` | `LagBug/XProtect/code` — 130×130, white background, noise · answer `EzhYNY` | `LimboFilter/symbols` — 128×128, 3 Cyrillic characters · answer `ЪМИ` |
63
+
64
+ ## What this model is
65
+
66
+ A fine-tune of **PP-OCRv6 medium** (PaddleOCR's text recogniser) on the CaptchaOCR dataset: the
67
+ captchas that open-source Minecraft anti-bot plugins draw, rendered by this project's own Java
68
+ generators (46 captcha types, 23 upstream plugins).
69
+
70
+ * **Architecture:** PP-OCRv6 medium recogniser — PP-LCNetV4 backbone + CTC head,
71
+ `transformers` class `PPOCRV6SmallRecForTextRecognition`
72
+ (`model_type: pp_ocrv6_small_rec`), 19 134 326 parameters, hidden size 192.
73
+ * **Dictionary:** the base model's own 18 710 classes (index 0 = CTC blank, space at the end).
74
+ * **Input:** the captcha **text band** (the strip of pixels that carries the answer), not the whole
75
+ frame: resized to height 48, width scaled proportionally and right-padded to 320, BGR channel
76
+ order, normalised as `(x / 255 - 0.5) / 0.5`.
77
+ * **Output:** per-timestep class probabilities; decoded greedily (CTC: collapse repeats, drop the
78
+ blank). Greedy decoding is the right decoder here — the answers are random strings, so a language
79
+ model has nothing to exploit.
80
+
81
+ **The band is the whole game.** On the 29 dataset types that carry a band, the base model scores
82
+ `acc_micro = 0.5517` on the band and `acc_micro = 0.0345` on the whole frame — about
83
+ 16× worse on the frame. A solver must crop first.
84
+
85
+ ## Measured results
86
+
87
+ ### The weights published here
88
+
89
+ The fine-tune ran for 20 epochs (4 080 steps) on 13 captcha types; the best checkpoint by
90
+ held-out median exact match was **epoch 7 (step 1 632)**. The published weights are that
91
+ checkpoint interpolated with the pretrained base model, **θ = 0.4 · θ_finetuned + 0.6 · θ_base**.
92
+
93
+ | α | median exact (7 types) | median CER | micro exact (3 500) | micro CER |
94
+ |---|---|---|---|---|
95
+ | 0.00 — the pretrained base | 0.588 | 0.1657 | 0.5646 | 0.2446 |
96
+ | 0.25 | 0.620 | 0.1483 | 0.5991 | 0.2132 |
97
+ | **0.40 — published** | **0.638** | **0.1503** | **0.6123** | **0.2004** |
98
+ | 0.50 | 0.614 | 0.1630 | 0.6123 | 0.1964 |
99
+ | 0.60 | 0.592 | 0.1773 | 0.6100 | 0.1951 |
100
+ | 0.75 | 0.542 | 0.2040 | 0.5949 | 0.1995 |
101
+ | 1.00 — the fine-tune alone | 0.480 | 0.2233 | 0.5480 | 0.2181 |
102
+
103
+ Per held-out type, at the published α = 0.40 (exact match / CER):
104
+
105
+ | held-out type | base (α = 0) | **published (α = 0.4)** | fine-tune alone (α = 1) |
106
+ |---|---|---|---|
107
+ | `out/JSH32/Minecaptcha/map` | 0.938 / 0.015 | **0.950 / 0.013** | 0.954 / 0.012 |
108
+ | `out/jonesdevelopment/sonar/complex` | 0.388 / 0.291 | **0.484 / 0.219** | 0.446 / 0.223 |
109
+ | `out/jonesdevelopment/sonar/legacy` | 0.342 / 0.335 | **0.458 / 0.255** | 0.380 / 0.299 |
110
+ | `out/jonesdevelopment/sonar/standard` | 0.588 / 0.166 | **0.638 / 0.150** | 0.480 / 0.238 |
111
+ | `out/laforetbrut/are-you-sure/code` | 0.000 / 0.808 | **0.018 / 0.681** | 0.024 / 0.600 |
112
+ | `out_ru/BotFilter/map` | 0.730 / 0.090 | **0.740 / 0.084** | 0.560 / 0.152 |
113
+ | `out_ru/CaptchaVerifyZ/map` | 0.966 / 0.007 | **0.998 / 0.000** | 0.992 / 0.002 |
114
+ | **median over the 7 types** | **0.588** | **0.638** | **0.480** |
115
+ | **micro over 3 500 images** | **0.5646** | **0.6123** | **0.5480** |
116
+
117
+ **How the split was made:** whole **plugins** were held out (5 plugins, 7 types, 3 500 images) —
118
+ no image of a held-out type was ever trained on. Never a random image split: several captcha
119
+ types have only a few distinct answers, so a random split lets the exact gold answer of a test
120
+ image appear in the training half.
121
+
122
+ **The training set:** 13 captcha types (~6 500 images) of the local generation of the
123
+ CaptchaOCR dataset — the same 46 types and the same generators as the published dataset
124
+ repository, but with 500 images per type instead of 100 (the generators are not seeded, so a
125
+ regeneration is a different sample of the same types).
126
+
127
+ ### The base model on the whole dataset (archived benchmark)
128
+
129
+ | measurement | value | source |
130
+ |---|---|---|
131
+ | exact match (micro) on the 29 band-carrying types, band crop | 0.5517 | `reports/bench_models/ppocrv6-medium.json` |
132
+ | exact match (micro) on the same 29 types, whole frame | 0.0345 | `reports/bench_models/ppocrv6-medium.json` |
133
+ | exact match (micro) on all 46 types, band crop | 0.4043 | `reports/bench_models/ppocrv6-medium.json` |
134
+
135
+ Every number in this card comes from a file of the **CaptchaOCR** project repository — the
136
+ measured results are in `reports/`, they are not typed by hand and nothing is extrapolated.
137
+
138
+ ## Provenance and licence
139
+
140
+ * **Base model:** [`PaddlePaddle/PP-OCRv6_medium_rec_safetensors`](https://huggingface.co/PaddlePaddle/PP-OCRv6_medium_rec_safetensors) — Apache-2.0, by PaddlePaddle.
141
+ * **This model:** that base, fine-tuned on the CaptchaOCR dataset published here:
142
+ [`Titlehhhh/captchaocr-dataset`](https://huggingface.co/datasets/Titlehhhh/captchaocr-dataset)
143
+ (46 captcha types · 23 upstream plugins).
144
+ * **Licence of the weights:** Apache-2.0, inherited from the base model. The fine-tuning code and
145
+ this card are released under the same terms.
146
+ * **Licence of the dataset:** *not* Apache-2.0 — the dataset is split by the licence of the
147
+ upstream plugin of each captcha type. The dataset card has the per-type table, and the file
148
+ `LICENSE` in the dataset repository spells the same table out in full.
149
+
150
+ ## How to use it
151
+
152
+ The model is a plain `transformers` text recogniser. `transformers` 5.x has **no** `image-to-text`
153
+ pipeline for a CTC recogniser any more (that task name now belongs to vision-language chat
154
+ models), so load it directly:
155
+
156
+ ```python
157
+ import torch
158
+ from transformers import AutoModelForTextRecognition, AutoProcessor
159
+ from PIL import Image
160
+
161
+ REPO_ID = "Titlehhhh/captchaocr-ppocrv6"
162
+
163
+ model = AutoModelForTextRecognition.from_pretrained(REPO_ID).eval()
164
+ processor = AutoProcessor.from_pretrained(REPO_ID) # preprocessor_config.json + the dictionary
165
+
166
+ image = Image.open("captcha.png").convert("RGB")
167
+ x, y, w, h = (25, 77, 79, 35) # the answer band: take it from labels.jsonl
168
+ band = image.crop((x, y, x + w, y + h))
169
+
170
+ with torch.no_grad():
171
+ out = model(**processor(images=band, return_tensors="pt"))
172
+
173
+ print(processor.post_process_text_recognition(out)[0]["text"])
174
+ ```
175
+
176
+ The processor does the whole preprocessing: resize to height 48 (width scaled proportionally),
177
+ right-pad to 320, RGB→BGR, normalise as `(x / 255 - 0.5) / 0.5`, and the greedy CTC decode that
178
+ drops the blank and collapses repeats. The dictionary (18 710 characters) travels in
179
+ `preprocessor_config.json` and is also readable as `character_list.txt`.
180
+
181
+ > The normalisation `(x / 255 − 0.5) / 0.5` is written **explicitly** into
182
+ > `preprocessor_config.json` (`image_mean = image_std = [0.5, 0.5, 0.5]`). The upstream file does
183
+ > not mention it, and the `transformers` image processor would then fall back to the ImageNet
184
+ > statistics — which is *not* what these weights were trained with.
185
+
186
+ **The band (`roi`) is not optional:** on scene-like captchas the answer is one line among several,
187
+ and the whole frame makes the model read the wrong line.
188
+
189
+ ## Reproducing
190
+
191
+ The training code lives in the **CaptchaOCR** project repository:
192
+
193
+ ```powershell
194
+ # 1. the fine-tune (13 training types, 5 plugins / 7 types held out)
195
+ .venv-gpu\Scripts\python.exe train\finetune_ppocr.py --out train\run_ppocr\big --epochs 20 --batch-size 32 --lr 1e-5 --head-lr 1e-5 --workers 6 --eval-every 1 --save-every 100 --max-minutes 36
196
+ # 2. the weight interpolation (7 values of alpha, the same evaluation)
197
+ .venv-gpu\Scripts\python.exe train\run_ppocr\big\soup_eval.py
198
+ # 3. the export into a Hugging Face folder
199
+ .venv-gpu\Scripts\python.exe train\run_ppocr\export_hf.py --ckpt train\run_ppocr\big\soup_best.pt --out train\run_ppocr\big\export_soup
200
+ ```
201
+
202
+ `train/finetune_ppocr.py` fine-tunes `PaddlePaddle/PP-OCRv6_medium_rec_safetensors` on the dataset, holds out whole **plugins**
203
+ (never a random image split: several captcha types have only a few distinct answers, so a random
204
+ split lets the exact gold answer of a test image appear in the training half), and reports exact
205
+ match and CER per type plus the median over types. `train/ppocr_data.py` holds the dataset loader
206
+ and the preprocessing shown above.
207
+
208
+ The measured report of the run quoted above is `reports/TRAINING_PPOCR_V6_BIG.md` (the analysis)
209
+ and `reports/TRAINING_PPOCR_V6_BIG.json` (the numbers), in the project repository.
210
+
211
+ ## Repository metadata
212
+
213
+ | file | what |
214
+ |---|---|
215
+ | `model.safetensors` | the weights: θ = 0.4·θ_finetuned (epoch 7, step 1 632) + 0.6·θ_pretrained |
216
+ | `config.json` | the architecture — copied from the base model, unchanged by the fine-tune |
217
+ | `preprocessor_config.json` | the input geometry, the normalisation and the 18 710-character dictionary |
218
+ | `processor_config.json` | which processor class `AutoProcessor` has to build |
219
+ | `character_list.txt` | the same dictionary, one character per line, index 0 = `blank` |
220
+ | `finetune_meta.json` | the provenance of this export (source checkpoint, epoch, step, α) |
221
+
222
+ There is deliberately **no** `pipeline_tag`: `transformers` 5.x removed the `image-to-text`
223
+ pipeline task (it belongs to vision-language chat models now), and a recogniser has to be called
224
+ through `AutoModelForTextRecognition`, as in the snippet above. `library_name: transformers` is
225
+ declared because the repository really is a `transformers` model.
226
+
227
+ ## Limitations
228
+
229
+ * **Only the text band is read.** This is a recogniser, not a solver: it does not reverse strings,
230
+ does not turn Russian number words into digits and does not compute arithmetic.
231
+ * **No Cyrillic in the dictionary.** The base model's 18 710 classes contain no Cyrillic at
232
+ all, so the Cyrillic captcha types of the dataset cannot be expressed in this model's alphabet.
233
+ * **The data is synthetic.** Every image is a render of this project's own generators — faithful to
234
+ the upstream algorithms, but not a screenshot of a live server.
235
+ * **Generalisation is the hard part.** A recogniser can memorise a plugin's style instead of
236
+ learning the characters; only the type-held-out numbers mean anything.
237
+ * **The headline number is optimistic.** α = 0.4 was picked on the same 7 held-out types the
238
+ result is reported on, and the best checkpoint was picked the same way. Treat 0.638 as an upper
239
+ estimate on *new* plugin styles, not as a guarantee.
240
+ * **One hard type stays hard.** `out/laforetbrut/are-you-sure/code` is at 1.8 % exact match
241
+ (CER 0.68): the answer band of that type does not carry a clean text line.
242
+ * **The weights are an interpolation, not a clean fine-tune.** `model.safetensors` is
243
+ 0.4·θ_finetuned + 0.6·θ_pretrained; the pure fine-tune is in the project repository
244
+ (`train/run_ppocr/big/best.pt`) and is *worse* on unseen plugin styles.
245
+
246
+ ## Citation
247
+
248
+ This model is a fine-tune of PP-OCRv6 by PaddlePaddle (Apache-2.0). The dataset is the
249
+ CaptchaOCR dataset, re-rendered from open-source Minecraft anti-bot plugins. See the dataset card
250
+ for the provenance and the per-type licence of every captcha type.
character_list.txt ADDED
The diff for this file is too large to render. See raw diff
 
config.json ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "pp_ocrv6_small_rec",
3
+ "backbone_config": {
4
+ "model_type": "pp_lcnet_v4",
5
+ "stem_channels": [
6
+ 3,
7
+ 64,
8
+ 128
9
+ ],
10
+ "stem_type": "large",
11
+ "out_features": [
12
+ "stage1",
13
+ "stage2",
14
+ "stage3",
15
+ "stage4"
16
+ ],
17
+ "out_indices": [
18
+ 1,
19
+ 2,
20
+ 3,
21
+ 4
22
+ ],
23
+ "block_configs": [
24
+ [
25
+ [
26
+ 3,
27
+ 128,
28
+ 128,
29
+ 1,
30
+ true
31
+ ]
32
+ ],
33
+ [
34
+ [
35
+ 3,
36
+ 128,
37
+ 256,
38
+ 1,
39
+ false
40
+ ],
41
+ [
42
+ 3,
43
+ 256,
44
+ 256,
45
+ 1,
46
+ false
47
+ ],
48
+ [
49
+ 3,
50
+ 256,
51
+ 256,
52
+ 1,
53
+ true
54
+ ]
55
+ ],
56
+ [
57
+ [
58
+ 3,
59
+ 256,
60
+ 512,
61
+ [
62
+ 2,
63
+ 1
64
+ ],
65
+ false
66
+ ],
67
+ [
68
+ 3,
69
+ 512,
70
+ 512,
71
+ 1,
72
+ true
73
+ ],
74
+ [
75
+ 3,
76
+ 512,
77
+ 512,
78
+ 1,
79
+ false
80
+ ],
81
+ [
82
+ 3,
83
+ 512,
84
+ 512,
85
+ 1,
86
+ true
87
+ ],
88
+ [
89
+ 3,
90
+ 512,
91
+ 512,
92
+ 1,
93
+ false
94
+ ],
95
+ [
96
+ 3,
97
+ 512,
98
+ 512,
99
+ 1,
100
+ true
101
+ ],
102
+ [
103
+ 3,
104
+ 512,
105
+ 512,
106
+ 1,
107
+ false
108
+ ]
109
+ ],
110
+ [
111
+ [
112
+ 3,
113
+ 512,
114
+ 768,
115
+ [
116
+ 2,
117
+ 1
118
+ ],
119
+ false
120
+ ],
121
+ [
122
+ 3,
123
+ 768,
124
+ 768,
125
+ 1,
126
+ true
127
+ ],
128
+ [
129
+ 3,
130
+ 768,
131
+ 768,
132
+ 1,
133
+ false
134
+ ]
135
+ ]
136
+ ]
137
+ },
138
+ "hidden_act": "silu",
139
+ "hidden_size": 192,
140
+ "mlp_ratio": 4.0,
141
+ "depth": 2,
142
+ "head_out_channels": 18710,
143
+ "conv_kernel_size": [
144
+ 1,
145
+ 7
146
+ ]
147
+ }
finetune_meta.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "base_model": "PaddlePaddle/PP-OCRv6_medium_rec_safetensors",
3
+ "checkpoint": "C:\\Users\\Title\\Projects\\CapthaModel\\train\\run_ppocr\\big\\soup_best.pt",
4
+ "epoch": 7,
5
+ "step": 1632,
6
+ "soup_alpha": 0.4,
7
+ "classes": 18710,
8
+ "parameters": 19176118,
9
+ "tensors": 308,
10
+ "normalisation": {
11
+ "mean": [
12
+ 0.5,
13
+ 0.5,
14
+ 0.5
15
+ ],
16
+ "std": [
17
+ 0.5,
18
+ 0.5,
19
+ 0.5
20
+ ]
21
+ },
22
+ "transformers_class": "PPOCRV6SmallRecForTextRecognition",
23
+ "recipe": "theta = alpha * theta_finetuned + (1 - alpha) * theta_pretrained",
24
+ "source_checkpoint": "C:\\Users\\Title\\Projects\\CapthaModel\\train\\run_ppocr\\big\\best.pt",
25
+ "source_epoch": 7,
26
+ "source_step": 1632,
27
+ "held_out_median_exact": 0.638
28
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:20b25a3e38d2bc4b6379d9f58c284950cad1ee9e6ca60b1b384421c5dc4b04eb
3
+ size 76747448
preprocessor_config.json ADDED
The diff for this file is too large to render. See raw diff
 
processor_config.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "image_processor_type": "PPOCRV6SmallRecImageProcessor",
3
+ "processor_class": "PPOCRV6SmallRecImageProcessor"
4
+ }