Tokens
Passo 1 de 7. Entra: o texto "o gato subiu no". Sai: cinco inteiros, [0, 2, 4, 5, 1]. ← Visão geral · próximo passo: Embeddings →
Uma rede neural calcula com números; então, antes de qualquer coisa, o texto precisa virar números. A unidade em que ele é cortado é o token: um pedaço de texto tirado de uma lista fixa, o vocabulário, em que cada pedaço tem um inteiro, o seu id. Um token pode ser uma palavra inteira (gato), parte de uma palavra (iu), um caractere ou até um byte. O modelo nunca vê letras, só a sequência de ids; tudo o que ele prevê também é um id.
Por que não palavras? Por que não caracteres?
As duas escolhas óbvias falham:
- Palavras. "subir", "sobe", "subiu" e "subindo" seriam quatro entradas sem relação, assim como cada nome, erro de digitação, número e palavra de cada língua: a lista nunca acaba. Pior: uma palavra que não estava na lista quando o modelo foi treinado não tem id nenhum.
- Caracteres (ou bytes). Nada fica desconhecido: 256 bytes escrevem qualquer texto. Mas "o gato subiu no" vira 15 símbolos em vez de 4 palavras, o custo da atenção cresce com o quadrado do comprimento (passo 3) e cada posição sozinha quase não carrega significado.
Os subwords ficam no meio: palavras frequentes ficam inteiras, as mais raras são escritas com alguns pedaços frequentes e o que sobra cai para bytes soltos, então nada é desconhecido. O GPT-2 escreve "the cat climbed the" com 4 tokens: the, cat, climbed, the.
Primeiro, bytes
O texto é guardado em bytes, em UTF-8: um byte para cada caractere ASCII, de dois a quatro para os outros — "a" é o byte 97, "ã" são dois bytes, 195 e 163. Um tokenizador em nível de byte como o do GPT-2 parte dos 256 bytes possíveis, então qualquer texto, em qualquer língua, pode ser codificado. O nosso brinquedo parte dos caracteres de um corpus pequeno (são 23), o que deixa as tabelas curtas; o algoritmo é o mesmo.
BPE: fundir o par mais frequente, de novo e de novo
O vocabulário é aprendido de um corpus pelo byte-pair encoding (BPE)1, antes de o modelo ser treinado e separado dele:
- Corte o corpus em palavras e cada palavra em caracteres (bytes, no GPT-2).
- Conte cada par de símbolos vizinhos. Funda o par mais frequente num símbolo novo e anote essa fusão.
- Repita até o vocabulário ter o tamanho desejado.
Para tokenizar um texto novo, aplique as fusões anotadas na mesma ordem. O GPT-2 aprendeu 50.000 fusões em texto da web, quase todo em inglês2. O nosso tokenizador de brinquedo aprende com oito frases:
o gato sentou no telhado. · o cão sentou no chão. · o gato pulou no muro. · o cão dormiu no sofá. · o gato quer subir no muro. · o cão quer subir no sofá. · o gato fugiu do telhado. · o cão dormiu no chão.
Como o GPT-2, ele primeiro corta antes de cada espaço e de cada pontuação; o espaço fica no começo da palavra seguinte (escrito ␣ abaixo). Depois, conta. O par mais frequente do corpus é ␣ + n, 7 vezes (cada "no"), então a fusão 1 é ␣n; depois ␣n + o (7), t + o (6), ␣ + s (6)… Depois de 41 fusões todo pedaço de que o modelo de brinquedo precisa existe; o vocabulário do tokenizador tem 23 caracteres + 41 fusões = 64 entradas.
As 41 fusões, na ordem em que foram aprendidas (em negrito, as que mudam a nossa frase)
| # | fusão | vezes | # | fusão | vezes |
|---|---|---|---|---|---|
| 1 | ␣ + n → ␣n | 7 | 22 | ␣telh + a → ␣telha | 2 |
| 2 | ␣n + o → ␣no | 7 | 23 | ␣telha + do → ␣telhado | 2 |
| 3 | t + o → to | 6 | 24 | ␣c + h → ␣ch | 2 |
| 4 | ␣ + s → ␣s | 6 | 25 | ␣ch + ão → ␣chão | 2 |
| 5 | ␣ + c → ␣c | 6 | 26 | ␣ + m → ␣m | 2 |
| 6 | ã + o → ão | 6 | 27 | ␣m + u → ␣mu | 2 |
| 7 | d + o → do | 5 | 28 | ␣mu + r → ␣mur | 2 |
| 8 | ␣ + g → ␣g | 4 | 29 | ␣mur + o → ␣muro | 2 |
| 9 | ␣g + a → ␣ga | 4 | 30 | ␣do + r → ␣dor | 2 |
| 10 | ␣ga + to → ␣gato | 4 | 31 | ␣dor + m → ␣dorm | 2 |
| 11 | ␣c + ão → ␣cão | 4 | 32 | ␣dorm + iu → ␣dormiu | 2 |
| 12 | ␣ + do → ␣do | 3 | 33 | ␣s + o → ␣so | 2 |
| 13 | i + u → iu | 3 | 34 | ␣so + f → ␣sof | 2 |
| 14 | ␣s + e → ␣se | 2 | 35 | ␣sof + á → ␣sofá | 2 |
| 15 | ␣se + n → ␣sen | 2 | 36 | ␣ + q → ␣q | 2 |
| 16 | ␣sen + to → ␣sento | 2 | 37 | ␣q + u → ␣qu | 2 |
| 17 | ␣sento + u → ␣sentou | 2 | 38 | ␣qu + e → ␣que | 2 |
| 18 | ␣ + t → ␣t | 2 | 39 | ␣que + r → ␣quer | 2 |
| 19 | ␣t + e → ␣te | 2 | 40 | ␣s + u → ␣su | 2 |
| 20 | ␣te + l → ␣tel | 2 | 41 | ␣su + b → ␣sub | 2 |
| 21 | ␣tel + h → ␣telh | 2 |
A frase do exemplo não está no corpus. Tokenizá-la é partir dos seus 15 caracteres e aplicar as 41 fusões em ordem; só 10 delas acham um par para fundir:
| depois da fusão | pedaços | quantos |
|---|---|---|
| início: caracteres | o ␣ g a t o ␣ s u b i u ␣ n o | 15 |
1: ␣ + n | o ␣ g a t o ␣ s u b i u ␣n o | 14 |
2: ␣n + o | o ␣ g a t o ␣ s u b i u ␣no | 13 |
3: t + o | o ␣ g a to ␣ s u b i u ␣no | 12 |
4: ␣ + s | o ␣ g a to ␣s u b i u ␣no | 11 |
8: ␣ + g | o ␣g a to ␣s u b i u ␣no | 10 |
9: ␣g + a | o ␣ga to ␣s u b i u ␣no | 9 |
10: ␣ga + to | o ␣gato ␣s u b i u ␣no | 8 |
13: i + u | o ␣gato ␣s u b iu ␣no | 7 |
40: ␣s + u | o ␣gato ␣su b iu ␣no | 6 |
41: ␣su + b | o ␣gato ␣sub iu ␣no | 5 |
"subiu" não aparece no corpus e o BPE o escreve mesmo assim, com dois pedaços aprendidos em outras palavras: ␣sub (de "subir") e iu (de "dormiu" e "fugiu"). As fusões que não tocam a frase (␣sentou, ␣muro, ␣quer…) são de palavras que ela não contém. O GPT-2, coincidência, corta "subiu" do mesmo jeito: sub + iu.
O espaço pertence à palavra seguinte
"o" no começo do texto e " o" depois de um espaço seriam dois tokens diferentes; o mesmo vale para "the" e " the" no GPT-2 (ids 1169 e 262; "The", com maiúscula, é um terceiro, 464). Nem o tokenizador nem o modelo sabem que são "a mesma palavra". É também por isso que um prompt terminado em espaço muda o que o modelo prevê: o espaço normalmente seria o começo do próximo token (o capítulo de LLMs mede isso).
Vocabulário e ids
O modelo de brinquedo fica com 12 tokens: 11 dos 64 pedaços (os cinco da frase e outros seis que ele pode prever) mais o token especial <|endoftext|> (abaixo).
| id | pedaço | id | pedaço | id | pedaço |
|---|---|---|---|---|---|
| 0 | o | 4 | ␣sub | 8 | ␣sofá |
| 1 | ␣no | 5 | iu | 9 | ␣chão |
| 2 | ␣gato | 6 | ␣telhado | 10 | . |
| 3 | ␣cão | 7 | ␣muro | 11 | <|endoftext|> |
"o gato subiu no" → o ␣gato ␣sub iu ␣no → [0, 2, 4, 5, 1]
Um modelo de verdade tem uma linha para cada entrada do seu tokenizador: o vocabulário do GPT-2 tem 256 bytes + 50.000 fusões + 1 token especial = 50.257; ele lê a frase como [78, 308, 5549, 850, 16115, 645]. O brinquedo fica com 12 para que as suas tabelas caibam na página.
Os ids são nomes, não quantidades: 4 não é "o dobro" de 2 e nada no número diz "verbo". O próximo passo transforma cada id num vetor.
Tokens especiais
Algumas entradas não vêm do BPE. Elas são acrescentadas à mão e texto comum nunca as produz:
<|endoftext|>(id 50256 no GPT-2, 11 no nosso) separa documentos nos dados de treino e deixa o modelo terminar o próprio texto: quando a geração o sorteia, ela para (passo 7).- Modelos de chat acrescentam marcadores de quem tem a vez; o BERT usa
[CLS],[SEP]e[MASK]. Um batch de frases de tamanhos diferentes é completado com um token de padding, que uma máscara depois esconde (passo 4).
O português custa mais
Um tokenizador é ajustado ao seu corpus; uma língua que ele viu pouco é escrita com pedaços menores. O GPT-2 foi ajustado em inglês:
| texto | palavras | tokens do GPT-2 | tokens por palavra |
|---|---|---|---|
| the cat climbed the | 4 | 4 | 1,00 |
| o gato subiu no | 4 | 6: o g ato sub iu no | 1,50 |
| The cat climbed onto the roof of the house because it was scared of the dog. | 16 | 17 | 1,06 |
| O gato subiu no telhado da casa porque estava com medo do cachorro. | 13 | 25 | 1,92 |
Os dois tokenizadores de brinquedo destas páginas mostram o mesmo efeito pelo outro lado: o português, ajustado em frases em português, mantém gato inteiro (o GPT-2 não), enquanto o inglês corta "the cat climbed the roof." em 22 pedaços. Mais tokens quer dizer mais conta (a atenção cresce com \(n^2\)), menos texto na mesma janela de contexto e uma conta maior para a mesma frase3.
Veja rodar
O simulador roda o tokenizador de brinquedo, fusão a fusão, em qualquer texto que você digitar. Teste os casos: a frase com a resposta, uma palavra que o corpus nunca viu ("pássaro", escrita letra a letra) e a frase em inglês passando pelo tokenizador português.
Verifique você mesmo — o tokenizador de brinquedo nunca viu “subiu”. Por que ele não falha?
Porque o BPE nunca precisa que uma palavra inteira esteja no vocabulário. Ele parte dos caracteres (que estão todos lá) e funde o que consegue: "subiu" termina como ␣sub + iu, dois pedaços aprendidos de "subir", "dormiu" e "fugiu". Mesmo uma palavra feita de caracteres que ele nunca viu seria escrita, um caractere (um byte, no GPT-2) de cada vez.
Verifique você mesmo — “o” e “no” terminam em “o”. Eles dividem algum token?
Não. O primeiro é o (id 0) e o segundo é ␣no (id 1): a fusão ␣n + o acontece antes de qualquer outra coisa olhar para aquele "o". O próximo passo dá aos dois vetores diferentes e só o treino pode aproximá-los, se isso ajudar.
← Visão geral · próximo passo: Embeddings →
-
Sennrich, R., Haddow, B., & Birch, A. (2016). Neural Machine Translation of Rare Words with Subword Units — ACL. O BPE trazido para o processamento de linguagem. ↩
-
Radford, A., Wu, J., Child, R., Luan, D., Amodei, D., & Sutskever, I. (2019). Language Models are Unsupervised Multitask Learners. O GPT-2 e o seu BPE em nível de byte. ↩
-
Petrov, A., La Malfa, E., Torr, P. H. S., & Bibi, A. (2023). Language Model Tokenizers Introduce Unfairness Between Languages — NeurIPS. ↩