Tokenizacja: atomy w świecie LLM-ów
Chcemy, by model generował nam poetyckie zdania albo kod programistyczny — ale przy ogromnej liczbie możliwych wejść praca na całych korpusach danych naraz nie jest możliwa. W tym pokoju dowiesz się, jak badacze sobie z tym poradzili, i przejdziemy przez I etap: tokenizację.
I etap
Nawet bez technicznego zaplecza większość z nas przeczuwa, że model nie traktuje tekstu jako jednej, niepodzielnej całości. Praca na całych zdaniach naraz zbyt mocno ograniczałaby samo działanie — więc potrzeba czegoś drobniejszego i policzalnego. Język naturalny jest niezwykle bogaty — nieustannie powstają nowe zdania, a nawet nowe słowa. Gdyby model próbował traktować każde słowo jako osobny element słownika, jego rozmiar musiałby być ogromny, a mimo to nie obejmowałby wszystkich możliwych wyrazów. Zdecydowanie lepszym rozwiązaniem jest podzielenie tekstu na mniejsze jednostki, z których można złożyć zarówno znane, jak i wcześniej niewidziane słowa oraz zdania.
Samo dzielenie tekstu na mniejsze części nie jest niczym odkrywczym. Kluczowe jest jednak to, jak małe powinny być te części. Moglibyśmy ograniczyć się do pojedynczych liter, jednak litery same w sobie niosą niewiele informacji. Dodatkowo prowadziłoby to do znacznie dłuższych sekwencji, przez co model musiałby wykonywać więcej obliczeń podczas przetwarzania tekstu i generowania każdej kolejnej odpowiedzi (Byłoby to kosztowne, szczególnie jeśli płacimy za każdy token).
Sennrich i współautorzy znaleźli kompromis. W 2015 roku zaproponowali wykorzystanie algorytmu pierwotnie przeznaczonego w celach kompresji danych — Byte Pair Encoding (BPE) do automatycznego budowania słownika tokenów. Algorytm analizuje ogromny zbiór tekstów i stopniowo łączy najczęściej występujące obok siebie fragmenty znaków. W rezultacie często spotykane słowa lub ich części stają się pojedynczymi tokenami, natomiast rzadsze wyrazy są rozbijane na kilka mniejszych fragmentów. Dzięki temu słownik pozostaje stosunkowo niewielki, a model nadal potrafi przetwarzać słowa, których nigdy wcześniej nie widział.
Schemat działania BPE:
- Podziel tekst na pojedyncze znaki — każdy jest osobnym tokenem.
- Znajdź parę sąsiadujących tokenów, która występuje najczęściej.
- Połącz tę parę w jeden nowy token i dodaj go do słownika.
- Powtarzaj kroki 2–3, aż słownik osiągnie zadany rozmiar.
Ważne: pojedyncze znaki i krótkie tokeny scalone we wczesnych fazach pętli nie znikają ze słownika — zostają w nim na stałe. Podczas tokenizacji nowego tekstu przeszukiwanie zaczyna się od najdłuższych tokenów, a te najkrótsze (litery) służą jako „awaryjny" fallback dla fragmentów, których nie da się dopasować niczym większym.
Wiemy już, że na początku tworzony jest słownik tokenów, według którego tekst jest dzielony na mniejsze części. Aby ułatwić dalszą pracę z modelem, każdemu tokenowi przypisywany jest unikalny identyfikator (ID). Proces zamiany tekstu na sekwencję takich identyfikatorów nazywamy tokenizacją.
Tokenizacja w skrócie: algorytm analizuje tekst i, korzystając z wcześniej utworzonego słownika tokenów, wyszukuje fragmenty, które się w nim znajdują. Zawsze stara się dopasować jak największy fragment tekstu. Jeżeli całe słowo występuje w słowniku, zostaje potraktowane jako jeden token. W przeciwnym razie jest dzielone na mniejsze części, aż każda z nich zostanie odnaleziona w słowniku. Następnie każdy token zostaje zastąpiony swoim identyfikatorem (token ID). Pamiętaj też, że BPE nie jest gotowym słownikiem, lecz algorytmem, który na podstawie dużego zbioru tekstów automatycznie buduje słownik tokenów, dlatego jego zawartość zależy od danych użytych do jego utworzenia. Nie istnieje więc jeden uniwersalny słownik tokenów — twórcy poszczególnych modeli budują własne słowniki, wykorzystując algorytm BPE lub jego odmiany.
Czas na praktykę!
Zobaczymy to teraz na przykładzie. Skorzystamy ze słownika stworzonego przez twórców OpenAI oraz z biblioteki tiktoken, która dzięki wbudowanym funkcjom znacznie upraszcza cały proces. Dzięki temu będziemy tokenizować tekst dokładnie tak samo, jak robią to modele serii GPT.
Poniżej importujemy wspomnianą bibliotekę tiktoken i tworzymy instancję tokenizera funkcją
encoding_for_model. Udostępnia ona kilka metod — nam przyda się encode, która
zamienia nasz tekst na listę identyfikatorów tokenów (ID).
import tiktoken
text = "Tokenize me!"
enc = tiktoken.encoding_for_model("gpt-4o")
tokens = enc.encode(text)
print(tokens)
[4421, 750, 668, 0]
Jak widzimy, funkcja encoding_for_model() zwróciła tokenizer zgodny z modelem gpt-4o, natomiast wywołanie encode() podzieliło tekst na tokeny i zastąpiło każdy z nich odpowiadającym mu identyfikatorem (token ID), co zostało wyświetlone jako wynik programu.
Tak jak wspomnieliśmy, encode nie jest jedyną funkcją tokenizera — mamy jeszcze decode, aby przetłumaczyć wyjście modelu „na nasze". Twoim zadaniem jest sprawdzić, co model nam odpisał: [12194, 11, 29400, 869, 30]
Odpowiedź modelu (Wielkość liter ma znaczenie):
Podsumowując
- Token — podstawowa jednostka, na której operuje model. To fragment tekstu na tyle mały, by dało się z niego złożyć dowolne słowo, i na tyle duży, by nie schodzić do pojedynczych liter.
- BPE — algorytm, który na podstawie dużego korpusu automatycznie buduje słownik tokenów. Najczęściej występujące obok siebie fragmenty scala w większe tokeny, a rzadkie zostawia jako drobne części.
- Token ID — każdemu tokenowi przypisany jest unikalny identyfikator (liczba). Zamiana tekstu na sekwencję takich ID to właśnie tokenizacja.
- Brak uniwersalnego słownika — zawartość słownika zależy od danych treningowych, dlatego każdy model (np. seria GPT) ma własny. Ten sam tekst może dać różne ID w różnych modelach.