Фаза 05 · урок 07
POS-разметка и синтаксический разбор
Цель урока: Урок 01 обещал, что для лемматизации нужна часть речи. Не зная, что running — глагол, лемматизатор не сможет привести его к run . Не зная, что better — прилагательное, он не сможет привести его к good .
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
На какое-то время грамматика вышла из моды. Затем каждому конвейеру LLM понадобилась проверка структурированного извлечения — и она вернулась.
Тип: Сборка Языки: Python Предварительные требования: Фаза 5 · 01 (Обработка текста), Фаза 2 · 14 (Наивный Байес) Время: ~45 минут
Проблема
Урок 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.
Упражнения
- Легко. С помощью базовой модели наиболее частой метки на небольшом размеченном корпусе (например, подмножестве Brown из NLTK) измерьте точность на отложенных предложениях. Проверьте результат ~85%.
- Средне. Обучите приведённую выше биграммную HMM и сообщите точность/полноту по каждой метке. Какие метки HMM путает чаще всего?
- Сложно. Используйте разбор зависимостей spaCy для извлечения троек «субъект — глагол — объект» из выборки из 1000 предложений. Оцените на 50 размеченных вручную тройках. Задокументируйте, где извлечение не срабатывает (часто это пассивные конструкции, сочинения и опущенные подлежащие).
Ключевые термины
| Термин | Как обычно говорят | Что это на самом деле означает |
|---|---|---|
| POS-метка | Тип слова | Грамматическая категория. В PTB 36 меток; в UD — 17. |
| Penn Treebank | Стандартный набор меток | Специфичен для английского языка. Детально различает времена глаголов и число существительных. |
| Universal Dependencies | Многоязычный набор меток | Грубее PTB; нейтрален к языку; стандарт для кросс-языковой работы. |
| Разбор зависимостей | Дерево предложения | У каждого слова одно главное слово, у каждого ребра есть грамматическое отношение. |
| Витерби | Динамическое программирование | Находит последовательность меток с максимальной вероятностью по эмиссиям и переходам. |
Дополнительное чтение
- Jurafsky и Martin — Speech and Language Processing, главы 8 и 18 — каноническое изложение POS-разметки и синтаксического разбора.
- Проект Universal Dependencies — кросс-языковой набор меток и коллекция treebank, используемая каждым многоязычным парсером.
- Руководство spaCy по лингвистическим возможностям — практический справочник по каждому атрибуту
Token. - Chen и Manning (2014). A Fast and Accurate Dependency Parser using Neural Networks — статья, которая вывела нейронные парсеры в мейнстрим.
Источник: POS Tagging and Syntactic Parsing 05.06 — Распознавание именованных сущностей · Фаза 5 — Основы и продвинутые темы NLP · Полный каталог · 05.08 — CNN и RNN для текста