연구 질문
SMS 메시지가 스팸인지 아닌지 분류 및 연관 단어 추출
데이터 설명
데이터는 총 5,574개의 SMS 메시지로 구성되며, 각 행은 “ham” 또는
“spam”으로 레이블이 지정되어 있음.
라이브러리 로드
library(readr)
library(tm)
library(wordcloud)
library(caret)
library(e1071)
library(randomForest)
데이터 불러오기
url <- "https://archive.ics.uci.edu/ml/machine-learning-databases/00228/smsspamcollection.zip"
temp <- tempfile(fileext = ".zip")
download.file(url, temp)
sms <- read_delim(unz(temp, "SMSSpamCollection"), delim = "\t", col_names = c("type", "text"))
텍스트 전처리
docs <- VCorpus(VectorSource(sms$text))
docs <- tm_map(docs, content_transformer(tolower)) #소문자 변환
docs <- tm_map(docs, removePunctuation) #문장부호 제거
docs <- tm_map(docs, removeNumbers) #숫자 제거
docs <- tm_map(docs, removeWords, stopwords("english")) #불용어제거
docs <- tm_map(docs, stripWhitespace) #공백 제거
docs <- tm_map(docs, stemDocument) #어간 추출
단어 행렬 생성 (DTM)
dtm <- DocumentTermMatrix(docs)
freq <- colSums(as.matrix(dtm))
head(sort(freq, decreasing = TRUE), 20) # 가장 많이 등장한 단어 20개 보기
## call now get can will just come dont ham free ltgt know like day love want
## 599 443 413 367 344 338 276 270 270 251 251 249 239 232 232 226
## got ill good time
## 221 220 219 219
데이터프레임 변환
dtm_df <- as.data.frame(as.matrix(dtm)) #DTM을 데이터프레임으로 변환
dtm_df$type <- as.factor(sms$type) # 문자데이터를 factor로 변환
많이 나오는 단어 제외하고 제거
freq <- colSums(as.matrix(dtm))
top_terms <- names(sort(freq, decreasing = TRUE))[1:300] #빈도 상위 300개의 단어만 선택
dtm <- dtm[, top_terms]
dtm_df <- as.data.frame(as.matrix(dtm))
dtm_df$type <- as.factor(sms$type) # 열 이름 중복 방지
colnames(dtm_df) <- make.names(colnames(dtm_df), unique = TRUE)
데이터 분할 (학습/테스트)
set.seed(123)
train_idx <- createDataPartition(dtm_df$type, p = 0.7, list = FALSE)
train_data <- dtm_df[train_idx, ]
test_data <- dtm_df[-train_idx, ]
결측치 확인 및 제거
sum(is.na(train_data)) #결측치 없음으로 및의 코드는 없어도 상관없음
## [1] 0
train_data <- na.omit(train_data)
test_data <- na.omit(test_data)
모델별 학습
Naive Bayes
model.nb <- naiveBayes(type ~ ., data = train_data)
pred.nb <- predict(model.nb, test_data)
prob.nb <- predict(model.nb, test_data, type = "raw")[, "spam"]
cm.nb <- confusionMatrix(pred.nb, test_data$type, positive = "spam")
print(cm.nb)
## Confusion Matrix and Statistics
##
## Reference
## Prediction ham spam
## ham 43 0
## spam 1193 195
##
## Accuracy : 0.1663
## 95% CI : (0.1474, 0.1866)
## No Information Rate : 0.8637
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.0097
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 1.00000
## Specificity : 0.03479
## Pos Pred Value : 0.14049
## Neg Pred Value : 1.00000
## Prevalence : 0.13627
## Detection Rate : 0.13627
## Detection Prevalence : 0.96995
## Balanced Accuracy : 0.51739
##
## 'Positive' Class : spam
##
정확도: 0.1663
민감도(spam을 잘 잡아냈는지):: 1.00000
특이도(정상메일을 스팸으로 잘못 분류하지 않았는지):0.03479
Random Forest
model.rf <- randomForest(type ~ ., data = train_data)
pred.rf <- predict(model.rf, test_data)
prob.rf <- predict(model.rf, test_data, type = "prob")[, "spam"] # spam 확률
cm.rf <- confusionMatrix(pred.rf, test_data$type, positive = "spam")
print(cm.rf)
## Confusion Matrix and Statistics
##
## Reference
## Prediction ham spam
## ham 1225 39
## spam 11 156
##
## Accuracy : 0.9651
## 95% CI : (0.9542, 0.974)
## No Information Rate : 0.8637
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.842
##
## Mcnemar's Test P-Value : 0.0001343
##
## Sensitivity : 0.8000
## Specificity : 0.9911
## Pos Pred Value : 0.9341
## Neg Pred Value : 0.9691
## Prevalence : 0.1363
## Detection Rate : 0.1090
## Detection Prevalence : 0.1167
## Balanced Accuracy : 0.8956
##
## 'Positive' Class : spam
##
정확도: 0.9651
민감도(spam을 잘 잡아냈는지): 0.8051
특이도(정상메일을 스팸으로 잘못 분류하지 않았는지): 0.9903
SVM
model.svm <- svm(type ~ ., data = train_data, probability = TRUE)
pred.svm <- predict(model.svm, test_data) # 확률 추출
prob_attr <- predict(model.svm, test_data, probability = TRUE)
prob.svm <- attr(prob_attr, "probabilities")[, "spam"]
cm.svm <- confusionMatrix(pred.svm, test_data$type, positive = "spam")
print(cm.svm)
## Confusion Matrix and Statistics
##
## Reference
## Prediction ham spam
## ham 1235 43
## spam 1 152
##
## Accuracy : 0.9693
## 95% CI : (0.9589, 0.9776)
## No Information Rate : 0.8637
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.8564
##
## Mcnemar's Test P-Value : 6.37e-10
##
## Sensitivity : 0.7795
## Specificity : 0.9992
## Pos Pred Value : 0.9935
## Neg Pred Value : 0.9664
## Prevalence : 0.1363
## Detection Rate : 0.1062
## Detection Prevalence : 0.1069
## Balanced Accuracy : 0.8893
##
## 'Positive' Class : spam
##
정확도: 0.9693
민감도(spam을 잘 잡아냈는지): 0.7795
특이도(정상메일을 스팸으로 잘못 분류하지 않았는지): 0.9992
중요 단어 시각화
freq <- colSums(as.matrix(DocumentTermMatrix(VCorpus(VectorSource(sms$text[train_idx]))))) #학습데이터 텍스트를 DTM으로 다시 변환
wordcloud(names(freq), freq, max.words = 100, colors = brewer.pal(8, "Dark2"))

freq_sorted <- sort(freq, decreasing = TRUE)
head(freq_sorted, 20)
## you the and for your have call that are ham not but get with will i'm
## 1305 865 655 451 439 388 371 322 317 270 268 267 261 261 253 252
## can just this when
## 241 233 202 188
top_20 <- names(head(freq_sorted, 20)) #빈도 상위 20개 단어 출력
wordcloud_words <- names(freq)[order(freq, decreasing = TRUE)][1:100]
top_20 %in% wordcloud_words #워드클라우드에 포함된 단어에 상위 20개 단어가 포함되었는지 확인
## [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [16] TRUE TRUE TRUE TRUE TRUE
주요 결과 및 결론
- Naive Bayes는 속도가 빠르지만 다른 모델에
비해서는 정확도가 낮은편
- Random Forest, SVM은 비교적 높은
정확도
- 스팸 메시지는 특정 키워드(예: “you”, “call”, “can”, “get)와 연관이
많음.
- 대체로 얻다, 지금 등 받는 사람으로 하여금 바로 행동을 하도록
유도하는 듯한 패턴이 드러남.
- 워드클라우드 시각화는 스팸 메시지에서 자주 등장하는 단어를
파악하는 데 유용함.
결론
머신러닝 기법을 사용해 문자 메시지를 spam, ham으로 분류하는 시스템을
구축할 수 있으며, 비교적 간단한 모델(Naive Bayes)로도 결과를 도출해낼
수는 있음.