Tokenización
En el capítulo anterior vimos que los modelos de lenguaje son, en esencia, predictores del siguiente token. Pero, ¿qué es exactamente un token? Y, sobre todo, ¿cómo se obtienen esos tokens a partir de un texto en lenguaje humano?
Un modelo de lenguaje no puede, en realidad, “leer” texto. Solo puede hacer operaciones matemáticas sobre números. Antes de que una oración pueda entrar al modelo, tenemos que convertirla en una secuencia de pequeños fragmentos de texto, y luego convertir esos fragmentos en identificadores numéricos (IDs). A esos fragmentos les llamamos tokens.
¿Por qué no simplemente letras, o palabras completas?
Usar letras (o caracteres) es maravillosamente simple: cualquier texto se puede descomponer en sus caracteres. Pero el problema es que las secuencias serían larguísimas. Incluso un párrafo corto se convertiría en cientos o miles de pasos para el modelo, lo que ralentiza tanto el entrenamiento como la generación de respuestas.
Usar palabras completas suena tentador — es lo más natural para nosotros. Pero el número de palabras posibles es inmenso: decenas de miles solo en español, y mucho más si contamos nombres propios, jerga, y errores tipográficos. El modelo tendría que aprender un token distinto para cada una, y cada vez que apareciera una palabra nueva (como un nombre propio), no sabría cómo manejarla.
Los tokens son un compromiso inteligente entre ambos extremos. Una oración tokenizada puede tener más piezas que la misma oración contada en palabras, pero el vocabulario reutilizable puede ser mucho más pequeño que el conjunto de todas las palabras posibles. Las palabras comunes se convierten en un solo token; las palabras raras o nuevas se parten en pedazos ya conocidos.
Analogía: piensa en los tokens como piezas de Lego. Tienes un número limitado de piezas (el vocabulario), pero con ellas puedes construir prácticamente cualquier palabra. Algunas palabras comunes son piezas grandes (como una losa de 4×2), mientras que las palabras raras se construyen con varias piezas pequeñas combinadas.
| Caracteres | Palabras completas | Tokens (subpalabras) | |
|---|---|---|---|
| Tamaño del vocabulario | Muy pequeño (~100-300 símbolos) | Enorme (decenas de miles, sin contar nombres propios y jerga) | Intermedio (típicamente 30,000-100,000) |
| Longitud de la secuencia resultante | Muy larga | Corta | Intermedia |
| Maneja palabras nunca vistas | Sí, siempre — cualquier texto se descompone en caracteres conocidos | No — falla o requiere un token especial de “desconocido” | Sí — se parte en piezas ya conocidas |
| Costo computacional | Alto, por secuencias muy largas | Bajo por secuencia, pero el vocabulario gigante es costoso de mantener y entrenar | Balanceado — el punto intermedio que se usa en la práctica |
BPE: construyendo un tokenizador por fusiones
La técnica más extendida para crear estos tokens se llama Byte Pair Encoding (BPE). La idea, en cuatro pasos:
- Empieza dividiendo el texto en caracteres individuales (o incluso bytes).
- Cuenta qué pares de caracteres adyacentes aparecen con más frecuencia.
- Fusiona el par más frecuente en un nuevo símbolo.
- Repite el proceso hasta alcanzar un número deseado de fusiones (o hasta que ningún par supere un umbral mínimo).
El resultado es un conjunto de “bloques de construcción” que el tokenizador aprende automáticamente a partir de los datos. Las palabras comunes se fusionan rápido (porque sus pares aparecen mucho); las palabras raras quedan como varios pedazos pequeños, y en el peor caso siempre se puede caer al nivel de caracteres.
Ejemplo manual: la palabra “bajo” repetida 3 veces
Tomemos el texto: bajo bajo bajo. Apliquemos BPE paso a paso.
Paso 0 (inicio): cada carácter es su propio token — b-a-j-o (×3), ignorando espacios por simplicidad.
Contamos los pares adyacentes, ponderados por frecuencia:
| Par | Frecuencia |
|---|---|
| (b,a) | 3 |
| (a,j) | 3 |
| (j,o) | 3 |
Todos empatan — elegimos el primero: (b,a) → lo fusionamos en ba.
Paso 1: tokens: ba-j-o (×3). Recontamos: (ba,j)=3, (j,o)=3. Elegimos (ba,j) → fusionamos en baj.
Paso 2: tokens: baj-o (×3). Recontamos: (baj,o)=3 (única). Fusionamos → bajo.
Paso 3: tokens: bajo (×3). Tras 3 fusiones, la palabra completa se convirtió en un solo token. El algoritmo decidió por sí solo que “bajo” merece ser una unidad, basándose únicamente en su frecuencia.
La compresión, de un vistazo:
| Fusiones aplicadas | Forma de los tokens | Cantidad total de tokens |
|---|---|---|
| 0 | b-a-j-o (×3) | 12 |
| 1 | ba-j-o (×3) | 9 |
| 2 | baj-o (×3) | 6 |
| 3 | bajo (×3) | 3 |
De 12 símbolos sueltos a 3 tokens completos, en solo 3 fusiones — esta es, en miniatura, exactamente la compresión que BPE logra a escala real sobre corpus de miles de millones de palabras.
Ejemplo con una frase más natural
Ahora usemos: el pequeño gato jugó bajo el cálido sol
En 0 fusiones, cada carácter es su propio token — ineficiente y muy largo. A medida que el tokenizador va aplicando fusiones (aprendidas de un gran corpus, no solo de esta frase), va descubriendo bloques cada vez más grandes: primero fusiona pares muy frecuentes como (e,l) → el, luego pedazos más grandes, hasta formar palabras completas como ·gato.
Con suficientes fusiones, el resultado final podría verse así: [·el, ·pequeño, ·gato, ·jugó, ·bajo, ·el, ·cálido, ·sol] — dependiendo del vocabulario final aprendido.
El misterio del puntito (·)
En muchos tokenizadores BPE se añade un marcador especial al inicio de cada palabra — en nuestros ejemplos usamos · (algunos tokenizadores usan _ o <w> en su lugar, pero la idea es la misma). Esto es crucial por dos razones:
- Distingue el inicio de una palabra. El token
·gatoes distinto degatosin el marcador. El primero indica que la palabra empieza ahí; el segundo aparecería solo como continuación dentro de otra palabra más larga. Sin este marcador, el modelo no tendría forma de saber sigatoes una palabra completa o parte de otra. - Permite tokenizar sin ambigüedades. Al añadir
·antes de cada palabra, el espacio deja de tratarse como un carácter cualquiera y se convierte en la señal que dispara la inserción de ese marcador.
En la práctica, el tokenizador aprende que ·gato es un token muy común (porque “gato” aparece seguido como palabra independiente), mientras que gato sin marcador podría ser mucho más raro, o no existir en absoluto en el vocabulario final.
El algoritmo, paso a paso
def paso(palabras):
# 'palabras' es una lista de listas de símbolos, ej. [['b','a','j','o'], ...]
pares = contar_pares_adyacentes(palabras) # dict de (a,b) → frecuencia
mejor = max(pares, key=pares.get) # el par más frecuente
if pares[mejor] < 2:
return None # no hay nada que valga la pena fusionar
for i, palabra in enumerate(palabras):
palabras[i] = fusionar(palabra, mejor[0], mejor[1])
vocabulario.add(mejor[0] + mejor[1])
fusiones.append(mejor)
return palabras
Después de k fusiones, el vocabulario es el original (caracteres) más los k nuevos símbolos: |V_k| = |V_0| + k.
Para tokenizar texto nuevo, se aplican las mismas fusiones, en el mismo orden en que fueron aprendidas:
def codificar_palabra(palabra):
simbolos = ["·"] + list(palabra) # marcador de inicio de palabra
for regla in fusiones: # se aplican en el orden aprendido
simbolos = aplicar_fusion(simbolos, regla[0], regla[1])
return simbolos
Contador de fusiones BPE (el laboratorio)
El ejemplo de “bajo” se puede seguir a mano. Sobre una frase más larga, el mismo algoritmo de paso() —conteo de pares adyacentes y fusión del más frecuente— se ve mejor en vivo. Cada clic aplica una fusión; no hay entrenamiento ni descenso de gradiente. Como BPE solo fusiona pares que aparecen al menos dos veces, en esta frase corta apenas hay fusiones: «el» se repite, el resto no. Pegá el texto varias veces, o el ejemplo bajo bajo bajo, para ver cómo cae el número de tokens.
El tokenizador “de verdad”
En los modelos actuales, el tokenizador BPE no parte de caracteres Unicode directamente, sino de bytes crudos (la representación en UTF-8 de cada carácter). Esto tiene una ventaja enorme: cualquier texto, en cualquier idioma o con cualquier símbolo, se puede representar como una secuencia de bytes, y el tokenizador nunca se encuentra con un carácter genuinamente “desconocido” — en el peor caso, siempre puede caer al nivel de byte individual.
Los tokenizadores de GPT y muchos otros modelos modernos usan esta estrategia. Existen otras familias con principios similares, pero variantes distintas en cómo eligen qué fusionar o dividir:
| Familia | Usada en | Cómo decide las fusiones/divisiones |
|---|---|---|
| BPE (a nivel de byte) | GPT-2, GPT-3/4, Llama, y la mayoría de LLMs actuales | Fusiona repetidamente el par adyacente más frecuente — el algoritmo que acabamos de practicar |
| WordPiece | BERT | Similar en espíritu a BPE, pero elige la fusión que maximiza la probabilidad del corpus de entrenamiento, no solo la más frecuente |
| Unigram | SentencePiece, familia T5 | Empieza con un vocabulario grande y va quitando los símbolos que menos aportan, hasta llegar al tamaño deseado — el proceso inverso a BPE |
En producción, los tokenizadores BPE suelen entrenarse con decenas de miles de fusiones sobre cantidades ingentes de texto, lo que da lugar a vocabularios de entre 30,000 y 100,000 tokens. Pero la idea central —aprender bloques de construcción a partir de la frecuencia de coaparición— sigue siendo exactamente la que hemos practicado aquí con “bajo bajo bajo”.
¿Y después de tokenizar?
Una vez que tenemos los tokens, cada uno se asigna a un número entero — su ID en el vocabulario. Esa secuencia de IDs es la que realmente alimenta al modelo. Más adelante, en el capítulo de embeddings, veremos cómo esos IDs se convierten en vectores numéricos que el modelo puede procesar con operaciones matemáticas reales.
Por ahora, vale la pena que quede claro: el tokenizador es la puerta de entrada del modelo. Si tokenizamos mal —con un vocabulario mal elegido, o con pocas fusiones— el modelo va a tener dificultades para aprender patrones lingüísticos, sin importar qué tan buena sea la arquitectura del resto del sistema. La tokenización es un paso pequeño en apariencia, pero crítico para el éxito de cualquier modelo de lenguaje.
Resumen del capítulo
- Los tokens son fragmentos de texto intermedios entre caracteres y palabras completas.
- BPE aprende fusionando los pares más frecuentes en un corpus, de forma puramente estadística.
- Las palabras comunes se convierten en un solo token; las raras se dividen en piezas conocidas.
- Un marcador de inicio de palabra (
·) distingue dónde empieza cada palabra. - Los tokenizadores modernos parten de bytes para poder representar cualquier texto, sin excepciones.
En el próximo capítulo veremos cómo esos IDs numéricos se transforman en vectores, y cómo el modelo aprende a relacionarlos entre sí.
LinkedIn nelson.zepeda@simov.io SIMOV LABS
¿Encontraste un error? Sugerir una corrección