機械学習 〜 ナイーブベイズ分類器 〜

今回は ナイーブベイズ分類器について
ガウシアン、ベルヌーイ分布、多項分布の事象モデルについて scikit-learn を使って試してみます。

# ナイーブベイズ分類器

## ナイーブベイズ分類器とは

- 高速な分類アルゴリズム
- 高次元のデータセットに適している
- ナイーブベイズ＝単純ベイズ
- 迷惑メールの分類などに使われている
- ベイズの定理が使われている

## ベイズの定理

ベイズの定理は以下となり、確率および条件付き確率に関して、P(X) > 0 のとき次が成り立つ

```math
P(Y|X) = \frac{P(Y)P(X|Y)}{P(X)}\\
```

この時、P(Y|X) は事後確率、P(Y) は事前確率、P(X|Y) は尤度と呼ばれる。
また、X については定数と考えることが出来るため、比例関係で表すと以下のようになり、
事後確率は事前確率と尤度の積に比例する。

```math
{P(Y|X)\propto P(Y)P(X|Y)}
```

>メモ：
P(Y|X) は X を与えたときの Y の条件付き確率を示す。
※読み方：X given Y
https://atarimae.biz/archives/15536


## 単純ベイズ確率モデル
上記の比例式を元に確率モデルを作成する。

### 確率モデル

入力変数を x（N個の特徴）、クラスを C（K個） とすると以下のように表せる。

```math
\begin{eqnarray}
 P(C_k | x_1,x_2,...,x_N) &\propto& P(C_k, x_1,x_2,...,x_N)\\
&=& P(C_k)P(x_1, x_2,...,x_n|C_k)\\
&=& P(C_k)\prod_{n=1}^{N} P(x_n|C_k)
\end{eqnarray}
```

### 確率モデルから分類器を構築
分類器は以下の式で表すことができ、一番確率の高いクラスへ分類する。

```math
\hat{y} = argmax_{k\in{\{1,...,K\}}} 
\biggl[ P(C_k)\prod_{n=1}^{N} P(x_n|C_k) \biggr]\\
\\
\hat{y} \in{ \{1,...,K \} }
```

### 尤度

分類器を使う上で、確率分布毎に固有パラメータが必要となります。
固有パラメータの最適な値は、以下の尤度関数を最大化する問題を解く事によって求めます。

#### [尤度関数](https://ja.wikipedia.org/wiki/%E5%B0%A4%E5%BA%A6%E9%96%A2%E6%95%B0)

尤度関数をL(x, C)とすると、以下のように書くことが出来ます。

```math
L(x, C) =  \prod_{k=1}^{K}
\biggl[
 P(C_k)\prod_{i=1}^{n} P(x_i|C_k) 
\biggr]
```

計算の簡略化、及び、[アンダーフロー](https://www.weblio.jp/content/%E3%82%A2%E3%83%B3%E3%83%80%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC)防止のため対数を取ります。
>確率の積を和に変換する事が出来るため、
値が小さくなりすぎる事によるアンダーフロー発生を防止する事が出来ます。


```math
\log L(x, C) =  \sum_{k=1}^{K}
\biggl[
\log  P(C_k) + \sum_{n=1}^{N} \log P(x_i|C_k) 
\biggr]
```

あとは、それぞれの事象モデルごとに尤度関数を最大化する式（微分した結果＝０）を求め、
学習を繰り返す事によって、固有パラメータの最適な値を求めます。

以下、各事象モデルを scikit-learn で試して行きます。

## ガウスモデル (Gaussian naive Bayes)
- 特徴ベクトルにガウス分布(正規分布)を仮定する場合に使われる。
- 連続データを扱う場合に使われる。
- 固有パラメータは μ:平均 と σ^2:分散

#### 事象モデル（Event Model）

```math
P(x = v|C_k) = \frac{1}{\sqrt{2\pi\sigma_k^2}}e^{-\frac{(v-\mu_k)^2}{2\sigma_k^2}}
\\
\mu : 平均
\\
\sigma^2 : 分散
```

#### クラス

[sklearn.naive_bayes.GaussianNB](https://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.GaussianNB.html#sklearn.naive_bayes.GaussianNB) クラスを使用します。


#### サンプルコード

正規分布に従った乱数を発生させます。
データ件数は 100件、クラス数2、標準偏差は 1.5 になります。

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from sklearn.naive_bayes import GaussianNB
from sklearn.datasets import make_blobs

X, y = make_blobs(n_samples=100, n_features=2, random_state=2, centers=2, cluster_std=1.5)

print("X[:5] =", X[:5])
print("y[:5] =", y[:5])

plt.figure(figsize=(8, 7))
plt.scatter(X[:, 0], X[:, 1], c=y,  s=50, cmap='RdBu')
```
先頭５件のデータは以下の通りです。

```:result
X[:5] = [[ 0.92141506 -9.98499137]
 [-5.26927614 -9.6186543 ]
 [-0.45292089 -6.04316334]
 [-0.0856312  -2.16867404]
 [ 1.53194956 -0.36022153]]
y[:5] = [0 0 0 1 1]
```
データをプロットします。

![ナイーブベイズ_g01.png](https://qiita-image-store.s3.amazonaws.com/0/134550/b2f2cd87-02d7-7bd2-28ff-8e8d876647c4.png)

##### モデルの作成と予測

GaussianNB クラスを使い、モデルの作成と学習を行い、
データ先頭５件をそのまま予測します。

```python
model = GaussianNB()
model.fit(X, y)
model.predict(X[:5])
```
```:result
array([0, 0, 0, 1, 1])
```

##### 分類境界線可視化

テストデータとして、ランダムに2000件発生させ、
予測結果を全てプロットしてみます。

```python
rnd = np.random.RandomState(0)
X_test = [-6, -14] + [14, 18] * rnd.rand(2000, 2)

# 予測
Y_pred = model.predict(X_test)

# 境界線
plt.figure(figsize=(8, 7))
plt.scatter(X_test[:, 0], X_test[:, 1], c=Y_pred, s=50, cmap='RdBu')
```
なんとなく境界線がわかりました。
![ナイーブベイズ_g02.png](https://qiita-image-store.s3.amazonaws.com/0/134550/34d77a05-544c-8289-7b11-7d3482c1ab41.png)


##### 各クラスの確率を可視化

predict_proba() を使うと、各クラスの確率が取得出来ます。

```python
# 各クラスの確率
Y_prob = model.predict_proba(X_test)
print(Y_prob[20:30].round(2)) # 小数点第２位まで表示
```
```:result
[[0.98 0.02]
 [1.   0.  ]
 [0.   1.  ]
 [1.   0.  ]
 [1.   0.  ]
 [0.56 0.44]
 [1.   0.  ]
 [1.   0.  ]
 [1.   0.  ]
 [1.   0.  ]]
```

次に、確率で着色してみます。

```python
plt.scatter(X_test[:, 0], X_test[:, 1], c=Y_prob[:, 0], s=50, cmap='RdBu')
```
プロットしてみると、境界付近で確率に変化があるのがわかります。
![ナイーブベイズ_g03.png](https://qiita-image-store.s3.amazonaws.com/0/134550/e9caf472-fd92-f1ad-cc1b-fbf012c0afca.png)



## ベルヌーイ分布モデル (Bernoulli naive Bayes)

- 特徴ベクトルにベルヌーイ分布を仮定する場合に使われる。
- 入力特徴を x とした場合、 x は独立したバイナリ変数（0 または 1）となる。
- 固有パラメータは λ

#### 事象モデル（Event Model）

```math
P(x|C_k) = \prod_{i=1}^{n} λ_{k_i}^{x_i}(1 - λ_{k_i})^{(1-x_i)}
\\
x \in{ \{0,1 \} }\\
0 \leqq λ \leqq 1 

```

#### クラス

[sklearn.naive_bayes.BernoulliNB](https://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.BernoulliNB.html#sklearn.naive_bayes.BernoulliNB) クラスを使用します。

#### 引数（一部）

| パラメータ名 | 概要 | 備考 |
|:-----------|:------------:|:------------:|
| alpha | スムージング(Laplace/Lidstone)  | （初期値:1.0）<br>[ゼロ頻度問題対策](https://www.weblio.jp/content/%E3%82%BC%E3%83%AD%E9%A0%BB%E5%BA%A6%E5%95%8F%E9%A1%8C) |
| binarize | バイナリ化閾値(float) |（初期値:0.0）<br>閾値を超える値を1、それ以外を0として扱う。|

#### サンプルコード

入力 X にバイナリ変数(100次元)を設定し、BernoulliNB クラスを使って
学習・予測を行います。

```python
import numpy as np
from sklearn.naive_bayes import BernoulliNB

X = np.random.randint(2, size=(6, 100))
Y = np.array([1, 2, 3, 4, 4, 5])

print("X[:1] =", X[:1])

model = BernoulliNB()
model.fit(X, Y)

print("Predicted Class = ", model.predict(X[2:3]))
```

予測結果は以下となり、クラス"3"に分類されています。

```:result
X[:1] = [[1 0 1 1 0 1 1 0 1 0 1 1 1 0 0 1 0 0 0 1 0 1 0 0 0 0 1 0 0 0 1 0 0 1 1 1
  0 1 0 0 1 1 1 1 0 0 1 0 1 1 1 0 1 0 1 1 0 1 1 1 0 0 1 0 0 0 1 0 0 1 0 0
  1 1 0 0 0 1 1 1 0 1 0 0 1 1 0 1 1 0 1 1 1 1 1 1 1 1 0 1]]
Predicted Class =  [3]
```


## 多項分布モデル (Multinomial naive Bayes)

- 特徴ベクトルに多項分布を仮定する場合に使われる。

#### 事象モデル（Event Model）

```math
P(x|C_k) = \frac{(\sum_{i=1}^k x_i)!}{\prod_{i=1}^k x_i!}\prod_{i=1}^k p_{i}^{x_i}
```
```math
x_i \geqq 0\\
p_i > 0\\
p_1 + p_2 + ... + p_k = 1
```

#### クラス

[sklearn.naive_bayes.MultinomialNB](https://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.MultinomialNB.html#sklearn.naive_bayes.MultinomialNB) クラスを使用します。

#### 引数（一部）

| パラメータ名 | 概要 | 備考 |
|:-----------|:------------:|:------------:|
| alpha | スムージング(Laplace/Lidstone)  | （初期値:1.0）<br>[ゼロ頻度問題対策](https://www.weblio.jp/content/%E3%82%BC%E3%83%AD%E9%A0%BB%E5%BA%A6%E5%95%8F%E9%A1%8C) |

#### サンプルコード

入力 X に変数(100次元)を設定し、MultinomialNB クラスを使って
学習・予測を行います。

```python
import numpy as np
from sklearn.naive_bayes import MultinomialNB

X = np.random.randint(5, size=(6, 100))
Y = np.array([1, 2, 3, 4, 4, 5])

print("X[:1] =", X[:1])

clf = MultinomialNB()
clf.fit(X, Y)

print("Predicted Class = ", clf.predict(X[2:3]))
```
予測結果は以下となり、クラス"3"に分類されています。

```:result
X[:1] = [[2 3 2 2 3 2 0 2 4 4 4 0 0 3 3 2 1 2 3 1 1 2 2 0 4 0 0 4 0 4 0 4 0 0 4 2
  0 3 3 3 1 4 0 3 4 2 0 1 0 0 0 1 0 4 4 1 0 1 0 3 2 1 1 1 4 0 4 1 3 3 4 3
  3 3 4 2 2 3 1 0 4 0 4 1 1 3 2 0 4 4 3 4 1 0 2 2 3 0 1 3]]
Predicted Class =  [3]
```
predict_proba() を使うと、各クラスの確率が取得出来ます。

```python
# 各クラスの確率
Y_probs = clf.predict_proba(X[2:3])
      
for Y_prob in Y_probs:
  for i in range(Y_prob.shape[0]):
    print("Class [%d] : " % (i+1), Y_prob[i].round(2))
```
```:result
Class [1] :  0.0
Class [2] :  0.0
Class [3] :  1.0
Class [4] :  0.0
Class [5] :  0.0
```
<br>
今回は ナイーブベイズ分類器について、３つの事象モデルを試してみました。
<br>