개념
베이즈 정리(Bayes' Theorem) 빠른 복습
베이즈 정리는 조건부 확률(conditional probability)을 뒤집습니다.
P(class | features) = P(features | class) * P(class) / P(features)
우리가 알고 싶은 것은 P(class | features)입니다. 즉, 문서 안의 단어가 주어졌을 때 그 문서가 어떤 클래스에 속할 확률입니다. 이는 다음 값들로 계산할 수 있습니다.
P(features | class): 이 클래스의 문서에서 이런 단어들을 볼 가능도(likelihood)
P(class): 클래스의 사전 확률(prior probability). 예를 들어 스팸이 일반적으로 얼마나 흔한지를 나타냅니다.
P(features): 증거(evidence). 모든 클래스 비교에서 같은 값이므로 무시할 수 있습니다.
가장 높은 P(class | features)를 가진 클래스가 선택됩니다.
순진한 독립 가정(Naive Independence Assumption)
P(features | class)를 정확히 계산하려면 모든 특성 조합의 결합 확률(joint probability)을 추정해야 합니다. 어휘(vocabulary)가 10,000개라면 2^10,000개의 가능한 조합에 대한 분포(distribution)를 추정해야 합니다. 사실상 불가능합니다.
순진한 가정(naive assumption)은 클래스가 주어지면 모든 특성이 조건부 독립(conditional independence)이라고 봅니다.
P(w1, w2, ..., wn | class) = P(w1 | class) * P(w2 | class) * ... * P(wn | class)
불가능한 하나의 결합 분포 대신, 특성별 단순 분포 n개를 추정하는 방식으로 바뀝니다. 각 분포에는 빈도(count)만 있으면 됩니다.
이 가정은 명백히 틀렸습니다. 문서 안에서 "machine"과 "learning"은 독립이 아닙니다. 그러나 분류기(classifier)에 필요한 것은 정확한 확률 추정값이 아니라 올바른 순위입니다. 독립 가정은 체계적인 오차를 만들어 내지만, 그 오차가 모든 클래스에 비슷하게 작용하면 순위는 그대로 유지됩니다.
그래도 작동하는 이유
크게 세 가지 이유가 있습니다.
-
보정(calibration)보다 순위가 중요합니다. 분류는 최상위 순위(top-ranked) 클래스가 맞으면 됩니다. 실제 P(spam)이 0.7인데 모델이 0.99999로 추정해도 스팸을 고르기만 하면 분류 결과는 맞습니다. 정확한 확률보다 올바른 승자(winner)가 중요합니다.
-
높은 편향, 낮은 분산입니다. 독립 가정은 강한 사전(prior)입니다. 모델을 강하게 제약해 과대적합을 막아 줍니다. 학습 데이터가 적을 때는 이론적으로 더 정확하지만 불안정한 모델보다, 조금 틀려도 안정적인 모델이 더 낫습니다. 이것이 바로 편향-분산 균형이 실제로 작동하는 모습입니다.
-
특성 중복(feature redundancy)이 상쇄됩니다. 상관된 특성(correlated feature)은 중복된 증거를 제공합니다. 분류기가 그 증거를 이중으로 셈(double-count)하더라도, 올바른 클래스에 대해서도 같은 방식으로 이중으로 셉니다. "machine"과 "learning"이 항상 함께 나오고 둘 다 "tech" 클래스의 증거라면, 나이브 베이즈가 둘을 두 번 세더라도 올바른 클래스를 두 번 강화하는 셈입니다.
네 번째로, 현실적인 이유도 있습니다. 나이브 베이즈는 매우 빠릅니다. 학습은 빈도를 세기 위해 데이터를 한 번 훑는 것이 전부이고, 예측은 행렬 곱(matrix multiplication)으로 끝납니다. 백만 개 문서도 몇 초 안에 학습할 수 있을 정도입니다. 이런 속도 덕분에 더 빠르게 반복 실험하고, 더 많은 특성 조합과 실험을 시도해 볼 수 있습니다.
수학을 단계별로 보기
클래스가 스팸(spam)과 비스팸(not-spam) 두 개라고 가정해 봅시다. 어휘(vocabulary)에는 "free", "money", "meeting" 세 단어가 있습니다.
학습 데이터:
- 스팸 이메일에는
"free"가 80회, "money"가 60회, "meeting"이 10회 등장합니다. 총 150단어입니다.
- 비스팸 이메일에는
"free"가 5회, "money"가 10회, "meeting"이 100회 등장합니다. 총 115단어입니다.
- 전체 이메일 중 40%가 스팸이고 60%가 비스팸입니다.
라플라스 스무딩(Laplace smoothing, alpha=1)을 적용하면 다음과 같습니다.
P(free | spam) = (80 + 1) / (150 + 3) = 81/153 = 0.529
P(money | spam) = (60 + 1) / (150 + 3) = 61/153 = 0.399
P(meeting | spam) = (10 + 1) / (150 + 3) = 11/153 = 0.072
P(free | not-spam) = (5 + 1) / (115 + 3) = 6/118 = 0.051
P(money | not-spam) = (10 + 1) / (115 + 3) = 11/118 = 0.093
P(meeting | not-spam) = (100 + 1) / (115 + 3) = 101/118 = 0.856
새 이메일에는 "free"가 2번, "money"가 1번, "meeting"이 0번 들어 있습니다.
log P(spam | email) = log(0.4) + 2*log(0.529) + 1*log(0.399) + 0*log(0.072)
= -0.916 + 2*(-0.637) + (-0.919) + 0
= -3.109
log P(not-spam | email) = log(0.6) + 2*log(0.051) + 1*log(0.093) + 0*log(0.856)
= -0.511 + 2*(-2.976) + (-2.375) + 0
= -8.838
스팸이 큰 차이로 이깁니다. "free"가 두 번 등장한 것은 스팸에 대한 강한 증거입니다. 다항 나이브 베이즈(Multinomial NB)에서는 등장하지 않은 "meeting"이 로그 합(log sum)에 0 * log(P) 형태로 더해지므로 영향을 주지 않는다는 점에 주의하세요. 단어의 부재(absence)를 명시적으로 모델링하는 것은 베르누이 나이브 베이즈(Bernoulli NB)입니다.
세 가지 변형
나이브 베이즈에는 세 가지 주요 변형이 있습니다. 각각 P(feature | class)를 서로 다르게 모델링합니다.
다항 나이브 베이즈(Multinomial Naive Bayes)
각 특성을 빈도(count)로 봅니다. 단어 빈도(word frequency)나 TF-IDF 값처럼 텍스트 데이터에 가장 적합합니다.
P(word_i | class) = (count of word_i in class + alpha) / (total words in class + alpha * vocab_size)
alpha는 라플라스 스무딩 값입니다. 이 변형은 텍스트 분류의 핵심 기준 모델(baseline)로 사용됩니다.
가우시안 나이브 베이즈(Gaussian Naive Bayes)
각 특성을 정규분포(normal distribution)로 봅니다. 연속형 특성에 적합합니다.
P(x_i | class) = (1 / sqrt(2 * pi * var)) * exp(-(x_i - mean)^2 / (2 * var))
각 클래스는 특성별로 자신의 평균(mean)과 분산(variance)을 가집니다. 클래스 안에서 특성이 실제로 종 모양(bell curve) 분포에 가까울 때 잘 작동합니다.
베르누이 나이브 베이즈(Bernoulli Naive Bayes)
각 특성을 이진값(binary), 즉 등장(present)/비등장(absent)으로 봅니다. 짧은 텍스트나 이진 특성 벡터(binary feature vector)에 적합합니다.
P(word_i | class) = (docs in class containing word_i + alpha) / (total docs in class + 2 * alpha)
다항(Multinomial)과 달리 베르누이(Bernoulli)는 단어가 없는 것도 명시적으로 모델링합니다. "free"가 보통 스팸에 등장하는데 이번 이메일에는 없다면, 베르누이는 그것을 스팸에 반대되는 증거로 셉니다.
어떤 변형을 언제 쓸까
| 변형 | 특성 유형 | 적합한 경우 | 예시 |
|---|
| 다항(Multinomial) | 빈도(count) 또는 도수(frequency) | 텍스트 분류, 단어 가방(bag-of-words) | 이메일 스팸, 주제 분류 |
| 가우시안(Gaussian) | 연속값(continuous) | 대체로 정규분포에 가까운 표 형식(tabular) 특성 | 붓꽃(Iris) 분류, 센서 데이터 |
| 베르누이(Bernoulli) | 이진(0/1) | 짧은 텍스트, 이진 특성 벡터 | SMS 스팸, 등장/비등장(presence/absence) 특성 |
라플라스 스무딩(Laplace Smoothing)
테스트 데이터에 어떤 단어가 등장했는데 특정 클래스의 학습 데이터에서는 한 번도 등장하지 않았다면 어떻게 될까요?
스무딩이 없으면 P(word | class) = 0/N = 0이 됩니다. 곱셈 전체에 0이 한 번 들어가는 순간 P(class | features) = 0이 되어 버립니다. 다른 증거가 아무리 강해도, 보지 못한 단어 하나가 전체 예측을 무너뜨립니다.
라플라스 스무딩은 모든 특성 빈도에 작은 상수 alpha(보통 1)를 더합니다.
P(word_i | class) = (count(word_i, class) + alpha) / (total_words_in_class + alpha * vocab_size)
alpha=1이면 모든 단어가 최소한 아주 작은 확률을 갖습니다. 테스트 이메일에 "discombobulate" 같은 단어가 등장해도 스팸 확률이 0으로 떨어지지 않습니다. 이 스무딩은 베이지언(Bayesian) 관점에서 단어 분포에 균일 디리클레 사전(uniform Dirichlet prior)을 두는 것과 동일한 효과를 가집니다.
alpha가 클수록 스무딩이 강해져 분포가 더 균일해지고, 작을수록 데이터를 더 신뢰합니다. alpha는 튜닝해야 하는 하이퍼파라미터(hyperparameter)입니다.
alpha의 효과를 정리하면 다음과 같습니다.
| alpha | 효과 | 언제 쓰는가 |
|---|
| 0.001 | 스무딩 거의 없음, 데이터를 신뢰 | 매우 큰 학습 집합, 미관측 특성이 거의 없을 때 |
| 0.1 | 약한 스무딩 | 큰 학습 집합 |
| 1.0 | 표준 라플라스 스무딩 | 기본 시작점 |
| 10.0 | 강한 스무딩, 분포를 평평하게 만듦 | 매우 작은 학습 집합, 미관측 특성이 많을 때 |
로그 공간(Log-space) 계산
1보다 작은 확률 수백 개를 곱하면 부동소수점 언더플로(floating-point underflow)가 발생합니다. 실제 값은 아주 작은 양수인데 부동소수점에서는 0으로 떨어집니다.
해결책은 로그 공간(log space)에서 계산하는 것입니다. 확률을 곱하는 대신 로그를 더합니다.
log P(class | x1, x2, ..., xn) = log P(class) + sum_i log P(xi | class)
이렇게 하면 예측이 내적(dot product)으로 바뀝니다.
log_scores = X @ log_feature_probs.T + log_class_priors
prediction = argmax(log_scores)
행렬 곱입니다. 나이브 베이즈의 예측이 빠른 이유가 바로 이 점이며, 이는 단층 선형 모델(single-layer linear model)과 동일한 연산입니다.
나이브 베이즈와 로지스틱 회귀
둘 다 텍스트에 적용되는 선형 분류기(linear classifier)입니다. 차이는 무엇을 모델링하느냐에 있습니다.
| 관점 | 나이브 베이즈(Naive Bayes) | 로지스틱 회귀(Logistic Regression) |
|---|
| 유형 | 생성 모델(generative). P(X|Y)를 모델링 | 판별 모델(discriminative). P(Y|X)를 모델링 |
| 학습 | 빈도 계수(frequency count) | 손실 함수(loss function) 최적화 |
| 작은 데이터 | 강한 사전 덕분에 더 유리함 | 가중치 추정이 어려움 |
| 큰 데이터 | 잘못된 가정이 발목을 잡음 | 더 유연한 결정 경계 |
| 특성 | 독립 가정 | 상관관계(correlation) 처리 가능 |
| 속도 | 한 번 훑기로 끝, 매우 빠름 | 반복 최적화 |
| 보정(calibration) | 확률이 약함 | 확률이 더 안정적 |
경험칙은 간단합니다. 먼저 나이브 베이즈로 시작합니다. 데이터가 충분해지고 나이브 베이즈가 한계에 도달하면(plateau) 로지스틱 회귀로 넘어갑니다.
분류 파이프라인(Classification Pipeline)
flowchart LR
A[원시 텍스트] --> B[토큰화]
B --> C[어휘 구성]
C --> D[단어 빈도 계산]
D --> E[스무딩 적용]
E --> F[로그 확률 계산]
F --> G[예측: 클래스별 P를 비교해 argmax]
style A fill:#f9f,stroke:#333
style G fill:#9f9,stroke:#333
실제로는 부동소수점 언더플로를 피하기 위해 로그 공간에서 작업합니다. 작은 확률을 여러 번 곱하는 대신 로그값을 더하는 방식입니다.
log P(class | features) = log P(class) + sum_i log P(feature_i | class)
만들어 보기
code/naive_bayes.py는 MultinomialNB와 GaussianNB를 처음부터 구현합니다.
MultinomialNB
처음부터 구현한 흐름은 다음과 같습니다.
-
fit(X, y): 클래스별로 각 특성의 빈도를 셉니다. 라플라스 스무딩을 더하고, 로그 확률을 계산하며, 클래스 사전(class prior), 즉 클래스 빈도의 로그값을 저장합니다.
-
predict_log_proba(X): 각 표본과 클래스에 대해 log P(class) + sum log P(feature_i | class)를 계산합니다. 이는 X @ log_probs.T + log_priors 형태의 행렬 곱과 같습니다.
-
predict(X): 로그 확률이 가장 높은 클래스를 반환합니다.
class MultinomialNB:
def __init__(self, alpha=1.0):
self.alpha = alpha
def fit(self, X, y):
classes = np.unique(y)
n_classes = len(classes)
n_features = X.shape[1]
self.classes_ = classes
self.class_log_prior_ = np.zeros(n_classes)
self.feature_log_prob_ = np.zeros((n_classes, n_features))
for i, c in enumerate(classes):
X_c = X[y == c]
self.class_log_prior_[i] = np.log(X_c.shape[0] / X.shape[0])
counts = X_c.sum(axis=0) + self.alpha
self.feature_log_prob_[i] = np.log(counts / counts.sum())
return self
핵심은 학습(fit) 이후의 예측이 행렬 곱에 편향(bias)을 더하는 연산일 뿐이라는 점입니다. 그래서 나이브 베이즈가 그토록 빠릅니다.
GaussianNB
연속형 특성에 대해서는 클래스별, 특성별로 평균과 분산을 추정합니다.
class GaussianNB:
def __init__(self):
pass
def fit(self, X, y):
classes = np.unique(y)
self.classes_ = classes
self.means_ = np.zeros((len(classes), X.shape[1]))
self.vars_ = np.zeros((len(classes), X.shape[1]))
self.priors_ = np.zeros(len(classes))
for i, c in enumerate(classes):
X_c = X[y == c]
self.means_[i] = X_c.mean(axis=0)
self.vars_[i] = X_c.var(axis=0) + 1e-9
self.priors_[i] = X_c.shape[0] / X.shape[0]
return self
예측은 특성별 가우시안 확률 밀도(Gaussian PDF)를 사용하며, 곱하는 대신 로그 공간에서 더합니다.
데모: 텍스트 분류
코드는 기술 분야 기사(tech article)와 스포츠 기사(sports article) 두 클래스를 흉내 내는 합성 단어 가방(synthetic bag-of-words) 데이터를 만듭니다. 클래스마다 서로 다른 단어 빈도 분포를 가지며, MultinomialNB가 단어 빈도를 이용해 클래스를 분류합니다.
합성 데이터는 200개의 특성 열(feature column)을 "단어"처럼 사용합니다. 0-39번 단어는 기술 기사에서 자주 나오고 스포츠에서는 적게 나옵니다. 80-119번 단어는 반대로 스포츠에서 자주 나오고 기술에서는 적게 나옵니다. 40-79번 단어는 양쪽에서 비슷한 빈도로 등장합니다. 일부 단어는 강한 클래스 지표(class indicator)가 되고, 다른 단어는 잡음(noise)이 되는 현실적인 상황을 만들어 줍니다.
데모: 연속형 특성
코드는 붓꽃(Iris)과 유사한 데이터를 만듭니다. 3개의 클래스와 4개의 특성을 가진 가우시안 군집(Gaussian cluster) 형태입니다. GaussianNB는 클래스별 평균과 분산으로 분류합니다. 각 클래스는 서로 다른 중심(평균 벡터)과 퍼짐(분산)을 가지며, 측정값이 카테고리에 따라 체계적으로 달라지는 실제 데이터의 모습을 흉내 냅니다.
코드는 다음도 함께 보여줍니다.
- 스무딩 비교(smoothing comparison): 서로 다른
alpha 값으로 MultinomialNB를 학습해 스무딩 강도가 정확도(accuracy)에 주는 영향을 살펴봅니다.
- 학습 데이터 크기 실험(training size experiment): 학습 데이터가 20개에서 1600개로 늘어날 때 정확도가 어떻게 향상되는지 확인합니다. 나이브 베이즈는 표본이 매우 적어도 그럴듯한 정확도에 빠르게 도달하는데, 이것이 가장 큰 장점입니다.
- 혼동 행렬(confusion matrix): 클래스별 정밀도(precision), 재현율(recall), F1 점수(F1 score)를 통해 나이브 베이즈가 어디서 틀리는지 확인합니다.
예측 속도
나이브 베이즈의 예측은 결국 행렬 곱입니다. 표본 수가 n, 특성 수가 d, 클래스 수가 k일 때 다음과 같이 정리됩니다.
MultinomialNB: (n x d) @ (d x k) = O(n * d * k)인 행렬 곱 한 번
GaussianNB: 클래스별로 d개 특성 위에서 가우시안 PDF를 평가하므로 O(n * d * k)
둘 다 모든 차원에 대해 선형입니다. 모든 학습 점과 거리를 계산해야 하는 K-최근접 이웃(KNN)이나, 모든 서포트 벡터와 커널(kernel)을 평가해야 하는 방사 기저 함수(RBF) 커널 SVM과 비교하면, 예측 시점에서 수십 배 이상 빠릅니다.
사용하기
sklearn에서는 두 변형 모두 한두 줄의 코드로 사용할 수 있습니다.
from sklearn.naive_bayes import GaussianNB, MultinomialNB
gnb = GaussianNB()
gnb.fit(X_train, y_train)
print(f"GaussianNB 정확도: {gnb.score(X_test, y_test):.3f}")
mnb = MultinomialNB(alpha=1.0)
mnb.fit(X_train_counts, y_train)
print(f"MultinomialNB 정확도: {mnb.score(X_test_counts, y_test):.3f}")
sklearn으로 텍스트 분류를 할 때는 다음처럼 파이프라인(pipeline)을 구성합니다.
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
text_clf = Pipeline([
("vectorizer", CountVectorizer()),
("classifier", MultinomialNB(alpha=1.0)),
])
text_clf.fit(train_texts, train_labels)
accuracy = text_clf.score(test_texts, test_labels)
naive_bayes.py는 같은 데이터에서 처음부터 구현한 결과와 sklearn 결과를 비교해 구현이 맞는지 확인합니다.
나이브 베이즈와 TF-IDF
원시 단어 빈도(raw word count)는 단어가 한 번 나올 때마다 같은 가중치를 줍니다. 그러나 "the", "is" 같은 흔한 단어는 모든 클래스에서 자주 등장하며 정보를 거의 담고 있지 않습니다. TF-IDF(Term Frequency - Inverse Document Frequency)는 흔한 단어의 가중치를 낮추고, 드물지만 구분력이 있는 단어의 가중치를 높입니다.
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
text_clf = Pipeline([
("tfidf", TfidfVectorizer()),
("classifier", MultinomialNB(alpha=0.1)),
])
TF-IDF 값은 음수가 아니므로 MultinomialNB와 함께 쓸 수 있습니다. TF-IDF와 MultinomialNB의 조합은 텍스트 분류에서 가장 강력한 기준 모델 중 하나입니다. 학습 표본이 10,000개보다 적은 데이터셋에서는 더 복잡한 모델을 자주 능가합니다.
짧은 텍스트에는 BernoulliNB
짧은 텍스트(트윗, SMS, 채팅 메시지)에서는 BernoulliNB가 MultinomialNB보다 더 나은 결과를 낼 수 있습니다. 짧은 텍스트는 단어 빈도가 낮아 빈도 정보 자체가 잡음(noise)에 가깝습니다. BernoulliNB는 단어의 등장 여부만 보기 때문에, 짧은 텍스트에서 더 안정적으로 동작합니다.
from sklearn.naive_bayes import BernoulliNB
from sklearn.feature_extraction.text import CountVectorizer
text_clf = Pipeline([
("vectorizer", CountVectorizer(binary=True)),
("classifier", BernoulliNB(alpha=1.0)),
])
CountVectorizer의 binary=True는 모든 빈도를 0/1로 바꿉니다. 이 플래그(flag) 없이도 BernoulliNB는 실행되긴 하지만, 설계 의도와 다른 빈도 값을 보게 됩니다.
나이브 베이즈 확률 보정하기
나이브 베이즈의 확률은 보정이 좋지 않습니다. 나이브 베이즈가 P(spam) = 0.95라고 말해도 실제 확률은 0.7일 수 있습니다. 임계값(threshold)을 정하거나 다른 모델과 결합하기 위해 신뢰할 수 있는 확률 추정이 필요하다면 sklearn의 CalibratedClassifierCV를 사용합니다.
from sklearn.calibration import CalibratedClassifierCV
calibrated_nb = CalibratedClassifierCV(MultinomialNB(), cv=5, method="sigmoid")
calibrated_nb.fit(X_train, y_train)
proba = calibrated_nb.predict_proba(X_test)
이 방법은 교차 검증(cross-validation)을 사용해 나이브 베이즈의 원시 점수(raw score) 위에 로지스틱 회귀를 학습시킵니다. 결과적으로 산출되는 확률이 실제 클래스 빈도에 훨씬 가까워집니다.
흔한 주의점
-
음수 특성 값. MultinomialNB는 음수가 아닌(non-negative) 특성을 요구합니다. 일부 TF-IDF 설정이나 표준화된 특성처럼 음수 값이 있다면 GaussianNB를 사용하거나, 특성을 양수 범위로 평행이동(shift)해야 합니다.
-
분산이 0인 특성. GaussianNB는 분산으로 나누는 연산을 합니다. 어떤 클래스에서 특정 특성의 분산이 0이면(값이 모두 같으면) 확률 계산이 깨집니다. 코드는 모든 분산에 작은 스무딩 항(1e-9)을 더해 이를 방지합니다.
-
클래스 불균형. 이메일의 99%가 비스팸이면 P(not-spam)=0.99라는 사전이 너무 강해 가능도 증거(likelihood evidence)를 압도할 수 있습니다. sklearn에서는 class_prior 매개변수로 클래스 사전을 수동으로 설정할 수 있습니다.
-
특성 스케일링. MultinomialNB는 스케일링이 필요 없습니다. 빈도 위에서 작동하기 때문입니다. GaussianNB 역시 특성별 통계를 직접 추정하므로 스케일링이 꼭 필요하지 않습니다. 이는 스케일에 민감한 로지스틱 회귀나 SVM 대비 장점입니다.
산출물 만들기
이 lesson의 최종 산출물은 다음과 같습니다.
outputs/skill-naive-bayes-chooser.md: 적절한 나이브 베이즈 변형을 고르기 위한 의사결정 스킬(decision skill)
code/naive_bayes.py: MultinomialNB와 GaussianNB를 처음부터 구현하고 sklearn과 비교하는 예제
나이브 베이즈가 실패하는 경우
나이브 베이즈는 독립 가정이 확률뿐 아니라 클래스 순위까지 망가뜨릴 때 실패합니다. 주로 다음과 같은 상황에서 발생합니다.
-
강한 특성 상호작용(feature interaction). 클래스가 두 특성의 조합에 따라 달라지고 각각의 특성만으로는 정보가 없는 XOR과 같은 패턴이라면, 나이브 베이즈는 이를 놓칩니다. 각 특성이 단독으로는 어떤 증거도 제공하지 못하고, 나이브 베이즈는 이를 비선형적으로 결합할 수 없기 때문입니다.
-
서로 반대되는 증거를 주는, 강하게 상관된 특성. 특성 A는 스팸을, 특성 B는 비스팸을 가리키는데 실제로는 A와 B가 항상 같이 움직이는(완벽히 상관된) 경우, 나이브 베이즈는 존재하지 않는 충돌을 만들어 낼 수 있습니다.
-
매우 큰 학습 데이터. 데이터가 충분히 많아지면 로지스틱 회귀 같은 판별 모델이 실제 결정 경계를 더 잘 학습해 나이브 베이즈를 능가합니다. 작은 데이터에서 도움이 되던 독립 가정이 이제는 모델의 발목을 잡게 됩니다.
실제로 텍스트 분류에서는 이런 실패 양상이 상대적으로 드뭅니다. 텍스트 특성은 그 수가 많고 개별적으로는 약하며, 독립 가정의 오류가 상쇄되는 경향이 있기 때문입니다. 다만 특성 수가 적고 상관이 강한 표 형식 데이터에서는 로지스틱 회귀나 트리 기반(tree-based) 모델을 먼저 고려하는 것이 좋습니다.
연습문제
-
스무딩 실험 (쉬움). alpha 값을 0.01, 0.1, 1.0, 10.0, 100.0으로 바꿔 가며 텍스트 데이터에서 MultinomialNB를 학습합니다. alpha에 따른 정확도를 시각화합니다. 성능이 어디서 정점을 찍는지, 매우 큰 alpha가 성능을 떨어뜨리는 이유는 무엇인지 설명해 보세요.
-
특성 독립성 검증 (중간). 실제 텍스트 데이터셋에서 "machine"과 "learning"처럼 명백히 상관된 단어 두 개를 고릅니다. P(word1 | class) * P(word2 | class)와 P(word1 AND word2 | class)를 비교해 보세요. 독립 가정은 얼마나 틀렸나요? 그 차이가 분류 정확도에도 영향을 주나요?
-
베르누이 구현 (중간). 코드에 BernoulliNB 클래스를 추가합니다. 단어 가방을 이진(등장/비등장) 형태로 바꾸고 텍스트 데이터에서 MultinomialNB와 정확도를 비교합니다. 베르누이가 더 나은 경우는 언제인가요?
-
나이브 베이즈와 로지스틱 회귀 비교 (중간). 두 모델을 같은 텍스트 데이터에서 학습합니다. 학습 표본을 100개에서 시작해 10,000개까지 늘립니다. 학습 집합 크기에 따른 정확도를 시각화합니다. 어느 지점에서 로지스틱 회귀가 나이브 베이즈를 앞서나요?
-
스팸 필터 (어려움). 원시 이메일 텍스트를 토큰화(tokenize)하고, 어휘를 만들고, 단어 가방 특성을 생성한 뒤 MultinomialNB를 학습하는 완전한 스팸 분류기를 구현합니다. 정밀도(precision)와 재현율(recall)로 평가합니다. 정확도만 보면 안 되는 이유는 무엇인가요?
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 나이브 베이즈(Naive Bayes) | "단순한 확률 분류기" | 클래스가 주어지면 특성이 조건부 독립이라는 가정으로 베이즈 정리를 적용하는 분류기 |
| 조건부 독립(Conditional Independence) | "특성들이 서로 영향이 없음" | P(A, B | C) = P(A | C) * P(B | C). C를 알고 나면 B를 알아도 A에 대한 새로운 정보가 없다는 뜻 |
| 라플라스 스무딩(Laplace Smoothing) | "1 더하기 스무딩(add-one smoothing)" | 0 확률이 예측을 지배하지 않도록 모든 특성 빈도에 작은 상수를 더하는 기법 |
| 사전 확률(Prior) | "데이터를 보기 전의 믿음" | P(class). 특성을 보기 전 각 클래스의 확률 |
| 가능도(Likelihood) | "데이터가 얼마나 그럴듯한가" | P(features | class). 클래스를 알 때 이런 특성들을 볼 확률 |
| 사후 확률(Posterior) | "데이터를 본 뒤의 믿음" | P(class | features). 특성을 본 뒤 갱신된 클래스 확률 |
| 생성 모델(Generative model) | "데이터 생성 방식을 모델링한다" | P(X | Y)와 P(Y)를 학습한 뒤 베이즈 정리로 P(Y | X)를 얻는 모델 |
| 판별 모델(Discriminative model) | "결정 경계를 모델링한다" | X가 어떻게 생성되는지 모델링하지 않고 P(Y | X)를 직접 학습하는 모델 |
| 로그 확률(Log probability) | "언더플로 방지" | 작은 확률들의 곱이 부동소수점에서 0이 되는 것을 막기 위해 P 대신 log P로 계산하는 것 |
더 읽을거리