Фаза 05 · урок 07

POS-разметка и синтаксический разбор

Цель урока: Урок 01 обещал, что для лемматизации нужна часть речи. Не зная, что running — глагол, лемматизатор не сможет привести его к run . Не зная, что better — прилагательное, он не сможет привести его к good .

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering from Scratch
Фаза
NLP: от основ к продвинутым темам
Чтение
10 мин.
Проверено
Содержание урока
  1. Проблема
  2. Концепция
  3. Соберите сами
  4. Шаг 1: базовая модель наиболее частой метки
  5. Шаг 2: теггер биграммной HMM
  6. Шаг 3: почему современные теггеры лучше
  7. Шаг 4: набросок разбора зависимостей
  8. Примените
  9. Где это всё ещё важно в 2026 году
  10. Подготовьте к поставке
  11. Упражнения
  12. Ключевые термины
  13. Дополнительное чтение

На какое-то время грамматика вышла из моды. Затем каждому конвейеру LLM понадобилась проверка структурированного извлечения — и она вернулась.

Тип: Сборка Языки: Python Предварительные требования: Фаза 5 · 01 (Обработка текста), Фаза 2 · 14 (Наивный Байес) Время: ~45 минут

Схема к уроку «POS-разметка и синтаксический разбор»

Проблема

Урок 01 обещал, что для лемматизации нужна часть речи. Не зная, что running — глагол, лемматизатор не сможет привести его к run. Не зная, что better — прилагательное, он не сможет привести его к good.

За этим обещанием скрывалась целая поддисциплина. POS-разметка (part-of-speech tagging) присваивает грамматические категории. Синтаксический разбор восстанавливает древовидную структуру предложения: какое слово что модифицирует, какой глагол управляет какими аргументами. Классический NLP двадцать лет совершенствовал оба подхода. Затем глубокое обучение свело их к задаче классификации токенов поверх предобученного трансформера, и исследовательское сообщество переключилось на другое.

Но не прикладное сообщество. Каждый конвейер структурированного извлечения всё ещё использует под капотом POS-метки и деревья зависимостей. Сгенерированный LLM JSON проверяется на соответствие грамматическим ограничениям. Системы ответов на вопросы раскладывают запросы с помощью синтаксических разборов зависимостей. Оценщики качества машинного перевода проверяют выравнивание деревьев разбора.

Это полезно знать. Урок знакомит с наборами меток, базовыми моделями и моментом, когда стоит перестать реализовывать всё с нуля и вызвать spaCy.

Концепция

POS-разметка (POS tagging) назначает каждому токену грамматическую категорию. Набор меток Penn Treebank (PTB) — стандарт для английского языка. В нём 36 меток с различиями, которые случайному читателю могут показаться излишне тонкими: NN — существительное в единственном числе, NNS — существительное во множественном числе, NNP — имя собственное в единственном числе, VBD — глагол в прошедшем времени, VBZ — глагол в настоящем времени в третьем лице единственного числа и так далее. Набор меток Universal Dependencies (UD) более грубый (17 меток) и не зависит от языка; он стал стандартом для кросс-языковой работы.

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

Синтаксический разбор (syntactic parsing) строит дерево. Есть два основных стиля:

  • Разбор по составляющим (constituency parsing). Именные группы, глагольные группы и предложные группы вкладываются друг в друга. Результат — дерево нетерминальных категорий (NP, VP, PP), листьями которого служат слова.
  • Разбор зависимостей (dependency parsing). У каждого слова есть единственное главное слово, от которого оно зависит, а зависимость помечена грамматическим отношением. Результат — дерево, в котором каждое ребро представляет тройку (главное слово, зависимое слово, отношение).

Разбор зависимостей победил в 2010-х, поскольку хорошо обобщается на языки, особенно с относительно свободным порядком слов.

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

Соберите сами

Шаг 1: базовая модель наиболее частой метки

Самый простой работающий POS-теггер. Для каждого слова предсказывайте метку, которая чаще всего встречалась у него при обучении.

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]

На корпусе Brown эта базовая модель достигает точности ~85%. Неплохо, но это нижняя граница, ниже которой не должна опускаться никакая серьёзная модель.

Шаг 2: теггер биграммной HMM

Смоделируйте совместную вероятность последовательности:

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

Две таблицы: вероятности переходов (метка при условии предыдущей метки) и вероятности эмиссий (слово при условии метки). Оцените обе по подсчётам со сглаживанием Лапласа. Выполните декодирование алгоритмом Витерби (динамическое программирование по решётке меток).

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)]

Биграммная HMM на Brown достигает точности ~93%. Скачок с 85% до 93% в основном обеспечивают вероятности переходов: модель усваивает, что DET NOUN часто встречается, а NOUN DET — редко.

Шаг 3: почему современные теггеры лучше

Вероятности переходов и эмиссий локальны. Они не могут уловить, что saw — существительное в «I bought a saw», но глагол в «I saw the movie». CRF с произвольными признаками (суффикс, форма слова, предыдущее и следующее слово, само слово) достигает ~97%. BiLSTM-CRF или трансформер достигает ~98%+.

Предел для этой задачи задаётся разногласиями разметчиков. Разметчики-люди совпадают примерно в 97% случаев на Penn Treebank. Модели с результатом выше 98%, вероятно, переобучаются на тестовой выборке.

Шаг 4: набросок разбора зависимостей

Полная реализация разбора зависимостей с нуля выходит за рамки урока; каноническое изложение есть у Jurafsky и Martin. Два классических семейства, о которых стоит знать:

  • Парсеры на основе переходов (transition-based; arc-eager, arc-standard) работают как парсер shift-reduce: читают токены, помещают их в стек сдвигом и применяют действия свёртки, создающие дуги. Жадное декодирование быстро. Классическая реализация — MaltParser. Современная нейронная версия — парсер на основе переходов Chen и Manning.
  • Парсеры на основе графов (graph-based; алгоритм Eisner, биаффинный Dozat-Manning) оценивают каждое возможное ребро «главное слово — зависимое слово» и выбирают максимальное остовное дерево. Они медленнее, но точнее.

Для большинства прикладных задач вызовите 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

Читайте столбец dep снизу вверх — и грамматическая структура предложения становится очевидной.

Примените

Каждая промышленная библиотека NLP поставляет POS- и синтаксические парсеры как часть стандартного конвейера.

  • spaCy (en_core_web_sm / md / lg / trf). Быстрая, точная, интегрирована с токенизацией + NER + лемматизацией. token.tag_ (Penn), token.pos_ (UD), token.dep_ (отношение зависимостей).
  • Stanford NLP (stanza). Преемник Stanford CoreNLP. Передовой уровень для 60+ языков.
  • trankit. На основе трансформеров; хорошая точность UD.
  • NLTK. pos_tag. Пригодна, медленная, устаревшая. Подойдёт для обучения.

Где это всё ещё важно в 2026 году

  • Лемматизация. Уроку 01 нужен POS для корректной лемматизации. Всегда.
  • Структурированное извлечение из выводов LLM. Проверяйте, что сгенерированное предложение соблюдает грамматические ограничения (например, согласование подлежащего и сказуемого, обязательные модификаторы).
  • Аспектный анализ тональности. Разборы зависимостей показывают, какое прилагательное модифицирует какое существительное.
  • Понимание запросов. «movies directed by Wes Anderson starring Bill Murray» раскладывается через разбор на структурированные ограничения.
  • Кросс-языковой перенос. Метки UD и отношения зависимостей не зависят от языка, что даёт анализ новой структуры без примеров (zero-shot).
  • Конвейеры с малым вычислительным бюджетом. Если нельзя развернуть трансформер, POS + разбор зависимостей + словарь-справочник (gazetteer) дадут неожиданно много.

Подготовьте к поставке

Сохраните как 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.

Упражнения

  1. Легко. С помощью базовой модели наиболее частой метки на небольшом размеченном корпусе (например, подмножестве Brown из NLTK) измерьте точность на отложенных предложениях. Проверьте результат ~85%.
  2. Средне. Обучите приведённую выше биграммную HMM и сообщите точность/полноту по каждой метке. Какие метки HMM путает чаще всего?
  3. Сложно. Используйте разбор зависимостей spaCy для извлечения троек «субъект — глагол — объект» из выборки из 1000 предложений. Оцените на 50 размеченных вручную тройках. Задокументируйте, где извлечение не срабатывает (часто это пассивные конструкции, сочинения и опущенные подлежащие).

Ключевые термины

Термин Как обычно говорят Что это на самом деле означает
POS-метка Тип слова Грамматическая категория. В PTB 36 меток; в UD — 17.
Penn Treebank Стандартный набор меток Специфичен для английского языка. Детально различает времена глаголов и число существительных.
Universal Dependencies Многоязычный набор меток Грубее PTB; нейтрален к языку; стандарт для кросс-языковой работы.
Разбор зависимостей Дерево предложения У каждого слова одно главное слово, у каждого ребра есть грамматическое отношение.
Витерби Динамическое программирование Находит последовательность меток с максимальной вероятностью по эмиссиям и переходам.

Дополнительное чтение


Источник: POS Tagging and Syntactic Parsing 05.06 — Распознавание именованных сущностей · Фаза 5 — Основы и продвинутые темы NLP · Полный каталог · 05.08 — CNN и RNN для текста