確率・統計学を学ぶ際、最初に学ぶ最も重要な定理の1つが「ベイズの定理」である。この定理は演繹的推論の基礎であり、ある事象が発生する確率を、その事象に関連しうる条件についての事前知識に基づいて決定することに焦点を当てる。
ナイーブベイズ分類器は、この定理の力を機械学習に導入し、非常にシンプルかつ強力な分類器を構築している。この記事では、この分類器がどのように機能するか、どのような用途に適しているか、そしてPythonとScikit-Learnライブラリを使って数行で使用する方法についての概要を説明します。
ベイズの定理の背景となる理論
コンピュータサイエンスや数学など、統計学を学んだ人なら、どこかで次のような公式を目にしたことがあるのではないだろうか。
P(H|E) = (P(E|H) * P(H)) / P(E)
ここで
- P(H|E)
は事象
Eが与えられたときの仮説
Hの確率、つまり事後確率です。
は仮説
P(E|H)H
が真である場合に事象E
が起こる確率であり、事後確率である *P(H)
は仮説H
が真である場合に事象E
が起こる確率である。 - P(H)
は仮説
Hが真である確率、つまり
H` の事前確率である。 - P(E)` は、(仮説に関係なく)事象が発生する確率である。
これが「ベイズの定理」です。一見、意味がわかりにくいかもしれないが、例題を通して探ってみると非常に直感的である。
例えば、セックスという単語を含むメール(事象)がスパムかどうか(仮説)を知りたいとする。定理の説明に戻ると、この問題は次のように定式化できる。
P(class=SPAM|contains="sex") = (P(contains="sex"|class=SPAM) * P(class=SPAM)) / P(contains="sex")
であり、平たく言えば sexという単語を含むメールがスパムである確率は、sexという単語を含むSPAMメールの割合に、スパムであるメールの割合を掛け、sexという単語を含むメールの割合で割ったものに等しい。
これをひとつひとつ分解してみよう。
- P(class=SPAM|contains=”sex”)` は、あるメールにsexという単語が含まれている場合に、そのメールがSPAMである確率を表しています。私たちはこれを予測することに興味があるのです。
- P(contains=”sex”|class=SPAM)` は、このメールがSPAMと認識された場合に、SEXという単語を含むメールの確率である。これは学習データであり、SPAMと判断されたメールと、そのメールに含まれるsexの相関を表しています。
- P(class=SPAM)`は、あるメールがSPAMである確率である(含まれる単語についての事前知識はない)。これは単純に、学習セット全体におけるSPAMであるメールの割合である。この値を掛けるのは、SPAMメールに関する情報がどの程度重要であるかを知りたいからである。この値が低ければ、SPAMメールに関連する事象の有意性も低くなる。
-
P(contains="sex")
は、sexという単語を含むメールの確率を表します。これは単純に、学習セット全体におけるsexという単語を含むメールの割合である。sexという単語が排他的であればあるほど、それが出現する文脈が重要になるため、この値で割っています。したがって、この数値が低い場合(この単語が現れるのは非常にまれ)、この単語が現れる場合には、それが分析に関連する特徴であることを示す大きな指標となりえます。
まとめると、ベイズの定理は、現実世界で起こっている事象を、それを示唆する可能性のある観察結果の事前知識に基づいて、合理的に推論することを可能にする。この定理をどんな問題にも適用するためには、式に現れる2種類の確率を計算する必要がある。
クラス確率
この定理において,P(A)は各事象の確率を表しています.ナイーブベイズ分類器では、このクラス確率を、単純に事象の各インスタンスの頻度をインスタンスの総数で割ったものと解釈することができる。例えば、先ほどのスパム検出の例では、
P(class=SPAM)はスパムと分類されたメールの数を全インスタンスの合計で割ったものです(これは
spam + not spam` となります)。
P(class=SPAM) = count(class=SPAM) / (count(class=notSPAM) + count(class=SPAM))
条件付き確率
この定理において、 P(A|B)
は、ある事象 A
が別の事象 B
に与えられたときの条件付き確率を表している。ナイーブベイズ分類器では、B
が真であるときにA
が発生する事後確率を表現している。
スパムの例では、 P(class=SPAM|contains="sex")
は、あるメールがスパムとみなされ、かつセックスという単語を含む場合の数を、セックスという単語を含むメールの総数で割ったものを表します。
P(class=SPAM|contains="sex") = count(class=SPAM & contains=sex) / count(contains=sex)
アプリケーション
ナイーブベイズ分類器の応用は,様々なシナリオで成功することが示されています.古典的なユースケースは,ドキュメントの分類です:与えられたドキュメントが特定のカテゴリに対応するかどうかを決定します.しかしながら、この手法には利点と限界があります。
メリット
- ナイーブベイズはシンプルで実装が容易なアルゴリズムである。そのため、データ量が少ない場合、より複雑なモデルより優れている可能性がある。
- Naive Bayesは数値データ、カテゴリデータに有効である。また、ガウス型 Naive Bayes を用いて回帰を行うことも可能である。
制限事項
- この定理の構成からすると、学習データに特定の値の組み合わせがない場合、うまく機能しない。つまり、あるクラスラベルとある属性値の組み合わせがない場合(例:class=”spam”, contains=”$$$” )、頻度ベースの確率推定値はゼロとなる。Naive-Bayesの条件付き独立性の仮定を考えると、すべての確率を掛け合わせると0になります。
- ナイーブ・ベイズは、カテゴリが単純である限り、うまく機能する。例えば、キーワードを特徴量とする問題(スパム検出など)には有効だが、単語間の関係が重要な問題(センチメント分析など)には有効でない。
Scikit-Learnでのデモ
デモの時間です! Python 3とScikit-Learnを使って、SMSメッセージ(若い人たちのために、これは中世にメッセージに使われたものです)用の非常にシンプルなSPAM検出器を作ります。データセットはこのリンクからダウンロードできます。
コーディングを簡単にするために、3つのライブラリーが必要です。scikit-learn、
pandas、
nltkです。これらをインストールするには、
pipや
conda` を使用することができる。
データの読み込み
SMS Spam Collection v.1は、SMS Spam研究のために収集されたSMSタグ付きメッセージのセットです。5,574通の英語のSMSメッセージが含まれており、ハム(合法)かスパムかによってタグ付けされています。分布は、合計4,827通の正当なメッセージ(86.6%)と合計747通のスパムメッセージ(13.4%)である。
データセットを開くと、[label] [tab] [message]
という形式になっており、以下のような感じになっています。
ham Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...
ham Ok lar... Joking wif u oni...
spam Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's
ham U dun say so early hor... U c already then say...
データを読み込むには、PandasのDataframeの read_table
メソッドを使用することができます。このメソッドでは、セパレータ(この場合はタブ)を定義して、それに応じてカラムの名前を変更することができます。
import pandas as pd
df = pd.read_table('SMSSpamCollection',
sep=' ',
header=None,
names=['label', 'message'])
前処理
データの準備ができたら、前処理をしましょう。ここでは、このタスクのために無駄な分散を取り除くことに焦点を当てます。まず、分類器のためにラベルを文字列から2値に変換する必要があります。
df['label'] = df.label.map({'ham': 0, 'spam': 1})
次に、メッセージに含まれるすべての文字を小文字に変換します。
df['message'] = df.message.map(lambda x: x.lower())
3番目に、句読点を削除します。
df['message'] = df.message.str.replace('[^ws]', '')
四つ目は、nltk を使ってメッセージを単語単位にトークン化します。まず、コンソールからトークナイザーをインポートし、ダウンロードする必要があります。
import nltk
nltk.download()
インストール・ウィンドウが表示されます。Models」タブに移動し、「Identifier」列から「punkt」を選択する。そして、”Download “をクリックすると、必要なファイルがインストールされます。すると、うまくいくはずです これで、トークン化を適用することができます。
df['message'] = df['message'].apply(nltk.word_tokenize)
5 番目に、単語のステミングを行います。ステミングの概念は、時制に関係なく同じ意味を持つすべての単語のバリエーションについて、テキストを正規化することです。最も一般的なステミングアルゴリズムの 1 つに、Porter Stemmer があります。
from nltk.stem import PorterStemmer
stemmer = PorterStemmer()
df['message'] = df['message'].apply(lambda x: [stemmer.stem(y) for y in x])
最後に、データを出現頻度に変換します。これは、モデルに送り込む特徴量になります。
from sklearn.feature_extraction.text import CountVectorizer
# This converts the list of words into space-separated strings
df['message'] = df['message'].apply(lambda x: ' '.join(x))
count_vect = CountVectorizer()
counts = count_vect.fit_transform(df['message'])
メッセージごとの単純な単語数のままにしておくこともできますが、Term Frequency Inverse Document Frequency (tf-idf`として知られている) を使用する方がよいでしょう。
from sklearn.feature_extraction.text import TfidfTransformer
transformer = TfidfTransformer().fit(counts)
counts = transformer.transform(counts)
モデルのトレーニング
データから特徴抽出を行ったので、いよいよモデルを構築します。まず、データをトレーニングセットとテストセットに分割することから始める。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(counts, df['label'], test_size=0.1, random_state=69)
そして、ナイーブベイズ分類器を初期化し、データをフィットさせるだけです。テキスト分類の問題では、多項式ナイーブベイズ分類器が適しています。
from sklearn.naive_bayes import MultinomialNB
model = MultinomialNB().fit(X_train, y_train)
モデルの評価
分類器を組み上げたら、テストセットでその性能を評価します。
import numpy as np
predicted = model.predict(X_test)
print(np.mean(predicted == y_test))
おめでとうございます! この単純な Naive Bayes 分類器は、このテストセットに対して 98.2% の精度を持っています! しかし、このデータセットはラベルのバランスが悪いので、精度を出すだけでは十分ではありません(正規の86.6%に対し、スパムは13.4%)。分類器が正規のクラスに過剰に適合し、スパムのクラスを無視している可能性があります。この不確実性を解決するために、混同行列を見てみましょう。
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_test, predicted))
confusion_matrix`メソッドはこのような結果を出力します。
[[478 4]
[ 6 70]]
見ての通り、エラーの量は正規とスパムの間でかなりバランスが取れており、4つの正規のメッセージがスパムに分類され、6つのスパムメッセージが正規に分類されています。全体として、これは我々の単純な分類器としては非常に良い結果です。
結論
この記事では、Naive Bayes Classifierの理論と実践の両方について、クラッシュコースを見てきました。我々は、SMSメッセージのスパム検出において98.2%の精度を達成するシンプルなマルチモーダルナイーブベイズ分類器をまとめました。