Zadání znělo jednoduše: vezmi smlouvu z Registru smluv, tak jak ji stahuje a OCRuje Hlídač státu, a zařaď ji do jedné z 39 věcných kategorií. IT hardware, software, stavby, energie, léčiva, dotace, úklid... Podle obsahu, ne podle názvu. A s co nejvyšší přesností vůči ručně anotovanému golden datasetu.
Zní to jako učebnicová klasifikace textu. V praxi je to jeden z nejzajímavějších datasetů, na kterých jsem kdy dělal. České smlouvy jsou dlouhé, OCR je místy rozpadlé ("M I N I S T E R S T V O"), názvy v registru nic neříkají a hranice kategorií jsou mlhavé i pro lidi. A navíc se na tom dalo poctivě porovnat všechno, co se v klasifikaci v roce 2026 nabízí: od dvacet let starého TF-IDF po decision modely, které vyšly před pár týdny.
Výsledek v kostce
Nejdřív to, co vás nejspíš zajímá nejvíc: jak si proti sobě vedly jednotlivé přístupy. Nejlepší řešení z každé rodiny, všechno na stejných 1 754 validačních smlouvách, které žádný model při tréninku neviděl:
Ve zkratce:
- TF-IDF je překvapivě silný základ. 83.6 % zadarmo na CPU je laťka, kterou musí každé AI řešení přeskočit.
- Decision modely jsou nejlepší start bez dat. Jev bez jediného trénovacího příkladu 92.1 %. Open-weight konkurence (pplx-decider, Clef) je 1 až 3 body za ním.
- Velký zmražený embedding s obyčejnou lineární hlavou je skoro stejně dobrý a stojí haléře.
- Malý dotrénovaný encoder nestačil, velký ano. mmBERT (307M) skončil na 88.8 %, Qwen3-Embedding-8B s LoRA na 92.8 %.
- Nejvyšší čísla dávají kombinace modelu, který rozumí textu, a modelu, který zná konvence anotátorů. Ale jen u slabších modelů.
- Do provozu nešlo nejpřesnější řešení, ale nejlepší poměr přesnost/rychlost: Qwen3-Embedding-8B s LoRA. Kombinace s 27B decision modelem byla na hardwaru Hlídače víc než 15x pomalejší.
A tady je celá mapa. Každá tečka je jedno řešení, z každého modelu jen nejlepší varianta:
Zbytek článku jsou detaily: jak jsme k těm číslům došli, čemu věřit a čemu ne, a pár věcí, které vás na klasifikaci dlouhých dokumentů nejspíš překvapí.
- O co šlo a na čem to běželo
- Data a golden dataset
- Protokol: 80:20 a na validaci se nesahá
- Co přesně jsme testovali
- Decision modely: 39 pravděpodobností jedním průchodem
- Dlouhé smlouvy: kolik textu model uvidí a co s tím dělat
- Embeddingy s hlavou: 4 000 smluv ve 3D
- Malý encoder prohrál, velký vyhrál
- Kombinace: proč 1 + 1 = 94
- Poziční bias: díky research oddělení Seznamu
- Kde model chybuje: taxonomie, ne text
- Jak to dopadlo v provozu
- Co si z toho odnést
Jak číst čísla: všechny přesnosti v článku jsou na stejné validační sadě 1 754 smluv (20 % golden datasetu, které žádný model při tréninku ani ladění neviděl). Směrodatná chyba přesnosti je při tomhle počtu asi 0.7 bodu, takže rozdíly kolem jednoho bodu jsou v šumu. Závěry platí pro české smlouvy z Registru smluv a tuhle konkrétní taxonomii, ne pro "klasifikaci textu obecně". Stav k 6. 11. 2026.
O co šlo a na čem to běželo
Hlídač státu je nezávislý projekt, který od roku 2017 zpřístupňuje a analyzuje data o hospodaření státu. Jeho největší datovou sadou je právě Registr smluv: k dnešku přes 9.3 milionu platných smluv, v průměru 3 042 nových denně a jen za rok 2025 rekordních 1 089 475 smluv (statistiky Hlídače). Ručně to nikdo neroztřídí. A bez třídění se v tom špatně hledá, co stát kupuje, od koho a za kolik.
Hlídač státu vede jeho zakladatel Michal Bláha a já jsem jim s klasifikací pomáhal: návrh metodiky, výběr a srovnání modelů, vyhodnocení a doporučení pro provoz. Výpočty běžely na třech místech podle toho, co kde dávalo smysl:
- Notebook s RTX 4060 (8 GB VRAM): TF-IDF, menší fine-tuningy, lokální embeddingy, vyhodnocení a kombinace.
- DGX Spark Hlídače: všechno, co se do 8 GB nevejde. 27B embedding model, mmBERT na 8 192 tokenech a LoRA trénink Qwen3-Embedding-8B.
- OpenRouter: decision modely a embeddingy přes API. Za celý projekt zhruba 40 dolarů.
Data a golden dataset
Každá smlouva jsou dvě volání API Hlídače: metadata z /api/v2/smlouvy/{id} (předmět, plátce, příjemce, hodnota, názvy příloh) a texty příloh z /api/v2/smlouvy/text/{id}. Ten druhý endpoint je důležitý, protože detail smlouvy u anonymizovaných smluv místo textu vrací jen značku. Celkem 543 MB surových dat, z toho jen 33 smluv úplně bez textu.
Jedno rozhodnutí platilo od prvního dne pro všechno: každý model dostává metadata i text. Samotný předmět z registru je často jen "Objednávka č. 123" a oddíl "Předmět smlouvy" najde regulární výraz jen zhruba u 42 % smluv. TF-IDF jen na předmětu dalo 74.2 %, jen na textu 81.4 % a na obojím 83.6 %.
Moje první hypotéza byla, že smlouvy jsou z velké části právní boilerplate ("smluvní strany se dohodly...") společný všem kategoriím a stačí ho vyhodit. Data ji rychle zabila: věty doslova shodné napříč kategoriemi tvoří kolem 1 % slov. Boilerplate je parafrázovaný, ne kopírovaný, a filtr by skoro nic neodřízl.
Co je golden dataset? Sada smluv, u kterých člověk ručně určil správnou kategorii. Slouží jako "pravda", proti které se měří všechny modely.
Golden tady připravil člověk z Michalova týmu, který se dřív profesionálně živil anotací dat pro AI. Díky tomu byly vyřešené základy, na kterých podobné projekty často padají: vzorek smluv není zaujatý k pár velkým úřadům ani k jednomu období, kategorie jsou rozumně vyvážené (i ty nejmenší mají desítky příkladů, ne jednotky) a značení drží jednotná pravidla. Tým Hlídače ho během projektu ještě dočistil: z původních 8 818 smluv odebral 51 a 768 labelů opravil. Zůstalo 8 767 smluv, 27 až 643 na kategorii.
Jak moc je golden pravdivý
Než začnete měřit modely proti "pravdě", měli byste vědět, jak moc je ta pravda pravdivá. Udělali jsme proto audit: přes 190 smluv z golden ve více kolech naslepo klasifikoval Claude Sonnet (bez znalosti golden labelu, jen s popisy 39 kategorií a textem) a neshody pak naslepo posuzoval Claude Opus, který nevěděl, která z nabízených kategorií je ta z golden. Stejným postupem jsme si navíc označili 500 smluv mimo golden pro pozdější kontrolu.
Sonnet se s golden shodl zhruba u devíti smluv z deseti. Zajímavější ale byl zbytek:
- Opus rozhodoval neshody systematicky proti golden. To není důkaz chyb v datech, ale typický otisk společného biasu dvou modelů stejné rodiny. Verdikt AI arbitra je kandidát na chybu, ne chyba.
- Skutečných chyb v golden je odhadem jen jednotky procent. Tým Hlídače většinu kandidátů zkontroloval a opravil.
- Většina neshod nebyla chyba ani modelu, ani golden. Byl to rozdíl mezi tím, co říká popis kategorie, a tím, jak anotátor reálně značí. Když jsme modelu ukázali podobné smlouvy z golden, shoda výrazně vzrostla, protože se naučil konvence.
Moje oblíbená hraniční kauza: lyžáky a školy v přírodě. Popis kategorie "Kultura, sport a volný čas" na ně sedí jako ulitý a všechny chytré modely je tam dávají. Golden ale drtivou většinu takových smluv značí jako "Stravování a ubytování", protože se platí hlavně za pobyt. Ani jedna strana nemá "chybu". Je to rozhodnutí o taxonomii, které musí udělat člověk, ne model.
Protokol: 80:20 a na validaci se nesahá
Tohle je nudná sekce, ale bez ní by zbytek článku nic neznamenal. Pravidla, která platila pro každý z těch 184 běhů:
- Split 80:20 stratifikovaný podle kategorie, zafixovaný jednou pro všech 8 766 smluv s labelem: 7 012 train, 1 754 validace. Split má kontrolní otisk, který musel sedět na všech strojích.
- Validace se nepoužívá k výběru. Hyperparametry se ladí křížovou validací na train, varianty promptů se screenují na smlouvách z train (decision modely jsou zero-shot, takže train je pro ně volná dev sada). Na validaci jde každá finální konfigurace jednou.
- Párové testy. Všechny metody se hodnotí na stejných smlouvách, takže u každého rozdílu jde spočítat McNemarův test. Bez něj byste v tabulce s půlbodovými rozdíly četli horoskop.
- Doplňková metrika "nový plátce": přesnost jen na smlouvách úřadů, které v tréninku nebyly. Kontrola, jestli se model neučí šablony konkrétních úřadů.
Co je macro-F1? F1 (harmonický průměr precision a recall) spočítané pro každou kategorii zvlášť a pak prostě zprůměrované. Malá kategorie s 8 smlouvami ve validaci váží stejně jako velká se 132. Accuracy vám řekne, kolik smluv model trefí. Macro-F1 vám řekne, jestli neignoruje malé kategorie.
Co přesně jsme testovali
TF-IDF + lineární model
Spočítá, jak typická jsou slova a znakové n-gramy pro dokument, a naučí nad tím LinearSVC. Klasická baseline, běží na CPU v milisekundách.
Decision model
Nová kategorie modelů ("System One"). Dostane text a seznam možností a jedním průchodem vrátí pravděpodobnost každé. Bez generování textu, bez tréninku na našich datech.
Embedding + naučená hlava
Velký zmražený model převede smlouvu na vektor (u Qwen3-8B 4 096 čísel) a nad ním se na 7 012 smlouvách naučí obyčejný lineární klasifikátor.
Fine-tuned encoder
Model se dotrénuje přímo na klasifikaci: celý (mmBERT) nebo přes LoRA adaptér (Qwen3-Embedding-8B). Pattern z mého červencového článku.
Zero-shot encoder
Žádný trénink: smlouva se porovná s textovými popisy kategorií (GLiClass, podobnost embeddingů).
Kombinace
Pravděpodobnosti dvou modelů se spojí vážením logaritmů. Typicky decision model, který rozumí textu, a naučený model, který zná konvence anotátora.
Generativní LLM typu "napiš mi kategorii" jsme do srovnání vědomě nedali. Sonnet a Opus posloužily na audit golden, ale na klasifikaci milionů smluv je to drahá okružní jízda (víc o tom v rozcestníku encoder vs LLM).
Decision modely: 39 pravděpodobností jedním průchodem
Tahle kategorie modelů je pro mě největší novinka roku. Jev od TypeSafe vyšel 15. 9. 2026, open-weight Clef od Cloudflare 1. 10. a pplx-decider od Perplexity zhruba ve stejné době.
Princip je pro klasifikaci ideální. Model dostane smlouvu (metadata + prvních 12 000 znaků), seznam 39 kategorií s krátkými popisy a vrátí rozdělení pravděpodobnosti. Žádný JSON k parsování, žádné "omlouvám se, ale...". A protože vrací pravděpodobnosti, dá se s nimi dál počítat. Proklikejte si dva skutečné běhy Jevu:
Model dostane smlouvu a seznam 39 kategorií a jedním průchodem vrátí pravděpodobnost každé z nich. Žádný generovaný text. Pozice v seznamu vlevo od pruhu je místo, kde kategorie v promptu skutečně stála.
Všechny decision modely šly přes jedno API se stejným promptem a stejným vstupem, na celé validaci:
| decision model (1 754 smluv) | přesnost | macro-F1 | medián latence (API) | cena za validaci |
|---|---|---|---|---|
| Jev 1.13 uzavřený | 92.0 % | 0.880 | 0.37 s | 0.90 $ |
| Jev 1.14 uzavřený | 91.8 % | 0.877 | 0.4 s | 0.90 $ |
| Mercury Decide uzavřený | 90.6 % | 0.867 | 1.13 s | 0.34 $ |
| GPT-6 Luna Decisions uzavřený | 90.5 % | 0.860 | - | 1.66 $ |
| pplx-decider v1.2 open | 89.5 % | 0.853 | 0.83 s | 0.34 $ |
| Clef 27B open | 88.8 % | 0.843 | 1.11 s | 0.91 $ |
| Clef-flash 9B open | 86.6 % | 0.815 | 0.67 s | 0.39 $ |
| Clef-omni open | 76.7 % | 0.722 | - | 3.32 $ |
Celou validaci projede Jev za necelý dolar. S celým textem smlouvy místo prvních 12 000 znaků dal 92.1 %, tedy v šumu stejně. Má to ale háček: Jev je uzavřený a Hlídač chtěl model, který si provozuje sám. Proto mě nejvíc zajímala open-weight trojice Clef 27B, pplx-decider a jejich kombinace.
- Jev 1.14 není lepší než 1.13. Shodují se v 98.1 % smluv a rozdíl je v šumu. Nejnovější verze neznamená automaticky lepší na vašich datech.
- Pravděpodobnosti Jevu jsou použitelné. Při pravděpodobnosti aspoň 0.8 (88 % validačních smluv) má Jev přesnost 96.4 %, pod tím zhruba 61 %. Hotový práh pro to, co poslat člověku na kontrolu.
- Na promptu záleží i tady. Pro pplx-decider jsme na smlouvách z train porovnali šest variant promptu. Obecné definice kategorií místo výčtu klíčových slov zvedly samotný pplx na validaci z 89.5 na 90.8 % (průkazně, p = 0.005).
A jedna zajímavost k benchmarkům. Na veřejném JevBench mají některé open-weight modely výrazně vyšší "intelligence index" než Jev. Na našich českých smlouvách to skoro nic nepředpovídalo: JevBench je převážně anglický, s krátkými vstupy a malým počtem možností. Benchmark si změřte na svých datech, jinak měříte cizí úlohu.
Dlouhé smlouvy: kolik textu model uvidí a co s tím dělat
Tohle je sekce, která platí pro jakoukoli klasifikaci dlouhých dokumentů. Smlouvy mají medián kolem 8 000 znaků, ale chvost je extrémní: 90 % smluv má pod 40 000 znaků a nejdelší má 4.7 milionu. Každý model má jiné kontextové okno a ne každý vám dá vědět, když ho přetečete.
Než jsme začali okna u všech modelů systematicky ověřovat, našli jsme tři tichá selhání:
- bge-m3 (8 192 tokenů): delší smlouvy API odmítlo a jejich výsledky v logu prostě chyběly. Žádná výjimka, jen díra v datech.
- multilingual-e5-large: API ořezávalo na 512 tokenů, tedy asi 1 380 znaků. Žádné varování.
- pplx-decider v1.1: katalog uvádí 262k kontextu, ale limit na jedno rozhodnutí podle model card je 8 192 tokenů. Po přeměření v limitu přidal 3 body.
context_lengthv katalogu poskytovatele není limit, který reálně platí pro váš request. Před během si ověřte okno z model card, změřte počet tokenů na vašich datech (čeština má u Qwen a OpenAI tokenizerů ~2 znaky na token, u bge a e5 ~3.2) a při porušení limitu přeměřte. Jinak porovnáváte modely, které viděly jiná data.
Strategie pro dlouhé dokumenty: co jsme zkoušeli a co ne
Když se dokument do okna nevejde, máte v zásadě pět možností. Část jsme změřili, část ne. Shrnuté včetně toho, jak to dopadlo:
Ořez na začátek
Výchozí volba pro všechny modely: metadata + prvních 12 000 znaků (u encoderů podle okna). Funguje nejlépe, protože smlouva skoro vždy začíná stranami a předmětem plnění.
Začátek + konec
Klasický trik z Sun a kol. (2019): část okna dát začátku, část konci. U nás na TF-IDF poměr 75:25 při 4 000 znacích 83.5 % proti 82.7 % jen ze začátku, ale neprůkazně. Samotný konec je vždy horší, 50:50 taky.
Delší kontext
Jev s celou smlouvou až do 200 000 znaků, harrier a Qwen na 32k tokenech, mmBERT na 2k, 4k a 8k. Celkově v šumu (Jev 92.0 -> 92.1 %, mmBERT 88.7 / 88.5 / 88.8 %). Mírně to pomohlo jen u nejdelších smluv a stojí to násobně víc výpočtu.
Kusy + agregace
Smlouva rozsekaná na kusy po 5 500 znacích, každý zvlášť embedding a pak průměr, maximum nebo první kus, jako v hierarchických transformerech. Výsledek 89.1 až 89.5 %, tedy o kousek hůř než jeden embedding prvních 12 000 znaků (89.7 %).
Naučený attention pooling
Stejné kusy, ale váhy kusů se model učí sám (multiple instance learning). Doufal jsem, že se naučí ignorovat boilerplate. Výsledek 88.6 %, průkazně hůř. Ten signál v datech skoro není.
Výběr relevantní pasáže
Najít oddíl "Předmět smlouvy" nebo klíčové věty a klasifikovat jen je (např. CogLTX). Regulární výraz oddíl najde jen u ~42 % smluv a naučený selektor by potřeboval vlastní anotace. Kandidát na další kolo.
Shrnutí LLM před klasifikací
Nechat LLM smlouvu zkrátit na pár vět a klasifikovat shrnutí. Na 9 milionech smluv drahé, pomalé a s rizikem, že shrnutí vynechá přesně to rozhodující. U nás ani nebylo potřeba.
Long-document architektury
Modely se sparse attention (Longformer, BigBird a spol., srovnání v Dai a kol. 2022). Kvalitní česká varianta chybí a vzhledem k výsledkům výš od nich nečekám zázrak.
Proč je to u téhle klasifikace tak jednoznačné? O kategorii smlouvy rozhoduje začátek a metadata. Zbytek jsou obchodní podmínky, podpisy, ceníky, výkresy a OCR šum z příloh. Delší vstup modelu nedává víc signálu, jen víc šumu a delší výpočet. U jiného typu dokumentů (posudky, zprávy, kde je podstata uprostřed) to může vyjít úplně jinak, tak si to změřte.
Embeddingy s hlavou: 4 000 smluv ve 3D
Druhý hlavní přístup je nejméně "AI" a nejvíc inženýrský: vezmete velký předtrénovaný embedding model, nic na něm netrénujete, jen z každé smlouvy uděláte vektor a nad vektory naučíte obyčejný LinearSVC. Embeddingy se dají koupit přes API za drobné. Celý golden přes Qwen3-Embedding-8B stál desítky centů.
Jak to vypadá uvnitř? Tady je 4 000 skutečných smluv z golden datasetu ve vektorovém prostoru Qwen3-Embedding-8B, promítnutých z 4 096 rozměrů do 3D:
Zkuste si přepnout na "Chyby modelu". Červené tečky (smlouvy, kde se nejlepší zmražený open model spletl) nejsou rozházené náhodně. U správně zařazené smlouvy je v průměru 87 % z jejích 10 nejbližších sousedů ze stejné kategorie, u chybné jen 17 %. Chyby leží uvnitř cizích shluků, typicky u "Darů a dotací", "Úklidu a správy objektů", "IT provozu" a zbytkové kategorie "Ostatní". Model se plete hlavně tam, kde smlouva svým obsahem opravdu vypadá jako jiná kategorie.
| embedding + hlava (1 754 smluv) | přesnost | macro-F1 |
|---|---|---|
| Qwen3-Embedding-8B + instrukce + LinearSVC open | 91.6 % | 0.867 |
| harrier-oss-v1-27b, 32k tokenů open | 91.6 % | 0.871 |
| Voyage 4 large + LinearSVC API | 90.9 % | 0.866 |
| Gemini Embedding 2 + LogReg API | 90.2 % | 0.848 |
| pplx-embed-4B open | 89.3 % | 0.841 |
| harrier-oss-v1-0.6b, 32k tokenů open | 88.3 % | 0.838 |
| bge-m3 open | 84.2 % | 0.785 |
| multilingual-e5-large open | 82.8 % | 0.769 |
Nejlevnější trik celého projektu: instrukce na začátku vstupu. Qwen3-Embedding je trénovaný tak, že na začátek dotazu můžete napsat, k čemu embedding je. Jedna česká věta "Urči kategorii veřejné zakázky / smlouvy z českého Registru smluv podle předmětu plnění (co se dodává, poskytuje nebo financuje)." přidala průkazně přes bod. Anglická verze té samé instrukce dala skoro stejně. Pomáhá samotná přítomnost instrukce, jazyk je jedno. harrier, se kterým mám dobré zkušenosti z češtiny, ve 27B verzi Qwen3-8B vyrovnal.
Co naopak nepomohlo, i když to na papíře vypadalo skvěle:
- Historie plátce a dodavatele jako rysy. Na train to vypadalo jako zlepšení, na validaci zhoršení. Učebnicový overfit.
- Popisy kategorií jako další rysy. Křížová validace jim sama dala váhu nula.
- MLP místo lineární hlavy. Horší u Qwen i Voyage. Nad dobrým embeddingem stačí přímka.
- kNN místo naučené hlavy. Vždy o 5 až 9 bodů horší.
Malý encoder prohrál, velký vyhrál
V červenci jsem napsal rozcestník, kde jsem tvrdil, že na uzavřenou klasifikaci s daty máte sáhnout po dotrénovaném encoderu. Tady mi fine-tuned mmBERT-base nejdřív skončil na 88.8 %, tři body za zmraženým Qwen3-Embedding s obyčejnou hlavou. Chvíli jsem si myslel, že budu psát opravu vlastního článku.
Trénink přitom rozbitý nebyl: na trénovacích smlouvách měl mmBERT přes 98 %, učil se dobře, jen se silně přeučoval. Brzdí ho dvě věci:
- Velikost. 307M parametrů proti 8B (Qwen3-Embedding) nebo 27B (harrier, Clef, pplx). Na 39 kategoriích s mlhavými hranicemi a 7 012 příklady se ten rozdíl v "znalosti světa" projeví. Velký model ví, že "drť 4-8 zimní" je posypový materiál, i když to v trénovacích datech nikdy neviděl.
- Čeština v tokenizeru. 2 048 tokenů mmBERT je jen ~6 000 znaků. Delší okno ale, jak už víme, nepomohlo.
Řešení bylo dotrénovat velký encoder. Qwen3-Embedding-8B je technicky decoder-only model, ale používá se přesně jako encoder: text dovnitř, vektor ven, žádné generování. Na DGX Sparku jsme na něm natrénovali LoRA (r = 16 na attention i MLP) s lineární hlavou na 39 tříd a vstupem 8 192 tokenů. Dvě epochy trvaly zhruba 29 hodin:
| Qwen3-Embedding-8B (1 754 smluv) | přesnost | macro-F1 | nový plátce |
|---|---|---|---|
| zmražený, 8k tokenů + LinearSVC | 90.9 % | 0.860 | - |
| + LoRA, 2 epochy | 92.8 % | 0.890 | 94.8 % |
92.8 % je nejlepší samostatný open-weight model celého projektu. Je lepší než Jev bez tréninku (92.1 %) a jediným modelem dorovná kombinaci pplx-decider + TF-IDF (92.9 %). Červencový závěr "na uzavřenou klasifikaci s daty dotrénuj encoder" tady nakonec platí, jen s jednou opravou: na dlouhých českých dokumentech s 39 mlhavými kategoriemi musí být ten encoder velký. mmBERT má pořád své místo tam, kde potřebujete desítky milisekund na smlouvu nebo běh na CPU.
Proč jsme nezkoušeli RobeCzech nebo XLM-R? Mají okno 512 tokenů a RobeCzech navíc nekomerční licenci. Na smlouvy, kde 96 % dokumentů je delších, je to k ničemu. A EuroBERT ani LFM2.5-Encoder češtinu v seznamu jazyků nemají.
Kombinace: proč 1 + 1 = 94
Nejvyšší čísla v celém projektu dávají dvojice decision model + naučený model. Proč to funguje tak dobře?
- Decision model rozumí textu a popisům kategorií, ale nezná konvence anotátora (lyžáky do ubytování).
- Naučený model (TF-IDF nebo embedding s hlavou) viděl 7 012 smluv z golden a ty konvence zná, jen hůř zvládá nové formulace.
- Chybují na jiných smlouvách. U Jevu a TF-IDF je teoretický strop "aspoň jeden má pravdu" 95.7 %.
Spojení je jednoduché: vážený součet logaritmů pravděpodobností. Jediný parametr je váha w. Tady je skutečná křivka z validace, posuňte si ji:
Všimněte si, jak málo stačí. Samotný pplx-decider má 90.8 %, samotné TF-IDF 83.6 %, a už s váhou kolem 0.2 (tedy "věř hlavně TF-IDF, ale nech decision model rozhodovat, když si je jistý") je kombinace na 92.9 %. Pravděpodobnosti TF-IDF jsou totiž hodně ploché a i malá váha decision modelu je dokáže přetlačit.
| nejlepší kombinace (1 754 smluv) | přesnost | macro-F1 |
|---|---|---|
| Jev 1.13 (dlouhý text) + Gemini Embedding 2 uzavřené | 94.0 % | 0.906 |
| Jev 1.13 + TF-IDF uzavřené | 93.6 % | 0.899 |
| Mercury Decide + Gemini Embedding 2 uzavřené | 93.3 % | 0.894 |
| pplx-decider v1.2 + TF-IDF open | 92.9 % | - |
| Clef 27B + Qwen3-Embedding-8B open | 91.9 % | 0.878 |
| Clef 27B + TF-IDF open | 91.8 % | 0.875 |
Poctivá poznámka k těm 94.0 %: vítěz byl vybraný z ~184 běhů na stejné validaci, takže je mírně optimistický. Váha kombinace se volila 5-fold křížovou validací uvnitř validace, takže žádná smlouva nebyla hodnocená váhou vyladěnou na ní samotné. Výběr nejlepšího z mnoha ale tenhle trik neřeší. Třetí model do kombinace jsme mimochodem nedávali: přidával 0 až 0.6 bodu za další závislost v produkci.
A lekce, která přišla až na konci: kombinace pomáhá slabším modelům, ne silným. Decision modelům bez tréninku TF-IDF přidá 1.2 až 3.5 bodu, protože jim dodá konvence anotátora. Dotrénovaný Qwen s LoRA ale ty konvence už zná. Jeho kombinace s TF-IDF vyšla na 92.5 %, tedy o 0.3 bodu hůř než Qwen samotný.
Poziční bias: díky research oddělení Seznamu
Když jsem průběžný report sdílel dál, ozvali se mi lidé z research oddělení Seznamu s pár připomínkami k metodice. Ruku na srdce: proti tomu, jak měří lidi z pořádného ML výzkumu, jsem metodická bačkora. Ale bačkora, která má párové testy a fixní split, takže se dalo rychle ověřit, co z připomínek čísla posouvá a co ne. Ve všem podstatném měli pravdu. A jednu věc jsem vůbec netestoval: poziční bias.
Co je poziční bias? Tendence modelu preferovat možnosti podle jejich pozice v seznamu (typicky první nebo poslední), ne podle obsahu. U modelů, které vybírají z 39 možností, je to legitimní obava. Do té doby jsme všem modelům dávali kategorie ve stejném pevném pořadí: podle ID, tematicky seskupené, "Ostatní" na konci.
Test: celá validace znovu, ale v dalších dvou bězích dostala každá smlouva vlastní náhodné pořadí všech 39 kategorií. Díky tomu je pozice nezávislá na kategorii a dá se měřit přímo. Pustili jsme to na Jev 1.13 a Clef 27B, stálo to 5.5 $:
| model (1 754 smluv) | pevné pořadí | náhodné 1 / 2 | změněné odpovědi | závislost na pozici |
|---|---|---|---|---|
| Jev 1.13 | 92.1 % | 91.6 / 91.4 % | 2.1 % | χ² p = 0.67 |
| Clef 27B | 88.8 % | 88.9 / 88.9 % | 0.3 % | χ² p = 0.94 |
Klasická preference prvních nebo posledních možností se neprokázala u žádného modelu. Přesnost nezávisí na tom, kde v seznamu stála správná kategorie, a podíl predikcí v každém pásmu pozic odpovídá jeho velikosti. Clef je na pořadí prakticky necitlivý. Jev mění odpověď asi u 2 % smluv a s náhodným pořadím vychází o 0.5 až 0.7 bodu hůř (jednou průkazně, p = 0.03).
To zajímavé je kde se Jev mění. Viděli jste to výš na eRecept: hraniční smlouvy mezi IT provozem a IT vývojem, kde je model na 52:47 a pořadí ho přehoupne. Pevné tematické pořadí (IT u sebe, stavby u sebe) dává modelu strukturu, kterou náhodné pořadí rozbije. Průměr přes dvě náhodná pořadí rozdíl zmenší, ale pevné pořadí nepřekoná a stojí dvojnásobek volání. Doporučení: pevné tematické pořadí, "Ostatní" na konci. A reportovaná čísla platí.
Research oddělení Seznamu, díky. Bez vás bych tohle číslo nikdy neměl a teď ho mám černé na bílém. Stejný test jsem pak rovnou přidal do zadání pro každý další decision model.
Kde model chybuje: taxonomie, ne text
Analýza chyb nejlepšího modelu potvrdila to, co naznačil už audit golden. Nejslabší jsou málo četné a mlhavé kategorie ("Ostatní", "Věda a výzkum", "IT provoz"), a stejný vzorec má i Jev, který se na našich datech nikdy netrénoval. Hlavní příčinou tedy není nevyvážený trénink, ale hranice kategorií.
Nejlepší příklad: kategorie 2 (Software a licence), 4 (IT provoz, podpora a cloud) a 5 (Kybernetická bezpečnost) třídí každá podle jiného hlediska. Dvojka podle toho, co se kupuje, čtyřka podle činnosti, pětka podle účelu. "Podpora bezpečnostního produktu" je podpora výrobce (2), podpora systému (4) i bezpečnostní technologie (5) zároveň a definice neříkají, které hledisko má přednost. Golden pak takové smlouvy dává jednou sem, jednou tam a model se to nemá podle čeho naučit.
Tohle je strop, přes který žádný model nepřeskočí. Další body už nepřinese větší model, ale jasnější taxonomie: rozhodnout, jestli má přednost forma (dotace, spolupráce), nebo obor, a podle toho sjednotit popisy i anotace. Hlídač teď kategorie zjednodušuje a popisky zpřesňuje. Pozor ale při srovnávání: model na nové taxonomii řeší jinou úlohu, takže jeho čísla nejdou porovnávat s tabulkami v tomhle článku.
Jak to dopadlo v provozu
Na papíře byla nejlepší open-weight volba kombinace pplx-decider + TF-IDF. Na hardwaru, kde to má běžet, se ale ukázalo, co žádná tabulka přesnosti neukáže: 27B decision model, který přes API odpovídá za 0.83 s, potřeboval na DGX Sparku 9 až 15 sekund na smlouvu a 49 GB paměti. To je pár tisíc smluv denně, tedy roky na celý registr. Kvantizace na 4 bity paměť snížila, ale přesnost spadla a rychlejší to nebylo.
Do provozu proto šel Qwen3-Embedding-8B s LoRA, sám, bez TF-IDF. Je víc než třikrát menší, nic negeneruje, jen jednou projde vstup, a ve vLLM zvládne smlouvu za zhruba půl až tři čtvrtě sekundy na jedné GPU. Přesnost se proti pplx + TF-IDF prakticky nezměnila (92.8 proti 92.9 %), rychlost o řád.
Hlídač teď klasifikuje celý archiv na čtyřech GPU. Podle Michalova odhadu bude celý registr zklasifikovaný zhruba za deset dní.
Co si z toho odnést
- Začněte TF-IDF. 83.6 % zadarmo na CPU je laťka, kterou musí každé AI řešení přeskočit.
- Decision modely jsou nejlepší start bez dat. Jev bez jediného trénovacího příkladu 92.0 % za necelý dolar na 1 754 smluv.
- Velký zmražený embedding + lineární hlava je nejlepší poměr cena/výkon pro rychlý start. Qwen3-Embedding-8B s jednou větou instrukce: 91.6 %.
- Když máte data, dotrénujte velký encoder. Qwen3-Embedding-8B s LoRA: 92.8 % jedním modelem, bez kombinací, pod sekundu na smlouvu na jedné GPU.
- Kombinujte dva různé druhy znalostí, ale jen u slabších modelů. Decision modelu TF-IDF přidá 1.2 až 3.5 bodu. Dotrénovanému Qwenu ubere 0.3.
- U dlouhých dokumentů nejdřív zkuste obyčejný ořez na začátek. Ověřte reálná kontextová okna a změřte, jestli delší vstup vůbec pomáhá. U nás nepomohl.
- Rychlost změřte na hardwaru, kde to poběží. 0.83 s přes API se na vlastním GPU změnilo na 12 s.
- Nevěřte slepě ani golden datasetu, ani AI arbitrovi. Největší strop není model, je to taxonomie.
- Párové testy, nebo horoskop. Na 1 754 smlouvách je ±0.7 bodu šum. Víc než polovina rozdílů v grafu "co pomohlo" je v šumu.
- Nechte si to zkontrolovat někým, kdo měří líp než vy. Já jsem měl research oddělení Seznamu.
Z "máme Excel s 8 818 smlouvami" jsme se dostali k modelu, který trefí 92.8 % smluv, běží na běžné GPU a celý registr zklasifikuje zhruba za deset dní. Nejpřesnější kombinace z tabulky do provozu nešla, protože byla na reálném hardwaru víc než 15x pomalejší. Nevyhrál jeden zázračný model. Vyhrálo to, že jsme poctivě změřili 184 nezázračných, věděli, kterým číslům věřit, a nakonec je změřili i tam, kde to má běžet.
Velký dík Michalovi Bláhovi a týmu Hlídače státu za data, přístup k API, dočištěný golden a výpočetní kapacitu, a research oddělení Seznamu za připomínky, které z reportu udělaly lepší výzkum. Pokud vás zajímá teorie za tím, mám dva související rozcestníky: Encoder, nebo LLM? a Vyhledávání pomocí vektorů v roce 2026. Předchůdce téhle práce je mimochodem diplomka Jakuba Marouška Automatická klasifikace smluv pro portál HlidacSmluv.cz z roku 2020. Najdete mě na LinkedIn nebo na emailu.