Del texto al vector: tokenizar, normalizar y clasificar poemas de Machado y Bécquer
Antes de que un modelo clasifique un texto hay que partirlo, decidir qué diferencias se ignoran y convertirlo en números. Lo hacemos con poemas de Machado y Bécquer, comparando técnicas en cada paso, y descubrimos un clasificador que acierta el 89 % por la razón equivocada.
Páginas de este taller
Un modelo de lenguaje no lee palabras: lee números. Entre el poema y el número hay tres decisiones que casi nunca se ven: cómo se parte el texto, qué diferencias se ignoran y cómo se convierte en un vector. Este tutorial las hace visibles con una tarea de atribución de autoría: adivinar si un fragmento de cuatro versos es de Antonio Machado o de Gustavo Adolfo Bécquer.
Para probarlo sin instalar nada: abre el cuaderno en Google Colab · descarga el cuaderno completo · aprende a usar Colab.
Si buscas el mapa general de técnicas (qué es cada una y cuándo conviene), está en Preprocesar, tokenizar y representar; aquí las aplicamos a un corpus real.
El tutorial tiene tres partes: preparar el corpus y tokenizar (esta página), normalizar y representar, y clasificar. Todas las salidas son reales: las devolvió el cuaderno con NLTK 3.10, spaCy 3.8, tiktoken 0.14 y scikit-learn 1.9. Con otras versiones pueden variar un poco.
1. El corpus: dos libros y mucha limpieza
Usamos dos libros del Proyecto Gutenberg, de dominio público: las Poesías completas de Machado (edición de 1917) y las Obras escogidas de Bécquer, de las que solo tomamos las Rimas. Un archivo de Gutenberg trae licencia, índices, notas del transcriptor y, en el caso de Machado, anuncios de la editorial. Lo primero es quedarse con los poemas:
from urllib.request import urlopen
def descargar(numero): # baja el texto plano de Gutenberg
url = f"https://www.gutenberg.org/cache/epub/{numero}/pg{numero}.txt"
return urlopen(url).read().decode("utf-8").replace("\r\n", "\n")
def entre(texto, inicio, fin): # recorta entre dos marcas
i = texto.find(inicio)
return texto[i:texto.find(fin, i)]
machado = entre(descargar(68525), "SOLEDADES\n\n I", "PUBLICACIONES DE LA\n")
becquer = entre(descargar(53552), "RIMAS\n\n\n\n\n[Ilustración]", "ÍNDICE")
Cada poema empieza con un número romano en su propia línea, así que el cuaderno parte por ahí y descarta títulos e ilustraciones. Salen 259 poemas de Machado (5184 versos) y 76 rimas de Bécquer (1377 versos). La limpieza nunca es perfecta: alguna dedicatoria se cuela como si fuera un verso, y lo veremos aparecer entre los errores.
Machado escribió casi cuatro veces más. Si no lo corregimos, el clasificador aprendería que lo más seguro es responder siempre «Machado». Partimos los poemas en fragmentos de cuatro versos, tomamos 339 de cada autor y cada fragmento recuerda de qué poema viene. Ese dato parecerá un detalle, pero en la tercera parte decide si los resultados son de fiar.
2. Tokenizar: qué es una palabra
Tokenizar es partir el texto en unidades, los tokens. Probamos varias técnicas con dos versos de la rima LIII:
import re, nltk, spacy, tiktoken
verso = "Volverán las oscuras golondrinas\nEn tu balcón sus nidos á colgar,"
verso.split() # por espacios
nltk.word_tokenize(verso, language="spanish") # reglas de NLTK
[t.text for t in spacy.load("es_core_news_md")(verso)] # spaCy
gpt = tiktoken.get_encoding("o200k_base") # el tokenizador de GPT-4o
[gpt.decode([i]) for i in gpt.encode(verso)] # subpalabras
Salida real
Un verso, cuatro maneras de partirlo
«Volverán las oscuras golondrinas / En tu balcón sus nidos á colgar,» (Bécquer, rima LIII)
VolveránlasoscurasgolondrinasEntubalcónsusnidosácolgar,
«colgar,» se queda pegado a la coma: para el ordenador ya no es «colgar».
VolveránlasoscurasgolondrinasEntubalcónsusnidosácolgar,
Separa la puntuación con reglas. El salto de verso desaparece.
Volveránlasoscurasgolondrinas↵Entubalcónsusnidosácolgar,
Conserva el salto de línea (↵) como token: en poesía marca el fin de verso.
Volverán las oscuras golondrinas↵En tu balcón sus nidos á colgar,
22 tokens. Los trozos son frecuentes en su vocabulario, no morfemas: «gol + ond + r + inas».
La idea: No hay una tokenización correcta: cada técnica decide qué es una palabra, y esa decisión llega intacta al modelo.
Las tres primeras técnicas siguen reglas escritas a mano: espacios, puntuación, excepciones. Cada una toma decisiones distintas que importan después: con espacios, colgar, y colgar son palabras diferentes; spaCy conserva el salto de línea, que en poesía marca el fin de verso.
La cuarta es de subpalabras: su vocabulario se aprende de un corpus enorme, uniendo los pares de símbolos que aparecen juntos más a menudo (cómo funciona lo explica Fundamentos técnicos). Por eso golondrina sale como gol + ond + rina y el plural como gol + ond + r + inas: la frontera se mueve aunque el plural solo añada una -s. No busca morfemas, sino un vocabulario compacto que cubra cualquier texto.
Para clasificar poemas con los métodos de este tutorial nos quedamos con palabras enteras: son lo que un lingüista puede leer como pista. Las subpalabras son la entrada de los grandes modelos de lenguaje, y conviene saber que existen porque explican algunos de sus fallos con palabras raras o lenguas con poca presencia en internet.