Phase 05 - Lesson 07

Etiquetado de Categorías Gramaticales (POS) y Análisis Sintáctico

La gramática estuvo pasada de moda por un tiempo. Después, todo pipeline de LLM necesitó validar extracción estructurada, y volvió.

Tipo: Build Lenguajes: Python Prerrequisitos: Fase 5 · 01 (Procesamiento de Texto), Fase 2 · 14 (Naive Bayes) Tiempo: ~45 minutos

El Problema

La lección 01 prometió que la lematización necesita una etiqueta de categoría gramatical. Sin saber que running es un verbo, un lematizador no puede reducirlo a run. Sin saber que better es un adjetivo, no puede reducirlo a good.

Esa promesa ocultaba todo un subcampo. El etiquetado de categorías gramaticales asigna categorías gramaticales. El análisis sintáctico recupera la estructura en árbol de la oración: qué palabra modifica a cuál, qué verbo gobierna qué argumentos. El PLN clásico pasó veinte años refinando ambos. Después el aprendizaje profundo los colapsó en una tarea de clasificación de tokens sobre un transformer preentrenado, y la comunidad de investigación siguió adelante.

La comunidad aplicada no. Todo pipeline de extracción estructurada todavía usa POS y árboles de dependencia por debajo. El JSON generado por LLM se valida contra restricciones gramaticales. Los sistemas de preguntas y respuestas descomponen consultas usando análisis de dependencia. Los evaluadores de calidad de traducción automática verifican la alineación de los árboles de análisis.

Vale la pena conocerlo. Esta lección presenta los conjuntos de etiquetas, las líneas base y el punto en el que dejas de implementar desde cero y llamas a spaCy.

El Concepto

Ejemplo de etiqueta POS + análisis de dependencia

El etiquetado POS etiqueta cada token con una categoría gramatical. El conjunto de etiquetas del Penn Treebank (PTB) es el predeterminado para el inglés. 36 etiquetas con distinciones que el lector casual encuentra minuciosas: NN sustantivo singular, NNS sustantivo plural, NNP sustantivo propio singular, VBD verbo en pasado, VBZ verbo en 3ª persona del singular del presente, y así sucesivamente. El conjunto de etiquetas de las Universal Dependencies (UD) es más grueso (17 etiquetas) e independiente del idioma; se convirtió en el predeterminado para el trabajo multilingüe.

The/DET cats/NOUN were/AUX running/VERB at/ADP 3pm/NOUN ./PUNCT

El análisis sintáctico produce un árbol. Dos estilos principales:

  • Análisis de constituyentes. Los sintagmas nominales, sintagmas verbales y sintagmas preposicionales se anidan unos dentro de otros. La salida es un árbol de categorías no terminales (NP, VP, PP) con las palabras como hojas.
  • Análisis de dependencia. Cada palabra tiene una única palabra-núcleo de la que depende, etiquetada con una relación gramatical. La salida es un árbol en el que cada arista es una tripleta (núcleo, dependiente, relación).

El análisis de dependencia ganó en la década de 2010 porque generaliza de forma limpia entre idiomas, especialmente los de orden de palabras libre.

running is ROOT
cats is nsubj of running
were is aux of running
at is prep of running
3pm is pobj of at

Constrúyelo

Paso 1: línea base de la etiqueta más frecuente

El etiquetador POS más tonto que funciona. Para cada palabra, predice la etiqueta que tuvo con más frecuencia en el entrenamiento.

from collections import Counter, defaultdict


def train_mft(train_examples):
    word_tag_counts = defaultdict(Counter)
    all_tags = Counter()
    for tokens, tags in train_examples:
        for token, tag in zip(tokens, tags):
            word_tag_counts[token.lower()][tag] += 1
            all_tags[tag] += 1
    word_best = {w: c.most_common(1)[0][0] for w, c in word_tag_counts.items()}
    default_tag = all_tags.most_common(1)[0][0]
    return word_best, default_tag


def predict_mft(tokens, word_best, default_tag):
    return [word_best.get(t.lower(), default_tag) for t in tokens]

En el corpus Brown, esta línea base alcanza ~85% de exactitud. No es buena, pero es el piso por debajo del cual ningún modelo serio debería caer.

Paso 2: etiquetador HMM de bigramas

Modela la probabilidad conjunta de la secuencia:

P(tags, words) = prod P(tag_i | tag_{i-1}) * P(word_i | tag_i)

Dos tablas: probabilidades de transición (etiqueta dada la etiqueta anterior), probabilidades de emisión (palabra dada la etiqueta). Estima ambas a partir de conteos con suavizado de Laplace. Decodifica con Viterbi (programación dinámica sobre el retículo de etiquetas).

import math


def train_hmm(train_examples, alpha=0.01):
    transitions = defaultdict(Counter)
    emissions = defaultdict(Counter)
    tags = set()
    vocab = set()

    for tokens, ts in train_examples:
        prev = "<BOS>"
        for token, tag in zip(tokens, ts):
            transitions[prev][tag] += 1
            emissions[tag][token.lower()] += 1
            tags.add(tag)
            vocab.add(token.lower())
            prev = tag
        transitions[prev]["<EOS>"] += 1

    return transitions, emissions, tags, vocab


def log_prob(table, given, key, smooth_denom, alpha):
    return math.log((table[given].get(key, 0) + alpha) / smooth_denom)


def viterbi(tokens, transitions, emissions, tags, vocab, alpha=0.01):
    tags_list = list(tags)
    n = len(tokens)
    V = [[0.0] * len(tags_list) for _ in range(n)]
    back = [[0] * len(tags_list) for _ in range(n)]

    for j, tag in enumerate(tags_list):
        em_denom = sum(emissions[tag].values()) + alpha * (len(vocab) + 1)
        tr_denom = sum(transitions["<BOS>"].values()) + alpha * (len(tags_list) + 1)
        tr = log_prob(transitions, "<BOS>", tag, tr_denom, alpha)
        em = log_prob(emissions, tag, tokens[0].lower(), em_denom, alpha)
        V[0][j] = tr + em
        back[0][j] = 0

    for i in range(1, n):
        for j, tag in enumerate(tags_list):
            em_denom = sum(emissions[tag].values()) + alpha * (len(vocab) + 1)
            em = log_prob(emissions, tag, tokens[i].lower(), em_denom, alpha)
            best_prev = 0
            best_score = -1e30
            for k, prev_tag in enumerate(tags_list):
                tr_denom = sum(transitions[prev_tag].values()) + alpha * (len(tags_list) + 1)
                tr = log_prob(transitions, prev_tag, tag, tr_denom, alpha)
                score = V[i - 1][k] + tr + em
                if score > best_score:
                    best_score = score
                    best_prev = k
            V[i][j] = best_score
            back[i][j] = best_prev

    last_best = max(range(len(tags_list)), key=lambda j: V[n - 1][j])
    path = [last_best]
    for i in range(n - 1, 0, -1):
        path.append(back[i][path[-1]])
    return [tags_list[j] for j in reversed(path)]

El HMM de bigramas en Brown alcanza ~93% de exactitud. El salto de 85% a 93% proviene principalmente de las probabilidades de transición: el modelo aprende que DET NOUN es común y NOUN DET es raro.

Paso 3: por qué los etiquetadores modernos superan esto

Las probabilidades de transición + emisión son locales. No pueden capturar que saw es un sustantivo en "I bought a saw" pero un verbo en "I saw the movie". Un CRF con características arbitrarias (sufijo, forma de la palabra, palabra anterior y posterior, la palabra misma) alcanza ~97%. Un BiLSTM-CRF o transformer alcanza ~98%+.

El techo de esta tarea lo fija el desacuerdo entre anotadores. Los anotadores humanos coinciden cerca del 97% de las veces en el Penn Treebank. Los modelos por encima del 98% probablemente estén sobreajustando el conjunto de prueba.

Paso 4: bosquejo del análisis de dependencia

El análisis de dependencia completo desde cero está fuera de alcance; el tratamiento canónico de libro de texto está en Jurafsky y Martin. Dos familias clásicas que conviene conocer:

  • Parsers basados en transiciones (arc-eager, arc-standard) actúan como un parser shift-reduce: leen tokens, los apilan en una pila y aplican acciones de reducción que crean arcos. La decodificación voraz es rápida. La implementación clásica es MaltParser. Versión neuronal moderna: el parser basado en transiciones de Chen y Manning.
  • Parsers basados en grafos (algoritmo de Eisner, biafín de Dozat-Manning) puntúan cada posible arista núcleo-dependiente y eligen el árbol de expansión máximo. Más lentos pero más precisos.

Para la mayor parte del trabajo aplicado, llama a spaCy:

import spacy

nlp = spacy.load("en_core_web_sm")
doc = nlp("The cats were running at 3pm.")
for token in doc:
    print(f"{token.text:10s} tag={token.tag_:5s} pos={token.pos_:6s} dep={token.dep_:10s} head={token.head.text}")
The        tag=DT    pos=DET    dep=det        head=cats
cats       tag=NNS   pos=NOUN   dep=nsubj      head=running
were       tag=VBD   pos=AUX    dep=aux        head=running
running    tag=VBG   pos=VERB   dep=ROOT       head=running
at         tag=IN    pos=ADP    dep=prep       head=running
3pm        tag=NN    pos=NOUN   dep=pobj       head=at
.          tag=.     pos=PUNCT  dep=punct      head=running

Lee la columna dep de abajo hacia arriba y la estructura gramatical de la oración se revela.

Úsalo

Toda biblioteca de PLN de producción entrega parsers de POS y dependencia como parte de un pipeline estándar.

  • spaCy (en_core_web_sm / md / lg / trf). Rápido, preciso, integrado con tokenización + NER + lematización. token.tag_ (Penn), token.pos_ (UD), token.dep_ (relación de dependencia).
  • Stanford NLP (stanza). El sucesor de CoreNLP de Stanford. Estado del arte en más de 60 idiomas.
  • trankit. Basado en transformers, buena exactitud en UD.
  • NLTK. pos_tag. Usable, lento, más antiguo. Adecuado para enseñanza.

Dónde esto todavía importa en 2026

  • Lematización. La lección 01 necesita POS para lematizar correctamente. Siempre.
  • Extracción estructurada de salidas de LLM. Valida que una oración generada respete restricciones gramaticales (por ejemplo, concordancia sujeto-verbo, modificadores obligatorios).
  • Análisis de sentimiento basado en aspectos. Los análisis de dependencia te dicen qué adjetivo modifica a qué sustantivo.
  • Comprensión de consultas. "películas dirigidas por Wes Anderson protagonizadas por Bill Murray" se descompone en restricciones estructuradas vía el análisis.
  • Transferencia multilingüe. Las etiquetas UD y las relaciones de dependencia son independientes del idioma, lo que permite análisis estructurado zero-shot de nuevos idiomas.
  • Pipelines de bajo cómputo. Si no puedes desplegar un transformer, POS + análisis de dependencia + gazetteer te lleva sorprendentemente lejos.

Entrégalo

Guarda como outputs/skill-grammar-pipeline.md:

---
name: grammar-pipeline
description: Design a classical POS + dependency pipeline for a downstream NLP task.
version: 1.0.0
phase: 5
lesson: 07
tags: [nlp, pos, parsing]
---

Given a downstream task (information extraction, rewrite validation, query decomposition, lemmatization), you output:

1. Tagset to use. Penn Treebank for English-only legacy pipelines, Universal Dependencies for multilingual or cross-lingual.
2. Library. spaCy for most production, stanza for academic-grade multilingual, trankit for highest UD accuracy. Name the specific model ID.
3. Integration pattern. Show the 3-5 lines that call the library and consume the needed attributes (`.pos_`, `.dep_`, `.head`).
4. Failure mode to test. Noun-verb ambiguity (`saw`, `book`, `can`) and PP-attachment ambiguity are the classical traps. Sample 20 outputs and eyeball.

Refuse to recommend rolling your own parser. Building parsers from scratch is a research project, not an application task. Flag any pipeline that consumes POS tags without handling lowercase/uppercase variants as fragile.

Ejercicios

  1. Fácil. Usando la línea base de la etiqueta más frecuente en un corpus etiquetado pequeño (por ejemplo, el subconjunto Brown de NLTK), mide la exactitud en oraciones reservadas para prueba. Verifica el resultado de ~85%.
  2. Medio. Entrena el HMM de bigramas anterior y reporta precisión/exhaustividad por etiqueta. ¿Qué etiquetas confunde más el HMM?
  3. Difícil. Usa el análisis de dependencia de spaCy para extraer tripletas sujeto-verbo-objeto de una muestra de 1000 oraciones. Evalúa en 50 tripletas etiquetadas manualmente. Documenta dónde falla la extracción (a menudo en voz pasiva, coordinaciones y sujetos elididos).

Términos Clave

Término Lo que la gente dice Lo que realmente significa
Etiqueta POS Tipo de la palabra Categoría gramatical. El PTB tiene 36; el UD tiene 17.
Penn Treebank Conjunto de etiquetas estándar Específico del inglés. Tiempos verbales y número de sustantivos de grano fino.
Universal Dependencies Conjunto de etiquetas multilingüe Más grueso que el PTB; neutral respecto al idioma; predeterminado para trabajo multilingüe.
Análisis de dependencia Árbol de la oración Cada palabra tiene un núcleo, cada arista tiene una relación gramatical.
Viterbi Programación dinámica Encuentra la secuencia de etiquetas de mayor probabilidad dadas las emisiones y transiciones.

Lectura Adicional

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