Gotowy model OCR czytał polskie dokumenty jak turysta bez polskiej klawiatury: „ł” zamieniał na „l”, „ą” na „a”, a „łódź” wychodziła z niego jako „lodz”. Dostroiłem go lokalnie metodą LoRA, na danych syntetycznych i laptopie z kartą 8 GB. Pokazuję wyniki, porównanie z Tesseractem i PP-OCRv6 oraz miejsca, w których ten model się nie sprawdza.
W skrócie
- Model MiniCPM-OCR 1.0 (OpenBMB, 1,3 mld parametrów) czytał polski tekst, ale gubił ogonki: zachowywał tylko ok. 69% polskich liter.
- Dostroiłem go metodą LoRA wyłącznie na danych syntetycznych, na laptopie z kartą 8 GB, w dwóch rundach po niecałej godzinie.
- Efekt: 69% → 94% zachowanych polskich liter na obrazach testowych i 82% → 96% na prawdziwych skanach. Błąd znakowy (CER) spadł ponad dwukrotnie.
- Uczciwie: na czytelnym druku Tesseract i PP-OCRv6 są co najmniej tak dobre, a lżejsze i szybsze. Mój model wygrywa na zdjęciach i trudnych obrazach i zwraca strukturę tabel.
- Nie czyta pisma odręcznego. Na rozmytym skanie z rękopisem zaczyna zmyślać tekst.
- Model jest publiczny na Hugging Face i działa w pełni lokalnie.
Problem: OCR, który „prawie” czyta po polsku
Nowoczesne modele OCR czytają dokumenty znacznie lepiej niż klasyczne silniki: rozumieją układ strony, tabele, czasem wzory. Ale większość z nich trenowano głównie na angielskim i chińskim. Po polsku widać to od razu, bo model po cichu zamienia polskie litery na łacińskie odpowiedniki.
Dla człowieka to drobiazg, coś jak SMS pisany w biegu bez polskich znaków. Dla wyszukiwarki, systemu księgowego albo kontroli umowy to po prostu inne słowo. A wynik wygląda przekonująco, więc błąd łatwo przeoczyć.
Zmierzyłem to na bazowym modelu: z tekstów testowych zachowywał ok. 69% polskich liter. Najgorzej było z „ę”, „ą”, „ł” i wielkimi literami.
Dlaczego fine-tuning, a nie lepszy prompt
Zanim cokolwiek trenowałem, sprawdziłem najprostszą hipotezę: może obraz jest za mały. Powiększyłem go, zmieniłem rozdzielczość. Wynik się nie zmienił. To nie jest kwestia jakości obrazu, tylko nawyk modelu językowego: tam, gdzie widzi „ł”, wolałby napisać „l”, bo tak najczęściej wyglądał jego trening.
„Model nie miał wady wzroku. Miał złe przyzwyczajenie. Okulary tu nie pomogą, trzeba poprawić nawyk.”
— Wniosek z pierwszego dnia testów
Takiego nawyku nie naprawi prompt ani RAG. Trzeba go zmienić w wagach, a najtańszy sposób to LoRA: dokładam do modelu niewielki adapter i uczę tylko jego, nie cały model. Trochę jak karteczka z poprawkami doklejona do grubego podręcznika zamiast drukowania go od nowa.
Jak to zrobiłem
1. Dane syntetyczne, bez Twoich dokumentów
Teksty po polsku napisał lokalnie Bielik 11B. Wyrenderowałem je różnymi czcionkami, z degradacjami: skan, zdjęcie z perspektywą, niska rozdzielczość, wyjustowany tekst. Czcionki treningowe i testowe są rozłączne, żeby model nie mógł się „nauczyć na pamięć”. Dodałem linie z rzadkimi literami i końcówkami typu „-ć”.
2. LoRA tylko na części językowej
Enkoder wizyjny i projektor zostały zamrożone. Rank 16, ok. 1,3 tys. próbek w pierwszej rundzie, ok. 1,8 tys. w drugiej, po dwie epoki. Cały trening zmieścił się na laptopie z RTX 5060 8 GB i trwał 43 oraz 54 minuty. Czyli krócej niż niejeden firmowy status.
3. Eksport do formatu lokalnego
Adapter scaliłem z modelem i skonwertowałem do GGUF w kwantyzacji Q8_0 (0,8 GB), tak żeby działał w Ollamie.
Dwie pułapki, które kosztowały mnie czas, a mogą zaoszczędzić Tobie
Pamięć: słownik 248 tys. tokenów plus ok. tysiąca tokenów obrazu na próbkę kończy się brakiem pamięci na 8 GB. Pomogło ograniczenie rozmiaru wycinków. (O tym, czemu rozmiar pliku to nie zużycie pamięci, pisałem przy okazji modeli na 32 GB VRAM.)
Szablon czatu: własny eksport z domyślnym szablonem sprawiał, że Ollama wpisywała odpowiedź w pole „thinking” i zwracała puste wyniki. Trzeba użyć szablonu z oficjalnego GGUF.
Co osiągnąłem
Testowałem na obrazach z czcionkami, których model nie widział, oraz na wycinkach prawdziwych skanów dokumentów (nie wchodziły do treningu).
| Zbiór | Model | Polskie litery zachowane | Błąd znakowy (CER) | Słowa z błędem |
|---|---|---|---|---|
| 84 obrazy syntetyczne | bazowy | 68,9% | 2,63% | 14,1% |
| 84 obrazy syntetyczne | po fine-tuningu | 94,0% | 1,08% | 4,8% |
| 11 prawdziwych skanów | bazowy | 81,5% | 1,40% | 10,1% |
| 11 prawdziwych skanów | po fine-tuningu | 96,4% | 0,55% | 2,5% |
Przy okazji sprawdziłem, czy model nie zapomniał innych języków: angielski, chiński, niemiecki, francuski i czeski czytał tak samo dobrze jak przed treningiem (CER 0,21% → 0,18%).
Zastrzeżenie: 11 wycinków z czterech dokumentów to mała próbka. Kierunek jest jednoznaczny, ale dokładne liczby traktuj jako orientacyjne.
Czym się różni od Tesseracta i PP-OCR
Porównałem go na tych samych danych z Tesseractem (język polski) i PP-OCRv6, bez żadnej obróbki obrazu.
| Silnik | Skany (CER / polskie litery) | Obrazy syntetyczne (CER / polskie litery) |
|---|---|---|
| MiniCPM-OCR bazowy | 1,40% / 81,5% | 2,63% / 68,9% |
| MiniCPM-OCR po fine-tuningu | 0,55% / 96,4% | 1,08% / 94,0% |
| Tesseract (pol) | 0,25% / 100% | 14,16% / 86,3% |
| PP-OCRv6 (pl) | 0,32% / 96,4% | 1,61% / 88,3% |
Co z tego wynika, bez ściemy:
- Na czytelnych skanach druku Tesseract i PP-OCRv6 wygrywają lub remisują, są lżejsze i znacznie szybsze (Tesseract: pół sekundy na CPU). Jeśli masz czyste dokumenty, nie ma powodu sięgać po duży model.
- Na zdjęciach i obrazach z perspektywą Tesseract bez obróbki wstępnej się sypie (CER ok. 37%), a mój model trzyma stały poziom ok. 1%. PP-OCRv6 radzi sobie ze zdjęciami równie dobrze.
- Przewaga mojego modelu to struktura, nie liczba poprawnych liter: potrafi zwrócić tabelę jako tabelę, a nie luźne słowa. Tego w porównaniu nie mierzyłem, więc nie będę udawał, że mam liczby.
- Prywatność: wszystko działa lokalnie, na własnym sprzęcie. Czas: ok. 3 s na akapit na GPU albo ok. 15 s na samym CPU.
Mój wniosek: Tesseract albo PP-OCRv6 jako domyślny silnik, a model LLM-owy jako drugi krok tam, gdzie klasyczny OCR sobie nie radzi (tabele, zdjęcia, złożony układ). Wyniki obu silników można porównywać i rozbieżności kierować do ręcznej weryfikacji. Jak w przychodni: najpierw lekarz rodzinny, specjalista dopiero wtedy, gdy trzeba.
Czego ten model nie potrafi
To część, o której rzadko się pisze.
- Pismo odręczne. Sprawdziłem na archiwalnym skanie z rękopisem (rozmyty mikrofilm). Z ok. 340 słów wzorca model odnalazł 8. Przy dwóch stronach zmyślił płynny angielski i łaciński tekst, którego tam nie było. Do rękopisów użyj innego narzędzia, np. Transkribus.
- Halucynacje na słabych skanach. Przy bardzo kiepskiej jakości wynik wygląda wiarygodnie, a jest błędny. Klasyczny silnik zwróciłby błąd rozpoznania, który łatwiej zauważyć.
- Wciąż słabsze litery: „ł” i „Ą” (ok. 86–87% na zbiorze syntetycznym).
- Do dokumentów, w których liczy się kwota, data lub nazwisko, zawsze weryfikuj z oryginałem albo drugim silnikiem.
Gdzie pobrać
Model jest publiczny na Hugging Face: huggingface.co/OtoroSI/MiniCPM-OCR-1.0-PL-otoro-si-Q8_0
To modyfikacja modelu OpenBMB na licencji Apache 2.0, nie oficjalne wydanie. Karta modelu zawiera instrukcję dla Ollamy, wszystkie wyniki i ograniczenia. Uruchomisz go na własnym komputerze, bez wysyłania dokumentów do chmury.
FAQ
Czy mogę użyć tego do faktur i umów?
Do czytelnych skanów druku tak, ale zawsze z weryfikacją kwot, dat i nazwisk. Dla prostych, czystych dokumentów szybszy i tańszy w utrzymaniu będzie Tesseract.
Czy potrzebuję mocnej karty graficznej?
Niekoniecznie. Na GPU z 8 GB działa wygodnie (ok. 3 s na akapit), na samym CPU ok. 15 s. Model waży 0,8 GB plus 0,9 GB projektora wizyjnego.
Czy moje dokumenty trafiły do treningu?
Nie. Trening opierał się wyłącznie na danych syntetycznych, a prawdziwych skanów używałem tylko do testów.
Czy to zadziała na rękopisach?
Nie. To jedno z ograniczeń opisanych wyżej.
Chcesz sprawdzić, czy lokalny OCR ma sens w Twojej firmie?
Zanim zainwestujesz w duży model, warto zmierzyć, jak radzą sobie Twoje dokumenty: czy wystarczy Tesseract, czy potrzebujesz modelu rozumiejącego tabele i układ, i gdzie trzeba dodać weryfikację. Przeanalizuję próbkę Twoich skanów i powiem uczciwie, co się opłaca.
Umów darmowy audyt →