Новости и статьи об искусственном интеллекте и нейросетях. Мы собираем и обрабатываем самую актуальную информацию из мира AI. О проекте

Статьи

3 NLTK-приёма для продвинутой предобработки текста

В статье разбираются три продвинутые техники NLTK: использование MWETokenizer для сохранения целостности фраз, лемматизация с учётом частей речи и статистическое извлечение коллокаций. Эти методы повышают точность предобработки за счёт сохранения семантической структуры.

23 июня 2026 г.
5 мин
5
3 NLTK-приёма для продвинутой предобработки и лингвистического анализа

Введение

Обработка естественного языка (NLP) в последние годы пережила явный сдвиг парадигмы: большие языковые модели (LLM) и трансформеры теперь справляются со сложными задачами понимания текста целиком. Однако в любом реальном NLP-процессе сырой текст всё ещё нужно токенизировать, нормализовывать и анализировать, прежде чем он поступит на вход модели. Хотя современные NLP-библиотеки и экосистемы вроде SpaCy или Hugging Face отлично подходят для построения универсальных пайплайнов глубокого обучения или интеграции с LLM, Natural Language Toolkit (NLTK) остаётся надёжным и прозрачным инструментом для тонкого структурного лингвистического анализа, кастомной нормализации текста и статистического корпусного анализа.

К сожалению, многие разработчики ошибочно считают, что LLM делают традиционную предобработку текста ненужной, или пишут код предобработки наивными методами, которые игнорируют критически важные лингвистические структуры. Они разбивают многословные выражения, такие как «machine learning», на отдельные ничего не значащие слова; выполняют контекстно-слепую лемматизацию, которая выдаёт неточные базовые формы; или полагаются на простой подсчёт частот, упуская значимые ассоциации слов.

Чтобы строить надёжные, семантически точные NLP-модели, необходимо сохранять структурный и лингвистический контекст на этапе предобработки. В этой статье мы расскажем о трёх важных приёмах NLTK, которые помогут улучшить предобработку текста:

  1. сохранение целостности фраз с помощью MWETokenizer;
  2. контекстно-зависимая лемматизация с сопоставлением частей речи (POS-тегированием);
  3. статистическое извлечение коллокаций с использованием мер ассоциации.

1. Сохранение доменной терминологии с помощью MWETokenizer

Токенизация — основа любого NLP-пайплайна. Однако стандартные токенизаторы разбивают предложения строго по пробелам и знакам препинания. Это становится проблемой, когда в тексте встречаются специфичные для предметной области многословные выражения — например, "neural network", "decision tree" или "San Francisco" — где отдельные слова объединяются в одно смысловое понятие.

Если токенизатор разделит "neural network" на "neural" и "network", последующий векторизатор (такой как Bag-of-Words или TF-IDF) будет рассматривать их как несвязанные признаки, ослабляя сигнал и добавляя шум. Разработчики часто пытаются исправить это, написав регулярные выражения для замены в сыром тексте до токенизации.

Использование замен на уровне символов (например, text.replace("neural network", "neural_network")) ненадёжно: оно не учитывает границы слов, плохо обрабатывает пунктуацию и очень медленно работает на больших наборах данных. Оптимальный подход — сначала токенизировать текст, а затем применить встроенный в NLTK MWETokenizer для аккуратного слияния токенов.

Наивный подход с регулярными выражениями основан на манипуляции строками на уровне символов, что плохо масштабируется и может случайно изменить подстроки в несвязанных словах:

import re
import time

# Sample corpus
raw_texts = [
    "We are studying neural networks and deep learning.",
    "The decision tree is a popular model in machine learning.",
    "A neural network can have many layers."
] * 5000

cleaned_texts = []
for text in raw_texts:
    # Manual string replacements for domain terms
    text = re.sub(r"\bneural networks?\b", "neural_network", text, flags=re.IGNORECASE)
    text = re.sub(r"\bdecision trees?\b", "decision_tree", text, flags=re.IGNORECASE)
    text = re.sub(r"\bmachine learnings?\b", "machine_learning", text, flags=re.IGNORECASE)
    # Tokenize the processed string
    tokens = text.lower().split()
    cleaned_texts.append(tokens)

print("Sample tokens:", cleaned_texts[0])

Результат:

Sample tokens: ['we', 'are', 'studying', 'neural_network', 'and', 'deep', 'learning.']

Теперь посмотрим на оптимизированный вариант. Сначала токенизируем текст с помощью word_tokenize, а затем передаём поток токенов через инициализированный MWETokenizer, который эффективно объединяет токены на границах:

import nltk
from nltk.tokenize import word_tokenize, MWETokenizer
import time

# Ensure NLTK resources are downloaded
nltk.download('punkt', quiet=True)

raw_texts = [
    "We are studying neural networks and deep learning.",
    "The decision tree is a popular model in machine learning.",
    "A neural network can have many layers."
] * 5000

# Initialize tokenizer and register MWE tuples
mwe_tokenizer = MWETokenizer([
    ('neural', 'network'),
    ('neural', 'networks'),
    ('decision', 'tree'),
    ('decision', 'trees'),
    ('machine', 'learning')
], separator='_')

cleaned_texts_mwe = []
for text in raw_texts:
    # Tokenize words using NLTK's standard tokenizer
    tokens = word_tokenize(text.lower())
    # Merge specified multi-word expressions
    merged_tokens = mwe_tokenizer.tokenize(tokens)
    cleaned_texts_mwe.append(merged_tokens)

print("Sample tokens:", cleaned_texts_mwe[0])

Результат тот же, но получен более элегантным и лингвистически точным — и масштабируемым — способом:

Sample tokens: ['we', 'are', 'studying', 'neural_network', 'and', 'deep', 'learning.']

Использование MWETokenizer переносит операцию с медленного сопоставления на уровне символов на сравнение токенов.

  • Мы определяем многословные выражения как кортежи независимых токенов: ('neural', 'network').
  • Указав separator='_', токенизатор объединяет совпадающую последовательность в одну строку: "neural_network".
  • Поскольку он работает непосредственно с массивами токенов, он не зависит от ошибок границ и корректно обрабатывает знаки препинания (например, "neural networks." сначала разбивается на "neural", "networks", ".", а затем безопасно объединяется в "neural_networks", "."). Это работает быстрее и легко масштабируется на сотни доменных терминов.

2. Контекстно-зависимая лемматизация с POS-тегированием

Лемматизация — это процесс приведения слова к его словарной форме (лемме) — например, «running» → «run», «better» → «good». Это важный этап нормализации, так как он объединяет различные грамматические формы одного слова.

Однако WordNetLemmatizer из NLTK по умолчанию считает каждое слово существительным. Если передать глаголы или прилагательные без указания их части речи, лемматизатор вернёт слово без изменений. Например:

  • lemmatizer.lemmatize("running") даст "running" (вместо «run»);
  • lemmatizer.lemmatize("better") даст "better" (вместо «good»).

Чтобы решить эту проблему, необходимо динамически определять грамматическую роль каждого слова в предложении с помощью POS-теггера NLTK, сопоставлять эти теги с упрощёнными категориями WordNet (существительное, глагол, прилагательное, наречие) и передавать их лемматизатору.

Наивный подход подаёт слова напрямую лемматизатору. Он пропускает преобразования глаголов и прилагательных, что приводит к неоптимальной нормализации словаря:

import nltk
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize

nltk.download('punkt', quiet=True)
nltk.download('wordnet', quiet=True)

sentence = "The feet of the running runners are getting better and faster."
tokens = word_tokenize(sentence.lower())
lemmatizer = WordNetLemmatizer()

# Naive lemmatization: assumed to be all nouns
naive_lemmas = [lemmatizer.lemmatize(token) for token in tokens]

print("Tokens: ", tokens)
print("Naive Lemmas:", naive_lemmas)

Результат:

Tokens: ['the', 'feet', 'of', 'the', 'running', 'runners', 'are', 'getting', 'better', 'and', 'faster', '.']
Naive Lemmas: ['the', 'foot', 'of', 'the', 'running', 'runner', 'are', 'getting', 'better', 'and', 'faster', '.']

Рассмотрим оптимизированную версию. Мы напишем вспомогательную функцию для сопоставления тегов Penn Treebank (возвращаемых функцией pos_tag из NLTK) с константами WordNet POS, чтобы каждое слово лемматизировалось точно:

import nltk
from nltk.stem import WordNetLemmatizer
from nltk.tokenize import word_tokenize
from nltk.corpus import wordnet

# Download POS tagger resources
nltk.download('punkt', quiet=True)
nltk.download('wordnet', quiet=True)
nltk.download('averaged_perceptron_tagger', quiet=True)

sentence = "The feet of the running runners are getting better and faster."
tokens = word_tokenize(sentence.lower())

# Generate POS tags for each token
pos_tags = nltk.pos_tag(tokens)

# Map Penn Treebank tags to WordNet tags
def get_wordnet_pos(treebank_tag):
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        # Default to WordNet's default noun handling
        return None

lemmatizer = WordNetLemmatizer()

# Lemmatize utilizing mapped POS tags
context_lemmas = []
for token, tag in pos_tags:
    wn_tag = get_wordnet_pos(tag)
    if wn_tag:
        lemma = lemmatizer.lemmatize(token, pos=wn_tag)
    else:
        lemma = lemmatizer.lemmatize(token)
    context_lemmas.append(lemma)

print("POS Tagged: ", pos_tags)
print("Context Lemmas:", context_lemmas)

Результат:

POS Tagged: [('the', 'DT'), ('feet', 'NNS'), ('of', 'IN'), ('the', 'DT'), ('running', 'NN'), ('runners', 'NNS'), ('are', 'VBP'), ('getting', 'VBG'), ('better', 'RBR'), ('and', 'CC'), ('faster', 'RBR'), ('.', '.')]
Context Lemmas: ['the', 'foot', 'of', 'the', 'running', 'runner', 'be', 'get', 'well', 'and', 'faster', '.']

Функция pos_tag из NLTK помечает слова с помощью системы тегов Penn Treebank (например, 'VBG' для герундия, 'JJR' для сравнительной степени прилагательного).

  • Наша вспомогательная функция get_wordnet_pos() анализирует первый символ тега. В соответствии со стандартами WordNet POS, если он начинается с 'J', мы сопоставляем его с тегом прилагательного (wordnet.ADJ); если с 'V' — с глаголом (wordnet.VERB) и так далее.
  • Передавая правильный POS-тег в lemmatizer.lemmatize(token, pos=wn_tag), лемматизатор успешно преобразует «running» в «run», «are» в «be», «getting» в «get», «better» в «good» и «faster» в «fast». Это сохраняет семантическое ядро предложения, значительно снижая разреженность словаря для последующих моделей машинного обучения.

3. Статистическое извлечение фраз с помощью поиска коллокаций

Извлечение ключевых фраз или многословных концептов из текста ценно для тематического моделирования, поискового индексирования и анализа тональности. Такие фразы называются коллокациями — это последовательности слов, которые встречаются вместе чаще, чем можно было бы ожидать случайно.

Наивный способ поиска коллокаций — подсчитать все сырые биграммы (последовательности из двух слов) и отсортировать их по частоте. Однако такой подход даёт малоинформативные пары. Из-за естественного распределения частот такие комбинации, как «of the», «in the» и «on a», всегда будут доминировать в верхних результатах. Даже после фильтрации стоп-слов сырой подсчёт может отдавать предпочтение случайным совпадениям, которые повторяются всего несколько раз.

Оптимальное решение — использовать BigramCollocationFinder из NLTK в сочетании со статистическими метриками ассоциации. Вместо сырой частоты мы применяем такие меры, как взаимная информация (PMI) или статистика хи-квадрат. Эти метрики оценивают, действительно ли два слова появляются вместе значимо чаще, чем при чистой случайности.

Сначала наш наивный подход просто подсчитывает сырые биграммы и выбирает верхние совпадения, захватывая шум и общеупотребительные служебные слова:

from collections import Counter
import nltk
from nltk.tokenize import word_tokenize
from nltk.util import bigrams

# Sample corpus
corpus = """
Natural language processing is an active field of AI.
Machine learning plays a key role in natural language processing.
Deep learning architectures have revolutionized natural language processing.
We need machine learning models to solve these natural language tasks.
"""

tokens = word_tokenize(corpus.lower())

# Extract and count raw bigrams
raw_bigrams = list(bigrams(tokens))
bigram_counts = Counter(raw_bigrams)

print("Top 5 Raw Bigrams:")
for bigram, freq in bigram_counts.most_common(5):
    print(f"{bigram}: {freq}")

Результат:

Top 5 Raw Bigrams:
('natural', 'language'): 4
('language', 'processing'): 3
('machine', 'learning'): 2
('processing', '.'): 2
('processing', 'is'): 1

Теперь инициализируем поиск коллокаций из NLTK, применим фильтры и используем класс BigramAssocMeasures для оценки ассоциаций фраз с помощью взаимной информации (PMI):

import nltk
from nltk.collocations import BigramCollocationFinder
from nltk.metrics.association import BigramAssocMeasures
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

nltk.download('punkt', quiet=True)
nltk.download('stopwords', quiet=True)

corpus = """
Natural language processing is an active field of AI.
Machine learning plays a key role in natural language processing.
Deep learning architectures have revolutionized natural language processing.
We need machine learning models to solve these natural language tasks.
"""

tokens = word_tokenize(corpus.lower())

# Initialize the collocation finder
finder = BigramCollocationFinder.from_words(tokens)

# Filter out punctuation and stop words
stop_words = set(stopwords.words('english'))
filter_stops = lambda w: w in stop_words or not w.isalnum()
finder.apply_word_filter(filter_stops)

# Filter out bigrams that occur less than N times
finder.apply_freq_filter(2)

# Score bigrams using pointwise mutual information
pmi_measures = BigramAssocMeasures()
top_collocations = finder.score_ngrams(pmi_measures.pmi)

print("Top Collocations by PMI:")
for bigram, pmi_score in top_collocations[:5]:
    # Formulate a clean print representation
    phrase = " ".join(bigram)
    print(f"Phrase: {phrase:<30} | PMI Score: {pmi_score:.4f}")

Результат:

Top Collocations by PMI:
Phrase: machine learning                   | PMI Score: 3.8074
Phrase: language processing                | PMI Score: 3.3923
Phrase: natural language                   | PMI Score: 3.3923
  • BigramCollocationFinder.from_words() извлекает все двухсловные группы, сохраняя структурные позиции.
  • Мы очищаем кандидатов с помощью finder.apply_word_filter(), который динамически исключает биграммы, содержащие стоп-слова или знаки препинания, не изменяя контекст исходного расположения слов.
  • Установив apply_freq_filter(2), мы игнорируем случайные комбинации, которые встречаются лишь однажды, уменьшая статистический шум.
  • Наконец, оценка с помощью взаимной информации математически измеряет вероятность совместного появления двух слов, делённую на вероятность их независимого появления. Это выделяет сильно связанные термины, такие как «machine learning» и «natural language», игнорируя распространённые слабые сочетания.

Заключение

Кастомная предобработка текста — ключ к извлечению более чистого сигнала из сырых текстов, и NLTK предоставляет структурные инструменты, необходимые для настройки этих операций.

Используя эти три техники NLTK, можно строить значительно более надёжные NLP-пайплайны:

  • Сохранение доменной терминологии с помощью MWETokenizer объединяет составные слова на уровне токенов, предотвращая разрушение ключевых концепций при векторизации.
  • Контекстно-зависимая лемматизация связывает генерацию POS-тегов с отображением WordNet для получения лингвистически точных базовых форм, существенно снижая размерность словаря.
  • Статистическое извлечение коллокаций использует математические метрики ассоциации вроде PMI для выделения истинных семантических фраз из корпусных данных, минуя шум простых частотных подсчётов.

Применение этих структурных шаблонов на этапе инжиниринга признаков гарантирует, что последующие алгоритмы классификации, поиска и кластеризации получают высококачественные, семантически целостные токены.

Горячее

Загружаем популярные статьи...

3 NLTK-приёма для продвинутой предобработки текста