머신러닝 / 나이브 베이즈 알고리즘
44 나이브 베이즈 알고리즘 이론 설명
ㅁ머신러닝 종류 3가지
1. 지도학습 : 정답이 있는 데이터를 기계가 학습
- 분류 : knn(3장), naivebayes(4장)
- 회귀(수치예측)
2. 비지도학습 : 정답이 없는 데이터를 기계가 학습
3. 강화학습 : 주어진 환경을 기계가 스스로 이해하면서 데이터를 만들어가며 학습하는 것
ㅇ나이브 베이즈 알고리즘이 사용되는 분야 p147
1. 스팸 이메일 필터링과 같은 텍스트 분류
2. 컴퓨터 네트워크에서 침입이나 비정상적인 행위 탐지
3. 일련의 관찰된 증상에 따른 의학적 질병 진단
ㅇ나이브 베이즈 알고리즘 이론 설명
p154. 용어정리
사전확률 : 이미 알고 있는 사건의 확률
우도 : 이미 알고 있는 사건이 발생했다는 조건하에 다른 사건이 발생할 확률
사후확률 : 사전확률과 우도 확률을 통해서 알게되는 조건부 확률
나이브 베이즈 : 실제로 독립사건들로 볼 수 없지만, 모두 독립사건으로 순수하게 가정하여 보자는 것.
A와 B가 독립일 때, P(A∩B) = P(A) x P(B)
따라서, P(W1∩¬W2∩¬W3∩W4|SPAM) 일 확률은 각각의 독립사건이므로,
P(W1∩SPAM) x P(¬W2∩SPAM) x P(¬W3∩SPAM) x P(W4∩SPAM)로 나타낼 수 있음.
¬ : 존재하지 않는다(부정)
∃ : 존재한다(긍정)
증거를 많이 확보하는 게 중요.
ㅇ 책의 예제로 보는 나이브베이즈
p.154
예제1. 비아그라가 포함되어져 있는 메일이 스팸메일일 확률을 구하시오.
P(비아그라|스팸) * P(스팸)
P(스팸|비아그라) = -----------------------------------
P(비아그라)
위의 결과 확률이 50%를 넘어가게 되면 이 메일은 햄보다는 스팸메일일 가능성이 더 크다.
P(비아그라|스팸) * P(스팸) 4/20 * 20/100
P(스팸|비아그라) = ----------------------------------- = ----------------------- = 0.8
P(비아그라) 5/100
비아그라 단어 하나만 가지고 스팸 메일인지를 분류하려면 정확하게 분류가 안될 수 있으니 다른 단어들도 같이 포함시켜야 합니다.
만약, P(스팸|비아그라 ∩¬돈 ∩ ¬식료품 ∩ 구독취소)의 확률은?
비아그라와 구독취소를 포함되어져 있는데 돈과 식료품은 포함되지 않은 메일이 스팸일 확률?
P(비아그라 ∩¬돈 ∩ ¬식료품 ∩ 구독취소|스팸) * P(스팸)
P(스팸|비아그라 ∩¬돈 ∩ ¬식료품 ∩ 구독취소) = ---------------------------------------------------------------------
P(비아그라 ∩¬돈 ∩ ¬식료품 ∩ 구독취소)
P(비아그라|스팸) * P(¬돈|스팸) * P(¬식료품|스팸) * P(구독취소|스팸) * P(스팸)
= -----------------------------------------------------------------------------------------------------
분모무시
p.157
분모는 타킷 클래스(스팸이나 햄)에 종속되지 않기 때문에 상수값으로 취급하며, 당분간 무시할 수 있음. 따라서 스팸에 대한 조건부 확률은 다음과 같이 표현됨.
P(비아그라|스팸) * P(¬돈|스팸) * P(¬식료품|스팸) * P(구독취소|스팸) * P(스팸)
= 4/20 * 10/20 * 20/20 * 12/20 * 20/100 = 0.012
P(비아그라|햄) * P(¬돈|햄) * P(¬식료품|햄) * P(구독취소|햄) * P(햄)
= 1/80 * 66/80 * 71/80 * 23/80 * 80/100 = 0.002
p.156
스팸의 전체 우도? 0.012
햄의 전체 우도? 0.002
0.012
스팸일 확률? ------------------------- = 0.857
0.012 + 0.002
0.002
햄일 확률? ------------------------- = 0.143
0.012 + 0.002
이 메시지에 있는 단어 패턴에 대해 85.7%의 확률로 메시지가 스팸이고 14.3%의 확률로 햄이라고 예상한다.
ㅁ라플라스 추정기(p159)
P(스팸|비아그라 ∩돈 ∩ 식료품 ∩ 구독취소) = ?
P(비아그라|스팸) * P(돈|스팸) * P(식료품|스팸) * P(구독취소|스팸) * P(스팸)
= 4/20 * 10/20 * 0/20 * 12/20 * 20/100 = 0
위와 같이 분자의 요소 하나가 0이면 전체가 0이 되면서 스팸의 우도가 0이 되어버림. 그러면 더 이상 계산을 진행할 수 없게 됨. 그래서 수학자 라플라스는 0을 1로 만들어주면서 아래와 같이 1을 다 더했음.
스팸의 우도?
P(비아그라|스팸) * P(돈|스팸) * P(식료품|스팸) * P(구독취소|스팸) * P(스팸)
= 5/24 * 11/24 * 1/24 * 13/24 * 20/100 = 0.0004
햄의 우도?
P(비아그라|햄) * P(돈|햄) * P(식료품|햄) * P(구독취소|햄) * P(햄)
= 2/84 * 67/84 * 9/84 * 24/84 * 80/100 = 0.0001
아주 작은값을 하나 더해서 계산이 될 수 있도록 하는데 이 값을 라플라스 값이라고 함.
knn일때는 k값을 조정해서 knn모델의 성능을 높였는데 나이브베이즈는 라플라스 값을 조정해서 나이브베이즈 모델의 성능을 높임.
하이퍼 파라미터? 머신러닝의 성능을 높이기 위해서 모델 개발자가 직접 조정해줘야하는 파라미터를 뜻함. 예를 들어 knn의 k값, 나이브베이즈의 laplace값
45 R을 이용해서 나이브베이즈 머신러닝 모델 만들기
"정상 버섯과 독버섯을 분류하는 머신러닝 모델 생성하기"
1. 데이터 로드
# R
mush <- read.csv("c:\\data\\mushrooms.csv", stringsAsFactors = TRUE)
head(mush)
str(mush)
> 데이터가 전부 문자형임을 확인함.
table(mush$type)
edible poisonous
4208 3916
> 정상버섯과 독버섯의 개수를 확인함
prop.table( table(mush$type) )
edible poisonous
0.5179714 0.4820286
> 정상버섯과 독버섯의 비율을 확인함.
> 두개가 딱 절반이어서 독버섯도 잘 학습할 수 있고 정상버섯도 잘 학습할 수 있음.
dim(mush)
8124 23
> 전체건수와 컬럼의 개수를 확인하는 방법. mushrooms.csv에는 23개의 컬럼, 8124건의 데이터가 있음.
2. 결측치 확인
colSums(is.na(mush))
> 결측치가 하나도 없음을 확인함
3. 이상치를 확인
> 전부 문자형 데이터라서 이상치를 확인할 필요 없음
4. 문자형인지 숫자형인지 데이터타입 확인
> 전부 문자형이라서 데이터타입 확인 필요하지 않음.
5. 데이터 정규화
> 전부 문자형이어서 정규화 작업도 필요하지 않음.
6. 훈련 데이터와 테스트 데이터를 분리
훈련과 테스트 데이터를 분리하면서 정상 버섯과 독버섯의 비율도 잘 분포될 수 있도록 분리하는 방법
> caret 패키지 안의 메소드 사용
install.packages("caret")
library(caret)
set.seed(1)
k <- createDataPartition(mush$type, p=0.8, list=F) #훈련데이터 80%, 테스트데이터 20%, type컬럼 기준
k #훈련데이터(80%) 행의 인덱스 번호가 출력됨
train_data <- mush[k, ]
test_data <- mush[-k, ]
dim(train_data)
dim(test_data)
k <- createDataPartition(mush$type, p=0.8, list=F)
#훈련데이터 80%, 테스트데이터 20%, type컬럼 기준, list = F를 써줘야 k가 행렬로 출력됨. dim(k)를 해보면 행렬로 출력됨을 확인할 수 있음. list=T가 기본값이고 이대로 dim(k)를 하면 null로 출력됨
> dim(train_data)
[1] 6500 23
> dim(test_data)
[1] 1624 23
+) 굳이 list = F일 필요는 없음.
trainIndex = createDataPartition(mush$type, p=0.8)
train = mush[trainIndex$Resample1, ]
test = mush[-trainIndex$Resample1, ]
행 검색 방식의 차이임.
prop.table(table(train_data$type))
prop.table(table(test_data$type))
> prop.table(table(train_data$type))
edible poisonous
0.518 0.482
> prop.table(table(test_data$type))
edible poisonous
0.5178571 0.4821429
훈련데이터와 테스트 데이터 모두 정상버섯과 독버섯이 50:50의 비율로 균등하게 잘 분포되었음.
7. 나이브베이즈 모델 생성
install.packages("e1071")
library(e1071)
model <- naiveBayes(type~ ., data = train_data)
model # mush데이터의 빈도표로 우도표를 생성함
> type은 정답컬럼이고 ~(물결) 다음에 다른 데이터 컬럼들을 써주는데 .(점) 하나만 찍으면 모든 다른 데이터 컬럼들을 다 가리킴(일일이 쓰지 않아도 됨).
8. 훈련 데이터로 모델 훈련
> 7번에서 훈련을 다 마침
9. 훈련된 모델로 테스트 데이터를 예측
result <- predict(model, test_data[ ,-1])
result
> test_data[ ,-1] : type컬럼 빼고 데이터를 입력함. type컬럼은 정답컬럼이기 때문
+) R에서 컬럼 검색할 때 특정 컬럼을 제외하고 출력하고 싶다면 컬럼 앞에 - (마이너스)를 붙이면 됨
length(result)
length(test_data[ ,1])
> 예측데이터와 테스트 결과 데이터의 양이 같음을 확인할 수 있음.
10. 모델의 성능을 평가
sum(result == test_data[,1]) / length(test_data[,1]) * 100
> 93.78079 의 정확도를 보임
> 정확도를 높이는 것과 더불어 FN값을 줄이는게 중요함
library(gmodels)
CrossTable( x=test_data[ ,1], y=result )
> gmodels를 라이브러리 해서 이원교차표 확인 : CrossTable( x = 실제값, y = 예측값)
> 이원교차표에서 FN값이 너무 높은 것을 확인할 수 있음. 모델의 성능을 높일 필요가 있음.
11. 모델의 성능 높이기
# 라플라스
library(e1071)
model2 <- naiveBayes(type~ ., data = train_data, laplace = 0.0001)
result2 <- predict(model2, test_data[ ,-1])
sum(result2 == test_data[,1]) / length(test_data[,1]) * 100
> 99.50739의 정확도. 라플라스 값을 지정했더니 성능이 높아짐. 라플라스는 아주 작은 값(ex.0.0001)부터 하나씩 늘려가면서 넣어보면 됨.
library(gmodels)
CrossTable(x=test_data[,1], y = result2)
> 이원교차표를 확인해보니 FN이 6개로 줄었음.
문제239. 위의 이원교차표에서 FN값만 추출하시오.
library(gmodels)
a <- CrossTable(x=test_data[,1], y = result2)
a$t[2,1]
문제240. FN값이 0이 되는 laplace값을 알아내시오.
# 다른 사람이 푼 코드 참고
lap <- c()
for (i in seq(1e-5,1e-3,1e-05)){ library(e1071)
model2 <- naiveBayes(type~ ., data = train_data, laplace = i)
result2 <- predict(model2, test_data[ ,-1])
sum(result2 == test_data[,1]) / length(test_data[,1]) * 100
library(gmodels)
a <- CrossTable(x=test_data[,1], y = result2)
if(a$t[2,1] <=1){lap <- c(lap, paste ( i,'일때 FN값', a$t[2,1])) }}
print(lap)
이원교차표를 사용하지 않고 출력하는 방법
# 내가 푼 코드
for (i in seq(1e-5,1e-3,1e-05)){ library(e1071)
model2 <- naiveBayes(type~ ., data = train_data, laplace = i)
result2 <- predict(model2, test_data[ ,-1])
sum(result2 == test_data[,1]) / length(test_data[,1]) * 100
library(gmodels)
#a = CrossTable(x=test_data[,1], y = result2)
a <- table( x=test_data[,1] , y = result2) # 이원교차표 보기 싫어서
if(a[2,1] <= 1){print(paste ( i,'일때 FN값', a[2,1])) }}
ㅇ나이브 베이즈 전체 코드
# R
# 1. 데이터 로드
mush <- read.csv("c:\\data\\mushrooms.csv", stringsAsFactors = TRUE)
head(mush)
str(mush)
# 2. 문자형 확인
table(mush$type)
# 3. 비율 확인
prop.table( table(mush$type) )
# 4. 결측치 확인
colSums(is.na(mush))
# 5. 훈련데이터와 테스트 데이터 분리
#install.packages("caret")
library(caret)
set.seed(1)
k <- createDataPartition(mush$type, p=0.8, list=F) #훈련데이터 80%, 테스트데이터 20%, type컬럼 기준
k #훈련데이터(80%) 행의 인덱스 번호가 출력됨
train_data <- mush[k, ]
test_data <- mush[-k, ]
# 데이터가 잘 나뉘어 졌는지 확인
dim(train_data) # 6500 23
dim(test_data) # 1624 23
prop.table(table(train_data$type))
prop.table(table(test_data$type))
# 6. 나이브베이즈 모델 생성
#install.packages("e1071")
library(e1071)
model <- naiveBayes(type~ ., data = train_data)
model # mush데이터의 빈도표로 우도표를 생성함, 훈련을 마침
# 7. 훈련된 모델로 테스트 데이터 예측
result <- predict(model, test_data[ ,-1])
result
# 데이터 양이 같음을 확인함
length(result)
length(test_data[ ,1])
# 8. 모델 성능 평가
sum(result == test_data[,1]) / length(test_data[,1]) * 100
# 이원교차표
library(gmodels)
CrossTable( x=test_data[ ,1], y=result )
# 9. 모델 성능 높이기
# 라플라스
for (i in seq(1e-5,1e-3,1e-05)){ library(e1071)
model2 <- naiveBayes(type~ ., data = train_data, laplace = i)
result2 <- predict(model2, test_data[ ,-1])
sum(result2 == test_data[,1]) / length(test_data[,1]) * 100
library(gmodels)
#a = CrossTable(x=test_data[,1], y = result2)
a <- table( x=test_data[,1] , y = result2) # 이원교차표 보기 싫어서
if(a[2,1] <= 1){print(paste ( i,'일때 FN값', a[2,1])) }}
문제241. 독감 데이터로(flucsv)로 나이브 베이즈 모델을 생성해서 독감환자인지 아닌지 분류하는 모델을 생성하시오.
1. 데이터 로드
# 데이터 로드
flu <- read.csv("c:\\data\\flu.csv", stringsAsFactors = TRUE)
flu
컬럼 설명
patient_id : 환자번호, chills : 오한, runny_nose : 콧물, headache : 두통, fever : 열, flue : 독감여부
2. 결측치 확인
colSums(is.na(flu))
3. 이상치 확인
전부 문자형 데이터이기 때문에 이상치 확인할 필요 없음
4. 문자형 데이터인지 확인
str(flu)
5. 데이터 정규화
전부 문자형 데이터이기 때문에 정규화 필요 없음
6. 훈련데이터와 테스트 데이터를 분리
train_data <- flu[1:7,-1] # -1은 patient_id컬럼을 제외시키는 것, 환자 1~7번까지 가져옴
test_data <- flu[8,-1]
7. 훈련 데이터로 나이브 베이즈 모델 생성
8. 모델 평가
9. 모델 성능을 높임
library(e1071)
model_flu <- naiveBayes(train_data[ ,-5], train_data$flu )
model_flu
result_flu <- predict(model_flu, test_data[ ,-5])
result_flu
sum(result_flu == test_data[,5]) / length(test_data[,5]) * 100
문제242. 위에서 만든 model_flu를 가지고 다음의 환자가 독감환자인지 아닌지 예측하시오.
오한(chills)이 있고 콧물(runny_nose)가 없으며 두통(headache)가 없으며 열(fever)이 있는 환자
test_data2 <- data.frame(chills = 'Y',runny_nose='N',headache='N',fever='Y')
result2 <- predict(model_flu,test_data2)
result2
> Y로 예측
참고블로그)
https://m.blog.naver.com/pmw9440/221891785884