Предварительная обработка текста

В классических методах анализа текстов (имеются в виду “классическое” машинное обучение, не глубинное обучение, не нейронные сети) часто используется такой подход к тексту, который называется мешок слов (bag of words): при анализе порядок слов, грамматическая и синтаксическая структура текста не учитываются.

Какие этапы обработки текста существуют? Другими словами, что нужно сделать с текстом, прежде, чем переходить к содержательно части анализа?

  1. Токенизация: разбиение текста на предложения, а предложений – на слова. Обычно по умолчанию разбиение на предложения происходит по знакам препинания, на слова – по пробелам, поэтому в качестве токена (минимальной единицы текста) выбирается “слово” – последовательность символов, отделенная от других последовательностей пробелом слева и справа. Это может иногда создавать трудности (подумайте о сочетаниях типа г.Москва или опеч атка).

  2. Стемминг: сокращение слова до его основы. Нужен для того, чтобы разные формы слова воспринимались как одно и то же слово, а не как разные. Например, депутату, депутата – это просто формы слова депутат [основа депутат], а прочитал, прочитала, прочитаю – разные формы глагола прочитать [основа прочита].

  3. Лемматизация: приведение слова к начальной форме (лемме – тому виду, в котором оно встречается в словаре). Как и стемминг, нужна для того, чтобы распознавать разные формы слова как одно и то же слово. В отличие от стемминга, ловит более сложные случаи (например, стемминг не позволит распознать слово хуже как форму наречия плохо, а лемматизация позволит). Если не совсем понятна разница между стеммингом и лемматизацией, еще пример:

  1. Удаление стоп-слов. Стоп-слова – слова, которые часто встречаются в языке и которые не несут серьезной содержательной информации. Как правило, к стоп-словам относят предлоги, частицы, союзы и прочие несамостоятельные части речи, а также местоимения разных видов. Но иногда в список стоп-слов можно включить вполне самостоятельные слова. Например, стоп-словом при анализе запросов можно считать слово ищу, если почти каждый пользователь его использует (понятно, что дополнительной смысловой нагрузки это слово не несет – раз все запросы про поиск чего-то).

Облака слов

Облако слов (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. Давайте пока не будем обсуждать лемматизацию отдельно, подробности см. здесь.