English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

invitado
1 / ?

Bienvenido

Bienvenido a una lección práctica de PNL.

Vas a construir un stemmer de inglés que funcione desde cero: un algoritmo que reduce palabras a su forma raíz.

Al final, tendrás un algoritmo real y probado que transforma palabras como runningrun, happinesshappi & organizationalorgan.

También escribirás pruebas unitarias, pruebas de integración y pruebas funcionales para tu stemmer: porque un algoritmo sin pruebas es solo una conjetura.

¿Qué es el stemming?

El problema

Los motores de búsqueda enfrentan un problema fundamental: un usuario busca "running" pero el documento contiene "run" o "runs" o "runner". Son todos el mismo concepto: pero son strings diferentes.


Stemming reduce palabras flexionadas a una forma base común (el raíz). No necesita ser una palabra real: solo necesita ser consistente.


PalabraRaíz
runningrun
runsrun
runnerrunner
happinesshappi
happilyhappi
happyhappi

Nota que happi no es una palabra inglesa real. Sin problema. Stemming se trata de agrupar, no de significado. Mientras happiness, happily & happy colapsen a la misma raíz, la búsqueda & recuperación mejoran.

Stemming: Muchas formas colapsan a una raíz, mejorando los resultados de búsqueda

Con tus propias palabras, explica por qué un motor de búsqueda que usa stemming devolvería mejores resultados que uno que solo coincide strings exactos. Da un ejemplo concreto.

Zellig Harris y Análisis Distribucional

El origen del stemming computacional

En 1955, el lingüista Zellig Harris publicó From Phoneme to Morpheme (Del Fonema al Morfema), describiendo un método para encontrar los límites entre unidades significativas (morfemas) en palabras.


Su insight fue distribucional: si miras un gran corpus de palabras inglesas, el límite entre una raíz & un sufijo se muestra como una señal estadística.


El método de variedad de sucesores

Para cualquier prefijo de una palabra, cuenta cuántos caracteres distintos lo siguen en el corpus. Harris llamó a esto la variedad de sucesores.


Considera el prefijo "work" en un corpus que contiene: worked, worker, working, works, workshop.


PrefijoQué sigueVariedad de sucesores
wo1
wor1
work1
worke, i, s, sh4
worked, r2

Después de "work", cuatro caracteres diferentes pueden seguir: un pico en variedad. Ese pico marca un límite de morfema. La raíz es work y todo lo que viene después es un sufijo.


Esto fue radical en 1955. Sin reglas lingüísticas, sin diccionario: solo contar. Harris demostró que la estructura del lenguaje se revela a sí misma a través de la distribución.

Harris Variedad de Sucesores: Pico en 'work' marca el límite del morfema

Entender la Variedad de Sucesores

El método de Harris funciona en cualquier idioma. No necesitas conocer la gramática: las estadísticas revelan los límites de morfemas.


En la práctica, la variedad de sucesores pura requiere un corpus grande y detección cuidadosa de picos. Investigadores posteriores: Lovins (1968), Porter (1980): simplificaron el enfoque en eliminación de sufijos basada en reglas: en lugar de calcular variedad de sucesores de un corpus, codificaron las reglas de sufijo directamente.


Hoy construirás un eliminador de sufijos basado en reglas inspirado en el insight de Harris. Definirás los sufijos explícitamente, luego los eliminarás de las palabras. Así es como funcionan la mayoría de los stemmers de producción.

¿Cuál es el insight clave del método de variedad de sucesores de Harris? En otras palabras, ¿qué señal estadística te dice dónde está el límite de un morfema?

Tu Primer Eliminador de Sufijos

Vamos a codificar

Comienza simple. Escribe una función llamada stem que toma una palabra & elimina estos sufijos (en este orden):


1. -ing (running → runn)

2. -ed (walked → walk)

3. -ly (quickly → quick)

4. -s (cats → cat)


Reglas:

- Convierte la palabra a minúsculas primero

- Solo elimina un sufijo (la primera coincidencia en el orden anterior)

- Solo elimina si la raíz restante tiene al menos 3 caracteres de largo

- Devuelve la raíz


Ejemplo:

def stem(word):
    word = word.lower()
    # tu lógica de eliminación de sufijos aquí
    return word
Escribe la función `stem`. Debería eliminar -ing, -ed, -ly, o -s (primera coincidencia, en orden) de la palabra, pero solo si la raíz restante tiene 3 o más caracteres. Pruébala imprimiendo stem('running'), stem('walked'), stem('quickly'), & stem('cats').

Manejo de Casos Edge

Haciendo el stemmer más inteligente

Tu eliminador básico tiene un problema: runningrunn & hopinghop. Necesitamos dos refinamientos:


1. Limpieza de consonante doble: si eliminar -ing o -ed deja una consonante doble al final (como runn), elimina la última letra → run

2. Restauración de e silenciosa: si eliminar -ing deja una raíz terminando en una consonante (no una vocal), & el original podría haber tenido una e silenciosa (como hop de hoping), añade e de vuelta → hope


Para la regla de e silenciosa, mantenlo simple: si después de eliminar -ing, la raíz es 3+ caracteres, termina con una consonante, & el penúltimo carácter es una vocal (un patrón como hop, mak, tak), añade e de vuelta.


También añade estos nuevos sufijos (revísalos antes de -ing, -ed, -ly, -s):

5. -tion (organization → organiza)

6. -ness (happiness → happi)

7. -ment (movement → move)

8. -able (readable → read)

9. -ible (sensible → sens)


Prioridad de sufijo actualizada: -tion, -ness, -ment, -able, -ible, -ing, -ed, -ly, -s


Mantén la regla de longitud mínima de raíz: solo elimina si la raíz restante es 3+ caracteres.

Actualiza tu función `stem` con los nuevos sufijos, limpieza de consonante doble & restauración de e silenciosa. Imprime resultados para: stem('running'), stem('hoping'), stem('happiness'), stem('organization'), stem('readable').

Reglas -ies & -ier

Más morfología

El inglés tiene otro patrón común: palabras terminando en -y cambian a -ies, -ied, o -ier cuando se flexionan.


PalabraDebería ser raíz
babiesbabi
carriedcarri
earlierearli
fliesfli
studiedstudi

Añade estas reglas antes de las revisiones -s & -ed:

- -ies → elimina & añade i (babies → babi)

- -ied → elimina & añade i (carried → carri)

- -ier → elimina & añade i (earlier → earli)


La misma regla de longitud mínima de raíz: solo transforma si el resultado es 3+ caracteres.

Añade las reglas -ies, -ied, & -ier a tu función stem. Imprime resultados para: stem('babies'), stem('carried'), stem('earlier'), stem('happiness'), stem('running').

¿Por qué probar?

Probar no es opcional

Tienes un stemmer que funciona. ¿Cómo sabes que realmente funciona? Ahora estás ejecutando algunos ejemplos a mano. Eso no escala.


El software profesional usa tres niveles de prueba:


Pruebas unitarias: prueba una función en aislamiento con entradas conocidas y salidas esperadas. Rápidas, numerosas, específicas.


Pruebas de integración: prueba que múltiples componentes funcionen juntos. Para un stemmer, esto significa probar contra un lote de palabras y verificar que los resultados sean consistentes.


Pruebas funcionales: prueba el sistema desde afuera, como lo haría un usuario. Para un stemmer, esto significa alimentarlo con texto real y verificar que la salida tenga sentido para un caso de uso real como búsqueda.


Escribirás las tres.

Tres Niveles de Prueba: Pirámide de Pruebas Unitarias, Integración y Funcionales

Escribe Pruebas Unitarias

Pruebas unitarias

Escribe una función llamada run_unit_tests que pruebe tu función stem con al menos 15 casos de prueba cubriendo:


1. Eliminación básica de sufijos: palabras terminando en -ing, -ed, -ly, -s

2. Sufijos complejos: -tion, -ness, -ment, -able, -ible

3. Flexión-Y: -ies, -ied, -ier

4. Casos edge: palabras cortas que no deberían eliminarse, palabras sin sufijo, palabras ya radizadas

5. Limpieza de consonante doble: running → run, sitting → sit

6. Restauración de e silenciosa: hoping → hope

7. Insensibilidad a mayúsculas: entrada en mayúsculas debería convertirse a minúsculas


Estructura tus pruebas así:

def run_unit_tests():
    tests = [
        ('running', 'run'),
        ('cats', 'cat'),
        # ... al menos 15 casos de prueba
    ]
    passed = 0
    failed = 0
    for word, expected in tests:
        result = stem(word)
        if result == expected:
            passed += 1
        else:
            failed += 1
            print(f'FAIL: stem({word}) = {result}, expected {expected}')
    print(f'{passed}/{passed + failed} unit tests passed')
    return failed == 0
Incluye tu función `stem` completa & escribe `run_unit_tests` con al menos 15 casos de prueba cubriendo las 7 categorías arriba. Llama a `run_unit_tests()` al final.

Escribe Pruebas de Integración

Pruebas de integración

Las pruebas unitarias verifican entradas individuales. Las pruebas de integración verifican que los componentes funcionen juntos correctamente.


Para un stemmer, una propiedad de integración clave es consistencia: si radizas la misma palabra dos veces, obtienes el mismo resultado. Y palabras que deberían agruparse deberían producir la misma raíz.


Escribe una función llamada run_integration_tests que pruebe:


1. Idempotencia: radizar una palabra ya radizada debería devolver la misma raíz. stem(stem(word)) == stem(word) para todas las palabras.

2. Agrupamiento: palabras que deberían compartir raíz realmente lo hacen. Prueba al menos 3 familias de palabras (ej., run/runs/running/runner deberían compartir raíz).

3. Procesamiento en lote: procesa una lista de 20+ palabras y verifica que no haya crashes, strings vacíos, valores None.


def run_integration_tests():
    # Prueba 1: idempotencia
    # Prueba 2: agrupamiento de familia de palabras
    # Prueba 3: estabilidad en lote
    ...
Incluye tu función `stem` & escribe `run_integration_tests` con las tres categorías de prueba. Llámala al final.

Escribe Pruebas Funcionales

Pruebas funcionales

Las pruebas funcionales verifican que el sistema funcione para su caso de uso previsto. Tu stemmer existe para mejorar la búsqueda: así que prueba eso.


Escribe una función llamada run_functional_tests que:


1. Simulación de búsqueda: dada una lista de strings de documentos & una palabra de consulta, radiza tanto los documentos como la consulta, luego verifica si los términos de consulta radicalizados aparecen en documentos radicalizados. Prueba que buscar 'running' encuentra un documento conteniendo 'run' & 'runner'.

2. Verificación de precisión: verifica que radicalizar NO agrupe incorrectamente palabras no relacionadas. 'university' & 'universe' podrían compartir raíz: verifica si tu stemmer maneja esto (está OK si las agrupa; documenta el comportamiento).

3. Procesamiento de texto real: radicaliza cada palabra en un párrafo de texto inglés real. Verifica que la salida sea razonable: sin strings vacíos, sin crashes, salida tiene el mismo número de palabras que entrada.


def run_functional_tests():
    # Prueba 1: búsqueda encuentra documentos relacionados
    # Prueba 2: precisión: verificar sobre-radicalizacion
    # Prueba 3: procesamiento de párrafo real
    ...
Incluye tu función `stem` & escribe `run_functional_tests` con las tres categorías de prueba. Llámala al final.

Lo Que Construiste

Lo que construiste

Implementaste un stemmer de inglés que funciona con:

- 12 reglas de sufijo (-tion, -ness, -ment, -able, -ible, -ies, -ied, -ier, -ing, -ed, -ly, -s)

- Limpieza de consonante doble

- Restauración de e silenciosa

- Pruebas unitarias, pruebas de integración, & pruebas funcionales


La línea de descendencia

Tu stemmer desciende de una línea de trabajo que comenzó con Zellig Harris en 1955:


- Harris (1955): Descubrió que los límites de morfema aparecen como señales estadísticas (variedad de sucesores)

- Lovins (1968): Primer algoritmo de stemming publicado, 294 reglas de sufijo

- Porter (1980): Simplificado a ~60 reglas en 5 pasos, se convirtió en el estándar durante décadas

- Snowball (2001): Marco de Porter generalizado a múltiples idiomas

- Tu stemmer (hoy): 12 reglas, el mismo principio fundamental


Lo que podrías hacer después

- Implementar el algoritmo completo de Porter (está bien documentado & es un gran ejercicio)

- Portar tu stemmer a C para una mejora de velocidad de 100x

- Construir un motor de búsqueda simple que use tu stemmer para indexar & consultar archivos de texto

- Comparar la salida de tu stemmer contra PorterStemmer de NLTK para medir precisión


El código que escribiste hoy es la misma operación fundamental que se ejecuta dentro de cada motor de búsqueda del planeta. No está mal para un día de trabajo.

Línea de Descendencia del Stemmer: Harris 1955 a través de Snowball 2001

Reflexiona sobre lo que construiste. ¿Cuál fue la cosa más sorprendente que aprendiste? Si fueras a mejorar tu stemmer, ¿qué añadirías o cambiarías?