Tokenizace: jak se text rozseká na kousky pro model
Znaky proti slovům, algoritmus BPE napsaný od nuly a naučený na článcích tohoto webu, velikost slovníku, proč je čeština s cizím tokenizátorem dražší, bajty UTF-8 a podivné dělení čísel.
Série Základy AI Díl 71 z 83
- Mapa umělé inteligence: co je co a jak to do sebe zapadá
- Stručná historie umělé inteligence: od Turinga po jazykové modely
- Data, model, trénink, inference: základní pojmy na jednom příkladu
- Jak číst matematický zápis: symboly, indexy, sumy a kvantifikátory
- Množiny a relace: stavebnice, ze které se skládají definice v AI
- Funkce a zobrazení: proč je každý model v AI funkce
- Logika a důkazy: pravidla, podle kterých se v AI uvažuje
- Grafy a stromy: struktura, na které stojí hledání i neuronové sítě
- Lineární algebra pro strojové učení: vektory, matice a proč AI hlavně násobí
- Derivace, gradient a řetízkové pravidlo: jak model zjistí, kterým směrem se učit
- Gradientní sestup: jak se model učí
- Co je pravděpodobnost: od hodu kostkou ke Kolmogorovovým axiomům
- Podmíněná pravděpodobnost a Bayesova věta: jak změnit odhad, když se dozvíte něco nového
- Náhodná veličina a distribuční funkce: jedna funkce, která o rozdělení řekne všechno
- Funkce hustoty a pravděpodobnostní funkce: proč má spojitá veličina v bodě pravděpodobnost nula
- Střední hodnota, rozptyl a kovariance: kde rozdělení leží, jak je široké a co s čím souvisí
- Přehled pravděpodobnostních rozdělení: Bernoulli, binomické, Poisson, rovnoměrné, normální a exponenciální
- Zákon velkých čísel a centrální limitní věta: proč se průměry chovají tak předvídatelně
- Statistika a odhady parametrů: maximální věrohodnost, MAP a intervaly spolehlivosti
- Teorie informace: entropie, křížová entropie a KL divergence — odkud se berou ztrátové funkce
- Prohledávání stavového prostoru: BFS, DFS, UCS a A* s animacemi
- Hry a protivník: minimax, alfa-beta ořezávání a Monte Carlo tree search
- Splňování omezujících podmínek: backtracking, propagace omezení a AC-3 na sudoku a mapě krajů
- Logika a znalostní systémy: dopředné a zpětné řetězení, rezoluce a expertní systémy
- Plánování: STRIPS, svět kostek, heuristiky z relaxace a plánování v AI agentech
- Lokální hledání a simulované žíhání: obchodní cestující přes krajská města
- Genetické algoritmy: populace, křížení, mutace a výběr na batohu na výlet
- Evoluční strategie a genetické programování: od pravidla jedné pětiny po Keplerův zákon
- Optimalizace mravenčí kolonií: jak feromonová stopa najde krátkou cestu
- Optimalizace hejnem částic: setrvačnost, vlastní zkušenost a zkušenost hejna
- Neuroevoluce: evoluce vah i tvaru neuronových sítí a algoritmus NEAT
- Fuzzy množiny a funkce příslušnosti: jak zapsat „trochu teplo“
- Fuzzy logika a pravděpodobnost: proč „trochu teplo“ neznamená „pravděpodobně teplo“
- Fuzzy logika: t-normy, implikace a pravidla KDYŽ–PAK
- Fuzzy řízení: Mamdani a Sugeno od ventilátoru po kyvadlo na vozíku
- Fuzzy shlukování: algoritmus fuzzy c-means a body mezi shluky
- Neuro-fuzzy systémy (ANFIS): fuzzy pravidla, která se učí z dat
- Markovovy řetězce: přechodová matice, stacionární rozdělení a PageRank
- Skryté Markovovy modely: Viterbiho algoritmus a nepoctivé kasino
- Bayesovské sítě: proč je tráva mokrá a co z toho plyne pro postřikovač
- Kalmanův filtr: jak z nepřesných měření odhadnout, kde auto je a jak rychle jede
- Druhy strojového učení: s učitelem, bez učitele, s částečným učitelem a další
- Přeučení a regularizace: proč model, který zná data nazpaměť, selže
- Vyhodnocení modelu: křížová validace, F1 a křivka ROC
- Shlukování a snížení dimenze: k-means, PCA a t-SNE
- Klasifikace a regrese: dvě úlohy učení s učitelem
- Lineární regrese: nejmenší čtverce geometricky i maticově
- Polynomiální a nelineární regrese: jak přímku ohnout
- Logistická regrese: sigmoida, šance a křížová entropie
- Vícetřídní klasifikace a softmax: od dvou tříd k mnoha
- Naivní Bayesův klasifikátor: spamový filtr, který funguje lépe, než by měl
- Ztrátové funkce: co přesně model minimalizuje
- Rozhodovací stromy a k nejbližších sousedů: modely bez vzorců
- Ansámbly: náhodný les a gradient boosting
- SVM a jádrový trik: nejširší hranice a jak ji ohnout
- Signál a vzorkování: proč platí Nyquistův teorém
- Fourierova transformace: rozklad signálu na frekvence
- Filtry a konvoluce: vyhlazení, hrany a odstranění šumu
- Zvuk a řeč: spektrogram, melová škála a MFCC
- Obraz jako signál: pixely, barvy, konvoluce a hrany
- Perceptron a umělý neuron: nejjednodušší neuronová síť a co neumí
- Neuronová síť od nuly: zpětné šíření chyby krok za krokem
- Aktivace, inicializace a normalizace: drobnosti, na kterých hluboká síť stojí
- Konvoluční sítě: jak síť poznává obrázky
- Rekurentní sítě: paměť pro posloupnosti a mizející gradient
- LSTM a GRU: brány, které rozhodují, co si síť zapamatuje
- Autoenkodéry: síť, která se učí data stlačit a zase rozbalit
- Embeddingy a word2vec: jak se slovo stane vektorem
- Mechanismus pozornosti: jak se model naučí, kam se dívat
- Transformery a velké jazykové modely: architektura krok za krokem
- Tokenizace: jak se text rozseká na kousky pro model
- Předtrénování a škálování: proč větší model a víc dat funguje
- Doladění a RLHF: jak se z doplňovače textu stane asistent
- Prompty, kontext a RAG: jak modelu dát správné informace
- Variační autoenkodéry (VAE): nové obrázky ze spojitého latentního prostoru
- Generativní adversariální sítě (GAN): padělatel proti detektivovi
- Difúzní modely: jak se z čistého šumu stane obrázek
- Markovovy rozhodovací procesy: stavy, akce, odměny a Bellmanovy rovnice
- Q-learning a DQN: učení hodnoty akcí od tabulky po neuronovou síť
- Policy gradient a PPO: učení přímo strategie
- Jak funguje AI agent: jazykový model ve smyčce s nástroji
- Programování s AI agenty: zadání, testy a kontrola
- Vyhodnocení a bezpečnost AI systémů: jak měřit a jak hlídat
Celá osnova série 83 z 83 témat hotovo
- Proč modely nečtou ani znaky, ani celá slova.
- Jak funguje BPE a co se na češtině naučí.
- Proč stejný text stojí v různých jazycích různě tokenů a proč modely klopýtají v počítání.
Co se hodí znát předem: Transformery a velké jazykové modely: architektura krok za krokem, Embeddingy a word2vec: jak se slovo stane vektorem
Jazykový model nečte písmena ani slova. Čte tokeny — kousky textu, na které ho rozseká tokenizátor, ještě než model vůbec začne počítat. Z tohohle nenápadného kroku plyne překvapivě mnoho: kolik stojí dotaz (platí se za tokeny), kolik textu se vejde do kontextu, proč modely občas neumějí spočítat písmena ve slově nebo sečíst dvě dlouhá čísla — a proč je čeština u mnoha modelů dražší než angličtina. V tomhle dílu naprogramujeme nejpoužívanější metodu, BPE, a naučíme ji na textu článků z tohoto webu.
Navazuje na díly Transformery a velké jazykové modely a Embeddingy a word2vec.
1. Znaky, nebo slova?
Definice — token a tokenizátor
Nechť Σ je konečná neprázdná abeceda (množina znaků, které se v textu mohou objevit). Slovník je konečná množina V neprázdných řetězců znaků z Σ; jejím prvkům říkáme tokeny a jsou očíslované čísly 0, 1, …, |V| − 1. Tokenizátor je postup, který
- každý text s (konečný řetězec znaků z Σ) rozloží na posloupnost tokenů v1, …, vn ∈ V,
- každý token nahradí jeho číslem (indexem do tabulky embeddingů, viz díl Embeddingy a word2vec),
- je obratitelný: zřetězení v1v2…vn je přesně původní text s.
Model tedy místo textu dostane posloupnost čísel tokenů. Přesný zápis je v definici „tokenizátor formálně“ níže.
Dvě krajní možnosti. Po znacích: slovník je malý (v našich českých článcích 185 různých znaků), ale texty jsou dlouhé — každé písmeno je jeden krok modelu. Po slovech: texty jsou krátké, ale slovník obrovský, a hlavně neúplný. Čeština má ke každému slovu desítky tvarů: model, modelu, modelem, modely, modelům…
Definice — tokenizátor formálně
Nechť Σ je konečná neprázdná abeceda a Σ* množina všech konečných řetězců nad Σ (včetně prázdného). Nechť V ⊆ Σ* ∖ {prázdný řetězec} je konečný slovník a V* množina všech konečných posloupností tokenů z V. Pro s ∈ Σ* značí |s| jeho délku ve znacích, pro posloupnost tokenů |·| jejich počet. Tokenizátor je dvojice zobrazení
enc : Σ* → V*, dec : V* → Σ*, dec(v1, v2, …, vn) = v1v2…vn (zřetězení),
pro která platí
∀ s ∈ Σ* : dec(enc(s)) = s.
Číslování tokenů je bijekce V → {0, 1, …, |V| − 1}; model vidí jen posloupnost čísel tokenů délky |enc(s)|. Aby šel zakódovat každý text, musí slovník obsahovat aspoň všechny jednotlivé znaky (Σ ⊆ V) — slovní tokenizátor tuhle podmínku nesplňuje, a proto potřebuje značku „neznámé slovo“, po které už text zpátky složit nejde.
Data: 21 starších článků tohoto webu česky a v anglickém překladu. Na 14 článcích se tokenizátory učí, na zbylých 7 se zkoušejí. Slovník slov z 14 článků má 4 365 položek, a přesto v nových článcích 15 % slov chybí — nový text je plný tvarů, které model nikdy neviděl, a slovní tokenizátor by je musel nahradit značkou „neznámé slovo“.
Definice — podíl neznámých slov
Nechť S je konečná množina různých slov, která se vyskytla v trénovacích textech (slovník slov), a w1, …, wn, n ≥ 1, posloupnost všech výskytů slov v testovacích textech v pořadí, jak jdou za sebou (u nás vše převedené na malá písmena; v testu se počítají jen slova z písmen, bez čísel a interpunkce). Podíl neznámých slov (out-of-vocabulary rate) je číslo
OOV = |{ i ∈ {1, …, n} : wi ∉ S }|/n ∈ [0, 1],
tedy podíl výskytů, ne různých slov: slovo, které chybí a v testu stojí pětkrát, se počítá pětkrát. U nás S má 4 365 slov a OOV = 0,153 ≈ 15 %: ze sta slov nového textu by slovní tokenizátor asi 15 neznal.
2. BPE: slučuj, co chodí spolu
Definice — byte-pair encoding (BPE)
Byte-pair encoding (BPE) je algoritmus učení tokenizátoru.
Vstup: trénovací text rozdělený na slova w s četnostmi n(w) ∈ ℕ (kolikrát slovo v textu stojí), abeceda Σ všech znaků těchto slov a největší počet sloučení K ∈ ℕ.
Výstup: uspořádaný seznam sloučení (a1, b1), …, (ak, bk), k ≤ K, a slovník V = Σ ∪ {a1b1, …, akbk}, takže |V| ≤ |Σ| + k.
Kroky:
- Každé slovo w rozděl na jednotlivé znaky (každý znak je jeden symbol); polož j = 0.
- Pro každou dvojici symbolů (a, b) spočti její četnost ∑w n(w) · cab(w), kde cab(w) je počet míst, kde v aktuálním dělení slova w stojí symbol a hned před symbolem b.
- Zastavení: je-li j = K, nebo má-li každá dvojice četnost nejvýš 1, skonči.
- Vyber dvojici (aj+1, bj+1) s největší četností (při shodě rozhodne pevné pořadí, u nás abecední) a připiš ji na konec seznamu sloučení.
- V dělení každého slova nahraď zleva doprava každý výskyt „aj+1 hned před bj+1“ jediným novým symbolem aj+1bj+1 (zřetězení); zvyš j o 1 a pokračuj krokem 2.
Tokenizace nového textu: každé slovo se rozdělí na znaky a dokud v jeho dělení stojí vedle sebe dvojice ze seznamu sloučení, provede se sloučení té z nich, která je v seznamu nejdřív. Původně kompresní algoritmus (Philip Gage, 1994), na tokenizaci ho použili Rico Sennrich a kol. v roce 2016.
Kdo si píše poznámky, začne zkracovat, co se opakuje. Nejdřív dvojice písmen, pak slabiky, pak celá častá slova. Vzácná slova zůstanou rozložená na kousky, které se ale vždycky dají složit — žádné „neznámé slovo“ nikdy nenastane, nejhůř se slovo napíše po písmenech.
(a*, b*) = argmax(a, b) ∑w n(w) · cab(w)
- (a, b)
- kandidát na sloučení: dva symboly, které v aktuálním dělení nějakého slova stojí hned za sebou. Jen sousedé — token má být souvislý kus textu, aby ho šlo zřetězením složit zpátky
- ∑w
- součet přes různá slova trénovacího textu. Text se předem rozdělí na slova (s mezerou ␣ na začátku), takže dvojice nikdy nepřeskočí přes hranici slova a tokeny nespojí konec jednoho slova se začátkem dalšího
- n(w)
- kolikrát slovo w stojí v textu. Každé různé slovo se rozdělí jen jednou a jeho dvojice se vynásobí četností — časté slovo jako „␣je“ se nemusí procházet znovu při každém výskytu, stačí slovník četností
- cab(w)
- kolikrát dvojice ab stojí v aktuálním dělení slova w. Dělení se po každém sloučení mění, proto se dvojice musí po každém kroku spočítat znovu
- argmax
- vezme se nejčastější dvojice: každý její výskyt se po sloučení zapíše o symbol kratší, takže nejčastější dvojice zkrátí trénovací text nejvíc (až na překryvy jako „aaa“). BPE je tedy hladová komprese — v každém kroku nejlepší jednotlivý krok, ne nejlepší slovník celkově. Při shodě četností rozhodne pevné pořadí (u nás abecední), aby byl výsledek pokaždé stejný
Příklad — BPE ručně na pěti slovech
Text o 16 slovech, zapsaný jako slovník četností: „model“ 5×, „modelu“ 3×, „modely“ 2×, „modelem“ 2×, „den“ 4×. Symboly v dělení odděluje tečka.
| krok | sloučí se | četnost | dělení slov po kroku | symbolů v textu |
|---|---|---|---|---|
| 0 | — | — | m·o·d·e·l, m·o·d·e·l·u, m·o·d·e·l·y, m·o·d·e·l·e·m, d·e·n | 81 |
| 1 | d + e → de | 16 | m·o·de·l, m·o·de·l·u, m·o·de·l·y, m·o·de·l·e·m, de·n | 65 |
| 2 | de + l → del | 12 | m·o·del, m·o·del·u, m·o·del·y, m·o·del·e·m, de·n | 53 |
| 3 | m + o → mo | 12 | mo·del, mo·del·u, mo·del·y, mo·del·e·m, de·n | 41 |
| 4 | mo + del → model | 12 | model, model·u, model·y, model·e·m, de·n | 29 |
| 5 | de + n → den | 4 | model, model·u, model·y, model·e·m, den | 25 |
| 6 | model + u → modelu | 3 | model, modelu, model·y, model·e·m, den | 22 |
krok 1: dvojice d + e je ve všech slovech, cde(w) = 1 pro každé: 5 · 1 + 3 · 1 + 2 · 1 + 2 · 1 + 4 · 1 = 16 (barvy podle vzorce výše); ostatní dvojice (m + o, o + d, e + l …) chybí ve slově „den“, mají nejvýš 12
krok 2: de + l, m + o i o + de mají shodně 12 — vyhraje abecedně první, de + l
krok 5: de + n (4) předběhne model + u (3), model + y a model + e (po 2)
délka textu: 81 − (16 + 12 + 12 + 12 + 4 + 3) = 81 − 59 = 22 symbolů — každé sloučení ubere přesně tolik, kolik je jeho četnost
Po šesti krocích je „model“ jeden token, „modelu“ taky, ale „modelem“ zůstalo „model·e·m“ — koncovka -em byla moc vzácná. Přesně tohle se děje i se 4 000 sloučeními, jen ve větším.
def nauc_bpe(text, k):
cetnost = Counter(rozdel_na_slova(text)) # slovo → kolikrát je v textu
deleni = {w: list(w) for w in cetnost} # začínáme po znacích
slouceni = []
for _ in range(k):
pary = Counter()
for w, n in cetnost.items():
for a, b in sousedni_dvojice(deleni[w]):
pary[a, b] += n # ∑ n(w) · c_ab(w)
if not pary or max(pary.values()) < 2: break # nic se už neopakuje
a, b = nejcastejsi(pary) # při shodě abecedně první
slouceni.append((a, b))
for w in deleni: # nahraď všechny výskyty
deleni[w] = sluc(deleni[w], a, b)
return slouceni
def tokenizuj(text, slouceni):
poradi = {par: i for i, par in enumerate(slouceni)}
tokeny = []
for w in rozdel_na_slova(text):
s = list(w)
while True: # vždy nejdřív to sloučení,
kandidati = [p for p in sousedni_dvojice(s) if p in poradi]
if not kandidati: break # které vzniklo při učení nejdřív
s = sluc(s, *min(kandidati, key=poradi.get))
tokeny += s
return tokeny
Prvních dvacet sloučení na českých článcích (␣ označuje mezeru před slovem): ␣p ␣s ␣j ␣n ␣v en ro ␣k st od ␣a ␣je ␣d no te ␣m ra ní ch in. Na anglických: ␣t ␣a he ␣i ␣the on ␣s re in ti er ␣w ␣o ␣p es ␣c nd ␣f at ␣is. Tokenizátor objeví jazyk sám: v češtině předložky a předpony (s, v, k, na, je), koncovku „ní“, v angličtině „the“ a „is“.
Se 4 000 sloučeními je „pravděpodobnější“ jeden token — v článcích o pravděpodobnosti je to časté slovo. Předpona „Nej“ s velkým písmenem ale v datech skoro nebyla, a tak zůstala rozložená na písmena. Tokenizátor nerozumí slovům, jen četnostem. Poslední řádek je stejná věta v tokenizátoru naučeném na angličtině: písmena s háčky a čárkami zůstávají samostatně a tokenů je skoro třikrát víc.
3. Kolik tokenů a jak velký slovník
Po znacích má sedm testovacích článků v češtině 51 682 tokenů. Prvních 250 sloučení počet skoro způlí (28 416), dalších 3 750 už ubere jen čím dál méně, na 16 997. To je 3,04 znaku na token. Větší slovník znamená kratší texty (levnější výpočet, delší dosah kontextu), ale větší tabulku embeddingů a vzácnější tokeny, které se model hůř naučí. Velké jazykové modely mají slovníky o desítkách tisíc až kolem 200 000 tokenech.
|V| = |Σ| + k, ρ = |s|/|enck(s)|
- |Σ|
- počet znaků, se kterými BPE začíná (u nás 185, mezera v podobě ␣). Zůstávají ve slovníku i po všech sloučeních — jsou pojistkou, že jde zakódovat každý text
- k
- počet sloučení; každé přidá jeden token (obecně nejvýš jeden — dvě sloučení jako „a + bc“ a „ab + c“ mohou dát týž řetězec; u nás je všech 4 000 různých) a staré tokeny nemaže (i „mo“ zůstane, i když se z něj stalo „model“). U nás 185 + 4 000 = 4 185 tokenů. Velikost slovníku si tedy volíme předem a k je jediný „knoflík“ BPE
- |s|
- délka textu ve znacích — nezávisí na tokenizátoru, proto je to dobrý jmenovatel pro srovnání různých tokenizátorů na stejném textu
- |enck(s)|
- počet tokenů po k sloučeních — tolik kroků musí model udělat a tolik místa text zabere v kontextu
- ρ
- kompresní poměr, znaků na token: čím víc, tím je text pro model „kratší“. Pro k = 0 je ρ = 1
Příklad — kolik stojí velký slovník
Každý token má v modelu vlastní řádek tabulky embeddingů o šířce d, takže tabulka má |V| · d parametrů. Llama 3 (Meta, 2024) má slovník o 128 256 tokenech a šířku d = 4 096:
128 256 · 4 096 = 525 336 576 parametrů, u modelu s 8 miliardami parametrů 525 336 576/8 · 109 ≈ 6,6 %
A stejný tvar má i výstupní vrstva, která z posledního vektoru počítá pravděpodobnosti všech tokenů. Zdvojnásobit slovník tedy znamená o stovky milionů parametrů víc — a každý vzácný token v nich má řádek, který se učí jen z mála výskytů.
4. Daň za jazyk
Teď to nejzajímavější. Testovací články máme v obou jazycích — stejný obsah. Kolik tokenů zabere s různými tokenizátory (všechny po 4 000 sloučeních)?
| Tokenizátor naučený na | česká verze | anglická verze |
|---|---|---|
| české texty | 16 997 | 31 293 |
| anglické texty | 31 072 | 17 212 |
| obojí dohromady | 18 030 | 18 220 |
Se „svým“ tokenizátorem vyjdou oba jazyky skoro stejně draze (16 997 a 17 212 tokenů). Česká verze tokenizovaná anglickým tokenizátorem ale zabere 1,83krát víc tokenů — anglické sloučení „ti“ nebo „er“ češtině moc nepomůže, a slova s háčky se rozpadnou na písmena. Totéž obráceně. Společný tokenizátor je kompromis: každý jazyk stojí asi o 6 % víc než se svým.
daň = |encen(scs)|/|enccs(scs)|
- scs
- tentýž český text v čitateli i jmenovateli — proto se v podílu obsah textu zkrátí a zbude jen vliv tokenizátoru
- |encen(scs)|
- tokeny s tokenizátorem naučeným na angličtině (modře v grafu výše) — tak dopadne čeština u modelu, jehož slovník vznikl hlavně z anglických textů
- |enccs(scs)|
- tokeny s tokenizátorem naučeným na češtině (zeleně) — nejlepší, čeho jde se stejně velkým slovníkem dosáhnout, proto je ve jmenovateli jako měřítko
Skutečné tokenizátory se učí na textu, kde výrazně převažuje angličtina. Čeština tak bývá dražší: stejná otázka stojí víc tokenů, je pomalejší, dražší a do kontextu se jí vejde méně. Aleksandar Petrov a kolegové v roce 2023 změřili, že u některých jazyků je rozdíl až patnáctinásobný. Novější modely mají slovníky větší a vícejazyčnější, a rozdíl se proto zmenšuje.
5. Bajty, háčky a čísla
Definice — bajtové BPE
Nechť 𝔹 = {0, 1, …, 255} je množina všech 256 hodnot bajtu a pro text s označme utf8(s) ∈ 𝔹* posloupnost bajtů jeho zápisu v kódování UTF-8 (definice níže). Bajtové BPE (GPT-2, 2019) je BPE (definice výše), ve kterém roli abecedy hraje 𝔹 místo znaků:
- učí se na posloupnostech utf8(w) slov w trénovacího textu, výchozí slovník je 𝔹 (|𝔹| = 256) a sloučení spojují sousední posloupnosti bajtů;
- text s se tokenizuje tak, že se BPE použije na utf8(s), a dekóduje se zřetězením bajtů tokenů a jejich převodem zpět z UTF-8.
Protože 𝔹 je ve slovníku vždy celé, jde zakódovat libovolný text, včetně emoji a cizích písem — v nejhorším případě bajt po bajtu.
V UTF-8 má písmeno bez diakritiky jeden bajt, „ž“ nebo „ř“ dva. Česká testovací verze má 51 682 znaků, ale 58 260 bajtů (o 13 % víc), anglická jen o 3 % víc. Když tokenizátor češtinu moc neviděl, rozpadne se slovo s háčkem i na půlky písmen.
Definice — kódování UTF-8
Každý znak c má v Unicode číslo, kódový bod cp(c) ∈ {0, 1, …, 10FFFF16} (píše se šestnáctkově s předponou U+, „ž“ je U+017E = 382). Mimo zůstávají jen kódové body D80016 až DFFF16, které znakům nepatří. UTF-8 je kódování, které znak c zapíše posloupností b(c) bajtů, kde
- b(c) = 1, právě když cp(c) ≤ 7F16;
- b(c) = 2, právě když 8016 ≤ cp(c) ≤ 7FF16;
- b(c) = 3, právě když 80016 ≤ cp(c) ≤ FFFF16;
- b(c) = 4, právě když 1000016 ≤ cp(c) ≤ 10FFFF16;
a bity bajtů jsou určené tabulkou:
| kódové body | bajtů | bity | příklad |
|---|---|---|---|
| U+0000 – U+007F | 1 | 0xxxxxxx | e, 7, mezera (ASCII) |
| U+0080 – U+07FF | 2 | 110xxxxx 10xxxxxx | ž, ř, á, ü, cyrilice |
| U+0800 – U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx | „ (U+201E), €, čínské znaky |
| U+10000 – U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | emoji |
Kódový bod zapsaný dvojkově se zleva doplní nulami na počet míst x v daném řádku (7, 11, 16, nebo 21) a jeho bity se zleva doprava rozepíšou do míst x. Text s = c1c2…cm se zakóduje zřetězením kódů jeho znaků, takže jeho délka v bajtech je
B(s) = ∑i=1m b(ci), b(ci) ∈ {1, 2, 3, 4}.
ž = U+017E → 11000101 10111110 = C5 BE
- 00101 111110
- samotné číslo znaku: 0x17E = 382 = 1 0111 1110 dvojkově, doplněné na 11 bitů, které se do dvou bajtů vejdou (5 + 6). Proto dva bajty stačí až po U+07FF = 211 − 1
- 110
- úvodní bajt: počet jedniček na začátku říká, z kolika bajtů se znak skládá (zde dvou). Dekodér tak ví, kolik bajtů má přečíst, bez jakékoli tabulky
- 10
- pokračovací bajt: začíná vždy 10, a to žádný úvodní bajt ani ASCII znak nedělá. Kdo začne číst uprostřed, pozná, že je uprostřed znaku. Pro tokenizátor to znamená, že token z jediného bajtu C5 je „půlka ž“ — sama o sobě žádný znak
Čísla se dělí podle toho, které kousky byly v textu časté: „2026“ na „20|2|6“, „123456“ na „12|3|4|5|6“. Model pak nevidí číslice na stejných místech, a to je jeden z důvodů, proč jazykové modely dlouho chybovaly v počítání. A protože „jahoda“ je pro model třeba jeden nebo dva tokeny, ne šest písmen, je pro něj spočítat písmena ve slově překvapivě těžké. Novější tokenizátory proto čísla rozdělují vždy po jedné až třech číslicích.
# předzpracování čísel, ještě než na text pustíme BPE
# náš tokenizátor: číslo = jedno „slovo“ [\p{N}]+, BPE z něj sloučí, co bylo časté
"2026" → "20" | "2" | "6" # podle četností v trénovacích textech
# GPT-3.5 a GPT-4 (cl100k_base, OpenAI): regulární výraz \p{N}{1,3}, skupiny po třech zleva
"2026" → "202" | "6"
"123456" → "123" | "456"
# LLaMA (Meta 2023): každá číslice zvlášť, model vidí vždy stejné jednotky
"2026" → "2" | "0" | "2" | "6"
6. Shrnutí
- Model čte tokeny; tokenizátor převádí text na čísla a musí jít přesně obrátit.
- Po slovech chybí v nových článcích 15 % slov; po znacích jsou texty dlouhé. BPE je kompromis, který žádné „neznámé slovo“ nezná.
- BPE opakovaně slučuje nejčastější dvojici; 4 000 sloučení zkrátilo české texty z 51 682 na 16 997 tokenů.
- Stejný obsah: se svým tokenizátorem čeština 16 997 tokenů, angličtina 17 212; český text s anglickým tokenizátorem 1,83krát víc.
- Dělení čísel a slov na tokeny vysvětluje, proč modely klopýtají v počítání a hláskování.
Chcete si to osahat? Nechte AI agenta načíst tokenizátor nějakého otevřeného modelu (třeba přes knihovnu tiktoken) a porovnat, kolik tokenů stojí tenhle odstavec česky a jeho anglický překlad.
Máte na to jiný názor? Napište na support@coden-app.com.