naive bayesian classification model

나이브베이즈 분류모형

나이브 가정

모든 차원의 개별 독립변수 요소들이 서로 조건부 독립이라는 가정을 나이브 가정이라고 한다. 이 가정은 그냥 생각해봐도 말이 안 된다. 예를 들어 iris데이터에서 독립변수 $x_1$은 꽃잎의 길이, $x_2$는 꽃잎의 폭이라고 할 때, 꽃잎의 길이가 길어지면 상식적으로 폭도 커지므로 두 변수 사이에는 매우 큰 상관관계가 있다. 그런데 수많은 데이터에서 상관관계를 모두 구하기가 현실적으로 힘들기 때문에 어떨 수 없이 나이브하게 변수들이 서로 독립이라고 가정하는 것이다. 이 가정을 베이즈 분류모형에 적용한 것이 나이브 베이즈 분류모형(Naive Bayes classification model) 이다.

나이브베이즈 분류모형에서는 데이터들이 서로 독립이라서, 데이터들의 확률분포가 개별 데이터의 확률의 곱으로 표현된다. 주의할 점은 그냥 독립인 게 아니라 y가 특정 클래스라는 조건 하에서 독립, 즉 조건부 독립이다.

  • 나이브베이즈 분류모형의 강점

    이렇게 나이브한 가정을 했음에도 실제로는 분류가 잘 된다.

    x들을 서로 독립으로 놓음으로써 qda와 lda가 가지지 못한 또다른 장점이 생기는데, 각 x마다 개별적으로 맞는 모델을 사용할 수 있다는 점이다. 꼭 하나의 분포를 모든 데이터에 대해 사용하지 않아도 되는 것이다.

    가우시안 정규분포를 따르지 않아도 되므로 x가 연속이 아니라 이산분포(베르누이, 다항분포 등)인 경우에도 모델링할 수가 있게 된다.

Scikit-Learn의 naive_bayes 서브패키지에서는 다음과 같은 세가지 나이브 베이즈 모형 클래스를 제공한다.

  • GaussianNB: 가우시안 정규 분포 나이브 베이즈
  • BernoulliNB: 베르누이 분포 나이브 베이즈
  • MultinomialNB: 다항 분포 나이브 베이즈

이 클래스들은 다양한 속성값 및 메서드를 가진다. 우선 사전 확률과 관련된 속성은 다음과 같다.

  • classes_
    • 종속 변수 y의 클래스
  • class_count_
    • 종속 변수 y의 값이 특정한 클래스인 표본 데이터의 수
  • class_prior_
    • 종속 변수 y의 무조건부 확률 분포 $P(Y)$ (가우시안 정규 분포의 경우에만)
  • class_log_prior_
    • 종속 변수 y의 무조건부 확률 분포의 로그 $\text{log}P(Y)$(베르누이 분포와 다항 분포의 경우에만)

1) 가우시안 정규분포 나이브 베이즈 모형

가우시안 분포에서는 $\mu$(기댓값) 과 $\sigma$ (표준편차)만 구하면 된다.

  • theta_: 가우시안 정규 분포의 기댓값 $\mu$
  • sigma_: 가우시안 정규 분포의 분산 $\sigma^2$

예를 들어 두 개의 실수인 독립변수 $x_1, x_2$ 와 두 종류의 클래스 $y=0,1$ 을 가지는 분류 모형이 있다고 하자. 독립변수의 분포는 y의 클래스에 다라 다음처럼 분포가 달라진다.

이 데이터를 가우시안 나이브베이즈모형으로 다음처럼 풀 수 있다.

1
2
3
4
5
6
7
8
9
10
np.random.seed(0)
rv0 = sp.stats.multivariate_normal([-2, -2], [[1, 0.9], [0.9, 2]])
rv1 = sp.stats.multivariate_normal([2, 2], [[1.2, -0.8], [-0.8, 2]])
X0 = rv0.rvs(40)
X1 = rv1.rvs(60)
X = np.vstack([X0, X1])
y = np.hstack([np.zeros(40), np.ones(60)])

from sklearn.naive_bayes import GaussianNB
model_norm = GaussianNB().fit(X, y)

클래스 값이 0일 때와 1일 때 각각 x가 이루는 확률분포의 모수를 계산하면 다음과 같다.

1
2
3
4
5
6
7
model_norm.theta_[0], model_norm.sigma_[0]
#클래스 0일 때 결과
(array([-1.96197643, -2.00597903]), array([1.02398854, 2.31390497]))

model_norm.theta_[1], model_norm.sigma_[1]
#클래스 1일 때 결과
(array([2.19130701, 2.12626716]), array([1.25429371, 1.93742544]))

추정 결과 실제 모수와 유사한 모수를 구할 수 있다.

다만, 아래 그래프를 보면 알 수 있듯이, 원래 데이터의 분포는 무시된다. 위 그래프는 원래 데이터의 분포를 나타낸 플롯이고, 아래 그래프는 나이브베이즈 모형으로 추정한 데이터의 분포 그래프이다.

원래 데이터의 분포를 보면 0번 클래스(파란색)에 해당하는 데이터는 약간 양의상관관계가 보이고, 1번 클래스(빨간색)에 해당하는 데이터는 약간 음의 상관관계를 보인다. 그러나 나이브베이즈 모형에서는 그러한 상관관계를 없다고 배제해버리고 두 클래스의 분포를 동일하다고 가정하기 때문에 아래와 같은 모양이 된다.

만들어진 모형으로 $x_{new}=(-0.7, -0.8)$ 인 데이터의 y 값을 예측해보자.

1
2
3
4
5
x_new = [-0.7, -0.8]
model_norm.predict_proba([x_new])
#결과:
array([[0.98300323, 0.01699677]])
#x_new가 클래스 0일 확률, 1일 확률

따라서 $y=0$일 확률이 $y=1$일 확률보다 훨씬 크다는 것을 알 수 있다.

2) 베르누이 분포 나이브베이즈 모형

베르누이 모형에서는 타겟변수뿐 아니라 독립변수도 0 또는 1의 값을 가져야 한다. 예를 들어 문서에 특정 단어가 포함되어있는지의 여부를 베르누이 확률변수로 모형화할 수 있다. 여기서 추정해야 되는 것은 개별 x와 y마다의 뮤 값이다.

스팸 메일을 디텍팅하는 데 이 모형을 사용한다고 해보자. 아래의 경우 총 10개의 메일을 4개의 키워드의 포함여부에 따라 0 또는 1 값을 부여한 것이다. 독립변수가 0이면 특정 키워드가 포함되지 않은 것이고, 1이면 특정 키워드가 포함된 것이다. 종속변수 값은 0이면 정상메일, 1이면 스팸메일에 해당한다.

1
2
3
4
5
6
7
8
9
10
11
12
X = np.array([
[0, 1, 1, 0], #행 하나가 메일 하나, 열 하나가 키워드 하나
[1, 1, 1, 1],
[1, 1, 1, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 1, 1, 0],
[0, 1, 1, 1],
[1, 0, 1, 0],
[1, 0, 1, 1],
[0, 1, 1, 0]])
y = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1])#정상메일인지 아닌지 라벨링
  • 스무딩(Smoothing)

    : 표본 데이터의 수가 적은 경우에는 베르누이 모수가 0 또는 1이라는 극단적인 추정값이 나올 수도 있다. 하지만 현실적으로는 그럴 가능성은 매우 적다. 따라서 베르누이분포 나이브베이즈 모형 내에서, 추정한 모수들의 값이 0.5에 좀더 가까워지도록 각각의 x에 가상의 데이터 0과 1을 하나씩 추가한다.

    이렇게 스무딩을 거치면 1에 가까웠던 모수는 작아지고, 0에 가까웠던 모수는 커져서 모두 0.5에 조금씩 더 가까워지게 된다.

    만약 2개의 데이터에서 개수를 더 늘려서 $\alpha$ 개 만큼의 데이터를 추가하면 모수값이 좀 더 0.5에 가까워질 것이다.

이제 위 데이터를 베르누이 나이브 베이즈 모형으로 예측해 보자.

1
2
from sklearn.naive_bayes import BernoulliNB
model_bern = BernoulliNB().fit(X, y)

각 클래스와 키워드별로 총 8개의 베르누이 확률변수의 모수를 구해보면 실제 값은 다음과 같다.

1
2
3
4
5
fc = model_bern.feature_count_
fc / np.repeat(model_bern.class_count_[:, np.newaxis], 4, axis=1)
#결과
array([[0.5 , 1. , 0.75 , 0.25 ],
[0.33333333, 0.5 , 0.83333333, 0.5 ]])

그런데 모형으로 예측해보면 값이 다르다. 모형 내부에서 디폴트 알파값이 1인 스무딩을 거쳐 각 모수가 0.5에 가까워진 추정값을 출력하기 때문이다.

1
2
3
4
5
theta = np.exp(model_bern.feature_log_prob_)
theta
#결과
array([[0.5 , 0.83333333, 0.66666667, 0.33333333],
[0.375 , 0.5 , 0.75 , 0.5 ]])

이렇게 만들어진 모형에 테스트데이터를 넣고 클래스 예측을 해 보면 다음처럼 정상메일일 확률을 구할 수 있다.

1
2
3
4
x_new = np.array([1, 1, 0, 0])
model_bern.predict_proba([x_new])
#결과
array([[0.72480181, 0.27519819]])

3) 다항 분포 나이브 베이즈 모형

다항 분포 나이브베이즈 모형에서는 독립변수 x가 0 또는 자연수이다. 베르누이에서 x가 특정 단어의 출현 여부였다면, 다항분포에서는 특정단어가 한 문서에 나온 빈도 수가 된다. 여기서는 각 클래스 k에서 x의 개수 d(아래의 경우 4개)만큼의 면을 가진 주사위를 던졌을 때 d번째 면이 나온 횟수가 입력변수로 들어간다.

따라서 다항 분포 가능도모형을 기반으로 하는 나이브 베이즈 모형은 주사위를 던진 결과로부터 $1,K_1,\cdots,K_K$ 중 어느 주사위를 던졌는지를 찾아내는 모형이라고 할 수 있다.

다항분포 나이브베이즈 모형에서 스무딩 공식은 다음과 같다. 주사위의 각 면이 $\alpha$ 번씩 나온 경우를 추가해주는 것이라고 생각하면 된다. $N_{d,k}$ 는 클래스 k에서 d번째 면이 나온 횟수를 의미한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
X = np.array([
#[1, 1, 1, 1] 클래스 0의 스무딩. 주사위 각면이 1번씩 나온 경우를 추가
[3, 4, 1, 2],
[3, 5, 1, 1],
[3, 3, 0, 4],
[3, 4, 1, 2],
#여기까지 클래스 0
[1, 2, 1, 4],
[0, 0, 5, 3],
[1, 2, 4, 1],
[1, 1, 4, 2],
[0, 1, 2, 5],
[2, 1, 2, 3]])
#여기까지 클래스 1
y = np.array([0, 0, 0, 0, 1, 1, 1, 1, 1, 1])

베르누이 분포에서와 마찬가지로 실제 데이터의 모수와 스무딩을 거친 모형에서 추정한 모수는 다르게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
#실제 모수
fc = model_mult.feature_count_
fc / np.repeat(fc.sum(axis=1)[:, np.newaxis], 4, axis=1)
#결과
array([[0.3 , 0.4 , 0.075 , 0.225 ],
[0.10416667, 0.14583333, 0.375 , 0.375 ]])

#추정 모수
theta = np.exp(model_mult.feature_log_prob_)
theta
#결과
array([[0.29545455, 0.38636364, 0.09090909, 0.22727273],
[0.11538462, 0.15384615, 0.36538462, 0.36538462]])

다항분포에서는 스무딩을 하면 각 모수가 0.25에 조금씩 더 가까워진다.

이 모형으로 새로운 데이터의 클래스를 예측해보면 다음과 같이 클래스 1에 해당할 확률이 2배 가량 높다는 결론을 낼 수 있다.

1
2
3
4
5
x_new = np.array([10, 10, 10, 10])
model_mult.predict_proba([x_new])

#결과
array([[0.38848858, 0.61151142]])
  • 만약 문서에 TF-IDF(inverse document frequency) 인코딩을 하게되면 텍스트에 나온 단어의 빈도수가 정수가 아닌 실수값이 나올 수도 있다. 이 때에도 다항분포 나이브베이즈 모형을 쓸 수 있을까?

아래와 같이 TF-IDF 인코딩을 거친 벡터가 있을 때, 원소를 모두 정수로 만들어주는 과정을 거치게 되면 숫자가 커질 뿐 기존의 다항분포와 똑같은 데이터가 된다.

1
2
3
4
5
6
7
8
9
TF_IDF_X = np.array([
[3.2, 5.4, 1.1, 0.3],
[1.1, 2.3, 10.9, 5.8]])
#한 클래스가 이런 식으로 있다고 할 때, 모든 값에 10을 곱하면

TF_IDF_X = np.array([
[32, 54, 11, 3],
[11, 23, 109, 58]])
#총 횟수가 늘어났을 뿐 MultinomialNB 계산이 가능하다.
  • 그렇다면 만약 x 값에 실수 변수, 0/1 값을 가지는 변수, 일정 변수 집합이 특정한 분포를 이루는 변수들이 섞여있다면 어떻게 풀 수 있을까?

나이브베이지안 모형에서 각 변수는 서로 독립이므로, 성격을 공유하는 변수끼리 모아서 각각에 맞는 모형에 넣으면 된다.

예를 들어 데이터가 100개일 때 1~20개는 가우시안, 21~50은 베르누이, 51~100은 다항 분포라면, 세 모델을 predict_proba 한 후 P(y)값만을 제거하고 다 곱해준다. 그 후 P(y)를 한번만 곱해주면 구하고자 하는 조건부확률을 구할 수 있게 된다.

참조:

Share