Abstract
ナイーブベイズ分類を試すナイーブベイズを用いてスパムメールの判別を行う
# データ収集
## 1列目にhamかspamか, 2列目にメール本文
sms_raw <- read.csv("sms_spam.csv", stringsAsFactors = FALSE)
head(sms_raw)
## type
## 1 ham
## 2 ham
## 3 ham
## 4 spam
## 5 spam
## 6 ham
## text
## 1 Hope you are having a good week. Just checking in
## 2 K..give back my thanks.
## 3 Am also doing in cbe only. But have to pay.
## 4 complimentary 4 STAR Ibiza Holiday or £10,000 cash needs your URGENT collection. 09066364349 NOW from Landline not to lose out! Box434SK38WP150PPM18+
## 5 okmail: Dear Dave this is your final notice to collect your 4* Tenerife Holiday or #5000 CASH award! Call 09061743806 from landline. TCs SAE Box326 CW25WX 150ppm
## 6 Aiya we discuss later lar... Pick u up at 4 is it?
# 前処理
sms_raw$type <- as.factor(sms_raw$type)
table(sms_raw$type)
##
## ham spam
## 4812 747
## テキストマイニング用パッケージ
library(tm)
## Loading required package: NLP
##
## Attaching package: 'NLP'
## The following object is masked from 'package:ggplot2':
##
## annotate
## コーパスの作成
sms_corpus <- VCorpus(VectorSource((sms_raw$text)))
print(sms_corpus)
## <<VCorpus>>
## Metadata: corpus specific: 0, document level (indexed): 0
## Content: documents: 5559
## コーパスの要約情報
inspect(sms_corpus[1:2])
## <<VCorpus>>
## Metadata: corpus specific: 0, document level (indexed): 0
## Content: documents: 2
##
## [[1]]
## <<PlainTextDocument>>
## Metadata: 7
## Content: chars: 49
##
## [[2]]
## <<PlainTextDocument>>
## Metadata: 7
## Content: chars: 23
## コーパス内の文書表示
lapply(sms_corpus[1:2], as.character)
## $`1`
## [1] "Hope you are having a good week. Just checking in"
##
## $`2`
## [1] "K..give back my thanks."
## クリーニング
sms_corpus_clean <- sms_corpus %>%
## 全て小文字にする
tm_map(content_transformer(tolower)) %>%
## 数字を取り除く
tm_map(removeNumbers) %>%
## ストップワードを取り除く(removewordsで取り除き, stopwordsで辞書を指定)
tm_map(removeWords, stopwords()) %>%
## 句読点を取り除く
tm_map(removePunctuation)
##ステミング(原形に戻す)
library(SnowballC)
sms_corpus_clean <- tm_map(sms_corpus_clean, stemDocument)
## 余分なスペースを取り除く
sms_corpus_clean <- tm_map(sms_corpus_clean, stripWhitespace)
## トークン化(行に文書, 列に単語)
### スパースになっている
sms_dtm <- DocumentTermMatrix(sms_corpus_clean)
### 特徴量=terms(単語数)が確認できる
sms_dtm
## <<DocumentTermMatrix (documents: 5559, terms: 6542)>>
## Non-/sparse entries: 42112/36324866
## Sparsity : 100%
## Maximal term length: 40
## Weighting : term frequency (tf)
## 訓練データとテストデータの割り振り
sms_dtm_train <- sms_dtm[1:4169, ]
sms_dtm_test <- sms_dtm[4170:5559, ]
## ラベル(目的変数)
sms_train_labels <- sms_raw[1:4169, ]$type
sms_test_labels <- sms_raw[4170:5559, ]$type
## ワードクラウド
library(wordcloud)
## Loading required package: RColorBrewer
### 最低50回, 頻度の高いものを中央に表示
wordcloud(sms_corpus_clean, min.freq = 50, random.order = FALSE)
## spam, hamそれぞれのワードクラウド
### spam
spam <- sms_raw %>%
filter(type == "spam")
#### 最大単語数, 最大単語と最小単語のフォント
wordcloud(spam$text, max.words = 40, scale = c(3, 0.5))
## Warning in tm_map.SimpleCorpus(corpus, tm::removePunctuation): transformation
## drops documents
## Warning in tm_map.SimpleCorpus(corpus, function(x) tm::removeWords(x,
## tm::stopwords())): transformation drops documents
### ham
ham <- sms_raw %>%
filter(type == "ham")
wordcloud(ham$text, max.words = 40, scale = c(3, 0.5))
## Warning in tm_map.SimpleCorpus(corpus, tm::removePunctuation): transformation
## drops documents
## Warning in tm_map.SimpleCorpus(corpus, tm::removePunctuation): transformation
## drops documents
### 出現頻度の高いワードに絞り込む(5回以上出現)
sms_freq_words <- findFreqTerms(sms_dtm_train, 5)
glimpse(sms_freq_words)
## chr [1:1137] "£wk" "abiola" "abl" "abt" "accept" "access" "account" ...
## 訓練データ, テストデータを頻出ワードだけに絞り込む
sms_dtm_freq_train <- sms_dtm_train[, sms_freq_words]
sms_dtm_freq_test <- sms_dtm_test[, sms_freq_words]
## Yes, Noにおきかえ
convert_counts <- function(x){
x<- ifelse(x>0, "Yes", "No")
}
sms_train <- apply(sms_dtm_freq_train, MARGIN = 2, convert_counts)
sms_test <- apply(sms_dtm_freq_test, MARGIN = 2, convert_counts)
# モデルを訓練する
library(e1071)
## 訓練データ, 訓練ラベル, ラプラス推定量(デフォルト0)
sms_classifier <- naiveBayes(sms_train, sms_train_labels, laplace = 0)
# モデルを評価する
## 訓練済データ, テストデータ
sms_test_pred <- predict(sms_classifier, sms_test)
## 性能評価
library(gmodels)
### dnnで名前を変えられる
CrossTable(sms_test_pred, sms_test_labels,
prop.chisq = F, prop.c = F, prop.r = F,
dnn = c("predict", "actual"))
##
##
## Cell Contents
## |-------------------------|
## | N |
## | N / Table Total |
## |-------------------------|
##
##
## Total Observations in Table: 1390
##
##
## | actual
## predict | ham | spam | Row Total |
## -------------|-----------|-----------|-----------|
## ham | 1201 | 30 | 1231 |
## | 0.864 | 0.022 | |
## -------------|-----------|-----------|-----------|
## spam | 6 | 153 | 159 |
## | 0.004 | 0.110 | |
## -------------|-----------|-----------|-----------|
## Column Total | 1207 | 183 | 1390 |
## -------------|-----------|-----------|-----------|
##
##
正解率 97.4%(1201+153/1390)
適合率 97.6%(1201/1231)
再現率 99.5%(1201/1207)
実際にはspamなのにhamと予測されているものが30件, 実際はhamなのにspamと判断されているものが6件ある. この場合, この6件が迷惑メールフォルダに入ったり, はじかれてしまったりするとまずいので, ここでは再現率を上げたい
# モデルの性能を向上させる
## ラプラス推定量を設定してみる(出現確率が0になるのを防ぐ)
## spamにしか出てこない単語があったとしても全てspamに割り振るのはおかしい
sms_classifier2 <- naiveBayes(sms_train, sms_train_labels, laplace = 0.1)
sms_test_pred2 <- predict(sms_classifier2, sms_test)
CrossTable(sms_test_pred2, sms_test_labels,
prop.chisq = F, prop.c = F, prop.r = F,
dnn = c("predict", "actual"))
##
##
## Cell Contents
## |-------------------------|
## | N |
## | N / Table Total |
## |-------------------------|
##
##
## Total Observations in Table: 1390
##
##
## | actual
## predict | ham | spam | Row Total |
## -------------|-----------|-----------|-----------|
## ham | 1202 | 26 | 1228 |
## | 0.865 | 0.019 | |
## -------------|-----------|-----------|-----------|
## spam | 5 | 157 | 162 |
## | 0.004 | 0.113 | |
## -------------|-----------|-----------|-----------|
## Column Total | 1207 | 183 | 1390 |
## -------------|-----------|-----------|-----------|
##
##
ラプラス推定量を0.1にしてみたら精度があがった. 偽陽性は6から5に偽陰性は30から26になった.