«Классификация - это определение, состоящее из системы определений». - Карл Вильгельм

Основываясь на моем последнем посте, мы классифицируем данные SMS-спама как спам или нет, используя наивный байесовский классификатор. Ребята, вы можете увидеть мой последний пост об этом здесь.

Давайте запустим код R для Наивного байесовского классификатора!

1. Исследование данных

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

Начнем с импорта данных CSV и сохранения их во фрейме данных:

sms_raw <- read.csv("sms_spam.csv", stringsAsFactors = FALSE)

Используя функцию str (), мы видим, что фрейм данных sms_raw включает всего 5 574 SMS-сообщения с двумя функциями: тип и текст. Тип SMS был закодирован как спам или радиолюбитель. В текстовом элементе хранится полный необработанный текст SMS.

str(sms_raw)

Элемент типа в настоящее время является вектором символов. Поскольку это категориальная переменная, было бы лучше преобразовать ее в коэффициент, как показано в следующем коде:

sms_raw$type <- factor(sms_raw$type)

Изучая это с помощью функций str () и table (), мы видим, что тип теперь соответствующим образом перекодирован как фактор. Кроме того, мы видим, что 747 (около 13 процентов) SMS-сообщений в наших данных были помечены как спам, а остальные - как ветчины:

str(sms_raw$type)

table(sms_raw$type)

А пока оставим текст сообщения в покое. Как вы узнаете из следующего раздела, обработка необработанных SMS-сообщений потребует использования нового набора мощных инструментов, разработанных специально для обработки текстовых данных.

2. Подготовка данных - очистка и стандартизация текстовых данных.

Первый шаг в обработке текстовых данных включает создание корпуса, который представляет собой набор текстовых документов. Чтобы создать корпус, мы будем использовать функцию VCorpus () в пакете tm, мы будем использовать функцию чтения VectorSource () для создания исходного объекта из существующего вектора sms_raw $ text, который затем может быть передан в VCorpus () следующим образом:

library(tm)
sms_corpus <- VCorpus(VectorSource(sms_raw$text))

Результирующий объект корпуса сохраняется под именем sms_corpus. Распечатав корпус, мы видим, что он содержит документы для каждого из 5 574 SMS-сообщений в данных обучения:

print(sms_corpus)

Поскольку корпус tm по сути представляет собой сложный список, мы можем использовать операции со списком для выбора документов в корпусе. Чтобы получить сводку конкретных сообщений, мы можем использовать функцию inspect () с операторами списка. Например, следующая команда будет просматривать сводку первого и второго SMS-сообщений в корпусе:

inspect(sms_corpus[1:2])

Чтобы просмотреть фактический текст сообщения, необходимо применить функцию as.character () к нужным сообщениям. Чтобы просмотреть одно сообщение, используйте функцию as.character () для одного элемента списка, отметив, что требуется запись в двойные скобки:

as.character(sms_corpus[[1]])

Чтобы просмотреть несколько документов, нам нужно использовать as.character () для нескольких элементов в объекте sms_corpus. Для этого мы воспользуемся функцией lapply (), которая является частью семейства функций R, применяющих процедуру к каждому элементу структуры данных R. Команда lapply () для применения as.character () к подмножеству элементов корпуса выглядит следующим образом:

lapply(sms_corpus[1:2], as.character)

Как отмечалось ранее, корпус содержит необработанный текст из 5 574 текстовых сообщений. Чтобы провести наш анализ, нам нужно разделить эти сообщения на отдельные слова. Но сначала нам нужно очистить текст, чтобы стандартизировать слова, удалив знаки препинания и другие символы, мешающие результату. Например, мы хотим, чтобы строки Hello !, HELLO и hello считались экземплярами одного и того же слова.

Функция tm_map () предоставляет метод для применения преобразования (также известного как отображение) к корпусу tm. Мы будем использовать эту функцию для очистки нашего корпуса с помощью серии преобразований и сохранения результата в новом объекте с именем corpus_clean.

sms_corpus_clean <- sms_corpus %>%
  tm_map(content_transformer(tolower)) %>%
  tm_map(removeNumbers) %>%
  tm_map(removeWords, stopwords()) %>%
  tm_map(removePunctuation) %>%
  tm_map(stemDocument) %>%
  tm_map(stripWhitespace)

3. Подготовка данных - разбиение текстовых документов на слова

Теперь, когда данные обрабатываются по нашему усмотрению, последний шаг - разделить сообщения на отдельные компоненты с помощью процесса, называемого токенизацией. Токен - это отдельный элемент текстовой строки; в данном случае жетоны - это слова.

Как вы могли предположить, пакет tm предоставляет функциональные возможности для токенизации корпуса SMS-сообщений. Функция DocumentTermMatrix () возьмет корпус и создаст структуру данных, называемую Матрицей терминов документа (DTM), в которой строки указывают документы (SMS-сообщения), а столбцы - термины (слова).

Создание разреженной матрицы ЦММ с учетом корпуса tm включает одну команду:

sms_dtm <- DocumentTermMatrix(sms_corpus_clean)

Это создаст объект sms_dtm, содержащий токенизированный корпус с использованием настроек по умолчанию, которые применяют минимальную обработку. Настройки по умолчанию подходят, потому что мы уже подготовили корпус вручную.

С другой стороны, если бы мы не выполняли предварительную обработку, мы могли бы сделать это здесь, предоставив список опций управляющих параметров для отмены значений по умолчанию. Например, чтобы создать DTM непосредственно из необработанного, необработанного корпуса SMS, мы можем использовать следующую команду:

sms_dtm2 <- DocumentTermMatrix(sms_corpus, control = list(
  tolower = TRUE,
  removeNumbers = TRUE, 
  stopwords = TRUE, 
  removePunctuation = TRUE, 
  stemming = TRUE))

Это применяет те же шаги предварительной обработки к корпусу SMS в том же порядке, что и ранее. Однако, сравнивая sms_dtm с sms_dtm2, мы видим небольшую разницу в количестве членов в матрице.

4. Подготовка данных - создание наборов данных для обучения и тестирования.

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

Мы разделим данные на две части: 75 процентов для обучения и 25 процентов для тестирования. Поскольку SMS-сообщения отсортированы в случайном порядке, мы можем просто взять первые 4169 сообщений для обучения и оставить оставшиеся 1390 для тестирования. Чтобы убедиться, что подмножества репрезентативны для полного набора данных SMS, давайте сравним долю спама в обучающем и тестовом кадрах данных:

prop.table(table(sms_train_labels))

prop.table(table(sms_test_labels))

Как данные обучения, так и данные тестирования содержат около 13% спама. Это говорит о том, что спам-сообщения были равномерно разделены между двумя наборами данных.

5. Создание визуализации "Облако слов"

Облако слов позволит нам визуально посмотреть на наш корпус, чтобы быстро увидеть частоту слов в нем. Слова, которые появляются чаще, будут отображаться в облаке более крупным шрифтом и реже меньшим размером шрифта. Это можно сделать с помощью пакета wordcloud R.

library(wordcloud)
wordcloud(sms_corpus_clean, min.freq = 50, random.order = FALSE)

Мы также можем создать визуализацию частоты для наших сырых sms-данных, используя функцию subset(), используя категориальную функцию $type:

par(mfcol = c(1, 2))
spam <- sms_raw %>%
  subset(type == "spam")
spamCloud <- wordcloud(spam$text, max.words = 40, scale = c(3, 0.5))
ham <- sms_raw %>%
  subset(type == "ham")
hamCloud <- wordcloud(ham$text, max.words = 40, scale = c(3, 0.5))

6. Создание индикаторов для частых слов.

Чтобы завершить предварительную обработку данных, мы должны уменьшить количество функций в наших тестовых и обучающих матрицах DT. Для этого мы воспользуемся функцией findFreqTerms() (она снова находится в пакете tm).

sms_dtm_freq_train <- sms_dtm_train %>%
  findFreqTerms(5) %>%
  sms_dtm_train[ , .]
sms_dtm_freq_test <- sms_dtm_test %>%
  findFreqTerms(5) %>%
  sms_dtm_test[ , .]

Теперь мы напишем функцию, которая преобразует наши разреженные матрицы DT из числовых в категориальные матрицы «да / нет», которые наш алгоритм может обработать.

convert_counts <- function(x) {
  x <- ifelse(x > 0, "Yes", "No")
}

Применяя нашу convert_counts функцию:

sms_train <- sms_dtm_freq_train %>%
  apply(MARGIN = 2, convert_counts)
sms_test <- sms_dtm_freq_test %>%
  apply(MARGIN = 2, convert_counts)

7. Обучение модели на данных.

Теперь наступает довольно простой шаг обучения нашей модели на данных и последующего использования этого классификатора для создания прогнозов на тестовом наборе. Для этого требуется, чтобы пакет e1071 применил функцию naiveBays():

sms_classifier <- naiveBayes(sms_train, sms_train_labels)
sms_pred <- predict(sms_classifier, sms_test)

Оценка производительности модели

Теперь мы можем использовать функцию CrossTable() из пакета gmodels, чтобы увидеть, как наши прогнозы оправдались.

CrossTable(sms_pred, sms_test_labels, prop.chisq = FALSE, chisq = FALSE, 
           prop.t = FALSE,
           dnn = c("Predicted", "Actual"))
##    Cell Contents
## |-------------------------|
## |                       N |
## |           N / Col Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1390 
## 
##  
##              | actual 
##    predicted |       ham |      spam | Row Total | 
## -------------|-----------|-----------|-----------|
##          ham |      1201 |        30 |      1231 | 
##              |     0.995 |     0.164 |           | 
## -------------|-----------|-----------|-----------|
##         spam |         6 |       153 |       159 | 
##              |     0.005 |     0.836 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      1207 |       183 |      1390 | 
##              |     0.868 |     0.132 |           | 
## -------------|-----------|-----------|-----------|

Общий коэффициент точности модели составляет 0,974, в то время как 6 легитимных сообщений, идентифицированных как спам, и 30 спам-сообщений не могут быть распознаны. Законные сообщения, помеченные как спам (ложные срабатывания), могут вызвать серьезные проблемы, поскольку люди могут упустить важную информацию. Мы будем работать над улучшением показателей.

Как улучшить характеристики модели?

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

sms_classifier2 <- naiveBayes(sms_train, sms_train_labels, laplace = 1)
sms_pred2 <- predict(sms_classifier2, sms_test)
CrossTable(sms_pred2, sms_test_labels, prop.chisq = FALSE, chisq = FALSE, 
           prop.t = FALSE,
           dnn = c("Predicted", "Actual"))
##    Cell Contents
## |-------------------------|
## |                       N |
## |           N / Col Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1390 
## 
##  
##              | actual 
##    predicted |       ham |      spam | Row Total | 
## -------------|-----------|-----------|-----------|
##          ham |      1202 |        28 |      1230 | 
##              |     0.996 |     0.153 |           | 
## -------------|-----------|-----------|-----------|
##         spam |         5 |       155 |       160 | 
##              |     0.004 |     0.847 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      1207 |       183 |      1390 | 
##              |     0.868 |     0.132 |           | 
## -------------|-----------|-----------|-----------|

Во второй пересмотренной модели показатель точности улучшился (% 97,6). Количество ложных срабатываний уменьшено с 6 до 5 сообщений. Получаем еще лучший результат по точности! Но что нам дало добавление сглаживающей переменной Лапласа?

Сглаживание Лапласа для повышения производительности

Это сглаживание? В исследовательской работе 1996 года профессора Чен и Гудман из Гарвардского университета заявили, что «сглаживание - это метод, необходимый для построения моделей языка n -грамм… [или] вероятности распределения по строкам P (s) P (s), который пытается отразить частоту, с которой каждая строка ss встречается как предложение в естественном тексте ». (Chen and Goodman 1996) Сглаживание просто позволяет избежать присвоения словам, которые появляются реже, с меньшим весом, чем слова которые появляются чаще.

⬛ Заключение

Мы действительно показали высокую точность, которую имеет алгоритм Наивного Байеса для классификации данных текстовой базы, и что он менее точен для числовых данных. Добавление оценщика Лапласа уменьшило количество ложных срабатываний (любительские сообщения, ошибочно классифицируемые как спам). Это важно, учитывая, что точность модели и без того была впечатляющей.

⬛ Рекомендация

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

Это все, что касается моего сообщения о Наивном байесовском классификаторе. Надеюсь, это будет полезно для всех вас, ребята! Оставьте свой комментарий ниже и хлопайте мне в ладоши 👏👏👏

Ссылки
Алмейда, Тьяго А. и Хосе Мария Гомес Идальго. 2011. Сборник SMS-спама. Федеральный университет Сан-Карлоса. Http://www.dt.fee.unicamp.br/~tiago/smsspamcollection/.

Инго Файнерер, Курт Хорник и Дэвид Мейер. 2008. Инфраструктура интеллектуального анализа текста в R. Журнал статистического программного обеспечения. doi: 10.18637 / jss.v025.i05.

Ланц, Бретт. 2015. Машинное обучение с R. Бирмингем, Великобритания: Packt Publishing.