확률의 이해
P(A): 사건(event) A가 일어날 확률
P(A) = (사건 A가 발생한 횟수) / (전체 시행 횟수)
상호 배타적이고 완전한(mutually exclusive and exhaustive) 사건
○ 스팸 메일 / 스팸이 아닌 메일
○ 동전 앞면 / 동전 뒷면
○ 비가 온다 / 비가 오지 않는다
베이즈 정리를 이용한 조건부 확률 계산
P(A|B): 사건 B가 발생한 경우 사건 A가 일어날 조건부 확률(conditional probability)
P(스팸|비아그라) = (스팸 갯수) / (비아그라가 들어있는 메일 갯수) = 4 / 5 = 0.8
P(비아그라|스팸) = (비아그라가 들어있는 메일 갯수) / (스팸 갯수) = 4 / 20 = 0.2
P(비아그라) = 5 / 100 = 0.05
P(스팸) = 20 / 100 = 0.2
결합확률 P(스팸∩비아그라) = 4 / 100 = 0.04
나이브 베이즈는 조건부 확률 모델이다. 분류될 인스턴스들은 N 개의 특성 (독립변수)을 나타내는 벡터 {\displaystyle \mathbf {x} =(x_{1},\dots ,x_{n})} 로 표현되며, 나이브 베이즈 분류기는 이 벡터를 이용하여 k개의 가능한 확률적 결과들 (클래스)을 다음과 같이 할당한다. - {\displaystyle p(C_{k}\vert x_{1},\dots ,x_{n})\,}
베이즈 정리와 조건부 확률을 이용하여 다음과 같이 정리 가능하다. - {\displaystyle p(C_{k}\vert \mathbf {x} )={\frac {p(C_{k})\ p(\mathbf {x} \vert C_{k})}{p(\mathbf {x} )}}.\,}
베이지안 확률 용어를 사용하여 위의 식은 다음과 같은 식으로도 표현 가능하다. (posterior : 사후 확률, prior : 사전확률, likelihood : 우도, evidence : 관찰값) - {\displaystyle {\mbox{posterior}}={\frac {{\mbox{prior}}\times {\mbox{likelihood}}}{\mbox{evidence}}}.\,}
실제로, 위 식에서 분자 부분만이 의미가 있다. 분모 부분의 경우에는 주어진 {\displaystyle C} 값에 의존하지 않고, 특성들의 값 {\displaystyle F_{i}}의 경우 분모의 값이 상수가 되도록 주어지기 때문이다. 분자 부분은 다음과 같은 결합확률 모델이다. - {\displaystyle p(C_{k},x_{1},\dots ,x_{n})\,}
위의 식은 조건부 확률을 반복적으로 적용한 연쇄 법칙을 사용하여 다음과 같이 다시 쓸 수 있다.
- {\displaystyle {\begin{aligned}p(C_{k},x_{1},\dots ,x_{n})&=p(C_{k})\ p(x_{1},\dots ,x_{n}\vert C_{k})\\&=p(C_{k})\ p(x_{1}\vert C_{k})\ p(x_{2},\dots ,x_{n}\vert C_{k},x_{1})\\&=p(C_{k})\ p(x_{1}\vert C_{k})\ p(x_{2}\vert C_{k},x_{1})\ p(x_{3},\dots ,x_{n}\vert C_{k},x_{1},x_{2})\\&=p(C_{k})\ p(x_{1}\vert C_{k})\ p(x_{2}\vert C_{k},x_{1})\ \dots p(x_{n}\vert C_{k},x_{1},x_{2},x_{3},\dots ,x_{n-1})\end{aligned}}}
이런 식으로 확장되는 벡터에 대하여 공식을 확대적용 할 수 있다.
(출처 : 위키피디아)
즉, n개의 특성(벡터)에 대하여 사전확률, 우도, 관찰값을 학습하면 관찰값에 대한 확률을 근거로 사후확률을 추정할 수 있다.
사후확률 계산값이 더 큰 경우로 스팸인지 아닌지 판단한다.
예를 들어 어떤 메일에 '비아그라', '돈', '구독취소'라는 문구가 들어있는 특성이 관찰된다면,
학습된 사전확률 등의 데이터를 근거로 이 메일이 스펨메일일 확률을 계산한다.
확률>0.5가 되면, 이 메일을 스펨메일로 분류한다.
○ 데이터 세트의 모든 변수(특징)은 동등하게 중요하고 독립적(independent)이다.
rm(list = ls())
# Naive Bayse를 이용한 스팸 SMS 분류
# 데이터 준비
sms_raw <- read.csv(file = 'mlwr/sms_spam.csv', stringsAsFactors = F,
encoding = 'UTF-8')
str(sms_raw)
head(sms_raw)
#type 변수(특징)을 factor로
sms_raw$type<-factor(sms_raw$type)
str(sms_raw$type)
table(sms_raw$type)
# Text Mining: SMS 메세지에 포함된 단어들을 분석
install.packages('tm')
library(tm)
search()
# SMS 메시지들의 전집(Corpus)을 생성
sms_corpus <- VCorpus(VectorSource(sms_raw$text))
print(sms_corpus)
# Corpus
> sms_corpus[[1]]
<<PlainTextDocument>>
Metadata: 7
Content: chars: 49
> sms_corpus[[1]]$content
[1] "Hope you are having a good week. Just checking in"
> sms_corpus[[1]]$meta
author : character(0)
datetimestamp: 2019-10-10 05:18:57
description : character(0)
heading : character(0)
id : 1
language : en
origin : character(0)
# 모든 메세지를 소문자로 변환 : tm_map 함수 : 함수값에 데이터 인자를 전달 lapply랑 비슷함.
# corpus 객체의 content만 소문자로 변환.
# 함수가 tm패키지의 함수가 아닌 경우 content_transformer 함수를 적용해야 함.
# 변환 함수가 tm 패키지에 있는 경우에는
# tm_map(corpus, 함수) 형식으로 호출
sms_corpus_clean <- tm_map(sms_corpus, content_transformer(tolower))
> sms_corpus_clean[[1072]]$content
[1] "all done, all handed in. don't know if mega shop in asda counts as celebration but thats what i'm doing!"
# 숫자들을 제거
sms_corpus_clean <- tm_map(sms_corpus_clean,removeNumbers)
sms_corpus_clean[[4]]$content
[1] "complimentary star ibiza holiday or £, cash needs your urgent collection. now from landline not to lose out! boxskwpppm+"
# punctuation(구두점, 문장부호) 제거
sms_corpus_clean <- tm_map(sms_corpus_clean, removePunctuation)
sms_corpus_clean[[1072]]$content
[1] " done handed know mega shop asda counts celebration thats "
# 단어나 구두점 등을 제거하면서 생긴 추가적인 공백들을 제거
sms_corpus_clean <- tm_map(sms_corpus_clean, stripWhitespace)
sms_corpus_clean[[1072]]$content
[1] " done handed know mega shop asda counts celebration thats "
# 형태소 분석
install.packages('SnowballC') # 형태소 분석시 필요한 패키지
library(SnowballC)
search()
wordStem(c('learn','learning','learned','learner'))
[1] "learn" "learn" "learn" "learner"
> sms_corpus_clean[[1072]]$content
[1] "done hand know mega shop asda count celebr that"
# DTM(Document-Term Matrix): 문서-단어 행렬
# 행(row): Document - 문자 메시지, 이메일
# 열(column) : 모든 문서에서 추출한 단어들
sms_dtm <- DocumentTermMatrix(sms_corpus_clean)
str(sms_dtm)
List of 6
$ i : int [1:42110] 1 1 1 1 1 2 2 2 3 3 ...
$ j : int [1:42110] 958 2269 2568 2923 6190 428 2975 5611 194 907 ...
$ v : num [1:42110] 1 1 1 1 1 1 1 1 1 1 ...
$ nrow : int 5559
$ ncol : int 6539
$ dimnames:List of 2
..$ Docs : chr [1:5559] "1" "2" "3" "4" ...
..$ Terms: chr [1:6539] "‘morrow" "‘rent" "’llspeak" "’re" ...
- attr(*, "class")= chr [1:2] "DocumentTermMatrix" "simple_triplet_matrix"
- attr(*, "weighting")= chr [1:2] "term frequency" "tf"
# DTM를 사용해서 학습 데이터 세트(0.75)와 테스트 데이터 세트(0.25)로 나눔
sms_dtm_train <- sms_dtm[1:4169,]
sms_dtm_test <- sms_dtm[4170:5559,]
# 학습 데이터 레이블, 테스트 데이터 레이블 (Spam / Ham)
sms_dtm_train_label <- sms_raw[1:4169,1]
sms_dtm_test_label <- sms_raw[4170:5559,1]
> table(sms_dtm_train_label)
sms_dtm_train_label
ham spam
3605 564
> table(sms_dtm_test_label)
sms_dtm_test_label
ham spam
1207 183
# DTM은 희소 행렬(sparse matrix):
# 행렬의 대부분의 원소들의 값이 0이고,
# 0이 아닌 값을 갖는 원소들은 매우 희소한 행렬
# DTM에서 자주 등장하는 단어(Term)들만 선택해보자.
freq_terms <- findFreqTerms(sms_dtm_train, lowfreq = 5) # 적어도 5번 이상 등장하는 단어 선택
sms_dtm_freq_train <- sms_dtm_train[ , freq_terms ] # freq_terms는 컬럼 이름을 뜻한다
sms_dtm_freq_test <- sms_dtm_test[ , freq_terms]
> str(sms_dtm_test)
List of 6
$ i : int [1:10519] 1 1 1 2 2 2 2 2 2 2 ...
$ j : int [1:10519] 1103 1480 2548 849 1103 2798 4191 4244 4707 6192 ...
$ v : num [1:10519] 1 1 1 1 1 1 1 1 1 1 ...
$ nrow : int 1390
$ ncol : int 6539
$ dimnames:List of 2
..$ Docs : chr [1:1390] "4170" "4171" "4172" "4173" ...
..$ Terms: chr [1:6539] "‘morrow" "‘rent" "’llspeak" "’re" ...
- attr(*, "class")= chr [1:2] "DocumentTermMatrix" "simple_triplet_matrix"
- attr(*, "weighting")= chr [1:2] "term frequency" "tf"
> str(sms_dtm_freq_test)
List of 6
$ i : int [1:7981] 1 1 1 2 2 2 2 2 2 3 ...
$ j : int [1:7981] 175 247 439 131 175 723 735 814 1084 192 ...
$ v : num [1:7981] 1 1 1 1 1 1 1 1 1 1 ...
$ nrow : int 1390
$ ncol : int 1137
$ dimnames:List of 2
..$ Docs : chr [1:1390] "4170" "4171" "4172" "4173" ...
..$ Terms: chr [1:1137] "£wk" "abiola" "abl" "abt" ...
- attr(*, "class")= chr [1:2] "DocumentTermMatrix" "simple_triplet_matrix"
- attr(*, "weighting")= chr [1:2] "term frequency" "tf"
# 우리가 사용할 패키지의 나이브 베이즈 알고리즘 함수는 명목형 변수들만 처리할 수 있다.
# DTM에서 각 원소의 값이 0보다 크면 'Y', 그렇지 않으면 'N'
# 변환 함수
convert_content<- function(x){
x <- ifelse (x>0, 'Y','N')
return (x)
}
sms_train<- apply ( X = sms_dtm_freq_train,
MARGIN = 2,
FUN = convert_content)
sms_test<- apply ( X = sms_dtm_freq_test,
MARGIN = 2,
FUN = convert_content)
# MARGIN = 1 : FUN을 호출할 때 X의 행(row)을 파라미터로 전달
# MARGIN = 2 : FUN을 호출할 때 X의 열(column)을 파라미터로 전달
# 나이브 베이즈 분류 알고리즘을 구현한 패키지를 설치
install.packages('e1071')
library(e1071)
# 학습 데이터 세트를 가지고 분류기(classifier)를 생성
sms_classifier <- naiveBayes(sms_train, sms_dtm_train_label)
#분류기를 사용해서 테스트 데이터의 분류 결과 예측
sms_pred<- predict(sms_classifier, sms_test)
sms_pred
#분류기를 사용해서 테스트 데이터의 분류 결과 예측
sms_pred<- predict(sms_classifier, sms_test)
sms_pred
library(gmodels)
CrossTable(x = sms_dtm_test_label, # x = 행, y = 열
y = sms_pred,
prop.chisq = F)