Машинное обучение

Предобработка текста для NLP

Компьютер видит текст как последовательность байтов. Он понятия не имеет, что значит love или hate - для него это просто числа 6C 6F 76 65 и 68 61 74 65. Чтобы ML-модель могла анализировать текст, его нужно превратить из символов в осмысленные числа. И то, КАК именно вы это сделаете, определяет 80 процентов качества вашей модели. Плохая предобработка убьёт даже лучшую нейросеть. Хорошая - позволит простому линейному классификатору обогнать сложные модели.

  • **Спам-фильтры Gmail** обрабатывают миллиарды писем ежедневно - токенизация, удаление стоп-слов и TF-IDF лежат в основе первой линии фильтрации, отсеивая 99.9% спама ещё до применения нейросетей
  • **Поисковые системы** (Google, Yandex) используют TF-IDF и его вариации для ранжирования документов по релевантности запросу - BM25 (модифицированный TF-IDF) остаётся ключевым компонентом поиска даже в эпоху Transformer-моделей
  • **Медицинская классификация** врачебных записей: TF-IDF + SVM классифицирует диагнозы по текстам анамнезов с точностью 92-95%, работая в реальном времени без GPU, что критично для больничных систем с ограниченной инфраструктурой

Предварительные знания

  • Generative Adversarial Networks (GAN)

От правил стемминга к выученным подсловам

Задолго до нейросетевого NLP исследователи уже бились над тем, как превратить грязный текст в чистые токены. В 1980 году Мартин Портер опубликовал свой алгоритм стемминга - компактный набор правил отсечения суффиксов, который сводил слова вроде "running" и "runs" к общему корню, и на десятилетия стал стандартным стеммером. Рядом с ним токенизация и нормализация выросли в типовой конвейер предобработки. Современный поворот случился в 2016 году, когда Рико Зеннрих с коллегами адаптировали Byte-Pair Encoding - старую схему сжатия данных Филипа Гейджа - для разбиения слов на частые подсловные единицы. Вместо рукописных правил BPE выучивает свой словарь из данных и сегодня лежит в основе токенизаторов больших языковых моделей.

Токенизация: разбиение текста на атомы

Компьютер не понимает слова - он работает с числами. Первый шаг любой NLP-задачи: **разбить текст на отдельные единицы (токены)**. Это называется токенизация. Казалось бы, просто разрежь по пробелам - но реальный текст сложнее. Как токенизировать "don't"? Это одно слово или два: "do" + "n't"? А "Нью-Йорк" - одна сущность или две? А URL, email, эмодзи? Разные стратегии токенизации дают разные результаты, и этот выбор критически влияет на качество модели.

**BPE (Byte Pair Encoding) - стандарт индустрии:** Алгоритм начинает с символов и итеративно объединяет самые частые пары: 1. Начало: ["l", "o", "w", "e", "r"] - каждый символ отдельно 2. Самая частая пара "l"+"o" -> объединяем в "lo" 3. Самая частая пара "lo"+"w" -> объединяем в "low" 4. Продолжаем пока не достигнем нужного размера словаря Результат: частые слова остаются целыми ("the", "is"), редкие разбиваются на подслова ("tokenization" -> "token" + "ization"). GPT-4 использует ~100K BPE токенов.

**Tokenization - это необратимое решение!** Ошибка на этом этапе каскадно разрушает весь pipeline. Примеры: - Текст с опечатками: "helllo" -> word-level не найдёт в словаре, subword разобьёт на "hell" + "##lo" (потеря смысла) - Код и формулы: "x^2 + y^2 = z^2" - большинство токенизаторов разрушат структуру - Мультиязычный текст: "Привет world" - нужен токенизатор с поддержкой обоих языков Всегда проверяйте результат токенизации на реальных примерах ваших данных!

Какой уровень выбрать? Для **классического ML** (SVM, Naive Bayes, Logistic Regression) обычно достаточно word-level токенизации с NLTK или spaCy. Для **нейросетевых моделей** (BERT, GPT) используется subword-level - BPE или WordPiece. Character-level применяется в специфических задачах: определение языка, работа с опечатками, генерация текста посимвольно. В этом уроке мы сосредоточимся на word-level, потому что именно он используется в классическом NLP pipeline вместе с TF-IDF и Bag of Words.

Почему subword-токенизация (BPE) стала стандартом в современных моделях вроде BERT и GPT?

Стемминг, лемматизация и нормализация

После токенизации у нас есть список слов, но одно и то же понятие может выглядеть по-разному: "бегу", "бежал", "бежать", "бег" - это одна идея, но четыре разных токена. Если модель считает их разными словами, она теряет связь между ними. **Нормализация** приводит слова к единой форме, уменьшая словарь и помогая модели находить закономерности. Два основных подхода: грубый, но быстрый стемминг и точная, но медленная лемматизация.

**Стемминг - алгоритм Портера (1980):** Набор из ~60 правил, применяемых последовательно: - Шаг 1: Множественное число: "cats" -> "cat", "ponies" -> "poni" - Шаг 2: Суффиксы: "ational" -> "ate", "fulness" -> "ful" - Шаг 3: Ещё суффиксы: "icate" -> "ic", "alize" -> "al" - ... Преимущества: работает за O(n), не требует словарей, занимает 0 памяти. Недостатки: иногда объединяет разные слова ("organization" и "organ" -> "organ"), иногда не объединяет одинаковые ("ran" и "running").

Помимо стемминга и лемматизации, полный pipeline нормализации включает ещё несколько шагов. **Приведение к нижнему регистру**: "The" и "the" должны быть одним токеном (но осторожно: "US" и "us" - разные вещи). **Удаление стоп-слов**: артикли, предлоги, местоимения ("the", "is", "at", "which") встречаются в каждом тексте и не несут различительной информации. **Удаление пунктуации и спецсимволов**: точки, запятые, скобки обычно не нужны для классификации.

**Не всегда нужно удалять стоп-слова!** Стоп-слова важны в задачах, где грамматика несёт смысл: - Анализ тональности: "not good" без "not" становится "good" - смысл инвертирован - Авторский стиль: частота "the", "and", "but" - уникальный отпечаток автора - Вопросно-ответные системы: "who", "what", "where" - ключевые для понимания вопроса Правило: для **классификации по теме** (спам, новости) - удаляйте стоп-слова. Для **анализа смысла и стиля** - оставляйте.

Для какой задачи удаление стоп-слов может НАВРЕДИТЬ качеству модели?

TF-IDF: взвешивание важности слов

После токенизации и нормализации нужно превратить слова в числа. Простейший способ - посчитать, сколько раз каждое слово встречается в документе. Но частое слово - не всегда важное. Слово "данные" встречается в каждой статье по ML - оно частое, но бесполезное для различения статей. А слово "градиент" встречается только в статьях об оптимизации - оно редкое и информативное. **TF-IDF** решает именно эту задачу: повышает вес слов, которые характерны для конкретного документа, и понижает вес слов, встречающихся везде.

**Почему TF-IDF работает - информационная интуиция:** IDF по сути измеряет **информативность** слова. Слово, встречающееся в 90% документов, несёт мало информации о конкретном документе - это как сказать "небо голубое". Слово, встречающееся в 1% документов, - это уникальная характеристика. Математически IDF связан с понятием **самоинформации** из теории информации Шеннона: I(event) = -log(P(event)). Чем реже событие - тем больше информации оно несёт. IDF(t) = log(N/DF(t)) - это именно самоинформация появления слова.

СловоDoc 0Doc 1Doc 2Doc 3Интерпретация
обучение0.150.300.000.00Часто в корпусе -> IDF низкий
данные0.180.000.200.00Встречается в 2 из 4 -> средний IDF
нейросети0.000.000.350.40Только в 2 документах -> IDF выше
градиентный0.000.000.000.55Только в 1 документе -> IDF максимальный
предсказаний0.500.000.000.00Уникально для Doc 0 -> TF-IDF высокий

На практике `TfidfVectorizer` из sklearn делает всё за один вызов: токенизация, подсчёт TF, вычисление IDF, нормализация. Результат - **разреженная матрица**, где каждая строка - документ, каждый столбец - слово из словаря. Эту матрицу можно сразу подать в любой классификатор: SVM, Logistic Regression, Naive Bayes. TF-IDF + SVM с линейным kernel - классическая комбинация для текстовой классификации, которая работает удивительно хорошо даже по сравнению с нейросетями на небольших датасетах.

  • `max_df=0.9` - игнорировать слова, встречающиеся в >90% документов (по сути, стоп-слова)
  • `min_df=2` - игнорировать слова, встречающиеся менее чем в 2 документах (опечатки, мусор)

Bag of Words и его ограничения

**Bag of Words (BoW)** - самое базовое представление текста в виде чисел. Идея предельно проста: создаём словарь всех уникальных слов корпуса, а каждый документ представляем как вектор, где каждая позиция - количество вхождений соответствующего слова. Название "мешок слов" точно отражает суть: мы буквально высыпаем все слова документа в мешок, теряя информацию о порядке и структуре. "Собака укусила человека" и "Человек укусил собаку" получат **одинаковый** BoW-вектор.

**BoW vs TF-IDF - в чём разница?** Оба создают вектор из чисел для каждого документа. Но: - **BoW** считает абсолютные вхождения: "кот" встречается 3 раза -> значение 3 - **TF-IDF** взвешивает по информативности: "кот" встречается 3 раза, но есть в 90% документов -> значение 0.05 TF-IDF - это улучшенный BoW, где обычные подсчёты заменены на взвешенные значения. Оба подхода **теряют порядок слов**. Оба создают **разреженные векторы** (большинство значений = 0).

Главное ограничение BoW - **потеря порядка слов**. Фразы "не плохой фильм" и "плохой, не фильм" неразличимы. Частичное решение - **n-граммы**: вместо отдельных слов учитываем пары (биграммы) или тройки (триграммы). Биграмма "не_плохой" сохраняет контекст отрицания. Но n-граммы взрывают размер словаря: 10000 слов дают 100 миллионов возможных биграмм. На практике используют `ngram_range=(1,2)` с ограничением `max_features`.

КорпусДокументовРазмер словаряСредняя длинаЗаполненность
Отзывы на фильмы50 00074 000 слов230 слов0.3% (99.7% нулей)
Новости 20 Newsgroups18 000130 000 слов150 слов0.1%
Wikipedia (1M статей)1 000 000500 000+ слов800 слов0.01%
Twitter (короткие тексты)100 00040 000 слов15 слов0.04%

**Разреженность - не баг, а фича:** BoW-вектор документа из 200 слов в словаре из 100K будет иметь 99.8% нулей. Это кажется расточительным, но: - scipy.sparse хранит только ненулевые элементы - матрица 50K x 100K занимает не 40 GB, а ~50 MB - LinearSVC и Logistic Regression эффективно работают с разреженными матрицами - Разреженность позволяет масштабироваться на миллионы документов Но если нужно сравнивать семантику ("авто" и "машина" - синонимы), BoW бессилен. Для этого нужны word embeddings - следующий урок.

Несмотря на простоту, BoW + TF-IDF остаётся сильным baseline. На многих задачах классификации текстов (спам, рубрикация, анализ тональности) комбинация TF-IDF + линейный SVM даёт accuracy 90-95% при обучении за секунды. Нейросетевые модели (BERT, GPT) могут быть точнее на 2-5%, но обучаются часами на GPU. Для задач, где скорость и простота важнее последних процентов accuracy, BoW-подход - рациональный выбор. Именно так текст, который для компьютера был просто набором байтов, превращается в числа, пригодные для машинного обучения.

Итоги

  • **Токенизация** - разбиение текста на единицы: word-level для классического ML, subword (BPE) для нейросетей; выбор уровня токенизации определяет баланс между размером словаря и покрытием новых слов
  • **Нормализация** - приведение слов к единой форме: стемминг грубо отрезает суффиксы (быстро), лемматизация находит словарную форму (точно); удаление стоп-слов полезно для классификации по теме, но может навредить анализу тональности
  • **TF-IDF** - взвешивание слов по информативности: высокий вес получают слова, частые в конкретном документе, но редкие в корпусе; математически связан с самоинформацией Шеннона
  • **Bag of Words** - представление текста как вектора подсчётов слов; теряет порядок, создаёт разреженные матрицы, но в связке с TF-IDF и SVM остаётся сильным baseline - ведь то, как мы превращаем байты в числа, определяет 80% качества модели

Связанные темы

Предобработка текста - фундамент NLP pipeline, связывающий сырые данные с моделями машинного обучения:

  • Word Embeddings — Следующий шаг после BoW: вместо разреженных векторов подсчётов - плотные вектора, где семантически близкие слова имеют похожие представления. Word2Vec, GloVe решают проблему BoW с синонимами и контекстом
  • Naive Bayes — Классический партнёр BoW для текстовой классификации: Naive Bayes предполагает независимость слов (что совпадает с допущением BoW о потере порядка) и работает быстрее SVM при сопоставимом качестве на коротких текстах
  • SVM — Линейный SVM с TF-IDF - золотой стандарт текстовой классификации: высокая размерность TF-IDF векторов идеально подходит для SVM, который эффективен именно при большом числе признаков и малом числе примеров
  • BERT и GPT — Transformer-модели используют subword-токенизацию (BPE/WordPiece) из этого урока, но заменяют BoW/TF-IDF на контекстные эмбеддинги, где значение слова зависит от окружения

Вопросы для размышления

  • Почему subword-токенизация (BPE) стала стандартом для нейросетей, но для классического ML (TF-IDF + SVM) по-прежнему используется word-level? Как связаны свойства модели и требования к токенизации?
  • TF-IDF присваивает нулевой вес словам, которые встречаются во всех документах. Но слово "не" встречается почти везде, при этом критически важно для смысла. Как бы вы модифицировали pipeline для задачи анализа тональности?
  • BoW теряет порядок слов, но при этом даёт 90-95% accuracy на задачах классификации. Почему порядок слов оказывается менее важным, чем набор слов, для определения темы документа?

Связанные уроки

  • ml-33-gan — Продолжает последовательность уроков deep learning
  • ml-35-word-embeddings — Чистые токены идут в эмбеддинги
  • ml-15-naive-bayes — Bag-of-words кормит Naive Bayes
  • ml-37-bert-gpt — Токенизация предшествует трансформерам
  • aie-04-tokens-context-window — Токенизация LLM повторяет эти шаги предобработки
  • nlp-01
Предобработка текста для NLP

0

1

Войти

  • `ngram_range=(1,2)` - кроме отдельных слов, учитывать пары слов ("machine learning", "не нравится")
  • `max_features=10000` - ограничить словарь 10K самых информативных слов
  • `sublinear_tf=True` - использовать log(1+TF) вместо TF, подавляя влияние слишком частых слов
  • Слово "нейросеть" встречается 5 раз в статье A и 5 раз в статье B. Но TF-IDF этого слова в статье A = 0.45, а в статье B = 0.12. Какова наиболее вероятная причина?

    Bag of Words полностью устарел, нейросети решают все задачи NLP лучше

    BoW + TF-IDF + линейный SVM остаётся конкурентоспособным для задач классификации текстов, работая в 100 раз быстрее нейросетей при сопоставимом качестве на датасетах до 100K документов

    На задаче классификации 20 Newsgroups: TF-IDF + SVM даёт ~94% accuracy за 2 секунды обучения на CPU. Fine-tuned BERT даёт ~96% accuracy, но требует 30 минут обучения на GPU и в 1000 раз больше памяти. Для продакшн-систем с жёсткими требованиями к latency и стоимости инфраструктуры классические методы часто являются оптимальным выбором. Нейросети выигрывают на задачах, требующих понимания контекста и семантики (перевод, генерация, вопросно-ответные системы), но для простой классификации разница минимальна.

    Два предложения: "Собака укусила человека" и "Человек укусил собаку". Что верно при их представлении через Bag of Words?