実務を彩るNaive-Bayes ~自社HP判定モデル~

こんにちは、スクラムサインの大塚です。私はスクラムサインのインターン生として、Naive-Bayesを用いたクラス分類アルゴリズムを実装していました。今回はNaive-Bayesの解説と、実務で実際にどのようにそれを利用したかをまとめて紹介いたします。

# 目次
[・まず、Naive-Bayesとはそもそも何か？](#-まず、Naive-Bayesとはそもそも何か？)
[1.ベイズの定理](##-1.ベイズの定理)
[2.Naive-Bayesの数式モデル](##-2.Naive-Bayesの数式モデル)
[3.NaiveなBayesの意味とは？](##-3.NaiveなBayesの意味とは？)

[・Pythonを用いた実装方法](#-Pythonを用いた実装方法)
[1. データの用意](##-1. データの用意)
[2. データの前処理](##-2. データの前処理)
[3. 学習](##-3. 学習)
[4. 予測](##-4. 予測)
[5. 精度の評価](##-5. 精度の評価)

[・まとめ](#-まとめ)

# まず、Naive-Bayesとはそもそも何か？
Naive-Bayesとは、**ベイズの定理をもとにした、教師あり学習アルゴリズム**のことです。
特に分類タスク向けのアルゴリズムで、文書データに対してよい結果を出すことが知られています。

このNaive-Bayesについて数式的な面からまずは解説していきます。

## 1.ベイズの定理
まずは、このNaive-Bayesのもとになっている**ベイズの定理**から解説します。

ある事象Aが起こる確率を$P(A)$、ある事象が起こる確率を$P(B)$とします。
このとき、$B$が起こった上で$A$が起こる確率を、$P(A|B)$のように表すことができ、これを
**$B$が与えられた時の$A$の事後確率と言います。**

ベイズの定理とは、この事後確率に関する定理で、以下の数式で表されます。
$$
P(A|B) = \frac{P(B|A)P(A)}{P(B)}
$$

つまり、関連ある事象の事前確率から、ある事象の確率を記述する方法を導く定理です。

## 2.Naive-Bayesの数式モデル

それでは、Naive-Bayes本体の数式モデルを見ていきます。

あるクラス変数$y$と、その特徴量である$x_1, x_2, \cdots x_n$において、ベイズの定理から
$$
P(y|x_1, x_2, \cdots ,x_n) = \frac{P(x_1, x_2, \cdots ,x_n|y)P(y)}{P(x_1, x_2, \cdots ,x_n)}
$$
が成り立ちます。

しかし、右辺の分子に存在する$P(x_1, x_2, \cdots ,x_n|y)$を考えることは非常に難しいです。なぜなら、$P(x_1, x_2, \cdots, x_n)$つまり、$x_1$から$x_n$という$n$個の特徴ベクトルを持つ確率を考えることはできないからです。

そこで、**「クラス値が与えられれば」という前提のもと、条件付き独立を仮定します。**
つまり、

```math
\begin{align}

P(x_1, x_2, \cdots ,x_n|y) &=&P(x_1|y) P(x_2|y) \cdots P(x_n|y)\\
&=& \prod_{i = 1}^{n} P(x_i| y)

\end{align}
```
と仮定してしまうわけです。

すると、先ほどの式は

```math
\begin{align}

P(y|x_1, x_2, \cdots ,x_n) &=& \frac{P(x_1, x_2, \cdots ,x_n|y)P(y)}{P(x_1, x_2, \cdots ,x_n)}\\
\\
&=& \frac{P(y)\prod_{i = 1}^{n} P(x_i| y)}{P(x_1, x_2, \cdots ,x_n)}

\end{align}
```
と変換することができます。

ここで、右辺の分母である$P(x_1, x_2, \cdots ,x_n)$は定数であるため、

```math
\begin{align}

P(y|x_1, x_2, \cdots ,x_n) &=& \frac{P(x_1, x_2, \cdots ,x_n|y)P(y)}{P(x_1, x_2, \cdots ,x_n)}\\
\\
&=& \frac{P(y)\prod_{i = 1}^{n} P(x_i| y)}{P(x_1, x_2, \cdots ,x_n)}\\

&\propto& P(y)\prod_{i = 1}^{n} P(x_i| y)

\end{align}
```
という比例の関係が成り立ちます。

つまり、ある特徴量$x_1, x_2, \cdots, x_n$が与えられた時にあるクラス変数$y$である確率は、
$P(y)\prod_{i = 1}^{n} P(x_i| y)$に比例するということが言えます。

学習させたデータを元にテストデータの特徴量からこれを計算させることで、テストデータがどのクラスに属するか予測することができます。

各クラスにおいて$P(y)\prod_{i = 1}^{n} P(x_i| y)$の値が一番大きい$y$がそのテストデータの予測クラスです。
つまり、予測したクラスを$\hat{y}$とすると、Naive-Bayesのモデル式は

$$
\hat{y} = arg max P(y)\prod_{i = 1}^{n} P(x_i| y)
$$
のように表すことができます。

## 3.NaiveなBayesの意味とは？
これで、Naive-Bayesに関する数式的な評価はできました。
しかし、このNaiveとはどういう意味なのでしょうか。

naiveとは、日本語で「単純な」と言う意味です。では、どこが単純なのでしょうか？
それは、このアルゴリズムの特徴的な部分である**条件付き独立の仮定**にあります。

例えば、プログラミングの記事について考えるとします。この記事は「プログラミング」と言うクラスに属し、特徴量としてその記事に含まれる単語を持つとします。
この記事において、「Python」という単語がでてきたら、同時に「変数」や「条件分岐」など、同時に含まれる確率が高いであろう単語を考えることができます。逆に、「トマト」や「お米」など、同時に含まれる確率が低いであろう単語もあります。
このように、文書に含まれる単語間の関係は実際は無視できません。

しかしこのNaive-Bayesでは、クラスが与えられていると言う前提のもと、**これらの単語の関係は全て無視され、単語間の関係は全て独立である**と考えます。

この仮定によって、先ほどの数式のような考えやすいシンプルな数式でクラス分類アルゴリズムをを実装することができます。
これが、NaiveなBayesと呼ばれる理由となっています。

もちろん、この仮定が現実で成り立つことはありえませんが、**実応用上ではかなり優れた力を発揮します！**
これは、誤った独立性仮定による誤りの増加よりも、独立性仮定によってパラメータ数を減らしてパラメータの推定精度を向上させたことによる誤りの減少が勝っているからではないかと考えられます。

※ただし、この仮定のために、Naive-Bayesによって推定した確率値はあてにならないといわれているので注意！


# Pythonを用いた実装方法
それでは、実際にPythonでNaive-Bayesを実装してみます。

私がこのNaive-Bayesを用いて実装していた機械学習モデルは、
**Googleで特定の会社や施設を検索して、検索結果に出てきた上位10ページが「自社によるHP」か「他社によるHP」か分類するモデル**です。


この実装は、

    1. データの用意
    2. データの前処理
    3. 学習
    4. 予測
    5. 精度の確認
の順に紹介したいと思います。


## 1. データの用意

今回作成するモデルは、googleの検索結果から自社サイトか他社サイトか分類するものです。そのため、それらの分類に必要と考えられるデータは、

- description (掲載サイトの説明部分)
- url (サイトurl)
- title (サイトタイトル)
- 自社HPかどうか (自社なら1、他社なら0の2値データ)
- comp_name (調べたい会社や施設などの名前)

の5種類です。

次の具体例は、実際に使用した教師データの中の1つになります。

- description: 『神戸地方検察庁. ... 検察官又は検察庁をかたった虚偽公告にご注意ください。 神戸地方検察庁からのお知らせ一覧 ... 兵庫県警察本部 · 兵庫県弁護士会 ... 〒650-0016 神戸市中央区橘通1丁目4番1号 電話：078-367-6100(代表). Copyright (C) ...』
- url: 『http://www.kensatsu.go.jp/kakuchou/kobe/kobe.shtml』
- title: 『神戸地方検察庁』
- 自社HPかどうか: 『1』
- comp_name: 『神戸地方検察庁』


このようなデータの集まりを自社HPかどうかで二つに分けて、それぞれを教師データとすることで教師あり学習を行います。

## 2. データの前処理
次に、先ほどのデータを扱いやすいように加工します。今回の場合、データの前処理は以下の順番に行いました。

1. 文字列の置換・削除
2. 形態素解析による単語ごとの分割
3. 単語のカウント
4. 単語のベクトル化

### 文字列の置換・削除

まずは、タイトルやurl,descriptionを学習させやすいように変換します。
例えば、descriptionやtitleにふくまれる法人名はデータによって違いますが、これをそれぞれの法人名から**「法人名」という文字列**へ置換することで、すべてのデータへ適応可能な学習をすることができます。

また、url(『http://www.kensatsu.go.jp/kakuchou/kobe/kobe.shtml 』)をそのまま利用すると、urlが長い一つの単語とみなされてしまいます。そのため、urlの中の「/」や「.」をスペースに変換し、「https:」や「www」を消すことで、urlを意味のある単語の集団（『kensatsu go jp kakuchou kobe kobe shtml』）として扱うことができます。

他にも、

- 半角と全角を統一など文字表現の正規化
- 固有名詞を「固有名詞」と置換
- 数詞、記号、助詞を削除

など様々な処理を行いました。これで、文字列の置換・削除は完了です。次は特徴量となる単語カウントをするために単語ごとに分割していきます。

### 形態素解析による単語ごとの分割

学習データとして利用するために、descriptionなどに含まれる単語を特徴量としてカウントする必要があります。
この際に、例えば英語の場合「I have a pen」のように単語ごとにスペースで区切られているため、単語のカウントは容易です。
しかし、日本語の場合は、「私はペンを持っています」のように単語ごとに区切られていないため、**そのままでは単語ごとにカウントをすることができません**。そのため、言葉が意味を持つまとまりの単語(形態素)に分割する**形態素解析**をする必要があります。

そこで、今回は形態素解析エンジンであるMeCabを用いて形態素解析しました。`mecab-python3`というライブラリをインストールすると、PythonでMeCabを利用できます。
このMeCabを用いて形態素解析すると、先ほどの「私はペンを持っています」は、「私 は ペン を 持って います」のように分割されることになります。
実際は先ほどの作業で行った文字列の置換により、置換されたり助詞は削除されたりした意味があると考えられる単語の集まりになっています。これで単語の分割は完了しましたので、実際に次は単語のカウントに移っていきます。

### 単語のカウント・one-hotベクトル化

次は、先ほど分割した単語を仕分けしていきます。この際に、単語をベクトル化することで仕分けていきます。

例えば犬、猫、豚という3種類の単語を考えます。単語カウントの際に、それぞれの単語を`[1, 0, 0]`,`[0, 1, 0]`, `[0, 0, 1]`のというように単語を1つの成分が1で残りの成分が全て0であるようなベクトル(これを、one-hotベクトルといいます)で表します。こうすることで、**変数の全ての値を平等に扱う**ことができます。この変換には、`TensorFlow`を用いました。

このようにして、全ての単語に関してone-hotベクトル化をおこなって、リストの各要素がone-hotベクトルであるリストのリストである二次元リストを作成します。あとは、これらを一つにまとめる作業をすれば文書ごとの単語カウントを作成できます。

### 出現単語カウントを一つにまとめる
最後に、先ほど作成したone-hotベクトルを一つにまとめます。

先ほどの犬、猫、豚のone-hotベクトルについて、例えば一つの文書が`[1,0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 0], [0, 1, 0], [1, 0, 0]`を要素として持っているとします。
これを列に対して全て足すと、`[3, 2, 1]`となり、犬が3回、猫が2回、豚が一回出てきている文書ということになります。
このような作業を全文書に対して行うことで、文書ごとの出現単語カウントを要素に持つ1つの二次元リスト`X`が出来上がります。
これらを足す操作は、`Numpy`の`sum`関数を用いて行いました。

あとは、それぞれの文書が自社HPかどうかを要素に持つ`y`を用意することで、教師あり学習を行う準備が整いました。
また、先ほどの`X`,`y`を訓練用とテスト用に`X_train`, `X_test`, `y_train`, `y_test`のように分割しておくことで、学習とは別にテスト精度を評価することができます。

## 3. 学習

実際にNaive-Bayesを用いて学習をしていきます。Pythonには、`scikit-learn`という多くの機械学習アルゴリズムが手軽に実装できるライブラリがあります。これを用いると、簡単にNaive-Bayesを利用することができます。

`scikit-learn`では多数のNaive-bayesアルゴリズムを使えますが、メジャーなのは次の3つです。

- GaussianNB 
- BernoulliNB
- MultinomialNB

そして、これらの使い分けは簡単に言うと、

- クラスが与えられた条件で特徴量の分布が正規分布していると仮定できる場合は、GaussianNB
- 特徴量が0,1の2値で表される時はBernoulliNB
- 単語カウントなど、特徴量が連続値ではなく多数の値をとる時はMultinomialNB

と考えることができます。

今回のデータの場合、　特徴量は記事に含まれる単語カウントの集まりなので、`MultinomialNB`を用いた学習を行います。
学習は、`MultinomialNB`の`fit`メソッドで行うことができます。

```
from sklearn.naive_bayes import MultinomialNB

#MultinomialNBのインスタンス化
multi = MultinomialNB()

#先ほど作成した訓練用データで学習
multi.fit(X_train, y_train)

```
このように、scikit-learnを使うと、かなり短いコードでNaive-Bayesを利用することができます。
これで学習は終わりましたので、次はようやく予測をしてみようと思います！

## 4. 予測
予測には、先ほど学習させた`multi`というインスタンスの`predict`メソッドを使います。
先ほど使用した訓練用データとは別に用意したテストデータを用いてテストし、精度を確認します。

```
#テスト用データに対して予測する
pred = multi.predict(X_test)
```

これで、`pred`には、これらのテストデータの各文書に対して予測したクラスの数値が格納されます。

```
print(pred)
#[1, 0, 0, 1, 0, ..., 1, 0]のように各文書ごとの予測クラスが格納されています。
```

これで、予測も完了しました。あとは、予測したデータがどれくらい正解しているのか確認してみましょう！

## 5. 精度の評価

一般的に教師ありクラス分類の精度を評価するには、混同行列と呼ばれる表を用います。
混同行列とは、あるデータを分類したときに、その正解・不正解の数を整理しておく表です。

今回の場合、テストデータに対して予測をした結果が先ほどの`pred`、そのテストデータの正しいクラス分類が格納されているのが、2.で作成した`y_test`です。

この２つのデータを比較し、実際に私が作ったクラス分類モデルのテストデータに対する混同行列を作成すると次のようになりました。これは、`sklearn.metrics`モジュールの、`confusion_matrix`関数を用いて作成することもできます。

| | 実際の自社HP| 実際の他社HP　| 合計 |
| ---- | ---- | ---- | ---- |
| 自社HPと予測 | 25| 34 | 59|
| 他社HPと予測 | 13| 432| 445|
| 合計 | 38 | 466 | 504 | 

これをみると、他社HP466個のうち、正しく他社HPと予測できたのは432個であり、自社HP38個のうち、正しく自社HPと予測できたのは、25個であることがわかります。
また、誤判定も少ないことからNaive-Bayesは**単純でありながら実務でもなかなか良い結果を出す**ことがわかります。

このように、教師ありクラス分類モデルの精度評価は、混同行列を用いると正判定・誤判定を平等に評価することができます。

# まとめ

今回は、Naive-Bayesに関する数式的な解説を載せたうえで、Pythonによる実装方法を実務と関連づけながら簡単に紹介しました。結果を見ると、Naive-Bayesは簡潔なモデルながら**現実の問題でもかなりの力を発揮する**ことがわかると思います。
機械学習の技術は、日々すさまじいスピードで進歩しています。ライブラリの利用により、簡単に機械学習が実装できてしまうこの時代の中で、表面的に理解をするのではなく、内部の本質を理解していくことの重要性を学びました。