В классических методах анализа текстов (имеются в виду “классическое” машинное обучение, не глубинное обучение, не нейронные сети) часто используется такой подход к тексту, который называется мешок слов (bag of words): при анализе порядок слов, грамматическая и синтаксическая структура текста не учитываются.
Какие этапы обработки текста существуют? Другими словами, что нужно сделать с текстом, прежде, чем переходить к содержательно части анализа?
Токенизация: разбиение текста на предложения, а предложений – на слова. Обычно по умолчанию разбиение на предложения происходит по знакам препинания, на слова – по пробелам, поэтому в качестве токена (минимальной единицы текста) выбирается “слово” – последовательность символов, отделенная от других последовательностей пробелом слева и справа. Это может иногда создавать трудности (подумайте о сочетаниях типа г.Москва или опеч атка).
Стемминг: сокращение слова до его основы. Нужен для того, чтобы разные формы слова воспринимались как одно и то же слово, а не как разные. Например, депутату, депутата – это просто формы слова депутат [основа депутат], а прочитал, прочитала, прочитаю – разные формы глагола прочитать [основа прочита].
Лемматизация: приведение слова к начальной форме (лемме – тому виду, в котором оно встречается в словаре). Как и стемминг, нужна для того, чтобы распознавать разные формы слова как одно и то же слово. В отличие от стемминга, ловит более сложные случаи (например, стемминг не позволит распознать слово хуже как форму наречия плохо, а лемматизация позволит). Если не совсем понятна разница между стеммингом и лемматизацией, еще пример:
Облако слов (word cloud) – средство визуализации встречаемости слов в тексте (текстах). Наверное, многие знакомы с облаками слов, но на всякий случай, вот примеры облаков слов. Облака слов бывают очень разные и по форме, и по цвету, но главная идея состоит в следующем: чем больше размер слова в облаке, тем чаще оно встречается в тексте.
Давайте построим первое облако слов. Для начала возьмем текст на английском – “Рождественская песнь в прозе” Ч.Диккенса (A Christmas Carol).
Загрузим текст (обычный txt-файл):
text <- readLines("ccarol.txt")
Посмотрим на первые 20 строк:
head(text, 20)
## [1] "A CHRISTMAS CAROL"
## [2] ""
## [3] "In Prose"
## [4] ""
## [5] "BEING A GHOST STORY OF CHRISTMAS"
## [6] ""
## [7] "STAVE ONE"
## [8] ""
## [9] "MARLEY'S GHOST"
## [10] ""
## [11] ""
## [12] "Marley was dead, to begin with. There is no doubt whatever about that."
## [13] "The register of his burial was signed by the clergyman, the clerk, the"
## [14] "undertaker, and the chief mourner. Scrooge signed it. And Scrooge's name"
## [15] "was good upon 'Change for anything he chose to put his hand to. Old"
## [16] "Marley was as dead as a door-nail."
## [17] ""
## [18] "Mind! I don't mean to say that I know, of my own knowledge, what there"
## [19] "is particularly dead about a door-nail. I might have been inclined,"
## [20] "myself, to regard a coffin-nail as the deadest piece of ironmongery in"
Теперь загрузим библиотеку tm
(от text mining) и выполним предварительную обработку текста. (Еще понадобиться загрузить библиотеку SnowballC
– для стемминга).
[Код далее частично основан на этих материалах].
# install.packages("tm")
# install.packages("SnowballC")
library(tm)
Создадим корпус – набор текстов.
corp <- Corpus(VectorSource(text))
#inspect(corp)
Превратим все буквы в строчные:
corp <- tm_map(corp, content_transformer(tolower))
Удалим цифры, пунктуацию и лишние пробелы:
corp <- tm_map(corp, removeNumbers)
corp <- tm_map(corp, removePunctuation)
corp <- tm_map(corp, stripWhitespace)
Удалим стоп-слова для английского языка (а сначала на них посмотрим):
stopwords("english")
## [1] "i" "me" "my" "myself" "we"
## [6] "our" "ours" "ourselves" "you" "your"
## [11] "yours" "yourself" "yourselves" "he" "him"
## [16] "his" "himself" "she" "her" "hers"
## [21] "herself" "it" "its" "itself" "they"
## [26] "them" "their" "theirs" "themselves" "what"
## [31] "which" "who" "whom" "this" "that"
## [36] "these" "those" "am" "is" "are"
## [41] "was" "were" "be" "been" "being"
## [46] "have" "has" "had" "having" "do"
## [51] "does" "did" "doing" "would" "should"
## [56] "could" "ought" "i'm" "you're" "he's"
## [61] "she's" "it's" "we're" "they're" "i've"
## [66] "you've" "we've" "they've" "i'd" "you'd"
## [71] "he'd" "she'd" "we'd" "they'd" "i'll"
## [76] "you'll" "he'll" "she'll" "we'll" "they'll"
## [81] "isn't" "aren't" "wasn't" "weren't" "hasn't"
## [86] "haven't" "hadn't" "doesn't" "don't" "didn't"
## [91] "won't" "wouldn't" "shan't" "shouldn't" "can't"
## [96] "cannot" "couldn't" "mustn't" "let's" "that's"
## [101] "who's" "what's" "here's" "there's" "when's"
## [106] "where's" "why's" "how's" "a" "an"
## [111] "the" "and" "but" "if" "or"
## [116] "because" "as" "until" "while" "of"
## [121] "at" "by" "for" "with" "about"
## [126] "against" "between" "into" "through" "during"
## [131] "before" "after" "above" "below" "to"
## [136] "from" "up" "down" "in" "out"
## [141] "on" "off" "over" "under" "again"
## [146] "further" "then" "once" "here" "there"
## [151] "when" "where" "why" "how" "all"
## [156] "any" "both" "each" "few" "more"
## [161] "most" "other" "some" "such" "no"
## [166] "nor" "not" "only" "own" "same"
## [171] "so" "than" "too" "very"
corp <- tm_map(corp, removeWords, stopwords("english"))
Добавим в список стоп-слов свои стоп-слова и удалим их:
corp <- tm_map(corp, removeWords, c("scrooge", "said", "upon"))
А теперь выполним стемминг:
corp <- tm_map(corp, stemDocument)
Посмотрим, как выглядит корпус сейчас:
inspect(corp)
Создадим матрицу слово-документ (term-document matrix). В нашем случае это не так наглядно, потому что, строго говоря, документ у нас один – один txt-файл:
dtm <- TermDocumentMatrix(corp)
m <- as.matrix(dtm) # превратим в обычную матрицу
vec <- sort(rowSums(m), decreasing = TRUE) # превратим в вектор с частотами, отсортированный по убыванию
head(vec, 10)
## one spirit ghost work project hand man look
## 111 106 101 94 84 80 77 74
## christma old
## 73 70
data <- data.frame(word = names(vec), freq = vec) # превратим в базу данных
head(data, 10)
## word freq
## one one 111
## spirit spirit 106
## ghost ghost 101
## work work 94
## project project 84
## hand hand 80
## man man 77
## look look 74
## christma christma 73
## old old 70
Установим библиотеку wordcloud
, а заодно загрузим уже знакомую библиотеку RColorBrewer
– для палитр цветов:
# install.packages("wordcloud")
library(wordcloud)
## Loading required package: RColorBrewer
library(RColorBrewer)
Теперь наконец-то построим облако слов:
# для воспроизводимости - R будет располагать слова в случайном порядке
set.seed(1234)
# min.freq - минимальная частота слова, которое отображается в облаке
# max.words - максимальное число слов в облаке
# colors - палитра цветов
pdf("wc.pdf")
wordcloud(words = data$word, freq = data$freq, min.freq = 20,
max.words = 100, random.order = FALSE,
colors = brewer.pal(8, "Dark2"),
rot.per = 0.9)
Наверное, в случае с облаком слов, стемминг немного портит картину: с одной стороны, он делает полезное дело, избавляя нас от разных форм одного и того же слова, с другой – обрезанные слова в облаке выглядят неэтетично. Как это исправить? Использовать вместо стемминга лемматизацию! Тогда слова будут приводиться к начальной форме, и в облаке мы будем видеть не основу слова, а само слово.
Есть небольшая проблема: в R сложно найти какой-то легкий способ осуществить лемматизацию. Самый распространенный способ – использовать оболочку для TreeTagger из библиотеки koRpus
. Но для этого нужно установить не только саму библиотеку, но и TreeTagger. Давайте пока не будем обсуждать лемматизацию отдельно, подробности см. здесь.