Обработка естественного языка
Bag of Words и TF-IDF
2004 год. Gmail запускает бету с 1 ГБ хранилища - смешные 250 МБ у конкурентов. За месяц - миллион пользователей. И всё это держится на TF-IDF: спам-фильтр, который за 2 миллисекунды решает судьбу каждого письма ещё до тяжёлых нейросетей. Изобретённый в 1970-х алгоритм защищал почту миллиарда людей в 2024 году.
- **Elasticsearch** (BM25 = улучшенный TF-IDF) - движок поиска для Wikipedia, GitHub, Netflix, Stack Overflow
- **Спам-фильтры** Gmail используют TF-IDF + наивный Байес как первый быстрый фильтр перед тяжёлыми нейросетями
- **Рекомендательные системы** Amazon сравнивают TF-IDF описания товаров для раздела "Похожие товары"
Герард Солтон и изобретение TF-IDF
Герард Солтон из Cornell University разработал SMART (System for the Mechanical Analysis and Retrieval of Text) - первую систему автоматического поиска информации. TF-IDF возник как часть этой системы в 1972 году. Солтон хотел ответить на вопрос: как машина может понять, что документ про «квантовую механику» релевантен запросу, а документ про «the» - нет? Ответ оказался математически лаконичным: штрафовать частые слова и поощрять редкие. Спустя 50 лет алгоритм живёт в Elasticsearch, обрабатывающем триллионы запросов в год.
Предварительные знания
Bag of Words
**"Собака укусила человека"** и **"Человек укусил собаку"** - для BoW это ОДИНАКОВЫЕ тексты. Шокирует? Модель **Bag of Words** (мешок слов) - одно из самых простых, но эффективных представлений текста. Идея: забыть про порядок слов и считать только частоту каждого слова.
**Главная потеря BoW - порядок слов.** "Собака укусила человека" (новость) и "Человек укусил собаку" (сенсация) получат идентичные векторы. Для задач, где порядок критичен (перевод, генерация), BoW не подходит. Но для классификации тем - работает прекрасно.
**Bigrams и trigrams** (n-граммы) - простое расширение BoW, которое частично восстанавливает порядок. "not good" как единый токен несёт больше информации, чем "not" + "good" по отдельности. Но размер словаря растёт экспоненциально!
**CountVectorizer** в sklearn автоматически выполняет токенизацию, lowercasing и подсчёт частот. Параметры: `max_features` (ограничить словарь), `min_df`/`max_df` (фильтр по частоте), `stop_words` (удалить стоп-слова), `ngram_range` (n-граммы).
Два документа: "кот ест рыбу" и "рыбу ест кот". Какое утверждение верно для BoW (CountVectorizer)?
TF-IDF
Слово "the" встречается в каждом английском документе. Слово "quantum" - в каждом тысячном. Какое слово полезнее для поиска? **TF-IDF** (Term Frequency - Inverse Document Frequency) решает ключевую проблему BoW: не все слова одинаково важны. Частые повсюду слова получают низкий вес, редкие уникальные - высокий.
**Формула TF-IDF:** `TF-IDF(t, d) = TF(t, d) × IDF(t)` - **TF** измеряет важность слова *внутри* документа (локальный вес) - **IDF** измеряет уникальность слова *по всей коллекции* (глобальный вес) - Результат: высокий вес для слов, которые часты в ЭТОМ документе, но редки в других
| Слово | Встречается в документах | IDF-вес | Интерпретация |
|---|---|---|---|
| "the" | 9999 из 10000 | ~0.0001 | Бесполезно для поиска |
| "machine" | 500 из 10000 | ~3.0 | Средняя информативность |
| "quantum" | 10 из 10000 | ~6.9 | Очень информативно |
| "supercalifragilistic" | 1 из 10000 | ~9.2 | Уникальный маркер документа |
**sklearn TfidfVectorizer** по умолчанию использует сглаженный IDF: `log((1+N)/(1+df)) + 1`. Это предотвращает деление на ноль и даёт чуть другие числа, чем классическая формула. Параметр `smooth_idf=False` отключает сглаживание.
В коллекции из 1000 документов слово "алгоритм" встречается в 10 документах, а слово "и" - в 999. У какого слова TF-IDF вес будет выше (при одинаковой TF)?
Document-Term Matrix
BoW и TF-IDF создают **Document-Term Matrix** - таблицу, где строки = документы, столбцы = термины, а значения = частоты или TF-IDF веса. Эта матрица - основа для поиска похожих документов, кластеризации и классификации текста.
**Cosine Similarity** измеряет угол между двумя векторами: `cos(A, B) = (A · B) / (||A|| × ||B||)`. Значение от 0 (ничего общего) до 1 (идентичны). В отличие от Euclidean distance, cosine similarity не зависит от длины документа - длинный документ и его краткое содержание будут похожи.
**Этот подход используют реальные системы!** Elasticsearch под капотом применяет BM25 - усовершенствованную версию TF-IDF. Google начинал именно с TF-IDF (PageRank добавил анализ ссылок, но текстовый поиск был на TF-IDF).
Размер Document-Term Matrix зависит от словаря. Для 10 000 документов и 50 000 уникальных слов - матрица 10 000 × 50 000 = **500 миллионов ячеек**. Но подавляющее большинство из них - нули. Как хранить это эффективно? Ответ - в следующем concept.
Cosine similarity между двумя документами = 0.92. Что это означает?
Разреженные матрицы
Document-Term Matrix для реального корпуса: 100 000 документов × 200 000 слов = **20 миллиардов** ячеек. При 8 байтах на float64 - это 160 ГБ оперативной памяти. Но каждый документ содержит лишь ~200 уникальных слов из 200 000. Значит, **99.9% матрицы - нули**. Хранить их - безумие.
| Формат | Расшифровка | Быстрая операция | Когда использовать |
|---|---|---|---|
| CSR | Compressed Sparse Row | Строковые срезы, матричное умножение | После построения - для вычислений |
| CSC | Compressed Sparse Column | Столбцовые срезы | Колоночные операции |
| COO | Coordinate | Построение матрицы | При создании - затем конвертировать |
| LIL | List of Lists | Инкрементальное заполнение | Построение по элементам |
**Никогда не вызывайте `.toarray()` на больших sparse-матрицах!** Для корпуса из 100K документов это может съесть всю RAM и убить процесс. Все операции sklearn (fit, predict, cosine_similarity) работают с sparse-матрицами напрямую.
**Правило:** если плотность матрицы < 10%, sparse-формат экономит память. Для NLP-матриц (плотность ~0.1-2%) экономия составляет 50-1000x. Всегда проверяйте: `print(f"Плотность: {matrix.nnz / (matrix.shape[0] * matrix.shape[1]):.2%}")`.
Sparse-матрицы - не только про экономию памяти. Операции (умножение, cosine similarity) на sparse-данных тоже быстрее, потому что нули пропускаются. На матрице 100K × 50K с плотностью 0.1% sparse cosine_similarity работает в **~100 раз быстрее**, чем dense.
BoW и TF-IDF устарели - в эпоху BERT и GPT они бесполезны
BoW/TF-IDF остаются рабочими инструментами для многих задач. Elasticsearch (основа поиска Google, Amazon, Wikipedia) использует BM25 - развитие TF-IDF. Для классификации коротких текстов TF-IDF + SVM часто не уступает BERT, работая в 100-1000 раз быстрее и не требуя GPU.
Трансформеры побеждают в задачах, где важен глубокий контекст: перевод, QA, диалоги. Но для поиска по ключевым словам, фильтрации спама, классификации тем - TF-IDF достаточен и в разы дешевле. На production-системах с миллионами запросов в секунду стоимость inference BERT может быть неприемлемой.
Document-Term Matrix: 50 000 документов × 100 000 слов. Плотность - 0.1%. Сколько памяти нужно в dense vs sparse (float64, 8 байт)?
Ключевые идеи
- **Bag of Words** превращает текст в вектор частот слов, теряя порядок. "Собака укусила человека" = "Человек укусил собаку" - помните этот парадокс? N-граммы частично решают проблему
- **TF-IDF** взвешивает слова: частые повсюду ("the") получают вес ~0, уникальные для документа - высокий вес. Формула: TF × IDF
- **Document-Term Matrix** + **Cosine Similarity** = простой, но быстрый поисковый движок. Google начинался с этого
- **Sparse-матрицы** экономят 100-1000x памяти для NLP-данных, где 99%+ элементов - нули
Связанные темы
BoW и TF-IDF - фундамент для более продвинутых методов:
- Регулярные выражения и текст — Preprocessing и нормализация текста перед vectorization
- Word Embeddings — Следующий уровень: dense-векторы (Word2Vec, GloVe) вместо sparse TF-IDF
Вопросы для размышления
- Почему cosine similarity лучше Euclidean distance для сравнения документов разной длины?
- В каких ситуациях BoW с биграммами будет лучше, чем BoW с юниграммами - и когда хуже?
- Если TF-IDF всё ещё используется в 2026 году (BM25 в Elasticsearch), почему его часто называют "устаревшим"?
Связанные уроки
- nlp-02 — Препроцессинг текста и регулярные выражения предшествуют векторизации
- nlp-04 — Word2Vec и GloVe - следующий уровень после sparse TF-IDF
- ml-01 — TF-IDF векторы - прямой input для классификаторов (SVM, Naive Bayes)
- it-01 — IDF-взвешивание - частный случай информационной значимости токена
- alg-01 — Понимание big-O помогает оценить стоимость sparse операций
- prob-01-intro — TF-IDF + Naive Bayes требует понимания условных вероятностей
- ml-01-intro