Naive Bayesian - 텍스트 분류

문제

만약 "I do not want it"라는 문장이 있다면, 해당 문장이 긍정인지 부정인지 분류하는 문제를 푼다고 생각하자. 해당 문제를 아래와 같이 표현할 수 있다. 

$P(positive | I, do, not, want, it)$

$P(negative | I, do, not, want, it)$

위 수식을 자세하게 분석해 보면 [I, do, not, want, it]이 문장에 있을 때, positive일 확률과 negative일 확률을 각각 계산하는 것이다. 만약 positive가 더 크면 긍정, 반대면 부정을 뜻한다. 


Bayes rule 활용

베이즈 정리를 활용하면 위 문제를 쉽게 풀 수 있다. 

$P(label | tokens)=\cfrac{P(tokens | label)P(label)}{P(tokens)}$

그런데 Naive Bayesian 문제에서 두 확률값의 대소비교만 하면 되기 때문에 분모인 P(tokens)는 영향을 주지 않는다. 그렇다면 우리가 구해야할 식은 아래와 같이 정리된다. 

$P(tokens|label)P(label)$

그런데 P(tokens | label)도 구할 수 있는 명확한 방법이 없다. 따라서, tokens가 상호 독립적이라고 가정한다. 이러한 가정을 사용하면 계산 가능한 식이 도출된다. 

$P(label) P(tokens | label)$

$\rightarrow P(label) P(token_1 | label) P(token_2 | label) P(token_3 | label) ...$

$=P(label) \prod_i P(token_i | label)$


Bayes rule 변형

$P(label) \prod_i P(token_i | label)$ 

위 식에서 특정 token의 빈도수가 0일 경우, 특정 P(token | label)의 값이 0이 된다. 따라서, 최종적으로 곱한 결과도 0이 된다. 이러한 문제를 방지하기 위해 사용하는 방법이 있다. 

$\prod_i P(token_i | label)$

$=\prod_i  \cfrac{count(token_i | label)}{count(label)}$

$\rightarrow \prod_i \cfrac{count(token_i | label)+\alpha}{count(label)+\alpha d}$ 

위와 같이 특정 값을 더해 값이 0이 되지 않도록 만드는 방법이 있다. 이때 alpha=1, d=dimension으로 설정하는 방법이 있다. 쉽게 해석하면 아래와 같다. 

$ \prod_i \cfrac{count(token_i | label)+\alpha}{count(label)+\alpha d}$

$=\prod_i \cfrac{count(token_i | label)+1}{count(label)+d}$

$=\prod_i \cfrac{count(token_i | label)+1}{count(label)+count(unique \ token)}$

또는,

log를 활용해 곱이 아닌 합으로 연산하도록 만드는 방법도 있다. 

$P(label) \prod_i P(token_i | label)$

$\rightarrow log(P(label) \prod_i P(token_i | label))$

$=log(P(label)) + \sum_i log(P(token_i | label))$


nltk - 영화 리뷰 분석

nltk에서 NaiveBayesClassifier를 제공한다. 

import random

import nltk
from nltk.corpus import movie_reviews


# (토큰, 카테고리)로 정리
data_set = [
    (list(movie_reviews.words(fileid)), category)
    for category in movie_reviews.categories()
    for fileid in movie_reviews.fileids(category)
]
random.seed(1)
random.shuffle(data_set)

# 유일한 토큰 추출
all_tokens = [word.lower() for word in movie_reviews.words()]
unique_tokens = set(all_tokens)


def data_feature(X):
    # NaiveBayesClassifier 학습을 위한 형태로 정리
    unique_tokens_in_X = set(X)
    features = {token: (token in unique_tokens_in_X) for token in unique_tokens}
    return features

# 데이터셋
data_set = [(data_feature(X), y) for (X, y) in data_set]
train_set, test_set = data_set[200:], data_set[:200]

# NaiveBayesClassifier 학습
classifier = nltk.NaiveBayesClassifier.train(train_set)

# 학습된 모델 확인
acc = nltk.classify.accuracy(classifier, test_set)

pred = "It was too scared"
category = classifier.classify(data_feature(pred))

print(f"acc: {acc:.3f}")
print("-" * 30)
print(f"input: [ {pred} ]")
print(f"output: '{category}'")
acc: 0.810
------------------------------
input: [ It was too scared ]
output: 'neg'

classifier의 정확도가 81%가 나왔고 "It was too scared"에 대해 'neg'(부정)으로 분류하였다.

classifier.show_most_informative_features(10)
Most Informative Features
             uninvolving = True              neg : pos    =     13.0 : 1.0
               uplifting = True              pos : neg    =     12.3 : 1.0
              schumacher = True              neg : pos    =     11.7 : 1.0
                    slip = True              pos : neg    =     11.6 : 1.0
              astounding = True              pos : neg    =     11.0 : 1.0
                  avoids = True              pos : neg    =     11.0 : 1.0
             outstanding = True              pos : neg    =     10.4 : 1.0
                    3000 = True              neg : pos    =     10.4 : 1.0
                palpable = True              pos : neg    =     10.3 : 1.0
                seamless = True              pos : neg    =     10.3 : 1.0

uninvolving(관련되지 않은)은 부정이 13배, uplifting(희망적인)은 긍정이 12배 높은 것으로 나타났다.