Bag of Words и TF–IDF

Формулы, интерпретация весов.

В предыдущих главах мы говорили о тексте как о данных и о том, что компьютер не умеет читать слова "как человек". Для него текст – это набор символов, чисел и статистик. В этой главе мы разберём два базовых, но до сих пор крайне полезных подхода к представлению текста в виде чисел: Bag of Words и TF–IDF.

Они просты, почти наивны, но именно поэтому идеально подходят для понимания того, как из слов появляется математика. Более того, многие современные идеи в NLP логически вырастают именно из них.

Идея Bag of Words

BOW - Bag of Words (мешок слов) – это способ представить текст без учёта порядка слов. Нас интересует только то, какие слова встретились и сколько раз.

Представим два предложения:

  • "Кот ест рыбу"

  • "Рыбу ест кот"

Для человека они почти одинаковы. Для Bag of Words – абсолютно одинаковы.

Мы как бы высыпаем слова из текста в мешок, перемешиваем их и считаем количество каждого слова.

Формирование словаря

Первый шаг – построить словарь. Это просто список всех уникальных слов во всех документах.

Пусть у нас есть три документа:

  • D1: "кот ест рыбу"

  • D2: "кот любит рыбу"

  • D3: "собака ест мясо"

Словарь будет таким:

Каждому слову мы сопоставляем индекс.

Вектор Bag of Words

Теперь каждый документ превращается в вектор длины V|V|, где V|V| – размер словаря.

D1: "кот ест рыбу":

D2: "кот любит рыбу"

D3: "собака ест мясо"

Каждое число – это количество вхождений слова.

19.1 BOW - Мешок слов

Немного математики

Формально Bag of Words можно записать так.

Пусть:

  • V=w1,w2,,wnV = {w₁, w₂, …, wₙ} – словарь

  • dd – документ

Тогда вектор документа:

x(d)=(c1,c2,,cn)x(d) = (c₁, c₂, …, cₙ)

где cicᵢ – количество вхождений слова wiwᵢ в документ dd.

Это обычный вектор в Rn\mathbb{R}^n (для чистого Bag of Words – в Nn\mathbb{N}^n).

И уже на этом этапе мы можем:

  • сравнивать документы

  • обучать классификаторы

  • искать похожие тексты

Но есть одна проблема.

Проблема частот

Рассмотрим слово "кот" и слово "и".

Слово "и" будет встречаться почти в каждом документе. Его частота большая, но смысловая ценность почти нулевая.

Bag of Words не различает:

  • важные слова

  • служебные слова

  • редкие, но информативные термины

Все слова равны. А это плохо.

И тут появляется TF–IDF.

TF–IDF: интуиция

TF–IDF расшифровывается как: Term Frequency – Inverse Document Frequency

Идея очень простая:

  • слово важно, если оно часто встречается в документе

  • но оно теряет ценность, если встречается почти во всех документах

TF – "насколько часто слово встречается в данном документе"

IDF – "насколько слово редкое в корпусе"

Итоговый вес – их произведение.

Term Frequency (TF)

Самый простой вариант TF:

TF(w,d)=count(w,d)\mathrm{TF}(w, d) = count(w, d)

Но часто используют нормализацию:

TF(w,d)=count(w,d)d\mathrm{TF}(w, d) = \frac{count(w, d)}{|d|}

где d|d| – общее количество слов в документе.

Интерпретация:

  • 0 → слова нет

  • чем больше значение, тем важнее слово для конкретного документа

Inverse Document Frequency (IDF)

IDF показывает, насколько слово редкое.

Формула:

IDF(w)=log(Ndf(w))\mathrm{IDF}(w) = \log\left(\frac{N}{df(w)}\right)

где:

  • где loglog — натуральный логарифм (его же и используем далее)

  • NN – общее число документов

  • df(w)df(w) – количество документов, где встречается слово w

Иногда добавляют сглаживание:

IDF(w)=log(N+1df(w)+1)+1\mathrm{IDF}(w) = \log\left(\frac{N + 1}{df(w) + 1}\right) + 1

Интерпретация:

  • редкое слово → высокий IDF

  • частое слово → IDF мал (без сглаживания – близок к 0, со сглаживанием – близок к 1)

Итоговая формула TF–IDF

Вес слова w в документе d:

TF-IDF(w,d)=TF(w,d)×IDF(w)\mathrm{TF\text{-}IDF}(w, d) = \mathrm{TF}(w, d) \times \mathrm{IDF}(w)

Таким образом:

  • слово часто в документе → вес растёт

  • слово часто во всех документах → вес падает

19.2 Тепловая карта, отображающая значения TF-IDF

Пример расчёта

Пусть у нас 3 документа, и слово "кот" встречается в двух из них.

N=3df(кот)=2N = 3 \\\\ df(кот) = 2

Итого

IDF(кот)=log(32)0.176\mathrm{IDF}(\text{кот}) = \log\left(\frac{3}{2}\right) \approx 0.176

А слово "собака" встречается только в одном документе:

df(собака)=1df(\text{собака}) = 1

Поэтому

IDF(собака)=log(31)1.099\mathrm{IDF}(\text{собака}) = \log\left(\frac{3}{1}\right) \approx 1.099

Даже если в документе они встречаются по одному разу, "собака" будет весить значительно больше.

Вектор TF–IDF

Как и Bag of Words, TF–IDF – это вектор.

Отличие только в том, что вместо целых чисел мы получаем вещественные веса.

x(d)=(tfidf1,tfidf2,,tfidfn)x(d) = (\mathrm{tfidf}_1, \mathrm{tfidf}_2, \dots, \mathrm{tfidf}_n)

Этот вектор:

  • обычно хранится в разреженном виде

  • высокоразмерный

  • хорошо отражает смысл документа на базовом уровне

Сравнение документов

TF–IDF часто используют вместе с cosine similarity.

Почему? Потому что:

  • длины документов разные

  • важна не сумма весов, а направление вектора

Cosine similarity измеряет угол между векторами, а не расстояние между точками.

19.3 Косинусное сходство документов

Ограничения Bag of Words и TF–IDF

Важно понимать границы этих моделей.

Они:

  • не учитывают порядок слов

  • не понимают контекст

  • не различают омонимы

  • не знают семантики

"river bank" и "bank of money" для них – почти одно и то же.

Но при этом они:

  • быстрые

  • интерпретируемые

  • отлично работают на малых данных

  • являются хорошей базовой линией

Почему это всё ещё важно

Bag of Words и TF–IDF — это фундамент.

Если вы понимаете:

  • откуда берётся вектор

  • почему вес слова именно такой

  • как редкость влияет на значение

то эмбеддинги, attention и трансформеры перестают быть такими непонятными. Они просто делают то же самое, но сложнее и умнее.

Именно поэтому мы начали с мешка слов.

Простой пример TF–IDF на PHP (без библиотек)

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

Пусть у нас есть три документа:

Шаг 1. Токенизация

Шаг 2. Словарь

Шаг 3. Term Frequency (TF)

Шаг 4. Document Frequency и IDF

Шаг 5. TF–IDF вектор документа

Теперь $tfidfVectors содержит TF–IDF веса слов для каждого документа.

chevron-rightПолный пример кода для TF-IDFhashtag

Пример использования и результат

Добавим простой вывод результатов, чтобы увидеть реальные числа:

Вывод будет примерно таким:

Как это интерпретировать

Слова "кот", "ест" и "рыбу" встречаются в нескольких документах, поэтому их IDF маленький и итоговый вес низкий

Слова "любит", "собака" и "мясо" встречаются только в одном документе, поэтому их вес заметно выше

Даже при одинаковой частоте внутри документа редкость в корпусе сильно влияет на результат

Важно не то, что код "боевой", а то, что здесь буквально реализованы формулы из предыдущих разделов. Никакой магии — только частоты и логарифмы.

В следующей главе мы сделаем следующий шаг — поговорим о эмбеддингах как непрерывных пространствах смысла.

Last updated