R/ML & DL 공부

[R] 휴대폰 스팸 분류 - Naive bayes

dori_0 2022. 4. 20. 01:28

휴대폰 스팸 분류

Naive bayes로 휴대폰 스팸 여부 구별하기

 

데이터 셋

SMSSpamCollection.csv
0.46MB

 

5,572개의 SMS 메세지와 메세지의 type이 포함되어 있음

SMS type는 ham (일반 메세지) 또는 spam (스팸 메세지)로 분류됨


 

1. 데이터 준비와 탐구

> sms_raw = read.csv("C:/R/SMSSpamCollection.csv", stringsAsFactors=FALSE)
> str(sms_raw)
'data.frame':	5573 obs. of  2 variables:
$ ham                                                                                                            : chr  "ham" "spam" "ham" "ham" ...
$ Go.until.jurong.point..crazy...Available.only.in.bugis.n.great.world.la.e.buffet....Cine.there.got.amore.wat...: chr  "Ok lar... Joking wif u oni..." "Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question("| __truncated__ "U dun say so early hor... U c already then say..." "Nah I don't think he goes to usf, he lives around here though" ...

> names(sms_raw)[1] = "type"
> names(sms_raw)[2] = "text"
  • 열 이름을 각각 type, text로 지정해주었다.

 

type의 데이터 유형을 factor()로 바꾸어주고 table을 확인해보자

> sms_raw$type = factor(sms_raw$type)
> str(sms_raw)
'data.frame':	5573 obs. of  2 variables:
 $ type: Factor w/ 2 levels "ham","spam": 1 2 1 1 2 1 1 2 2 1 ...
 $ text: chr  "Ok lar... Joking wif u oni..." "Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question("| __truncated__ "U dun say so early hor... U c already then say..." "Nah I don't think he goes to usf, he lives around here though" ...

> table(sms_raw$type)
 ham spam 
4826  747
  • type의 유형이 factor로 바뀐 것을 확인할 수 있다.
  • 총 4826개의 일반 메세지와 747개의 스팸 메세지로 이루어져 있다.

 

 

2. 분석을 위한 텍스트 데이터 처리

SMS 메세지는 단어, 공백, 숫자, 마침표 등으로 구성된 문자열이다.

이러한 텍스트들을 처리해 주기 위해 먼저 말뭉치를 만들어보자

> library(tm)
> sms_corpus = Corpus(VectorSource(sms_raw$text)) # 말뭉치로 만들기

> print(sms_corpus)
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 0
Content:  documents: 5573

> inspect(sms_corpus[1:3]) # 말뭉치 내용 확인
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 0
Content:  documents: 3

[1] Ok lar... Joking wif u oni...                                                                                                                              
[2] Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's
[3] U dun say so early hor... U c already then say...

 

 

이 텍스트들을 단어로 나누기 전에 변환 함수로 몇 단계를 더 진행해야 한다.

  1. 소문자로 전부 변경하기
  2. 숫자 제거하기
  3. 불용어 제거하기 (to, and, but, or 등)
  4. 마침표 제거하기
> Sys.setlocale("LC_ALL", "C") # 오류떠서
[1] "C"

> corpus_clean = tm_map(sms_corpus, tolower) # 소문자로
> corpus_clean = tm_map(corpus_clean, removeNumbers) # 숫자제거
> corpus_clean = tm_map(corpus_clean, removeWords, stopwords()) #불용어 제거
> corpus_clean = tm_map(corpus_clean, removePunctuation) # 마침표제거

> inspect(corpus_clean[1:3])
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 0
Content:  documents: 3

[1] ok lar joking wif u oni                                                                                        
[2] free entry    wkly comp  win fa cup final tkts st may  text fa    receive entry questionstd txt ratetcs apply s
[3] u dun say  early hor u c already  say

 

  • 정리하기 전과 후의 메세지를 비교해보면 깔끔하게 처리된 것을 확인할 수 있다.

 

정리하기 전 1~3번째 메세지 정리한 후 1~3번째 메세지
[1] Ok lar... Joking wif u oni...  [1] ok lar joking wif u oni 잊
[2] Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's [2] free entry    wkly comp  win fa cup final tkts st may  text fa    receive entry questionstd txt ratetcs apply s
[3] U dun say so early hor... U c already then say...     [3] u dun say  early hor u c already  say  

 

 

DocumentTermMatrix() 함수는 말뭉치를 입력 받아 sparse matrix라는 데이터 구조를 만든다.

> sms_dtm = DocumentTermMatrix(corpus_clean) # 희소 매트릭스 만들기
  • 이제 단어 빈도에 대한 분석을 시작할 수 있다.

 

 

3. 훈련 데이터와 테스트 데이터 셋 생성 (75:25)

원래 데이터, 희소 매트릭스, 말뭉치 모두 훈련 데이터와 테스트 데이터 셋으로 나눠주었다.

> # 훈련과 테스트 데이터셋 생성 
> # raw data
> sms_raw_train = sms_raw[1:4179, ]
> sms_raw_test = sms_raw[4180:5572, ]
> 
> # document term matrix
> sms_dtm_train = sms_dtm[1:4179, ]
> sms_dtm_test = sms_dtm[4180:5572, ]
> 
> # corpus(말뭉치)
> sms_corpus_train = corpus_clean[1:4179]
> sms_corpus_test = corpus_clean[4180:5572]
> 
> # 비율 확인
> prop.table(table(sms_raw_train$type))
      ham      spam 
0.8648002 0.1351998 

> prop.table(table(sms_raw_test$type))
      ham      spam 
0.8693467 0.1306533

 

 

4. 텍스트 데이터 시각화 - 단어 클라우드

단어 클라우드는 텍스트 데이터 단어의 빈도를 시각적으로 묘사해주는 좋은 도구이다.

min.freq=40으로 클라우드에 나오는 최소 빈도수를 정해주었다. (일반적으로 말뭉치의 10%로 설정)

> library(wordcloud)
> wordcloud(sms_corpus_train, min.freq=40, random.order=FALSE)

 

 

이번에는 일반 메세지와 스팸 메세지를 구별해 시각화해보자

> spam = subset(sms_raw_train, type == "spam")
> ham = subset(sms_raw_train, type == "ham")
> 
> wordcloud(spam$text, max.words=40, scale=c(3, 0.5))
> wordcloud(ham$text, max.words=40, scale=c(3, 0.5))

spam
ham

  • 스팸 메세지는call, prize, mobile, txt 등이 많은 반면 일반 메제니는 can, good, just 등의 단어가 눈에 띈다.

 

 

5. 데이터 준비 : 빈도에 대한 지표 특성 생성

데이터 준비의 최종 단계는 희소 매트릭스를 나이브 베이즈 분류기를 훈련시키기 위해 사용하는 데이터 구조로 변환하는 것이다.

> sms_dict = findFreqTerms(sms_dtm_train, 5)
> str(sms_dict)
 chr [1:1229] "lar" "wif" "apply" "comp" "cup" "entry" "final" "free" "may" ...
  • 빈도수가 5 이상인 단어를 포함하는 문자 벡터가 1229개인 것을 확인할 수 있다.

훈련과 테스트 매트릭스를 sms_dict에 있는 단어로 제한해주자

( 메세지에 1번 이상 나타났다고 해서 모든 단어를 사용하는 것은 분류 작업에 유용하지 않기 때문)

> sms_train = DocumentTermMatrix(sms_corpus_train, list(dictionary = sms_dict))
> sms_test = DocumentTermMatrix(sms_corpus_test, list(dictionary = sms_dict))

 

 

나이브 베이즈 분류기는 일반적으로 범주형으로 된 데이터에 대해 훈련한다.

희소 매트릭스의 칸은 메세지에 나타난 단어의 빈도를 나타내기 때문에 이를 바꿔주어야 한다.

희소 매트릭스가 단어를 나타내는지 판단해 Yes, No로 표시하는 팩터 변수로 변환해주자

> convert_counts = function(x) {
+   x = ifelse(x>0, 1, 0)
+   x = factor(x, levels=c(0, 1), labels=c("No", "Yes"))
+   return(x)
+ }
> sms_train = apply(sms_train, MARGIN=2, convert_counts)
> sms_test = apply(sms_test, MARGIN=2, convert_counts)
  • 행으로 구성된 메세지에 나타난 열의 각 단어에 대해 Yes, No로 표시하는 각 팩터형 열을 가진 두 매트릭스를 만들었다.
  • MARGIN=2는 열에 대한 것, MARGIN=1은 행에 대한 것

 

 

6. 모델 훈련 후 성능 평가

> # 모델 훈련
> # install.packages("e1071")
> library(e1071)
> sms_classifier = naiveBayes(sms_train, sms_raw_train$type)
> 
> 
> # 모델 성능 평가
> sms_test_pred = predict(sms_classifier, sms_test)
> library(gmodels)
> CrossTable(sms_test_pred, sms_raw_test$type, 
+            prop.chisq = F, prop.t = F,
+            dnn = c('predicted', 'actual'))

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|-------------------------|

 
Total Observations in Table:  1393 

 
             | actual 
   predicted |       ham |      spam | Row Total | 
-------------|-----------|-----------|-----------|
         ham |      1205 |        28 |      1233 | 
             |     0.977 |     0.023 |     0.885 | 
             |     0.995 |     0.154 |           | 
-------------|-----------|-----------|-----------|
        spam |         6 |       154 |       160 | 
             |     0.037 |     0.963 |     0.115 | 
             |     0.005 |     0.846 |           | 
-------------|-----------|-----------|-----------|
Column Total |      1211 |       182 |      1393 | 
             |     0.869 |     0.131 |           | 
-------------|-----------|-----------|-----------|


> (1205+154)/1393*100   #정확도 97.56%
[1] 97.55922
  • 정확도가 97.56%로 높은 정확도를 보여주고 있다.

 

 

7. 모델 성능 높이기

Naive bayes 함수에는 laplace라는 파라미터가 존재한다.

모델을 훈련할 때 Laplace 측정기에 대한 설정을 해 모델의 성능을 높이는 방법이 있다.

> # 모델 성능 향상
> sms_classifier2 = naiveBayes(sms_train, sms_raw_train$type, laplace=1)
> sms_test_pred2 = predict(sms_classifier2, sms_test)

> CrossTable(sms_test_pred2, sms_raw_test$type, 
+            prop.chisq = F, prop.t = F,
+            dnn = c('predicted', 'actual'))

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|-------------------------|

 
Total Observations in Table:  1393 

 
             | actual 
   predicted |       ham |      spam | Row Total | 
-------------|-----------|-----------|-----------|
         ham |      1189 |        10 |      1199 | 
             |     0.992 |     0.008 |     0.861 | 
             |     0.982 |     0.055 |           | 
-------------|-----------|-----------|-----------|
        spam |        22 |       172 |       194 | 
             |     0.113 |     0.887 |     0.139 | 
             |     0.018 |     0.945 |           | 
-------------|-----------|-----------|-----------|
Column Total |      1211 |       182 |      1393 | 
             |     0.869 |     0.131 |           | 
-------------|-----------|-----------|-----------|


> (1189+172)/1393*100  #정확도 97.7%
[1] 97.7028
  • 정확도 97.7%로 성능이 조금 높아진 것이 확인된다.

 

 

8. 나이브 베이즈 알고리즘의 장단점

장점 단점
1. 단순하고 빠르며 효과적이다.
2. noise와 missing value가 있어도 잘 작동한다.
3. 상대적으로 적은 데이터가 있어도 가능하다.
4. 예측에 대한 추정된 확률을 얻기 쉽다.
1. 모든 설명변수는 동일한 중요도를 가진다.
2. 모든 설명변수는 독립적이라는 가정을 한다.
3. 수치 속성으로 구성된 데이터 셋에 대해서는 이상적인 분류 알고리즘이 아니다.