LECCIÓN 3 · BLOQUE 1

Tokenizers modernos

El tokenizer no es solo "dividir palabras". Es BPE real, tokens especiales, padding, truncation y la attention mask que el modelo necesita para saber qué ignorar.

Recuerda de las lecciones anteriores: tokenizar es convertir texto en IDs (números enteros). En el curso del Transformer viste la idea básica de BPE. Con HuggingFace, el tokenizer se carga con AutoTokenizer.from_pretrained(). Hoy vemos exactamente qué hace esa línea y por qué el tokenizer es específico de cada modelo.

1. Por qué cada modelo tiene su propio tokenizer — y no puedes mezclarlos

En el curso del Transformer estudiaste que la tokenización convierte texto en IDs y que cada ID tiene un vector asociado en la tabla de embeddings. La conexión es directa: el ID 1492 se convierte en el vector de la fila 1492 de la matriz E.

Ahora piensa en esto: si cambias el tokenizer (otro vocabulario, otros IDs), el ID 1492 puede corresponder a una palabra completamente diferente. El vector de la fila 1492 de la matriz E que aprendió el modelo ya no tiene nada que ver con lo que llega. El modelo recibiría basura.

La regla de oro: el tokenizer y el modelo deben ser siempre el mismo par — el que fue entrenado junto. Si usas bert-base-uncased como modelo, debes usar bert-base-uncased como tokenizer. HuggingFace te obliga a esto implícitamente: cuando cargas el modelo con su nombre, el tokenizer del mismo nombre ya tiene el vocabulario correcto.
✍️ Lo que pasa si mezclas tokenizer y modelo incorrectos
Vocabulario de BERT:
  ID 1492 → token "gato"
  ID 2103 → token "perro"

Vocabulario de LLaMA (diferente):
  ID 1492 → token "§código"      ← completamente distinto
  ID 2103 → token "##ing"        ← completamente distinto

Si tokenizas "gato" con BERT (ID 1492) pero pasas ese ID a LLaMA,
LLaMA busca en su tabla el embedding de "§código" — información incorrecta.
El modelo produce basura.

2. BPE real — cómo lo hace la librería en la práctica

En el curso del Transformer viste BPE como algoritmo. Ahora lo vemos funcionando de verdad con un tokenizer de producción. Usaremos bert-base-uncased — uno de los más usados del mundo y con 30.522 tokens en su vocabulario.

La palabra "uncased" en el nombre significa "sin distinción de mayúsculas" — el modelo convierte todo a minúsculas antes de tokenizar. Lo contrario sería "cased" (distingue "Casa" de "casa").

from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("bert-base-uncased")

# Tokenizar una frase simple
texto = "I love machine learning!"
resultado = tok.tokenize(texto)
print("Tokens:", resultado)

ids = tok.convert_tokens_to_ids(resultado)
print("IDs:   ", ids)
salida realTokens: ['i', 'love', 'machine', 'learning', '!'] IDs: [1045, 2293, 3698, 4083, 999]

Ahora con una palabra más rara — "embeddings" — que BPE dividirá en partes:

print(tok.tokenize("embeddings"))
print(tok.tokenize("unbelievable"))
print(tok.tokenize("supercalifragilístico"))
salida real['em', '##bed', '##ding', '##s'] ['un', '##believe', '##able'] ['super', '##cal', '##if', '##rag', '##ilis', '##tico']
✍️ Qué significa el prefijo ## — sin saltarnos nada
En BERT, los tokens que empiezan con ## son CONTINUACIONES de una palabra.
Es decir, van pegados al token anterior sin espacio.

"embeddings" → ['em', '##bed', '##ding', '##s']

Significa: em + bed + ding + s = embedding + s

Sin ##: el token empieza una nueva palabra (hay espacio antes).
Con ##: el token continúa la palabra anterior (no hay espacio).

Cuando reconstruyes el texto:
  'em' + '##bed' + '##ding' + '##s' → 'embeddings'   ✓

Esta convención (##) es específica de BERT. Otros modelos usan otras convenciones: GPT-2 usa un espacio al principio del token para indicar inicio de palabra (Ġ), y LLaMA usa un símbolo diferente. La idea es la misma pero el formato varía.

3. Tokens especiales — los que el modelo necesita

Cuando tokenizas texto con tok(texto) (en lugar de tok.tokenize(texto)), el tokenizer añade tokens especiales automáticamente:

# tok(texto) = tokenize + añadir tokens especiales + convertir a IDs
encoded = tok("I love learning")
print(encoded["input_ids"])

# Para ver los tokens como texto (no como IDs):
print(tok.convert_ids_to_tokens(encoded["input_ids"]))
salida real[101, 1045, 2293, 4083, 102] ['[CLS]', 'i', 'love', 'learning', '[SEP]']
✍️ Para qué sirve cada token especial de BERT
[CLS]  — ID 101 — "Classification" — siempre va al principio
        El vector del [CLS] al final del modelo captura el "resumen"
        de toda la frase. Se usa para clasificación de texto.

[SEP]  — ID 102 — "Separator" — siempre va al final (o entre dos frases)
        Le dice al modelo dónde termina una frase (o comienza la segunda).

[PAD]  — ID 0   — "Padding" — relleno cuando hay textos de distinto largo
        Veremos esto en detalle en la siguiente sección.

[UNK]  — ID 100 — "Unknown" — para caracteres que no están en el vocabulario
        En BPE entrenado con bytes este nunca aparece, pero en BERT sí puede.

[MASK] — ID 103 — "Mask" — usado en el entrenamiento BERT (rellenar huecos)
        Cuando ves "el perro [MASK] pescado", el modelo debe predecir "come".

Los tokens especiales varían entre modelos. GPT-2 solo usa <|endoftext|> al final. LLaMA usa <s> al principio y </s> al final. El tokenizer de HuggingFace sabe cuáles añadir para cada modelo.

4. Padding y truncation — el problema del lote

Los modelos procesan textos en lotes — grupos de varios textos a la vez — porque es mucho más eficiente que procesarlos uno a uno. Pero hay un problema: una matriz (el lote) necesita que todos los textos tengan la misma longitud. Y los textos naturales tienen longitudes distintas.

Analogía: imagina que estás llenando cajas con libros para enviar. Todas las cajas deben tener el mismo tamaño. Si el libro es pequeño, rellenas con papel burbuja (padding). Si el libro es demasiado grande, lo cortas (truncation).
PADDING Y TRUNCATION — DOS TEXTOS DE DISTINTO LARGO
Padding: se añaden tokens [PAD] para igualar longitudes Dos textos con distinta cantidad de tokens se rellenan con [PAD] para tener la misma longitud. Texto 1 (8 tokens): [CLS] i love machine learning ! [SEP] Texto 2 (5 tokens + 3 padding): [CLS] hi there [SEP] [PAD] [PAD] [PAD] texto real padding

Ambas filas tienen 8 columnas — el modelo puede procesarlas en un lote. Los [PAD] son relleno sin significado.

# Tokenizar dos frases de distinto largo en un lote
frases = [
    "I love machine learning!",     # larga
    "Hi there",                      # corta
]

encoded = tok(
    frases,
    padding=True,           # añade [PAD] hasta igualar al más largo del lote
    truncation=True,        # corta si supera max_length
    max_length=512,          # longitud máxima (BERT acepta hasta 512)
    return_tensors="pt",     # devuelve tensores de PyTorch (pt = PyTorch)
)

print("input_ids shape:", encoded["input_ids"].shape)
print(encoded["input_ids"])
print(encoded["attention_mask"])
salida realinput_ids shape: torch.Size([2, 8]) tensor([[ 101, 1045, 2293, 3698, 4083, 999, 102, 0], [ 101, 7632, 2045, 102, 0, 0, 0, 0]]) tensor([[1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 0, 0, 0, 0]])

El shape [2, 8] significa: 2 frases, 8 tokens cada una. La primera frase tiene texto real en las 8 posiciones; la segunda tiene texto real en 4 y relleno en las últimas 4 (ID 0 = [PAD]).

5. La attention mask — decirle al modelo qué ignorar

Viste en el output anterior que junto con input_ids siempre aparece attention_mask. Vamos a entenderla desde cero.

El modelo Transformer calcula atención entre todos los tokens — cada token "mira" a todos los demás (lo estudiaste en el curso del Transformer). El problema es que los tokens de padding ([PAD]) no contienen información real: son solo relleno para igualar los tamaños. Si el modelo les prestara atención, estaría "distrayéndose" con basura.

La attention mask es un filtro: un 1 significa "este token es real, atiéndelo" y un 0 significa "este token es padding, ignóralo". El modelo usa esta máscara para que la self-attention solo considere los tokens reales.
✍️ Leyendo la attention mask del ejemplo anterior
Frase 1: "I love machine learning!"
  input_ids:      [101, 1045, 2293, 3698, 4083,  999, 102,   0]
  attention_mask: [ 1,    1,    1,    1,    1,    1,   1,   0]
                                                            ↑
                                                        [PAD] — ignorar

Frase 2: "Hi there"
  input_ids:      [101, 7632, 2045, 102,   0,   0,   0,   0]
  attention_mask: [ 1,    1,    1,   1,   0,   0,   0,   0]
                                         ↑ ↑   ↑   ↑
                                      cuatro [PAD] — todos ignorados

Resultado: el modelo atiende solo donde hay 1.
Cada fila del lote puede tener distintas longitudes reales de forma eficiente.

6. return_tensors — en qué formato devuelve los datos

El parámetro return_tensors controla el tipo de objeto que devuelve el tokenizer:

ValorDevuelveCuándo usarlo
"pt"Tensores de PyTorch (torch.Tensor)Siempre que vayas a pasar al modelo de HuggingFace
"tf"Tensores de TensorFlowSi usas TensorFlow en vez de PyTorch
"np"Arrays de NumPyPara procesamiento o visualización
None (default)Listas PythonPara depurar o explorar
# Ejemplo con None — devuelve listas Python (más fácil de leer)
encoded_lista = tok("Hello world", return_tensors=None)
print(encoded_lista)
salida real{'input_ids': [101, 7592, 2088, 102], 'token_type_ids': [0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1]}

Ves tres claves: input_ids (los IDs), attention_mask (el filtro de padding que ya conoces) y token_type_ids — este último es específico de BERT cuando procesa dos frases a la vez (0 = primera frase, 1 = segunda). Para textos simples es siempre 0 y la mayoría de los modelos modernos no lo usan.

7. Decodificar — el camino de vuelta

El tokenizer también sabe ir al revés: de IDs a texto. Esto lo usarás cuando el modelo genere texto (los modelos generativos producen IDs, no texto directamente):

# De texto a IDs (encode)
ids = tok.encode("I love learning")
print("IDs:", ids)

# De IDs a texto (decode)
texto = tok.decode(ids)
print("Texto:", texto)

# Sin tokens especiales
texto_limpio = tok.decode(ids, skip_special_tokens=True)
print("Limpio:", texto_limpio)
salida realIDs: [101, 1045, 2293, 4083, 102] Texto: [CLS] i love learning [SEP] Limpio: i love learning

skip_special_tokens=True elimina [CLS], [SEP] y [PAD] del resultado final. En generación de texto siempre querrás esta opción para obtener texto limpio.

🎮 Pruébalo: tokeniza y decodifica en tiempo real

Escribe texto y ve cómo lo tokenizaría BERT (simulación con vocabulario reducido):

8. Lo que aprendiste

Lo que aprendiste hoy: el tokenizer es específico de cada modelo y no se pueden mezclar. BPE divide palabras desconocidas en partes (con ## en BERT para indicar continuación). El tokenizer añade tokens especiales automáticamente ([CLS], [SEP]). padding=True rellena con [PAD] para igualar longitudes en un lote. truncation=True corta textos demasiado largos. La attention mask tiene 1 donde hay texto real y 0 donde hay padding — el modelo la usa para ignorar el relleno. return_tensors="pt" devuelve tensores de PyTorch listos para el modelo.

En la próxima lección entendemos el problema central: por qué el modelo sabe mucho pero no lo que tú necesitas, y la diferencia entre pre-entrenamiento, fine-tuning e instrucción — que es lo que justifica todo el resto del curso.