Phase 05 - Lesson 01

Procesamiento de Texto — Tokenizacion, Stemming, Lematizacion

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

El lenguaje es continuo. Los modelos son discretos. El preprocesamiento es el puente.

Tipo: Build Lenguajes: Python Requisitos previos: Fase 2 · 14 (Naive Bayes) Tiempo: ~45 minutos

El Problema

Un modelo no puede leer "The cats were running." Lee enteros.

Todo sistema de NLP arranca con las mismas tres preguntas. Donde empieza una palabra. Cual es la raiz de la palabra. Como tratar "run", "running", "ran" como la misma cosa cuando conviene, y como cosas distintas cuando no.

Si te equivocas en la tokenizacion, el modelo aprende a partir de basura. Si tu tokenizador trata don't como un solo token pero do n't como dos, la distribucion de entrenamiento se parte. Si tu stemmer colapsa organization y organ a la misma raiz, el modelado de topicos muere. Si tu lematizador necesita contexto de categoria gramatical pero no se lo pasas, los verbos terminan tratados como sustantivos.

Esta leccion construye las tres primitivas de preprocesamiento desde cero y luego muestra como NLTK y spaCy hacen el mismo trabajo, para que veas los tradeoffs.

El Concepto

Tres operaciones. Cada una tiene una funcion y un modo de fallo.

Pipeline de preprocesamiento: texto crudo → tokens → raices o lemas → modelo

Tokenizacion divide una cadena en tokens. "Token" es deliberadamente vago, porque la granularidad correcta depende de la tarea. A nivel de palabra para NLP clasico. Subpalabra para transformers. Caracter para idiomas sin espacios en blanco.

Stemming recorta sufijos con reglas. Rapido, agresivo, tonto. running -> run. organization -> organ. Ese segundo caso es el modo de fallo.

Lematizacion reduce una palabra a su forma de diccionario usando conocimiento gramatical. Mas lento, preciso, necesita una tabla de busqueda o un analizador morfologico. ran -> run (necesita saber que "ran" es el pasado de "run"). better -> good (necesita conocer las formas comparativas).

Regla general. Haz stemming cuando importa la velocidad y puedes tolerar ruido (indexacion de busqueda, clasificacion gruesa). Haz lematizacion cuando importa el significado (preguntas y respuestas, busqueda semantica, cualquier cosa que el usuario vaya a leer).

Construyelo

Paso 1: un tokenizador de palabras con regex

El tokenizador util mas simple divide en caracteres no alfanumericos manteniendo la puntuacion como tokens propios. No es perfecto, no es definitivo, pero corre en una linea.

import re

def tokenize(text):
    return re.findall(r"[A-Za-z]+(?:'[A-Za-z]+)?|[0-9]+|[^\sA-Za-z0-9]", text)

Tres patrones en orden de precedencia. Palabras con apostrofo interno opcional (don't, it's). Numeros puros. Cualquier caracter unico no blanco y no alfanumerico como token aislado (puntuacion).

>>> tokenize("The cats weren't running at 3pm.")
['The', 'cats', "weren't", 'running', 'at', '3', 'pm', '.']

Modos de fallo a notar. 3pm se divide en ['3', 'pm'] porque alternamos entre secuencias de letras y secuencias de digitos. Suficiente para la mayoria de las tareas. URLs, correos y hashtags se rompen. Para produccion, agrega patrones antes de los generales.

Paso 2: un stemmer de Porter (solo el paso 1a)

El algoritmo completo de Porter tiene cinco fases de reglas. El paso 1a por si solo cubre los sufijos mas frecuentes del ingles y ensena el patron.

def stem_step_1a(word):
    if word.endswith("sses"):
        return word[:-2]
    if word.endswith("ies"):
        return word[:-2]
    if word.endswith("ss"):
        return word
    if word.endswith("s") and len(word) > 1:
        return word[:-1]
    return word
>>> [stem_step_1a(w) for w in ["caresses", "ponies", "caress", "cats"]]
['caress', 'poni', 'caress', 'cat']

Lee las reglas de arriba hacia abajo. La regla ies -> i es la razon de que ponies -> poni, y no pony. El Porter real tiene el paso 1b que lo corregiria. Las reglas compiten. Las reglas anteriores ganan. El orden importa mas que cualquier regla aislada.

Paso 3: un lematizador basado en busqueda

La lematizacion propiamente dicha necesita morfologia. Una version didactica manejable usa una pequena tabla de lemas y un fallback.

LEMMA_TABLE = {
    ("running", "VERB"): "run",
    ("ran", "VERB"): "run",
    ("runs", "VERB"): "run",
    ("better", "ADJ"): "good",
    ("best", "ADJ"): "good",
    ("cats", "NOUN"): "cat",
    ("cat", "NOUN"): "cat",
    ("were", "VERB"): "be",
    ("was", "VERB"): "be",
    ("is", "VERB"): "be",
}

def lemmatize(word, pos):
    key = (word.lower(), pos)
    if key in LEMMA_TABLE:
        return LEMMA_TABLE[key]
    if pos == "VERB" and word.endswith("ing"):
        return word[:-3]
    if pos == "NOUN" and word.endswith("s"):
        return word[:-1]
    return word.lower()
>>> lemmatize("running", "VERB")
'run'
>>> lemmatize("cats", "NOUN")
'cat'
>>> lemmatize("better", "ADJ")
'good'
>>> lemmatize("watched", "VERB")
'watched'

El ultimo caso es el momento didactico clave. watched no esta en nuestra tabla y nuestro fallback solo maneja ing. La lematizacion real cubre ed, verbos irregulares, adjetivos comparativos, plurales con cambios de sonido (children -> child). Por eso los sistemas de produccion usan WordNet, el morfologizador de spaCy o un analizador morfologico completo.

Paso 4: conectalos en un pipeline

def preprocess(text, pos_tagger=None):
    tokens = tokenize(text)
    stems = [stem_step_1a(t.lower()) for t in tokens]
    tags = pos_tagger(tokens) if pos_tagger else [(t, "NOUN") for t in tokens]
    lemmas = [lemmatize(word, pos) for word, pos in tags]
    return {"tokens": tokens, "stems": stems, "lemmas": lemmas}

La pieza que falta es un POS tagger. La Fase 5 · 07 (POS Tagging) construye uno. Por ahora, deja todo como NOUN por defecto y reconoce la limitacion.

Usalo

NLTK y spaCy traen las versiones de produccion. Pocas lineas cada uno.

NLTK

import nltk
nltk.download("punkt_tab")
nltk.download("wordnet")
nltk.download("averaged_perceptron_tagger_eng")

from nltk.tokenize import word_tokenize
from nltk.stem import PorterStemmer, WordNetLemmatizer
from nltk import pos_tag

text = "The cats were running."
tokens = word_tokenize(text)
stems = [PorterStemmer().stem(t) for t in tokens]
lemmatizer = WordNetLemmatizer()
tagged = pos_tag(tokens)


def nltk_pos_to_wordnet(tag):
    if tag.startswith("V"):
        return "v"
    if tag.startswith("J"):
        return "a"
    if tag.startswith("R"):
        return "r"
    return "n"


lemmas = [lemmatizer.lemmatize(t, nltk_pos_to_wordnet(tag)) for t, tag in tagged]

word_tokenize maneja contracciones, Unicode y casos limite que tu regex pierde. PorterStemmer corre las cinco fases. WordNetLemmatizer necesita que la etiqueta de POS sea traducida del esquema Penn Treebank de NLTK al conjunto de abreviaturas de WordNet. El cableado de traduccion de arriba es la parte que la mayoria de los tutoriales omite.

spaCy

import spacy

nlp = spacy.load("en_core_web_sm")
doc = nlp("The cats were running.")

for token in doc:
    print(token.text, token.lemma_, token.pos_)
The      the     DET
cats     cat     NOUN
were     be      AUX
running  run     VERB
.        .       PUNCT

spaCy oculta todo el pipeline detras de nlp(text). Tokenizacion, POS tagging y lematizacion corren todos. Mas rapido que NLTK a escala. Mas preciso de fabrica. El tradeoff es que no puedes intercambiar componentes individuales con facilidad.

Cuando elegir cual

Situacion Elige
Ensenanza, investigacion, intercambio de componentes NLTK
Produccion, multilingue, la velocidad importa spaCy
Pipeline de transformer (vas a tokenizar con el tokenizador del modelo de todas formas) Usa tokenizers / transformers y omite el preprocesamiento clasico

Los dos modos de fallo que nadie te advierte

La mayoria de los tutoriales ensena los algoritmos y se detiene ahi. Dos cosas van a morder un pipeline de preprocesamiento real, y casi nunca se cubren.

Deriva de reproducibilidad. NLTK y spaCy cambian el comportamiento de la tokenizacion y del lematizador entre versiones. Lo que producia ['do', "n't"] en spaCy 2.x puede producir ["don't"] en 3.x. Tu modelo fue entrenado en una distribucion. La inferencia ahora corre en otra. La precision se degrada silenciosamente y nadie sabe por que. Fija las versiones de las bibliotecas en requirements.txt. Escribe una prueba de regresion de preprocesamiento que congele la tokenizacion esperada de 20 frases de ejemplo. Ejecutala en cada actualizacion.

Desajuste entre entrenamiento e inferencia. Entrena con preprocesamiento agresivo (minusculas, eliminacion de stopwords, stemming), despliega sobre entrada cruda del usuario y mira como el rendimiento se desploma. Este es el fallo de NLP en produccion mas comun de todos. Si preprocesas durante el entrenamiento, debes ejecutar la funcion identica durante la inferencia. Entrega el preprocesamiento como una funcion dentro del paquete del modelo, no como una celda de notebook que el equipo de serving reescribe.

Entregalo

Un prompt reutilizable que ayuda a los ingenieros a elegir una estrategia de preprocesamiento sin leer tres libros de texto.

Guarda como outputs/prompt-preprocessing-advisor.md:

---
name: preprocessing-advisor
description: Recommends a tokenization, stemming, and lemmatization setup for an NLP task.
phase: 5
lesson: 01
---

You advise on classical NLP preprocessing. Given a task description, you output:

1. Tokenization choice (regex, NLTK word_tokenize, spaCy, or transformer tokenizer). Explain why.
2. Whether to stem, lemmatize, both, or neither. Explain why.
3. Specific library calls. Name the functions. Quote the POS-tag translation if NLTK is involved.
4. One failure mode the user should test for.

Refuse to recommend stemming for user-visible text. Refuse to recommend lemmatization without POS tags. Flag non-English input as needing a different pipeline.

Ejercicios

  1. Facil. Extiende tokenize para mantener las URLs como tokens unicos. Prueba: tokenize("Visit https://example.com today.") debe producir un unico token de URL.
  2. Medio. Implementa el paso 1b de Porter. Si una palabra contiene una vocal y termina en ed o ing, elimina el sufijo. Maneja la regla de consonante doble (hopping -> hop, y no hopp).
  3. Dificil. Construye un lematizador que use WordNet como tabla de busqueda pero recurra a tu stemmer de Porter cuando WordNet no tenga entrada. Mide la precision en un corpus etiquetado contra WordNet puro y Porter puro.

Terminos Clave

Termino Lo que dice la gente Lo que realmente significa
Token Una palabra Cualquier unidad que el modelo consume. Puede ser palabra, subpalabra, caracter o byte.
Raiz (stem) Raiz de una palabra Resultado de la eliminacion de sufijos basada en reglas. No siempre una palabra real.
Lema Forma de diccionario La forma que buscarias en el diccionario. Requiere contexto gramatical para calcularse correctamente.
Etiqueta de POS Categoria gramatical Categoria como NOUN, VERB, ADJ. Necesaria para lematizar con precision.
Morfologia Reglas de forma de las palabras Como una palabra cambia de forma segun tiempo, numero, caso. La lematizacion depende de ello.

Lectura Adicional

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).