나이브 베이즈 분류 모형(Naive Bayes classification model)은 대표적인 확률적 생성 모형이다.
타겟 변수 $y$의 각 클래스 $\{C_1,\cdots,C_K\}$ 에 대한 독립 변수 $x$의 조건부 확률 분포 정보 $p(x \mid y = C_k)$ 를 사용하여 주어진 새로운 독립 변수 값 $x_{\text{new}}$에 대한 타켓 변수의 각 클래스의 조건부 확률 $p(y = C_k \mid x_{\text{new}})$ 를 추정한 후 가장 조건부 확률이 큰 클래스 $k$를 선택하는 방법이다.
다음과 같이 베이즈 규칙을 사용하여 조건부 확률 $p(y = C_k \mid x_{\text{new}})$ 을 계산한다.
$$ P(y = C_k \mid x_{\text{new}}) = \dfrac{P(x_{\text{new}} \mid y = C_k)\; P(y = C_k)}{P(x_{\text{new}})} $$최종적으로는 각 클래스 $k$에 대한 확률을 비교하여 최고값을 계산하기만 하면 되므로 분모에 있는 주변 확률(marginal probability) ${P(x_{\text{new}})}$은 계산하지 않는다.
$$ P(y = C_k \mid x_{\text{new}}) \;\; \propto \;\; P(x_{\text{new}} \mid y = C_k) \; P(y = C_k) $$여기에서 사전 확률(prior) $P(y = C_k)$는 다음과 같이 쉽게 구할 수 있다.
$$ P(y = C_k) \approx \frac{\text{number of samples with }y = C_k}{\text{number of all samples}} $$$y$에 대한 $x$의 조건부 확률인 우도(likelihood)의 경우에는 일반적으로 정규 분포나 베르누이 분포와 같은 특정한 모형을 가정하여 다음과 같이 계산한다.
우도의 모형으로 많이 사용하는 것은 다음과 같다.
가우시안 정규 분포
$$ P(x_i \mid y = C_k) = \dfrac{1}{\sqrt{2\pi\sigma_k^2}} \exp \left(-\dfrac{(x_i-\mu_k)^2}{2\sigma_k^2}\right) $$
독립 변수 $x$가 다차원(multi-dimensional) $x = (x_1, \ldots, x_n)$ 이면 위에서 사용한 우도 $P(x \mid y = C_k)$ 는 원래 모든 $x_i$에 대한 결합 확률(joint probability) $P(x_1, \ldots, x_n \mid y = C_k)$ 을 사용해야 한다. 그러나 이러한 결합 확률은 실제로 입수하기 어렵기 때문에 모든 차원의 개별 독립 변수 요소들이 서로 독립(independent)이라는 가정을 흔히 사용한다. 이러한 가정을 나이브 가정(Naive assumption)이라고 한다.
나이브 가정하에서는 결합 확률이 개별 확률의 곱으로 나타난다.
$$ P(x_1, \ldots, x_n \mid y = C_k) = \prod_{i=1}^n P(x_i \mid y = C_k) $$$$ P(y = C_k \mid x_{\text{new}}) \;\; \propto \;\; \prod_{i=1}^n P(x_{\text{new},i} \mid y = C_k)\; P(y = C_k) $$Scikit-Learn의 naive_bayes 서브패키지에서는 다음과 같은 세가지 나이브 베이즈 모형 클래스를 제공한다.
BernoulliNB
: 베르누이 분포 나이브 베이즈MultinomialNB
: 다항 분포 나이브 베이즈GaussianNB
: 가우시안 정규 분포 나이브 베이즈이 클래스들은 다음과 같은 속성값 및 메서드를 가진다.
classes_
: 공통
class_count_
: 공통
feature_count_
: 베르누이 분포나 다항 분포
class_prior_
: 가우시안 정규 분포
class_log_prior_
: 베르누이 분포나 다항 분포
theta_
, sigma_
: 가우시안 정규 분포
feature_log_prob_
: 베르누이 분포나 다항 분포
베르누이 분포 혹은 다항 분포의 모수 벡터의 로그 $$ \log \theta = (\log \theta_1, \ldots, \log \theta_n) = \left( \log \dfrac{N_i}{N}, \ldots, \log \dfrac{N_n}{N} \right)$$
스무딩(smoothing) $$ \hat{\theta} = \frac{ N_{i} + \alpha}{N + \alpha n} $$
predict_proba(x_new)
: 공통
디폴트 파라미터는 알파가 1이다. 원래 1/6이라면 2/12라고 하는 것이다. 기본점수를 주는 것이다. 보통은 알파를 1로 주는 것
In [4]:
np.random.seed(0)
X0 = sp.stats.norm(-2, 1).rvs(40)
X1 = sp.stats.norm(2, 1).rvs(60)
X = np.hstack([X0, X1])[:, np.newaxis]
y0 = np.zeros(40)
y1 = np.ones(60)
y = np.hstack([y0, y1])
In [5]:
sns.distplot(X0, rug=True, kde=False, norm_hist=True, label="class 0")
sns.distplot(X1, rug=True, kde=False, norm_hist=True, label="class 1")
plt.legend()
plt.xlim(-6, 6)
plt.show()
In [6]:
X0.mean(), X0.std()
Out[6]:
In [7]:
X1.mean(), X1.std()
Out[7]:
In [8]:
from sklearn.naive_bayes import GaussianNB
clf_norm = GaussianNB().fit(X, y)
In [10]:
clf_norm.classes_ # y값이 가질 수 있는 클래스들
Out[10]:
In [12]:
clf_norm.class_count_ # 각각 y 값의 갯수
Out[12]:
In [14]:
clf_norm.class_prior_ # 각각 y의 확률(사전)
Out[14]:
In [15]:
clf_norm.theta_, clf_norm.sigma_
Out[15]:
In [16]:
xx = np.linspace(-6, 6, 100)
p0 = sp.stats.norm(clf_norm.theta_[0], clf_norm.sigma_[0]).pdf(xx)
p1 = sp.stats.norm(clf_norm.theta_[1], clf_norm.sigma_[1]).pdf(xx)
sns.distplot(X0, rug=True, kde=False, norm_hist=True, color='r', label='class 0 histogram')
sns.distplot(X1, rug=True, kde=False, norm_hist=True, color='b', label='class 1 histogram')
plt.plot(xx, p0, c='r', label='class 0 est. pdf')
plt.plot(xx, p1, c='b', label='class 1 est. pdf')
plt.legend()
plt.show()
In [18]:
x_new = -1
clf_norm.predict_proba([[x_new]]) # predict_proba(x_new) : 조건부 확률 분포
Out[18]:
In [19]:
px = sp.stats.norm(clf_norm.theta_, np.sqrt(clf_norm.sigma_)).pdf(x_new)
px
Out[19]:
In [24]:
clf_norm.class_prior_
Out[24]:
In [20]:
p = px.flatten() * clf_norm.class_prior_
p
Out[20]:
In [23]:
p / p.sum() # nomalize해서 합치면 1이 되는 확률값으로 변환
Out[23]:
In [25]:
X00 = sp.stats.norm(-3, 1).rvs(64)
X01 = sp.stats.norm(-4, 1).rvs(64)
X10 = sp.stats.norm(-1, 1).rvs(66)
X11 = sp.stats.norm(0, 1).rvs(66)
X20 = sp.stats.norm(3, 1).rvs(70)
X21 = sp.stats.norm(4, 1).rvs(70)
X1 = np.hstack([X00, X10, X20])[:, np.newaxis]
X2 = np.hstack([X01, X11, X21])[:, np.newaxis]
X = np.hstack([X1, X2])
y0 = np.zeros(64)
y1 = np.ones(66)
y2 = np.ones(70)*2
y = np.hstack([y0, y1, y2])
In [26]:
X.shape, y.shape
Out[26]:
In [27]:
dfX = pd.DataFrame(X)
dfy = pd.DataFrame(y)
dfX.tail(2)
Out[27]:
In [28]:
dfy.tail(3)
Out[28]:
In [29]:
df = pd.concat([dfX, dfy], axis=1)
In [30]:
df.tail(2)
Out[30]:
In [31]:
df.columns = ["X1", "X2", "y"]
In [32]:
df.head(2)
Out[32]:
In [33]:
sns.pairplot(df, hue="y")
Out[33]:
In [34]:
cmap = mpl.colors.ListedColormap(sns.color_palette("Set3"))
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap=cmap)
Out[34]:
In [35]:
xmin, xmax = -8, 6
ymin, ymax = -8, 6
XX, YY = np.meshgrid(np.arange(xmin, xmax, (xmax-xmin)/1000), np.arange(ymin, ymax, (ymax-ymin)/1000))
ZZ = np.reshape(clf_norm.predict(np.array([XX.ravel(), YY.ravel()]).T), XX.shape)
cmap = mpl.colors.ListedColormap(sns.color_palette("Set3"))
plt.contourf(XX, YY, ZZ, cmap=cmap, alpha=0.5)
plt.scatter(X[:,0], X[:,1], c=y, s=50, cmap=cmap)
plt.xlim(xmin, xmax)
plt.ylim(ymin, ymax)
Out[35]:
In [36]:
clf_norm = GaussianNB().fit(X, y)
In [37]:
clf_norm.classes_
Out[37]:
In [38]:
clf_norm.class_count_
Out[38]:
In [39]:
clf_norm.class_prior_
Out[39]:
In [40]:
clf_norm.theta_, clf_norm.sigma_
Out[40]:
In [41]:
clf_norm = GaussianNB().fit(df.ix[:, :2], df.ix[:, -1])
In [42]:
clf_norm.classes_
Out[42]:
In [43]:
clf_norm.class_count_
Out[43]:
In [44]:
clf_norm.theta_, clf_norm.sigma_
Out[44]:
In [45]:
print(clf_norm.theta_, "\n", clf_norm.sigma_)
In [46]:
xx = np.linspace(-6, 7, 100)
p0 = sp.stats.norm(clf_norm.theta_[0][0], clf_norm.sigma_[0][0]).pdf(xx)
p1 = sp.stats.norm(clf_norm.theta_[1][0], clf_norm.sigma_[1][0]).pdf(xx)
p2 = sp.stats.norm(clf_norm.theta_[2][0], clf_norm.sigma_[1][0]).pdf(xx)
sns.distplot(X00, rug=True, kde=False, norm_hist=True, color="r", label="class 00 histogram")
sns.distplot(X10, rug=True, kde=False, norm_hist=True, color="b", label="class 10 histogram")
sns.distplot(X20, rug=True, kde=False, norm_hist=True, color="y", label="class 20 histogram")
plt.plot(xx, p0, c="r", label="class 00 est. pdf")
plt.plot(xx, p1, c="b", label="class 10 est. pdf")
plt.plot(xx, p2, c="y", label="class 20 est. pdf")
plt.legend()
plt.show()
xx = np.linspace(-7, 6, 100)
p0 = sp.stats.norm(clf_norm.theta_[0][1], clf_norm.sigma_[0][1]).pdf(xx)
p1 = sp.stats.norm(clf_norm.theta_[1][1], clf_norm.sigma_[1][1]).pdf(xx)
p2 = sp.stats.norm(clf_norm.theta_[2][1], clf_norm.sigma_[1][1]).pdf(xx)
sns.distplot(X01, rug=True, kde=False, norm_hist=True, color="r", label="class 01 histogram")
sns.distplot(X11, rug=True, kde=False, norm_hist=True, color="b", label="class 11 histogram")
sns.distplot(X21, rug=True, kde=False, norm_hist=True, color="y", label="class 21 histogram")
plt.plot(xx, p0, c="r", label="class 01 est. pdf")
plt.plot(xx, p1, c="b", label="class 11 est. pdf")
plt.plot(xx, p2, c="y", label="class 21 est. pdf")
plt.legend()
plt.show()
In [47]:
x_new = [3, 1]
clf_norm.predict_proba([x_new])
Out[47]:
In [48]:
px = sp.stats.norm(clf_norm.theta_, np.sqrt(clf_norm.sigma_)).pdf(x_new)
In [49]:
p_x0 = px[:, 0] * clf_norm.class_prior_
p_x1 = px[:, 1] * clf_norm.class_prior_
In [50]:
p_x0 / p_x0.sum()
Out[50]:
In [51]:
p_x1 / p_x1.sum()
Out[51]:
In [52]:
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification
In [53]:
model = LogisticRegression().fit(X, y)
confusion_matrix(y, model.predict(X))
Out[53]:
In [54]:
print(classification_report(y, model.predict(X)))
In [55]:
y_pred = clf_norm.predict(X)
In [56]:
confusion_matrix(y, y_pred)
Out[56]:
In [57]:
print(classification_report(y, y_pred, target_names=["X0", "X1", "X2"]))
In [58]:
xmin, xmax = -8, 6
ymin, ymax = -8, 6
XX, YY = np.meshgrid(np.arange(xmin, xmax, (xmax-xmin)/1000), np.arange(ymin, ymax, (ymax-ymin)/1000))
ZZ = np.reshape(clf_norm.predict(np.array([XX.ravel(), YY.ravel()]).T), XX.shape)
cmap = mpl.colors.ListedColormap(sns.color_palette("Set1"))
plt.contourf(XX, YY, ZZ, cmap=cmap, alpha=0.5)
plt.scatter(X[:,0], X[:,1], c=y, s=50, cmap=cmap)
plt.xlim(xmin, xmax)
plt.ylim(ymin, ymax)
Out[58]:
베르누이 나이브 베이즈 모형에서는 타겟 변수뿐 아니라 독립 변수도 0 또는 1의 값을 가져야 한다. 예를 들어 전자우편과 같은 문서 내에 특정한 단어가 포함되어 있는지의 여부는 베르누이 확률 변수로 모형화할 수 있으므로 스팸 필터링에 사용할 수 있다.
In [59]:
np.random.seed(0)
X = np.random.randint(2, size=(10, 4))
y = np.array([0,0,0,0,1,1,1,1,1,1])
print(X)
print(y)
In [60]:
from sklearn.naive_bayes import BernoulliNB
clf_bern = BernoulliNB().fit(X, y)
In [61]:
clf_bern.classes_
Out[61]:
In [62]:
clf_bern.class_count_
Out[62]:
In [63]:
np.exp(clf_bern.class_log_prior_)
Out[63]:
In [64]:
fc = clf_bern.feature_count_
fc
Out[64]:
In [65]:
fc / np.repeat(clf_bern.class_count_[:, np.newaxis], 4, axis=1)
Out[65]:
In [66]:
theta = np.exp(clf_bern.feature_log_prob_)
theta
Out[66]:
In [67]:
x_new = np.array([1, 1, 0, 0])
In [68]:
clf_bern.predict_proba([x_new])
Out[68]:
In [69]:
p = ((theta**x_new)*(1-theta)**(1-x_new)).prod(axis=1)*np.exp(clf_bern.class_log_prior_)
p / p.sum()
Out[69]:
In [70]:
x_new = np.array([0, 0, 1, 1])
In [71]:
clf_bern.predict_proba([x_new])
Out[71]:
In [72]:
p = ((theta**x_new)*(1-theta)**(1-x_new)).prod(axis=1)*np.exp(clf_bern.class_log_prior_)
p / p.sum()
Out[72]:
In [73]:
from sklearn.naive_bayes import MultinomialNB
clf_mult = MultinomialNB().fit(X, y)
In [74]:
clf_mult.classes_
Out[74]:
In [75]:
clf_mult.class_count_
Out[75]:
In [76]:
fc = clf_mult.feature_count_
fc
Out[76]:
In [77]:
fc / np.repeat(fc.sum(axis=1)[:, np.newaxis], 4, axis=1)
Out[77]:
In [78]:
clf_mult.alpha
Out[78]:
In [79]:
(fc + clf_mult.alpha) / (np.repeat(fc.sum(axis=1)[:, np.newaxis], 4, axis=1) + clf_mult.alpha * X.shape[1])
Out[79]:
In [80]:
theta = np.exp(clf_mult.feature_log_prob_)
theta
Out[80]:
In [81]:
x_new = np.array([21, 35, 29, 14])
clf_mult.predict_proba([x_new])
Out[81]:
In [82]:
p = (theta**x_new).prod(axis=1)*np.exp(clf_bern.class_log_prior_)
p / p.sum()
Out[82]:
In [83]:
x_new = np.array([18, 24, 35, 24])
clf_mult.predict_proba([x_new])
Out[83]:
In [84]:
x_new = np.array([4, 4, 5, 4])
clf_mult.predict_proba([x_new])
Out[84]:
In [85]:
from sklearn.datasets import fetch_20newsgroups
from sklearn.cross_validation import train_test_split
news = fetch_20newsgroups(subset="all")
X_train, X_test, y_train, y_test = train_test_split(news.data, news.target, test_size=0.1, random_state=1)
In [86]:
from sklearn.feature_extraction.text import TfidfVectorizer, HashingVectorizer, CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
clf_1 = Pipeline([
('vect', CountVectorizer()),
('clf', MultinomialNB()),
])
clf_2 = Pipeline([
('vect', TfidfVectorizer()),
('clf', MultinomialNB()),
])
clf_3 = Pipeline([
('vect', TfidfVectorizer(token_pattern=r"\b[a-z0-9_\-\.]+[a-z][a-z0-9_\-\.]+\b")),
('clf', MultinomialNB()),
])
clf_4 = Pipeline([
('vect', TfidfVectorizer(stop_words="english",
token_pattern=r"\b[a-z0-9_\-\.]+[a-z][a-z0-9_\-\.]+\b")),
('clf', MultinomialNB()),
])
clf_5 = Pipeline([
('vect', TfidfVectorizer(stop_words="english",
token_pattern=r"\b[a-z0-9_\-\.]+[a-z][a-z0-9_\-\.]+\b")),
('clf', MultinomialNB(alpha=0.01)),
])
In [87]:
from sklearn.cross_validation import cross_val_score, KFold
from scipy.stats import sem
for i, clf in enumerate([clf_1, clf_2, clf_3, clf_4, clf_5]):
scores = cross_val_score(clf, X_test, y_test, cv=5)
print(("Model {0:d}: Mean score: {1:.3f} (+/-{2:.3f})").format(i, np.mean(scores), sem(scores)))
In [88]:
from sklearn.datasets import fetch_20newsgroups
from sklearn.cross_validation import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer, HashingVectorizer, CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
news = fetch_20newsgroups(subset="all")
X_train, X_test, y_train, y_test = train_test_split(news.data, news.target, test_size=0.1, random_state=1)
In [89]:
clf_0 = Pipeline([
("vect", CountVectorizer()),
("clf", LogisticRegression())
])
In [90]:
clf_0.fit(X_train, y_train)
Out[90]:
In [91]:
y_pred = clf_0.predict(X_train)
In [92]:
confusion_matrix(y_train, y_pred)
Out[92]:
In [93]:
print(classification_report(y_train, y_pred))
In [94]:
y_pred = clf_0.predict(X_test)
In [95]:
confusion_matrix(y_test, y_pred)
Out[95]:
In [96]:
print(classification_report(y_test, y_pred))
In [97]:
import codecs
def read_data(filename):
with codecs.open(filename, encoding='utf-8', mode='r') as f:
data = [line.split('\t') for line in f.read().splitlines()]
data = data[1:] # header 제외
return data
train_data = read_data('ratings_train.txt')
test_data = read_data('ratings_test.txt')
In [101]:
X = list(zip(*train_data))[1] # 같이 있던 애들을 쪼개
y = list(zip(*train_data))[2] # zip()은 묶어주는 것, zip(*)은 unzip. 풀어주는 것
y = np.array(y, dtype=int)
In [99]:
len(X), len(y)
Out[99]:
In [100]:
from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=10000, test_size=10000)
len(X_train), len(X_test)
Out[100]:
In [102]:
from konlpy.utils import pprint
pprint((X[0], y[0]))
In [103]:
import jpype
In [105]:
from konlpy.tag import Twitter
pos_tagger = Twitter()
In [106]:
%%time
from konlpy.tag import Twitter
pos_tagger = Twitter()
def tokenize(doc):
return ['/'.join(t) for t in pos_tagger.pos(doc, norm=True, stem=True)]
train_docs = [(tokenize(row[1]), row[2]) for row in train_data[:10000]]
tokens = [t for d in train_docs for t in d[0]]
import nltk
text = nltk.Text(tokens, name='NMSC')
mpl.rcParams["font.family"] = "NanumGothic"
plt.figure(figsize=(12,10))
text.plot(50)
plt.show()
In [107]:
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.metrics import classification_report
clf_1 = Pipeline([
('vect', CountVectorizer()),
('clf', MultinomialNB()),
])
In [108]:
%%time
clf_1.fit(X_train, y_train)
Out[108]:
In [109]:
pprint(list(clf_1.named_steps["vect"].vocabulary_)[:10])
In [110]:
%%time
print(classification_report(y_test, clf_1.predict(X_test)))
In [111]:
from sklearn.feature_extraction.text import TfidfVectorizer
clf_2 = Pipeline([
('vect', TfidfVectorizer()),
('clf', MultinomialNB()),
])
In [112]:
%%time
clf_2.fit(X_train, y_train)
Out[112]:
In [113]:
%%time
print(classification_report(y_test, clf_2.predict(X_test)))
In [114]:
from konlpy.tag import Twitter
pos_tagger = Twitter()
def tokenize_pos(doc):
return ['/'.join(t) for t in pos_tagger.pos(doc, norm=True, stem=True)]
clf_3 = Pipeline([
('vect', CountVectorizer(tokenizer=tokenize_pos)),
('clf', MultinomialNB()),
])
In [115]:
%%time
clf_3.fit(X_train, y_train)
Out[115]:
In [116]:
pprint(list(clf_3.named_steps["vect"].vocabulary_)[:10])
In [117]:
%%time
print(classification_report(y_test, clf_3.predict(X_test), digits=4))
In [118]:
vect3 = clf_3.named_steps["vect"]
idx3 = np.array(np.argsort(vect3.transform(X_train).sum(axis=0)))[0]
voca3 = np.array(vect3.get_feature_names()).flatten()
pprint(voca3[idx3[-20:]].tolist())
In [119]:
clf_4 = Pipeline([
('vect', TfidfVectorizer(tokenizer=tokenize_pos, ngram_range=(1,2))),
('clf', MultinomialNB()),
])
In [120]:
%%time
clf_4.fit(X_train, y_train)
Out[120]:
In [121]:
%%time
print(classification_report(y_test, clf_4.predict(X_test), digits=4))