N-граммы: возвращаем порядок слов

N-граммы — простой способ вернуть в bag-of-words хотя бы локальный порядок слов: считать не отдельные слова, а их короткие цепочки.

N-грамма — последовательность из N подряд идущих единиц (слов или символов). Биграмма — пара, триграмма — тройка.

Зачем нужны n-граммы

Мы знаем главную беду «мешка слов»: он теряет порядок, и «не понравилось» распадается на «не» и «понравилось». N-граммы лечат это частично: вместо отдельных слов берём их пары и тройки. Тогда «не понравилось» становится одним токеном-биграммой, и отрицание сохраняется.

Строим n-граммы

def ngrams(tokens, n):
    return [tuple(tokens[i:i + n]) for i in range(len(tokens) - n + 1)]

tokens = "мне не понравился этот фильм".split()
print("Униграммы:", ngrams(tokens, 1))
print("Биграммы :", ngrams(tokens, 2))
print("Триграммы:", ngrams(tokens, 3))

Вывод:

Униграммы: [('мне',), ('не',), ('понравился',), ('этот',), ('фильм',)]
Биграммы : [('мне', 'не'), ('не', 'понравился'), ('понравился', 'этот'), ('этот', 'фильм')]
Триграммы: [('мне', 'не', 'понравился'), ('не', 'понравился', 'этот'), ('понравился', 'этот', 'фильм')]

Биграмма ('не', 'понравился') — это уже осмысленная единица, которая прямо кодирует негатив. Униграммный «мешок» такого не умел.

Символьные n-граммы

N-граммы бывают не только из слов, но и из символов. Символьные n-граммы устойчивы к опечаткам и хорошо ловят морфологию: формы «бегать», «бегает» делят общие символьные триграммы «бег», «ега».

def char_ngrams(word, n):
    return [word[i:i + n] for i in range(len(word) - n + 1)]

print(char_ngrams("бегать", 3))
print(char_ngrams("бегает", 3))
# общие триграммы — мера похожести слов
common = set(char_ngrams("бегать", 3)) & set(char_ngrams("бегает", 3))
print("Общие триграммы:", common)

Вывод:

['бег', 'ега', 'гат', 'ать']
['бег', 'ега', 'гае', 'ает']
Общие триграммы: {'бег', 'ега'}

Цена за порядок: взрыв размерности

За всё надо платить. Униграмм в языке — десятки тысяч. Биграмм — миллионы (почти любое сочетание двух слов). Триграмм — ещё на порядки больше. Векторы становятся гигантскими и ещё более разрежёнными, а большинство n-грамм встречается один раз и бесполезно. Поэтому на практике:

  • Обычно ограничиваются униграммами и биграммами (иногда триграммами).
  • Отсекают слишком редкие n-граммы (встретившиеся 1-2 раза).
  • Комбинируют с TF-IDF-взвешиванием.

N-граммы — это ещё и языковые модели

Помимо признаков для классификации, n-граммы лежат в основе простейших языковых моделей: зная две предыдущие n-граммы, можно оценить вероятность следующего слова. Этим мы займёмся отдельно в разделе про классические модели — там n-граммы заиграют как генераторы текста.

Итог

  • N-грамма — цепочка из N подряд идущих слов или символов.
  • Биграммы и триграммы возвращают локальный порядок и ловят «не понравилось».
  • Символьные n-граммы устойчивы к опечаткам и морфологии.
  • Плата — резкий рост размерности, поэтому n обычно держат маленьким (1-2).
Проверьте себя
1. Какую проблему bag-of-words частично решают n-граммы?
AСлишком маленький словарь
BПотерю порядка слов (например, отрицание «не понравилось»)
CОтсутствие нормализации регистра
DСлишком медленную токенизацию
2. Чем плохо брать n-граммы большого порядка (например, 5-граммы слов)?
AОни теряют смысл слов
BРазмерность взрывается, большинство n-грамм встречается один раз и бесполезно
CОни работают только для символов
DИх нельзя посчитать на чистом Python
3. Чем полезны символьные n-граммы?
AОни полностью заменяют словарь
BОни устойчивы к опечаткам и хорошо ловят общие основы слов
CОни всегда короче слов
DОни не требуют токенизации текста
Поддержать проект