문제
만약 "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배 높은 것으로 나타났다.