본문 바로가기
Drawing (AI)/MachineLearning

Udemy - 머신러닝의 모든 것 (Naive Bayes)

by 생각하는 이상훈 2023. 6. 20.
728x90

Bayes theorem (베이즈 정리)

베이즈 정리는 데이터라는 조건이 주어졌을 때의 조건부확률을 구하는 공식이다. 베이즈 정리를 쓰면 데이터가 주어지기 전의 사전확률값이 데이터가 주어지면서 어떻게 변하는지 계산할 수 있다. 따라서 데이터가 주어지기 전에 이미 어느 정도 확률값을 예측하고 있을 때 이를 새로 수집한 데이터와 합쳐서 최종 결과에 반영할 수 있다. 데이터의 개수가 부족한 경우 아주 유용하다. 데이터를 매일 추가적으로 얻는 상황에서도 매일 전체 데이터를 대상으로 새로 분석작업을 할 필요없이 어제 분석결과에 오늘 들어온 데이터를 합쳐서 업데이트만 하면 되므로 유용하게 활용할 수 있다.

베이즈 정리의 식은 아래와 같이 쓸 수 있다.

  • 𝑃(𝐴|𝐵): 사후확률(posterior). 사건 B가 발생한 후 갱신된 사건 A의 확률
  • 𝑃(𝐴): 사전확률(prior). 사건 B가 발생하기 전에 가지고 있던 사건 A의 확률
  • 𝑃(𝐵|𝐴): 가능도(likelihood). 사건 A가 발생한 경우 사건 B의 확률
  • 𝑃(𝐵): 정규화 상수(normalizing constant) 또는 증거(evidence). 확률의 크기 조정

베이즈 정리는 사건 𝐵가 발생함으로써(사건 𝐵가 진실이라는 것을 알게 됨으로써, 즉 사건 𝐵의 확률 𝑃(𝐵)=1이라는 것을 알게 됨으로써) 사건 𝐴의 확률이 어떻게 변화하는지를 표현한 정리다. 따라서 베이즈 정리는 새로운 정보가 기존의 추론에 어떻게 영향을 미치는지를 나타낸다. 이제 베이즈 정리를 전체 확률로 확장해보자.

만약 사건 𝐴𝑖가 서로 배타적이고 완전하다고 하면 아래의 두 식이 성립한다.

𝐴𝑖 𝐴𝑗 =

𝐴1 𝐴2 = Ω

위의 베이즈 정리dp 응용하여 나이브 베이즈라는 classifier 메커니즘이 고안되었다.


나이브 베이즈 분류기

나이브 베이즈 분류기는 위의 베이즈 정리를 이용하여 분류를 수행한다. 예를 들어서 나이브 베이즈 분류기를 통해서 스팸 메일 필터를 만들어본다고 하자. 입력 텍스트가 주어졌을 때, 입력 텍스트가 정상 메일인지 스팸 메일인지 구분하기 위한 확률을 이와 같이 표현할 수 있다.

 

P(정상 메일 | 입력 텍스트) = 입력 텍스트가 있을 때 정상 메일일 확률
P(스팸 메일 | 입력 텍스트) = 입력 텍스트가 있을 때 스팸 메일일 확률

 

이를 베이즈의 정리에 따라서 식을 표현하면 아래와 같다.

 

P(정상 메일 | 입력 텍스트) = (P(입력 텍스트 | 정상 메일) × P(정상 메일)) / P(입력 텍스트)
P(스팸 메일 | 입력 텍스트) = (P(입력 텍스트 | 스팸 메일) × P(스팸 메일)) / P(입력 텍스트)

 

입력 텍스트가 주어졌을 때, P(정상 메일 | 입력 텍스트)가 P(스팸 메일 | 입력 텍스트)보다 크다면 정상 메일이라고 볼 수 있으며, 그 반대라면 스팸 메일이라고 볼 수 있다. 그런데 두 확률 모두 식을 보면 P(입력 텍스트)를 분모로 하고 있음을 알 수 있다. 그렇기 때문에 분모를 양쪽에서 제거하여 식을 간소화한다.

 

P(정상 메일 | 입력 텍스트) = P(입력 텍스트 | 정상 메일) × P(정상 메일)
P(스팸 메일 | 입력 텍스트) = P(입력 텍스트 | 스팸 메일) × P(스팸 메일)

 

만약 메일의 본문에 있는 단어가 3개라고 보면 기본적으로 나이브 베이즈 분류기는 모든 단어가 독립적이라고 가정한다. 메일의 본문에 있는 단어 3개를 w1, w2, w3라고 표현한다면 결국 나이브 베이즈 분류기의 정상 메일일 확률과 스팸 메일일 확률을 구하는 식은 아래와 같다.

P(정상 메일 | 입력 텍스트) = P(w1 | 정상 메일) × P(w2 | 정상 메일) × P(w3 | 정상 메일) × P(정상 메일)
P(스팸 메일 | 입력 텍스트) = P(w1 | 스팸 메일) × P(w2 | 스팸 메일) × P(w3 | 스팸 메일) × P(스팸 메일)

 

식에서 알 수 있듯이 나이브 베이즈 분류기에서 토큰화 이전의 단어의 순서는 중요하지 않다. 즉, BoW와 같이 단어의 순서를 무시하고 오직 빈도수만을 고려한다. 이제 실제 단어들로 이루어진 예제를 통해서 확률을 구해보자.


스팸 메일 분류기(Spam Detection)

아래와 같이 labeling되어있는 train set이 존재한다고 쳤을때 확률을 직접 구해보자.

you free lottery라는 입력 텍스트에 대해서 정상 메일일 확률과 스팸 메일일 확률 각각을 구해보자.

 

P(정상 메일 | 입력 텍스트) = P(you | 정상 메일) × P(free | 정상 메일) × P(lottery | 정상 메일) × P(정상 메일)
P(스팸 메일 | 입력 텍스트) = P(you | 스팸 메일) × P(free | 스팸 메일) × P(lottery | 스팸 메일) × P(스팸 메일)

P(정상 메일) = P(스팸 메일) = 총 메일 6개 중 3개 = 0.5


위 예제에서는 P(정상 메일)과 P(스팸 메일)의 값은 같으므로, 두 식에서 두 개의 확률은 생략이 가능하다.

 

P(정상 메일 | 입력 텍스트) = P(you | 정상 메일) × P(free | 정상 메일) × P(lottery | 정상 메일)
P(스팸 메일 | 입력 텍스트) = P(you | 스팸 메일) × P(free | 스팸 메일) × P(lottery | 스팸 메일)

 

P(you | 정상 메일)을 구하는 방법은 정상 메일에 등장한 모든 단어의 빈도 수의 총합을 분모로하고, 정상 메일인 'you free scholarship', 'free to contact me', 'you won award 에서 you가 총 등장한 빈도의 수를 분자로 하는 것이다. 이 경우에는 2/10 = 0.2가 된다. 이와 같은 원리로 식을 전개하면 이와 같습니다.

 

P(정상 메일 | 입력 텍스트) = 2/10 × 2/10 × 0/10 = 0
P(스팸 메일 | 입력 텍스트) = 2/10 × 3/10 × 2/10 = 0.012

 

결과적으로 P(정상 메일 | 입력 텍스트) < P(스팸 메일 | 입력 텍스트)이므로 입력 텍스트 you free lottery는 스팸 메일로 분류된다.

그런데 예제를 보니 이상한 점이 보인다. 물론, 직관적으로 보기에도 you, free, lottery라는 단어가 스팸 메일에서 빈도수가 더 높기때문에 스팸 메일인 확률이 더 높은 것은 확실하다. 그러나 입력 텍스트에 대해서 단, 하나의 단어라도 훈련 텍스트에 없었다면 확률 전체가 0이 되는 것은 지나친 일반화이다. 이 경우에는 정상 메일에 lottery가 단 한 번도 등장하지 않았고, 그 이유로 정상 메일일 확률 자체가 0%가 되었다.

이를 방지하기 위해서 나이브 베이즈 분류기에서는 각 단어에 대한 확률의 분모, 분자에 전부 숫자를 더해서 분자가 0이 되는 것을 방지하는 라플라스 스무딩을 사용하기도 한다.


라플라스 스무딩(Laplace Smoothing)

머신 러닝에서는 보통 데이터의 양이 한정적인데 이때 관찰된 값 중에 특히 특성의 값이 0일 때가 있다. 이런 값들은 모델 학습을 시킬때 통계학적 수치들을 이용하는 과정에 계산에 방해가 된다. 이런 문제를 제로 확률 문제(Zero Probability Problem)이라고 한다. 라플라스 스무딩은 이문제를 해결하기 위한 방법으로 추정치를 구할 때 임의의 새로운 모수 a를 각항에 더해줘서 추정치들이 0에 영향을 덜 받도록 한다.

a를 0과 1 사이의 상수 값으로 설정하여 우도를 계산하면 우도가 0이 되는 것을 방지한다.


Source code

# Naive Bayes
# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Social_Network_Ads.csv')
X = dataset.iloc[:, :-1].values
y = dataset.iloc[:, -1].values

# Splitting the dataset into the Training set and Test set
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, random_state = 0)

# Feature Scaling
from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X_train = sc.fit_transform(X_train)
X_test = sc.transform(X_test)

# Training the Naive Bayes model on the Training set
from sklearn.naive_bayes import GaussianNB
classifier = GaussianNB()
classifier.fit(X_train, y_train)

# Predicting a new result
print(classifier.predict(sc.transform([[30,87000]])))
[0]

# Predicting the Test set results
y_pred = classifier.predict(X_test)
print(np.concatenate((y_pred.reshape(len(y_pred),1), y_test.reshape(len(y_test),1)),1))
[[0 0]
 [0 0]
 [0 0]
 [0 0]
 [0 0]
 [0 0]
 [0 0]
 [1 1]
 [0 0]
   .
   .
   .
   
# Making the Confusion Matrix

from sklearn.metrics import confusion_matrix, accuracy_score
cm = confusion_matrix(y_test, y_pred)
print(cm)
accuracy_score(y_test, y_pred)

[[65  3]
 [ 7 25]]
0.9
#Visualising the Training set results

from matplotlib.colors import ListedColormap
X_set, y_set = sc.inverse_transform(X_train), y_train
X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 10, stop = X_set[:, 0].max() + 10, step = 0.25),
                     np.arange(start = X_set[:, 1].min() - 1000, stop = X_set[:, 1].max() + 1000, step = 0.25))
plt.contourf(X1, X2, classifier.predict(sc.transform(np.array([X1.ravel(), X2.ravel()]).T)).reshape(X1.shape),
             alpha = 0.75, cmap = ListedColormap(('salmon', 'dodgerblue')))
plt.xlim(X1.min(), X1.max())
plt.ylim(X2.min(), X2.max())
for i, j in enumerate(np.unique(y_set)):
    plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1], c = ListedColormap(('salmon', 'dodgerblue'))(i), label = j)
plt.title('Naive Bayes (Training set)')
plt.xlabel('Age')
plt.ylabel('Estimated Salary')
plt.legend()
plt.show()

# Visualising the Test set results
from matplotlib.colors import ListedColormap
X_set, y_set = sc.inverse_transform(X_test), y_test
X1, X2 = np.meshgrid(np.arange(start = X_set[:, 0].min() - 10, stop = X_set[:, 0].max() + 10, step = 0.25),
                     np.arange(start = X_set[:, 1].min() - 1000, stop = X_set[:, 1].max() + 1000, step = 0.25))
plt.contourf(X1, X2, classifier.predict(sc.transform(np.array([X1.ravel(), X2.ravel()]).T)).reshape(X1.shape),
             alpha = 0.75, cmap = ListedColormap(('salmon', 'dodgerblue')))
plt.xlim(X1.min(), X1.max())
plt.ylim(X2.min(), X2.max())
for i, j in enumerate(np.unique(y_set)):
    plt.scatter(X_set[y_set == j, 0], X_set[y_set == j, 1], c = ListedColormap(('salmon', 'dodgerblue'))(i), label = j)
plt.title('Naive Bayes (Test set)')
plt.xlabel('Age')
plt.ylabel('Estimated Salary')
plt.legend()
plt.show()


 

728x90