В прошлый раз я рассказывал про такие элементы препроцессинга текстовых данных, как приведение к одному регистру, удаление стоп-слов и пунктуации. Второй этап препроцессинга подобных данных, необходимый в некоторых задачах — это стеммизация, приведение слов к своей основной форме. Например, «работодателя» – «работодатель».

Существует несколько алгоритмов стемминга, наиболее известный из них — стеммер Портера. В R этот стеммер реализован в пакетах tm и SnowballC. К сожалению, этот стеммер разработан в первую очередь для английского языка, поэтому с русскоязычными текстами закономерно возникают сложности. К примеру:

library(SnowballC)
txt <- "Мама мыла в будуаре дегтярным мылом офицера Горчакова, пока его подчиненные курили в сенях"
wordStem(txt, language = "russian")
#> [1] "Мама мыла в будуаре дегтярным мылом офицера Горчакова, пока его подчиненные курили в сен"

Для русского языка наиболее перспективным представляется использование разработанного в Яндексе стеммера Mystem (первая версия — Илья Сегалович и Виталий Титов). Принцип работы стеммера Mystem, а так же сравнение с другими стеммерами, такими, как Snowball, Stemka и проч. можно найти здесь: http://download.yandex.ru/company/iseg-las-vegas.pdf. Примеры работы стеммера, а так же опции и нюансы вывода можно посмотреть на странице стеммера: https://tech.yandex.ru/mystem/. Там же можно скачать саму программу для своей операционной системы.

Так как Мystem — консольная программа, для ее использования в R необходимо писать отдельную функцию-обёртку (wrapper), передающую данные из R в Mystem и потом разбирающую результат до необходимого формата. Моя функция выглядит следующим образом:

stem_text <- function(x) {
    x <- enc2utf8(x)
    res <- system("../bin/mystem -cl", intern = TRUE, input = x)
    res <- gsub("[{}]", "", res)
    res <- gsub("(\\|[^ ]+)", "", res)
    res <- gsub("\\?", "", res)
    res <- gsub("\\s+", " ", res)
    res
}

Рассмотрим основные элементы:

  • system("../bin/mystem -cl", intern = TRUE, input = x) — указываем расположение mystem, передаём переменную x (input = x) на ввод и преобразуем полученный результат в объект R (intern = TRUE). Ключ -l запрещает вывод исходных словоформ, а ключ -c нужен для того, чтобы скопировать весь ввод на вывод. В противном случае, при использовании вместо него ключа -n, есть риск потери некоторых данных (в частности, если передаваемая строка состоит из цифр). Если текст имеет кодировку, отличную от UTF-8, то её нужно указать после ключа -e, например -e cp1251. Все параметры Mystem можно увидеть с помощью команды консоли ./mystem --help;
  • gsub("[{}]", "", res) — удаляем фигурные скобки из результатов вывода: {мама} – мама;
  • gsub("(\\|[^ ]+)", "", res) — в некоторых случаях Mystem предлагает разные варианты, через вертикальную черту |. Выражение удаляет все дополнительные варианты: подчиненный|подчинять|подчиненная – подчиненный;
  • gsub("\\?", "", res) — иногда Mystem возвращает результат, который определён с меньшей однозначностью и вызывает сомнения, и маркирует его вопросительным знаком. Как правило, это характерно при стемминге терминов, аббревиатур и проч. Удаляем вопросительные знаки;
  • gsub("\\s+", " ", res) — финальная чистка результата, удаление лишних пробелов (в принципе, необязательно).

Вот что мы получаем в результате работы этой функции:

# чистим данные
txt <- clean_text("Мама мыла в будуаре дегтярным мылом офицера Горчакова, пока его подчиненные курили в сенях")
#> Loading required package: stringr
#> Loading required package: tm
#> Loading required package: NLP
# стеммизируем
stem_text(txt)
#> [1] "мама мыло будуар дегтярный мыло офицер горчаков пока он подчиненный курить сени"

Как мы видим, несмотря на то, что Mystem явно эффективнее прочих стеммеров, он также в некоторых ситуациях ошибается. Так, вместо «мыть» при стемминге слова «мыла», мы получили слово “мыло”. Видимо, для контроля подобных неоднозначностей необходима более тонкая настройка стеммера, в частности, создание и подключение пользовательских словарей.

При регулярном использовании Mystem у вас может появится необходимость добавить свои исключения при обработке текста. Mystem позволяет использовать пользовательские словари, которые оформляются в формате:

лемма
[форма]1 граммемы1
[форма]2 граммемы2

Пример описания слова в пользовательской словаре (взято с сайта Mystem):

мурелок
[мурелок] S,m,nom,sg
[мурелк]а S,m,gen,sg
[мурелк]у S,m,dat,sg
[мурелок] S,m,acc,sg
[мурелк]ом S,m,ins,sg
[мурелк]е S,m,abl,sg
[мурелк]и S,m,nom,pl
[мурелк]ов S,m,gen,pl
[мурелк]ам S,m,dat,pl
[мурелк]и S,m,acc,pl
[мурелк]ами S,m,ins,pl
[мурелк]ах S,m,abl,pl

Расшифровку граммем можно посмотреть здесь: https://tech.yandex.ru/mystem/doc/grammemes-values-docpage/. Теперь добавим указание словарей для нашей функции stem_text(). Так как у нас нет созданного своего словаря, результат стемминга никак не изменится.

stem_text <- function(x, fixlist = NULL) {
    x <- enc2utf8(x)
    cmd <- "../bin/mystem -cl"
    if (!is.null(fixlist) && file.exists(fixlist))
        cmd <- paste(cmd, "--fixlist", fixlist)
    res <- system(cmd, intern = TRUE, input = x)
    res <- gsub("[{}]", "", res)
    res <- gsub("(\\|[^ ]+)", "", res)
    res <- gsub("\\?", "", res)
    res <- gsub("\\s+", " ", res)
    res
}