デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理を基礎からやり直す

TensorFlowコトハジメ Word2Vecで「君の名は。」と戯れてみた

はじめに

前回、Word2Vecを初めて試してみて面白いと思ったので、日本語に挑戦することにした。 yaju3d.hatenablog.jp

日本語に挑戦するにあたり、どうせなら旬なネタがいいなと思って、今のお気に入りは火曜ドラマ「逃げるは恥だが役に立つ」で「みくに - 平匡」の結果がどうなるか見てみたいと思ったわけですが、元となるデータ(2chデータやTVの感想)を収集するのが面倒くさいのと、まだドラマが中盤なので後回しにしました。
その代わりとして思いついたのが新海誠さんの「君の名は。」です。小説が出ているので小説をそのまま取り込めば、ノイズが入らないのでいい結果が得られると思ったからです。ただデータ量としては少ないのが懸念です。

小説のテキスト化

小説 君の名は。 (角川文庫) Kindle版を購入しました。

下記の手順に従って、「君の名は。」をテキスト化しました。個人でデータマイニングする上で問題ないので。
【追記 2017/02/10 参照サイトが削除されていたため、手順を記述しました。】
Kindleで購入した電子書籍には DRM(デジタル著作権管理)プロテクトが掛かっているので解除が必要です。DRM解除機能は「 Calible + DeDRM 」の合わせ技で実現できます。
参照:完全無料!Amazonで購入したKindleの縦書き電子書籍をPDFに変換する方法

Step1:Kindle for PCをインストールします。
Step2:Calibreインストールします。
Step3:DeDRM(tools_v6.2.1.zip)をダウンロードし、解凍するとDeDRM_calibre_pluginフォルダ内に「DeDRM_plugin.zip」が見つかります。
Step4:Kindle for PCに対象の本をダウンロードします。
Step5:Calibreを起動し、マイドキュメントの「My Kindle Content」フォルダにある対象の本「.azw」ファイルをCalibreにドラッグ&ドロップしてください。
Step6:Calibreで「設定」→「プラグイン」→「ファイルからプラグインを読み込む」で「DeDRM_plugin.zip」を選択します。 その際にセキュリティリスクの警告が表示されますが、そのまま「はい」→「OK」とクリックしていきます。プラグインを有効にするために、Calibreを再起動します。
Step7:Calibreで対象の本を選択し、「本を変換」の変換画面で出力フォーマットを「TXT」にして「OK」をクリックすると変換が開始されます。
Step8:変換されたTXTファイルは、マイドキュメントの「Calibre Library」フォルダに保存されています。

形態素解析

テキスト化された「君の名は。」ですが、Word2Vecで使用するには「分かち書き」にする必要があります。
分かち書きとは、ある単位ごとに区切って、その間に空白を置くことです。
形態素解析として、「MeCab」より「JUMAN++」の方が、表記揺れや話し言葉に強いということで、「JUMAN++」を使用することにしました。 qiita.com

JUMAN++のインストール

最初はいつも通りにDocker上のTensorFlowでターミナルを使用としたのですが、「boostlib」のインストールが出来ないため断念、Dockerを使う前に作成したVirtualBoxUbuntu(64bit)とPython数値計算環境「Anaconda」の環境があったため、こちらを久しぶりに使用しました。OSはUbuntu 14.04.2 LTSからアップグレードして、Ubuntu 16.04 LTSにしています。

yaju3d.hatenablog.jp

JUMAN++のインストールには下記サイトを参考にしました。 foolean.net
ただ、「sudo python setup.py install」としても「from pyknp import Jumanpp」で「ImportError: No module named pyknp」となるため、ネットで検索して「pip install ./pyknp-0.3」としたりして何とか使えるようになりました。ここらへんの仕組みがまだ理解出来てないですね。

JUMAN++は、VirtualBox上から使用しているから余計なんですが遅いなと思って、今見たらVirtualBox上のメモリが1GByteのままでした、JUMAN++の推奨はメモリ4GByteです。

JUMAN++による分かち書き

JUMAN++による分かち書きは、下記サイトのプログラムを使用させて頂きました。 foolean.net

最初にオプションを付けないまま、分かち書きをしてWord2Vecを使ったのですが、思ったような結果が得られないとTwitter上で呟いたところ、@K_Ryuichirouさんから、ストップワードを省いてみたらとのアドバイスを頂いて、ストップワードを省いて再挑戦しました。
ストップワードとは、一般的すぎる語や低頻度すぎる語などで、助詞や助動詞などの機能語(日本語ならば「は」「の」「です」「ます」など)のことです。

python3 main.py input.txt -t 名詞 動詞 形容詞 副詞

メモリが少ないこともあって、テキストデータを4つに分割してから、結果を結合しています。あと、事前にあとがき以降と空行を削除しています。

Gensim版Word2Vecのインストール

前回、Gensimを使わない「pip install word2vec」でインストールしたのですが、テキストファイルを読む「word2vec.Text8Corpus」メソッドが無いことに気がつきました。テキストファイルを読む処理などを作り込めばいいのですが、面倒なので素直にGensim版Word2Vecのインストールすることにしました。

今度は、前回と同じ環境であるDocker上のTensorFlowで構築した環境を使用します。
※別にそのままVirtualBox上の環境でも構いません。

$ easy_install -U gensim
Installed /usr/local/lib/python2.7/dist-packages/boto-2.43.0-py2.7.egg                                                                                        
Finished processing dependencies for gensim
$ pip install cython

Cythonを入れるのは、word2vecをスピードアップ(70倍違うとか)させるためです。

Word2Vecと戯れる

先にJupyter notebook上で分かち書きした「yourname.txt」をUploadしておきます。
※yourname.txtは、2000行で176KByteです。

「三葉」と「瀧」のそれぞれの結果で、「俺」と「私」が出てくるのは物語通りですね。

from gensim.models import word2vec
data = word2vec.Text8Corpus('yourname.txt')
model = word2vec.Word2Vec(data, size=200)

out=model.most_similar(positive=[u'三葉'])
for x in out:
    print x[0],x[1]

#結果0.999974846840.999966681004
する 0.9999563694
して 0.9999560117720.999956011772
こと 0.9999519586560.9999514818190.999950230122
先輩 0.999950110912
自分 0.999948740005
from gensim.models import word2vec
data = word2vec.Text8Corpus('yourname.txt')
model = word2vec.Word2Vec(data, size=200)

out=model.most_similar(positive=[u'瀧'])
for x in out:
    print x[0],x[1]

#結果0.9999613165860.999957501888
三葉 0.999945700169
して 0.999944329262
した 0.99993789196
先輩 0.999935507774
もう 0.9999344348910.9999333024020.9999322891240.999930977821

「俺 + 私 - 三葉」の結果が「瀧」ですね。

from gensim.models import word2vec
data = word2vec.Text8Corpus('yourname.txt')
model = word2vec.Word2Vec(data, size=200)

out=model.most_similar(positive=[u'俺',u'私'],negative=[u'三葉'])
for x in out:
    print x[0],x[1]

#結果0.99993789196
して 0.999936521053
こと 0.999934196472
した 0.999931871891
テッシー 0.9999276399610.999927401543
言う 0.999924242496
もう 0.9999235272410.9999231100080.999921619892

同様に「俺 + 私 - 瀧」の結果が「三葉」ですね。

from gensim.models import word2vec
data = word2vec.Text8Corpus('yourname.txt')
model = word2vec.Word2Vec(data, size=200)

out=model.most_similar(positive=[u'俺',u'私'],negative=[u'瀧'])
for x in out:
    print x[0],x[1]

#結果
三葉 0.999938130379
こと 0.9999338388440.999928653240.999924600124
テッシー 0.999921619892
して 0.999921500683
彗星 0.99992030859
する 0.9999191761020.9999167323110.999916255474

「三葉 - 瀧」の結果が「愛」なんてイキですね。

from gensim.models import word2vec
data = word2vec.Text8Corpus('yourname.txt')
model = word2vec.Word2Vec(data, size=200)

out=model.most_similar(positive=[u'三葉'],negative=[u'瀧'])
for x in out:
    print x[0],x[1]

#結果0.0158168040216
トンビ 0.0155568365008
地点 0.0151626057923
置いた 0.0145640941337
情報 0.0137448376045
金色 0.0131560843438
光って 0.0130646442994
古い 0.0127832395956
おばちゃん 0.0127398446202
地面 0.0123804248869

※この「愛」は「愛(いと)おしい」と思われます。

最後に

実際にやってみて、そこそこの結果が得られて良かったです。
データ量が少ないのか「WARNING:gensim.models.word2vec:under 10 jobs per worker: consider setting a smaller `batch_words' for smoother alpha decay」と警告が出ていますので、調整が必要なんだと思います。

Kindleなどの電子小説ならテキスト化できるので、自分の好きな小説を試してみるのも楽しいと思います。

参照

TensorFlowコトハジメ Word2Vecによる自然言語処理を試す

はじめに

以前、ベイジアンフィルタを実装して自然言語処理に興味を持ち始めたので、とりあえず「king - man + woman = queen」で有名になった「Word2Vec」を動かしてみたいと思った次第です。
yaju3d.hatenablog.jp

Word2Vecとは

Word2Vecは米グーグルの研究者であるトマス・ミコロフ氏らが提案した機械学習の分野で使われる、ニューラルネットというモデルを使ったツール/ライブラリです。名前の通り、word(単語)をvector(ベクトル)に変換します。
この技術をベースに、「単語」だけではなく「文書」にも意味を持たせてベクトルとして捉えて利用できる技術「Doc2Vec」も作成されました。

ベクトルは1行m列やn行1列のこと、または「大きさと向き」を持つ量のことです。単語を文字列としているだけでは分類することは出来ないので何かしら意味のある数値にするわけです。こうすることでベクトル同士の足し算・引き算・コサイン類似度などを計算できるようになり、有名になった「king(王様) - man(男) + woman(女性) = queen(女王)」ということが可能になるわけです。
他にも「東京 - 日本 + フランス = パリ」となるという例もあり、これは「首都」であるという関連情報が上位にあるからです。

単語をベクトル表現する方法は「word2vec」以前にもあったのですが、類似度の特徴を満たしていたものの足したり引いたりといった操作までは出来ませんでした。なので、「word2vec」が発表された時は一般には衝撃的だったのです。

enakai00.hatenablog.com

ベクトル空間モデル

word2vec以外で基本的なベクトル空間モデルを2点を列挙します。

BoW(Bag of Words)モデル

テキストデータを単語ごとの出現回数だけで表す方法です。ちなみに、bagは多重集合(multiset)の別名とのこと。
単語の出現順は考慮しないため。「彼女と僕」と「僕と彼女」は同じベクトルになる。

N-gramモデル

テキストデータをN文字単位で文字列を分解して表す方法です。
「彼女と僕」→ 彼女、彼女と、と僕、僕
「僕と彼女」→ 僕、僕と、と彼女、彼女

N-gramモデルでは、隣り合った文字列または単語の組み合わせを「共起関係」と呼びます。
また、「共起関係」がどの程度現れるかを集計した結果を「共起頻度」と呼びます。

実装環境

Pythonは、Docker上のTensorFlowで構築した環境を使用しています。
参照:WindowsユーザーがTensorFlowをインストールしてみた(Docker版) - デジタル・デザイン・ラボラトリーな日々

環境

  • Docker
  • Jupyter
  • Python 2.7
  • Word2Vec 0.9.1
  • CPython 0.25.1

Word2Vecのインストール

Word2Vecをインストールしたら「ImportError: No module named Cython.Build」とエラーになりましたので、先に「Cython」をインストールします。
※Word2Vecは、Pythonから扱える自然言語処理ライブラリ(Gensim)に含まれているのですが、今回はGensimを使わない方法にしました。
※pipはアップグレードしておいただけです。

# pip install --upgrade pip
 ︙
Successfully installed pip-9.0.1  
# pip install Cython
 ︙
Successfully installed Cython-0.25.1
# pip install word2vec
 ︙
Successfully installed word2vec-0.9.1

Word2Vecを試す

下記のword2vecのexamplesサイトを試しに動かしてみます。 http://nbviewer.jupyter.org/github/danielfrg/word2vec/blob/master/examples/word2vec.ipynb

text8.zipのダウンロードと解凍

Word2Vecのデモ用としてtext8という100MB程のコーパスのデータが用意されていますので、ダウンロードして解凍します。
wgetコマンドが無かったので、curlコマンドを使用します。

# curl http://mattmahoney.net/dc/text8.zip > text8.gz                                                                                                         
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                                                               
                                 Dload  Upload   Total   Spent    Left  Speed                                                                                 
100 29.8M  100 29.8M    0     0   497k      0  0:01:01  0:01:01 --:--:--  396k                                                                                
# gzip -d text8.gz -f                                                                                                                                         

Jupyter Notebook

Inの番号が飛んでいるのはミスしたからで意味はありません。 examplesサイトではtext8の格納フォルダ「/Users/drodriguez/Downloads/」になっていますが、自分はカレントフォルダに格納したのでフォルダは付けていません。

「king - man + woman」の部分

indexes, metrics = model.analogy(pos=['king', 'woman'], neg=['man'], n=10)
indexes, metrics
model.generate_response(indexes, metrics).tolist()

結果として、「queen」が先頭になっています。

[(u'queen', 0.2905402467729655),
 (u'empress', 0.2734506828577926),
 (u'prince', 0.2709167973829251),
 (u'wife', 0.2679356346054478),
 (u'monarch', 0.26728773557223445),
 (u'son', 0.2666904115522106),
 (u'throne', 0.2657155461401751),
 (u'regent', 0.26416170868397304),
 (u'pope', 0.2637096213206423),
 (u'pharaoh', 0.2619265026976325)]

最後に

Word2Vecをインストールして英語用のデモを試して雰囲気を楽しんだだけでしたが、次回は日本語を使っていろいろ試してみたいと思います。
今回初めて、GitHub Gistを使ってブログにJupyter Notebookを表示してみました。これ結構いけますね。

参照

TensorFlowコトハジメ 偶数と奇数に分類

はじめに

久しぶりにTensorFlowをさわってみました。
人工知能を勉強しようとしてもハードルが高いし、手書きの文字を分類したからって何って感じ、画像を集めるのも大変だし結果を出すにも時間がかかるしね。
先ずはリハビリとして何をやろうかと思ったのが、以前やったFizz-Buzz問題を応用して偶数と奇数に分類させてみるというもので、結果がすぐ出るのがいいよね。

yaju3d.hatenablog.jp

仕組み

101から127(27-1)までのデータで学習したニューラルネットワークに対して、1から100までの答え(偶数ならeven、奇数ならodd)の予測を出力するプログラムになっています。こんなんでも、贅沢にもディープラーニングを使ってます。
訓練する際に偶数か奇数かを振り分けるのに2の剰余(余り)を使っていますが答えを出すのに2の剰余(余り)を使っていません、コンピューターが学習して判断しています。

最低限で正しい結果が出るようにしたかったので、出来るだけ関連する数値を減らしています。

  • NUM_DIGITS = 7 … 101から学習させる範囲で2の指数値
  • NUM_HIDDEN = 5 … 隠れ層のユニット数
  • BATCH_SIZE = 1 … バッチ数
  • range(30) … エポック(学習ループの単位)の範囲

ソースコード

# coding: utf-8
# even odd in Tensorflow!

import numpy as np
import tensorflow as tf

NUM_DIGITS = 7

# Represent each input by an array of its binary digits.
def binary_encode(i, num_digits):
    return np.array([i >> d & 1 for d in range(num_digits)])

# One-hot encode the desired outputs: ["even", "odd"]
def even_odd_encode(i):
    if   i % 2 == 0: return np.array([1, 0])
    else:            return np.array([0, 1])

# Our goal is to produce even odd for the numbers 1 to 100. So it would be
# unfair to include these in our training data. Accordingly, the training data
# corresponds to the numbers 101 to (2 ** NUM_DIGITS - 1).
trX = np.array([binary_encode(i, NUM_DIGITS) for i in range(101, 2 ** NUM_DIGITS)])
trY = np.array([even_odd_encode(i)           for i in range(101, 2 ** NUM_DIGITS)])

# We'll want to randomly initialize weights.
def init_weights(shape):
    return tf.Variable(tf.random_normal(shape, stddev=0.01))

# Our model is a standard 1-hidden-layer multi-layer-perceptron with ReLU
# activation. The softmax (which turns arbitrary real-valued outputs into
# probabilities) gets applied in the cost function.
def model(X, w_h, w_o):
    h = tf.nn.relu(tf.matmul(X, w_h))
    return tf.matmul(h, w_o)

# Our variables. The input has width NUM_DIGITS, and the output has width 2.
X = tf.placeholder("float", [None, NUM_DIGITS])
Y = tf.placeholder("float", [None, 2])

# How many units in the hidden layer.
NUM_HIDDEN = 5

# Initialize the weights.
w_h = init_weights([NUM_DIGITS, NUM_HIDDEN])
w_o = init_weights([NUM_HIDDEN, 2])

# Predict y given x using the model.
py_x = model(X, w_h, w_o)

# We'll train our model by minimizing a cost function.
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(py_x, Y))
train_op = tf.train.GradientDescentOptimizer(0.05).minimize(cost)

# And we'll make predictions by choosing the largest output.
predict_op = tf.argmax(py_x, 1)

# Finally, we need a way to turn a prediction (and an original number)
# into a even odd output
def even_odd(i, prediction):
     return ["{0:3d}".format(i) + ":even", "{0:3d}".format(i) + ":odd "][prediction]

BATCH_SIZE = 1

# Launch the graph in a session
with tf.Session() as sess:
    tf.initialize_all_variables().run()

    for epoch in range(30):
        # Shuffle the data before each training iteration.
        # print(range(len(trX)))

        p = np.random.permutation(range(len(trX)))
        trX, trY = trX[p], trY[p]

        # Train in batches of 1 inputs.
        for start in range(0, len(trX), BATCH_SIZE):
            end = start + BATCH_SIZE
            sess.run(train_op, feed_dict={X: trX[start:end], Y: trY[start:end]})

        # And print the current accuracy on the training data.
        if epoch % 10 == 0:
            print(epoch, np.mean(np.argmax(trY, axis=1) ==
                             sess.run(predict_op, feed_dict={X: trX, Y: trY})))

    # And now for some even odd
    numbers = np.arange(1, 101)
    teX = np.transpose(binary_encode(numbers, NUM_DIGITS))
    teY = sess.run(predict_op, feed_dict={X: teX})
    output = np.vectorize(even_odd)(numbers, teY)

    print(output)

出力結果

見事に1から100まで偶数と奇数に分類することが出来ました。

(0, 0.66666666666666663)
(10, 0.85185185185185186)
(20, 1.0)
['  1:odd ' '  2:even' '  3:odd ' '  4:even' '  5:odd ' '  6:even'
 '  7:odd ' '  8:even' '  9:odd ' ' 10:even' ' 11:odd ' ' 12:even'
 ' 13:odd ' ' 14:even' ' 15:odd ' ' 16:even' ' 17:odd ' ' 18:even'
 ' 19:odd ' ' 20:even' ' 21:odd ' ' 22:even' ' 23:odd ' ' 24:even'
 ' 25:odd ' ' 26:even' ' 27:odd ' ' 28:even' ' 29:odd ' ' 30:even'
 ' 31:odd ' ' 32:even' ' 33:odd ' ' 34:even' ' 35:odd ' ' 36:even'
 ' 37:odd ' ' 38:even' ' 39:odd ' ' 40:even' ' 41:odd ' ' 42:even'
 ' 43:odd ' ' 44:even' ' 45:odd ' ' 46:even' ' 47:odd ' ' 48:even'
 ' 49:odd ' ' 50:even' ' 51:odd ' ' 52:even' ' 53:odd ' ' 54:even'
 ' 55:odd ' ' 56:even' ' 57:odd ' ' 58:even' ' 59:odd ' ' 60:even'
 ' 61:odd ' ' 62:even' ' 63:odd ' ' 64:even' ' 65:odd ' ' 66:even'
 ' 67:odd ' ' 68:even' ' 69:odd ' ' 70:even' ' 71:odd ' ' 72:even'
 ' 73:odd ' ' 74:even' ' 75:odd ' ' 76:even' ' 77:odd ' ' 78:even'
 ' 79:odd ' ' 80:even' ' 81:odd ' ' 82:even' ' 83:odd ' ' 84:even'
 ' 85:odd ' ' 86:even' ' 87:odd ' ' 88:even' ' 89:odd ' ' 90:even'
 ' 91:odd ' ' 92:even' ' 93:odd ' ' 94:even' ' 95:odd ' ' 96:even'
 ' 97:odd ' ' 98:even' ' 99:odd ' '100:even']

ちなみに、隠れ層のユニット数「NUM_HIDDEN = 3」にした場合、間違った答えになります。

(0, 0.33333333333333331)
(10, 0.66666666666666663)
(20, 1.0)
['  1:odd ' '  2:odd ' '  3:odd ' '  4:odd ' '  5:odd ' '  6:odd '
 '  7:odd ' '  8:odd ' '  9:odd ' ' 10:odd ' ' 11:odd ' ' 12:odd '
 ' 13:odd ' ' 14:odd ' ' 15:odd ' ' 16:even' ' 17:odd ' ' 18:odd '
 ' 19:odd ' ' 20:odd ' ' 21:odd ' ' 22:odd ' ' 23:odd ' ' 24:odd '
 ' 25:odd ' ' 26:odd ' ' 27:odd ' ' 28:odd ' ' 29:odd ' ' 30:odd '
 ' 31:odd ' ' 32:even' ' 33:odd ' ' 34:odd ' ' 35:odd ' ' 36:odd '
 ' 37:odd ' ' 38:odd ' ' 39:odd ' ' 40:even' ' 41:odd ' ' 42:odd '
 ' 43:odd ' ' 44:odd ' ' 45:odd ' ' 46:odd ' ' 47:odd ' ' 48:even'
 ' 49:odd ' ' 50:odd ' ' 51:odd ' ' 52:odd ' ' 53:odd ' ' 54:odd '
 ' 55:odd ' ' 56:even' ' 57:odd ' ' 58:odd ' ' 59:odd ' ' 60:odd '
 ' 61:odd ' ' 62:odd ' ' 63:odd ' ' 64:even' ' 65:odd ' ' 66:odd '
 ' 67:odd ' ' 68:even' ' 69:odd ' ' 70:odd ' ' 71:odd ' ' 72:even'
 ' 73:odd ' ' 74:odd ' ' 75:odd ' ' 76:even' ' 77:odd ' ' 78:odd '
 ' 79:odd ' ' 80:even' ' 81:odd ' ' 82:odd ' ' 83:odd ' ' 84:even'
 ' 85:odd ' ' 86:even' ' 87:odd ' ' 88:even' ' 89:odd ' ' 90:even'
 ' 91:odd ' ' 92:even' ' 93:odd ' ' 94:odd ' ' 95:odd ' ' 96:even'
 ' 97:odd ' ' 98:odd ' ' 99:odd ' '100:even']

最後に

人工知能を使っていろいろやってみたいのですが、それをどうやって組むのかがまだピンと来ないんですよね。
前回、「確率を理解してみる-ベイジアンフィルタを実装」をやってみて自然言語が面白そうなので挑戦してみます。

確率を理解してみる-ベイジアンフィルタを実装

はじめに

前回、「ベイズの定理」について説明しました。 yaju3d.hatenablog.jp

今回、ベイズの定理を利用したベイジアンフィルタの中で最もシンプルなナイーブベイズ(Naive Bayes)を実装してみます。

ベイジアンフィルタ

ベイジアンフィルタは、迷惑メールフィルタの仕組みとして広く知られている機械学習処理のアルゴリズムで、膨大な言葉の組合せで表現される自然言語の文章の分類に、その真価が発揮されます。
今回はベイジアンフィルタの中でもナイーブベイズ(Naive Bayes)というアルゴリズムを使用します。ナイーブは「ばからしいほど単純」という意味があるため「単純ベイズ分類器」とも呼びます。しかし、単純とはいえ馬鹿にできない分類性能を持ちます。

ナイーブベイズの問題点

あるクラスの学習データに存在しない単語を含む文書は決してそのクラスに分類されない(ゼロ頻度問題)があります。

実装環境

下記の記事を使用しますので、言語はPythonとなります。その記事では「Yahoo!デベロッパーズネットワークの日本語形態素解析」を使用しているのですが、その部分は形態素解析の定番である「MeCab」ならぬ「Janome」を使ってみました。 gihyo.jp

Pythonは、Docker上のTensorFlowで構築した環境を使用しています。
参照:WindowsユーザーがTensorFlowをインストールしてみた(Docker版) - デジタル・デザイン・ラボラトリーな日々

環境

Janomeについて

Janome (蛇の目) は, Pure Python で書かれた, 辞書内包の形態素解析器です. 依存ライブラリなしで簡単にインストールでき, アプリケーションに組み込みやすいシンプルな API を備える形態素解析ライブラリを目指しています
http://mocobeta.github.io/janome/

最初は形態素解析の定番である「MeCab」を使う予定だったのですが、インストールの参考サイトで提示されている「https://mecab.googlecode.com/files/mecab-python-0.996.tar.gz」が既にgooglecode自体の消滅により存在しない状態となっているのと、Docker上のJupyterを使用しているので、pipインストールで簡単に済ませたいと思っている中で下記サイトを見つけました。 ailaby.com

Janomeのインストール

Jupyter NotebookのTerminalにて、下記コマンドでインストールします。

# pip install janome
 ︙
Successfully installed janome-0.2.8

実装(morphological.py)

第3回 ベイジアンフィルタを実装してみよう:機械学習 はじめよう|gihyo.jp … 技術評論社
リスト1 形態素解析を使って,わかち書きをする(morphological.py)

Mecabに変更したサイト(Python の「Yahoo!デベロッパーズネットワークの日本語形態素解析で分かち書きする」スクリプトを MeCab を使ってやってみるメモ - 牌語備忘録 -pygo)を参考にJanomeに変更しました。

from janome.tokenizer import Tokenizer

def split(doc, word_class=["形容詞", "形容動詞", "感動詞", "副詞", "連体詞", "名詞", "動詞"]):
    t = Tokenizer()
    tokens = t.tokenize(doc)
    word_list = []
    for token in tokens:
        word_list.append(token.surface)
    return [word for word in word_list]


if __name__ == '__main__':
    doc = u'''Python(パイソン)は、オランダ人のグイド・ヴァンロッサムが作ったオープンソースのプログラミング言語。
オブジェクト指向スクリプト言語の一種であり、Perlとともに欧米で広く普及している。イギリスのテレビ局 BBC が製作したコメディ番組『空飛ぶモンティパイソン』にちなんで名付けられた。
Pythonは英語で爬虫類のニシキヘビの意味で、Python言語のマスコットやアイコンとして使われることがある。Pythonは汎用の高水準言語である。プログラマの生産性とコードの信頼性を重視して設計されており、核となるシンタックスおよびセマンティクスは必要最小限に抑えられている反面、利便性の高い大規模な標準ライブラリを備えている。
Unicodeによる文字列操作をサポートしており、日本語処理も標準で可能である。 多くのプラットフォームをサポートしており(動作するプラットフォーム)、また、豊富なドキュメント、豊富なライブラリがあることから、産業界でも利用が増えつつある。'''
    print ", ".join([s for s in split(doc)])

結果

Python, (, パイソン, ), は, 、, オランダ, 人, の, グイド・ヴァンロッサム, が, 作っ, た, オープン, ソース, の, プログラミング, 言語, 。, 
, オブジェクト, 指向, スクリプト, 言語, の, 一, 種, で, あり, 、, Perl, とともに, 欧米, で, 広く, 普及, し, て, いる, 。, イギリス, の, テレビ局,  , BBC,  , が, 製作, し, た, コメディ, 番組, 『, 空, 飛ぶ, モンティパイソン, 』, に, ちなん, で, 名付け, られ, た, 。, 
, Python, は, 英語, で, 爬虫類, の, ニシキヘビ, の, 意味, で, 、, Python, 言語, の, マスコット, や, アイコン, として, 使わ, れる, こと, が, ある, 。, Python, は, 汎用, の, 高水準, 言語, で, ある, 。, プログラマ, の, 生産, 性, と, コード, の, 信頼, 性, を, 重視, し, て, 設計, さ, れ, て, おり, 、, 核, と, なる, シンタックス, および, セマンティクス, は, 必要, 最小限, に, 抑え, られ, て, いる, 反面, 、, 利便, 性, の, 高い, 大, 規模, な, 標準, ライブラリ, を, 備え, て, いる, 。, 
, Unicode, による, 文字, 列, 操作, を, サポート, し, て, おり, 、, 日本語, 処理, も, 標準, で, 可能, で, ある, 。,  , 多く, の, プラットフォーム, を, サポート, し, て, おり, (, 動作, する, プラットフォーム, ), 、, また, 、, 豊富, な, ドキュメント, 、, 豊富, な, ライブラリ, が, ある, こと, から, 、, 産業, 界, で, も, 利用, が, 増え, つつ, ある, 。

実装と説明

Jupyter Notebookを使用しているため、実装は分けて書いていきます。
基本的な説明は、「第3回 ベイジアンフィルタを実装してみよう - ナイーブベイズのアルゴリズム」に書かれているのですが、自分なりに少し補足していきます。

今回は文章(doc)が与えられた時、カテゴリ(cat)に属する確率 P(cat|doc) を求める問題になります。

ベイズの定理

本文に合わせてみると、P(X)がP(doc)、P(Y)がP(cat)となります。
P(Y|X) = \displaystyle{\frac{P(Y)P(X|Y)}{P(X)}}P(cat|doc) = \displaystyle{\frac{P(cat)P(doc|cat)}{P(doc)}}

  • P(X) : X が起きる確率
  • P(Y) : Y が起きる確率(事前確率)
  • P(X|Y) : Y の後でX が起きる確率(条件付き確率、尤度)
  • P(Y|X) : X の後でY が起きる確率(条件付き確率、事後確率)

文章は分割した単語(word)の集合であるので単語の独立性を仮定すると,以下のように近似することができます。
P(doc|cat) = \displaystyle{P(word1|cat) P(word2|cat)  \cdots P(wordn|cat)}

対数は何のため

ソースリストの中で対数を使用していますので、何のためか疑問を持ちました。
そもそも対数と何かなんですが、底の乗数を返します。10を底としたなら1000だと10の3乗なので3となります。
math.log10なら底が10となりますが今回はmath.logで底の指定がないため、底は自然対数のネイピア数(2.71828182…)となります。
jbpress.ismedia.jp

logを取らないと値が0.000….01のような小数となり、小さすぎてアンダーフローを起こす可能性があります。よって対数をとってかけ算を足し算化します。事後確率の大小関係は対数をとっても変化しないので問題ありません。

math.log(0.0000000000001)    # -29.933606208922594
math.log(0.0000000000000001) # -36.841361487904734

参照:対数関連
aidiary.hatenablog.com
qiita.com

ソースリスト

import math
import sys

from janome.tokenizer import Tokenizer

def split(doc, word_class=["形容詞", "形容動詞", "感動詞", "副詞", "連体詞", "名詞", "動詞"]):
    t = Tokenizer()
    tokens = t.tokenize(doc)
    word_list = []
    for token in tokens:
        word_list.append(token.surface)
    return [word for word in word_list]

def getwords(doc):
    words = [s.lower() for s in split(doc)]
    return tuple(w for w in words)
class NaiveBayes:
    # コンストラクタ
    def __init__(self):
        self.vocabularies = set() # 単語の集合
        self.wordcount = {}       # {category : { words : n, ...}}
        self.catcount = {}        # {category : n}

    # 訓練フェーズ:単語のカウントアップ
    def wordcountup(self, word, cat):
        self.wordcount.setdefault(cat, {})
        self.wordcount[cat].setdefault(word, 0)
        self.wordcount[cat][word] += 1
        self.vocabularies.add(word) # 重複を除く

    # 訓練フェーズ:カテゴリのカウントアップ
    def catcountup(self, cat):
        self.catcount.setdefault(cat, 0)
        self.catcount[cat] += 1

    # 訓練
    def train(self, doc, cat):
        word = getwords(doc)
        for w in word:
            self.wordcountup(w, cat)
        self.catcountup(cat)

    # 推定フェーズ:分類
    def classifier(self, doc):
        best = None # 最適なカテゴリ
        max = -sys.maxint
        word = getwords(doc)
        
        # カテゴリ毎に確率の対数を求める
        for cat in self.catcount.keys():
            prob = self.score(word, cat)
            if prob > max:
                max = prob
                best = cat

        return best

    # 推定フェーズ:スコア計算
    def score(self, word, cat):
        score = math.log(self.priorprob(cat))
        for w in word:
            score += math.log(self.wordprob(w, cat))
        return score

    # 推定フェーズ:catの生起確率 P(cat)
    def priorprob(self, cat):
        return float(self.catcount[cat]) / sum(self.catcount.values())

    # 推定フェーズ:あるカテゴリの中に単語が登場した回数
    def incategory(self, word, cat):
        if word in self.wordcount[cat]:
            return float(self.wordcount[cat][word])
        return 0.0

    # 推定フェーズ:条件付き確率 P(word|cat)(補正つき)
    def wordprob(self, word, cat):
        prob = \
            (self.incategory(word, cat) + 1.0) / \
                  (sum(self.wordcount[cat].values()) + \
                   len(self.vocabularies) * 1.0)
        return prob

if __name__ == "__main__":
    nb = NaiveBayes()

    nb.train(u'''Python(パイソン)は、オランダ人のグイド・ヴァンロッサムが作ったオープンソースのプログラミング言語。
オブジェクト指向スクリプト言語の一種であり、Perlとともに欧米で広く普及している。イギリスのテレビ局 BBC が製作したコメディ番組『空飛ぶモンティパイソン』にちなんで名付けられた。
Pythonは英語で爬虫類のニシキヘビの意味で、Python言語のマスコットやアイコンとして使われることがある。Pythonは汎用の高水準言語である。プログラマの生産性とコードの信頼性を重視して設計されており、核となるシンタックスおよびセマンティクスは必要最小限に抑えられている反面、利便性の高い大規模な標準ライブラリを備えている。
Unicodeによる文字列操作をサポートしており、日本語処理も標準で可能である。 多くのプラットフォームをサポートしており(動作するプラットフォーム)、また、豊富なドキュメント、豊富なライブラリがあることから、産業界でも利用が増えつつある。''', 'Python')

    nb.train(u'''Ruby(ルビー)は、まつもとゆきひろ(通称Matz)により開発されたオブジェクト指向スクリプト言語であり、従来Perlなどのスクリプト言語が用いられてきた領域でのオブジェクト指向プログラミングを実現する。Rubyは当初1993年2月24日に生まれ、1995年12月にfj上で発表された。名称のRubyは、プログラミング言語Perlが6月の誕生石であるPearl(真珠)と同じ発音をすることから、まつもとの同僚の誕生石(7月)のルビーを取って名付けられた。''', 'Ruby')

    nb.train(u'''豊富な機械学習(きかいがくしゅう、Machine learning)とは、人工知能における研究課題の一つで、人間が自然に行っている学習能力と同様の機能をコンピュータで実現させるための技術・手法のことである。 ある程度の数のサンプルデータ集合を対象に解析を行い、そのデータから有用な規則、ルール、知識表現、判断基準などを抽出する。 データ集合を解析するため、統計学との関連も非常に深い。
機械学習は検索エンジン、医療診断、スパムメールの検出、金融市場の予測、DNA配列の分類、音声認識や文字認識などのパターン認識、ゲーム戦略、ロボット、など幅広い分野で用いられている。応用分野の特性に応じて学習手法も適切に選択する必要があり、様々な手法が提案されている。それらの手法は、Machine Learning や IEEE Transactions on Pattern Analysis and Machine Intelligence などの学術雑誌などで発表されることが多い。''', u'機械学習')
#Python
words = u'ヴァンロッサム氏によって開発されました.'
print u'%s => 推定カテゴリ: %s' % (words ,nb.classifier(words))

words = u'豊富なドキュメントや豊富なライブラリがあります.'
print u'%s => 推定カテゴリ: %s' % (words ,nb.classifier(words))

#Ruby
words = u'純粋なオブジェクト指向言語です.'
print u'%s => 推定カテゴリ: %s' % (words ,nb.classifier(words))

words = u'Rubyはまつもとゆきひろ氏(通称Matz)により開発されました.'
print u'%s => 推定カテゴリ: %s' % (words ,nb.classifier(words))

#機械学習
words = u'「機械学習 はじめよう」が始まりました.'
print u'%s => 推定カテゴリ: %s' % (words ,nb.classifier(words))

words = u'検索エンジンや画像認識に利用されています.'
print u'%s => 推定カテゴリ: %s' % (words , nb.classifier(words))
結果
ヴァンロッサム氏によって開発されました. => 推定カテゴリ: Ruby
豊富なドキュメントや豊富なライブラリがあります. => 推定カテゴリ: Python
純粋なオブジェクト指向言語です. => 推定カテゴリ: Ruby
Rubyはまつもとゆきひろ氏(通称Matz)により開発されました. => 推定カテゴリ: Ruby
「機械学習 はじめよう」が始まりました. => 推定カテゴリ: 機械学習
検索エンジンや画像認識に利用されています. => 推定カテゴリ: 機械学習

考察

結果を見るとカテゴライズされているのですが、最初の文章が期待通り「Python」に分類されず「Ruby」と分類されてしまっています。 原因については、下記サイト(Rubyに翻訳)で調査されていて、Mecab側にあるとのことです。
antimon2.hatenablog.jp

問題点は、いくつか出てきたのですが大きなのは以下の2つ。

  • 人名「グイド・ヴァンロッサム」が、この塊で一般名詞として認識されている。 特に「ヴァンロッサム」が単語として認識されていないのでカウントに引っかからない。
  • 日本語的には助動詞の「れ(る)」「られ(る)」が、動詞(接尾)として認識されている。 動詞は検索対象なので、これが含まれる量がカテゴリ推定に大きく左右されてしまっている。

学習

連載記事の最後の方にもきちんと書いてあります。

訓練データが増えることによって,より正確な分類ができるようになるので興味のある方はご自身で試してみてください

なので訓練データを追加します。

nb.train(u'''ヴァンロッサム氏によって開発されました。''', 'Python')

結果

ヴァンロッサム氏によって開発されました. => 推定カテゴリ: Python
豊富なドキュメントや豊富なライブラリがあります. => 推定カテゴリ: Python
純粋なオブジェクト指向言語です. => 推定カテゴリ: Ruby
Rubyはまつもとゆきひろ氏(通称Matz)により開発されました. => 推定カテゴリ: Ruby
「機械学習 はじめよう」が始まりました. => 推定カテゴリ: 機械学習
検索エンジンや画像認識に利用されています. => 推定カテゴリ: 機械学

最後に

ベイズの定理の理解からベイジアンフィルタを実装というところまでやりました。
実装といってもほとんど写経に終わってますし、ベイズの定理が少し分かったからといって一から実装するまでの知識は無いわけで、参考記事があって助かっています。今回、形態素解析ってのも初めてでしたし、いろいろと勉強になりましたね。

確率を理解してみる-ベイズの定理

はじめに

前回、ベイズ主義のところで「ベイズの定理」について軽くふれました。
yaju3d.hatenablog.jp

今回、「ベイズの定理」についてもう少し詳しく説明していきます。

ベイズの定理

P(Y|X) = \displaystyle{\frac{P(Y)P(X|Y)}{P(X)}}

  • P(X) : X が起きる確率
  • P(Y) : Y が起きる確率(事前確率)
  • P(X|Y) : Y の後でX が起きる確率(条件付き確率、尤度)
  • P(Y|X) : X の後でY が起きる確率(条件付き確率、事後確率)

確率変数

確率は p(X) という記号で表します。ここで X は「確率変数」、 p(X) は「 X の確率分布」あるいは単に「 X の確率」と言います。X の取り得る値 a に対してその確率を p(X=a),または簡単に p(a) と書きます。

サイコロの例にすると、X はサイコロをふって出る目の「確率変数」、p(X) がその「確率分布」とします。このとき,X の取り得る値は1から6までの6通りです。
すべての目が同じ割合で出るとすると、「確率の合計は1」という条件から、p(X=1) = ... = p(X=6) = 1/6 となります。

ここまで確率変数は1つでしたが、確率変数は複数個になる場合もあります。特に機械学習では、よほど簡単な例題でも複数の確率変数を持っています。

同時確率と条件付き確率

2個の確率変数 X, Y に対する確率分布を p(X, Y) と書き、XとYの「同時分布」または「同時確率」と言います。
確率変数 X に何かある値が与えられているときのYの確率を p(Y|X) と書き、Xが与えられているときのYの「条件付き確率」と言います。 確率変数 X は気にせずに、確率変数 Y のみの確率を考える場合もあります。これは p(X, Y) の Y に関する「周辺確率」と言い、単純に p(Y) と書きます。

例えば「あなたは女性だとして、美人である確率は?」という問題があった場合、先ほどの記号で表すと「P(美女|女性)」となります。
p(Y|X)で、X が前提の上で Y がで成り立つ確率となるわけです。
ベン図で表した場合
f:id:Yaju3D:20160911231744p:plain

数式 P(Y|X) = \displaystyle{\frac{P(X \cap Y)}{P(X)}}

数式の変形

最初の方でベイズの定理の数式を書いたのですが、先ほどの数式と違いますね。
これは式を変形した結果なのです。
P(Y|X) = \displaystyle{\frac{P(Y)P(X|Y)}{P(X)}}P(Y|X) = \displaystyle{\frac{P(X \cap Y)}{P(X)}}

分子の部分 参照:ベイズの定理の基本的な解説
f:id:Yaju3D:20160911234122p:plain

変形式は何故そうなるのか?
P(Y|X) = \displaystyle{\frac{P(X \cap Y)}{P(X)}}
なので、分母を消してみます。分母を消すには左辺と右辺にP(X)を掛けます。

P(X)P(Y|X) = \displaystyle{P(X \cap Y)}
変形式を分かりやすくするため、左辺と右辺の入れ替えて表示します。
P(X \cap Y) = \displaystyle{P(X)P(Y|X)}
分子は変形式と同じ? ちょっと待って下さいね。
よく見るとP(X)P(Y|X)P(Y)P(X|Y) とXとYの順序が違ってます。
実は、P(X)P(Y|X) = P(Y)P(X|Y) となることが証明(参照:ベイズの定理の証明)されています。
よって、
P(Y|X) = \displaystyle{\frac{P(X)P(Y|X)}{P(X)} = \frac{P(Y)P(X|Y)}{P(X)}}

問題例1 喫煙者の推定の問題

問題

「男性10人、女性7人が一室でパーティーを開いた。男子の喫煙者は5人、女性は3人である。部屋に入ったら煙草の吸殻が1本、灰皿の上にあった。このとき、吸った人が女性である確率を求めなさい(煙草の吸い回しはしていないと仮定する)」
出展:ベイズ推定の例題とその解

ベイズの定理による解法

吸った人が女性である確率を求めるということは、P(女性|喫煙者)を求めることである。
ベイズの定理によって、下記式を求めることになる。
P(女性|喫煙者) = \displaystyle{\frac{P(女性)P(喫煙者|女性)}{P(喫煙者)}}
P(女性) = \displaystyle{\frac{7}{(7+10)} = \frac{7}{17}}
P(喫煙者|女性) = \displaystyle{\frac{3}{7}}
P(喫煙者) = \displaystyle{\frac{(3+5)}{(7+10)} = \frac{8}{17}}
上式に代入して、分母の分母を消すために17を分母と分子に掛けます。17と7が相殺されます。
P(女性|喫煙者) = \displaystyle{\frac{\frac{7}{17} \times \frac{3}{7}}{\frac{8}{17}} = \frac{\frac{7}{17} \times \frac{3}{7} \times \frac{17}{1} }{\frac{8}{17} \times \frac{17}{1} }= \frac{3}{8}}

集合論による解法

次の図のような状況であった。吸殻を残したのは喫煙者の誰かであり、喫煙者が女性である確率は、図から明らかに\displaystyle{\frac{3}{8}}である。
f:id:Yaju3D:20161001194113p:plain

解説

この例題から分かるように、この問題をベイズの定理を使って解く理由は特にない。普通にベン図を使った集合論で解けば良い。
それでもベイズの定理を使うことを推奨できるとすれば、定理の各項にかなり機械的に数値を当てはめれば良いことだろうか。

問題例2 1976年の早稲田大学の入試問題

問題

5回に1回の割合で帽子を忘れるくせのあるY君が、正月にA、B、Cの3軒を順に年始回りをして家に帰ったとき、帽子を忘れてきたことに気がつきました。2 軒目の家Bに忘れてきた確率はいくらになりますか。 出展:その124 忘れ易い条件 その2

f:id:Yaju3D:20161016223312p:plain
f:id:Yaju3D:20161016223332p:plain

通常の解法

5回に1回忘れるとため、3軒のそれぞれで忘れる確率は\displaystyle{\frac{1}{5}}
しかし、回る順番が決まっておりそのうちの2軒目のBで忘れる確率が求められている。

1軒目のAで忘れないことが前提になるため、Bでの忘れる確率は1軒目Aで忘れない確率\displaystyle{\frac{4}{5}}
Bで忘れる確率\displaystyle{\frac{1}{5}}を掛けた、\displaystyle{\frac{4}{5} \times \frac{1}{5} = \frac{4}{25}}
同様にもしもCで忘れる確率となれば、\displaystyle{\frac{4}{5} \times \frac{4}{5} \times \frac{1}{5} = \frac{16}{125}}です。 f:id:Yaju3D:20161016225000p:plain
では、Bで忘れる確率\displaystyle{\frac{4}{5}}が正解であると思いがちですが、「確率の問題を取り扱う場合、すべてのケースの確率を合計すると1になる」という前提があります。
3軒のA、B、C どれかで忘れる確率は、\displaystyle{\frac{1}{5}}\displaystyle{\frac{4}{25}}\displaystyle{\frac{16}{125}}の合計になるので、\displaystyle{\frac{1}{5} + \frac{4}{25} + \frac{16}{125} = \frac{25}{125} + \frac{20}{125} + \frac{16}{125} = \frac{61}{125}}となりますが、これだけでは\displaystyle{1}になりません。
f:id:Yaju3D:20161016231231p:plain
では、その残りの\displaystyle{\frac{64}{125}}は何か?
それはこの合計を出したときの条件以外のときの確率で、これら3軒のどこにも帽子を忘れないという確率です。つまり\displaystyle{\frac{4}{5} \times \frac{4}{5} \times \frac{4}{5} = \frac{64}{125}}

f:id:Yaju3D:20161018033745p:plain
確率の問題を取り扱う場合、すべてのケースの確率を合計すると\displaystyle{1}とする必要があるため、そのようにするにはそれぞれに対し\displaystyle{\frac{61}{125}}倍すればいいのです。
計算すると\displaystyle{\frac{25}{61}+\frac{20}{61}+\frac{16}{61}=1}となって、A、B、Cそれぞれの割合、つまり帽子を忘れてくる確率が順に\displaystyle{\frac{25}{61}}\displaystyle{\frac{20}{61}}\displaystyle{\frac{16}{61}}と出てきます。
したがってBで帽子を忘れてきたとされる確率は\displaystyle{\frac{20}{61}}になります。

ベイズの定理による解法

X を帽子を忘れるという事象
Y を帽子を家 B に忘れるという事象とする。

求める確率は P(Y|X) = \displaystyle{\frac{P(X \cap Y)}{P(X)}}
(条件付き確率の定義)

P(X)は帽子をどこかに忘れる確率:
\displaystyle{\frac{1}{5} + \frac{4}{5} \times \frac{1}{5} + \frac{4}{5} \times \frac{4}{5} \times \frac{1}{5} = \frac{61}{125}}

P(X \cap Y) は帽子を家 B に忘れる確率:
\displaystyle{\frac{4}{5} \times \frac{1}{5} = \frac{4}{25}}

よって求める確率は
\displaystyle{\frac{\frac{4}{25}}{\frac{61}{125}} = \frac{\frac{4}{25} \times \frac{5}{5}}{\frac{61}{125}} = \frac{\frac{20}{125}}{\frac{61}{125}} = \frac{20}{61}}

参照

確率を理解してみる-頻度主義とベイズ主義

はじめに

前回、確率の基礎を説明しました。 yaju3d.hatenablog.jp

今回は、機械学習や統計で使っている確率を説明します。

最初に,機械学習にとって確率はどういう役割なのかを確認しておきましょう。実のところ,機械学習に確率が必須というわけではありません。ニューラルネットワークサポートベクターマシンなどの有名な手法も「確率を用いない機械学習」ですし,その他にも数多くの手法があります。しかし,「確率を用いない機械学習」の多くは,「結果のランキングを作りづらい(評価値の大小に意味がない)」「条件が異なる場合の結果を比較できない」などの欠点があります。一方の「確率を用いる機械学習」では,評価結果や推定されたパラメータが「どれくらい信用できるか(もっともらしいか)」を確率として計算します。確率同士は比較可能なので,計算結果を使ってランキングを作ったり,前提条件が異なっている結果同士を比較したり(よいモデルを探すときによく行われます),ということが自然にできるのです。
出展:第2回 確率の初歩:機械学習 はじめよう|gihyo.jp … 技術評論社

上記記事に「機械学習に確率が必須というわけではありません。」と書かれていて、少しやる気を失って前記事から間が空いてしまいました。記事を書くってパワーがいるんですよね。

頻度主義とベイズ主義

高校までで学んできた確率とこれから学ぶ確率は、そもそも何が違うのでしょうか?
それには、頻度主義とベイズ主義の用語がキーになります。
数理統計学では頻度主義(frequentism)とベイズ主義(Bayesianism)があり、今も大論争が続いているそうです。

統計的機械学習の最終目的は「有限回しか試行できない中で、すべての目が同じ確率で出ると言ってもよいか」という問題を工学的に(つまり現実的に)解くことなのです。

頻度主義

高校生程度で習う確率の概念は基本的に頻度主義で、すなわちランダムな事象が生起・発生する頻度をもって確率とする考えです。
例えばサイコロの目が1になる確率を無限の数のサイコロを投げて、以下のような式で表すとする考えとなります。

P(1の目) = 1の目が出た数 / サイコロを振った数

ベイズ主義

現実の世界で確率を求める時に、頻度主義で確率を求めることが出来ないことが多々ある。
例えば何かの検診を受けて何かの癌マーカーが陽性になった時、実際に癌である確率みたいな確率である。これも頻度主義で求めれないことは出来るかも知れないが、数字を出すには非常に時間と工数がかかる。
頻度主義では不確かさの定量化はランダム性にのみ基づくのに対し、ベイズ主義では情報が不足していることにも基づくとし、不確かさの定量化を広く考える。

ベイズ確率

ベイズ確率はベイズ主義による「確率」の考え方で、ベイズの定理に基づいて求める。

ベイズの定理

P(Y|X) = \displaystyle{\frac{P(Y)P(X|Y)}{P(X)}}

歴史

ベイズ確率は、ベイズの定理の特別な場合を証明した18世紀イギリスの確率論研究家トーマス・ベイズ(1702-1761)にちなんだ命名(実際の命名は1950年代)ではあるが、ベイズ自身が現在のようなベイズ確率やベイズ推定の考え方を持っていたかどうかは定かでない。ベイズ確率の考え方を積極的に用いたのはフランスの数学者シモン・ラプラスラプラス(1749-1827)(ベイズの定理の一般的な場合を証明した)である。
出展:ベイズ確率 - Wikipedia

活用

最も有名な例はスパムメールの判定で「ベイジアンフィルタ」と呼ばれています。
他にもがん検診、犯罪捜査、マーケティング人工知能など様々に使われている。

最後に

次回は、ベイズの定理についてもう少し説明していきます。

参照

確率を理解してみる-基礎編

はじめに

確率は最も実生活で役立つ分野といっても過言ではない。未来に起こりうる事象の割合である確率を勉強することは、予知能力を手に入れることにも等しい。元々、確率分野は「何とかして賭けに勝ちたい」という強い思いから始まった分野である。未来を予測することができれば、賭けでは非常に有利になる。人生には様々な分岐があり、どちらへ進むべきかを迷うことも多い。そんなとき、確率的思考ができる人とできない人では、どれだけ自分にとって有利な選択ができるかに差が生じてしまうのである。
出展:数A 確率 | 受験の月

確率が分かるようになると合理的な判断ができるようになりそうですね。
機械学習では「確率」が重要キーワードですし、これまで「順列と組合せを理解してみる」の記事を書いてきたのも確率を理解したいためです。

確率とは

「ある事象が発生する可能性の大きさを表す数値」のことである。
中学2年の時に確率の概念を習っているんですね。てっきり、高校の順列と組合せの後で習うと思い込んでいました。

確率といったら、「サイコロを1回振った時に3が出る確率は1/6である」といった例が有名です。

確率の求め方

\displaystyle 起こる確率=\frac{ある事柄が起こる場合の数}{起こりうるすべての場合の数}

一般式は、起こる場合が全部でn通り、事柄Aの起こる場合がa通り、その確率をpとする。
\displaystyle p = \frac{a}{n} (0≦p≦1)
※確率の英語「probability(プラバビリティ:アメリカ読み、プロバビリィ:イギリス読み)」

先程の「サイコロを1回振った時に3が出る確率」では、分母側は「出る目は全部で6通り」、分子側は「3の出る目は1通り」で \displaystyle  \frac{1}{6} となります。

問題と解説

1個のさいころを投げるとき,4以上の目が出る確率を求めよ

目が4以上になるのは、4,5,6の3通りです。
出る目は全部で6通りです。
\displaystyle p = \frac{3}{6} = \frac{1}{2} となります。

2個のサイコロを同時に投げるとき、2個のサイコロの目の和が6となる確率を求めよ

目の和が6になるのは、{1,5},{2,3},{3,3},{4,2},{5,1}の5通りです。
出る目は全部で 6^2 = 36 通りです。
\displaystyle p = \frac{5}{36}となります。

1~10までの異なる番号のついた10枚のカードから、2枚のカードを取り出すという試行において、次の事象が起こる確率を求めよ。

(1) 2枚とも偶数である。
同時に取り出すため、順番は考えないことで組合せを使う。
全事象は、\displaystyle _{10} C _2 通りとなる。
2枚とも偶数なのは、{2,4,6,8,10}の5枚のカードから同時に2枚を取り出すので、 \displaystyle _5 C _2 通りとなる。
\displaystyle p = \frac{_5 C _2}{_{10} C _2} = \frac{2}{9} となります。

(2) 少なくても1枚は奇数である。
少なくても1枚は奇数ということは2枚とも偶数でなければよいことになる。よって全体から引くことする。 このように「ある事象Aに対して、この事象Aが起こらないという事象」をAの余事象といいます。
2枚とも偶数であるのは先程求めましたので、全体「1」から引けばいいです。
※全事象の確率の方針は必ず「1」となる。
\displaystyle p = 1 - \frac{_5 C _2}{_{10} C _2} = 1 - \frac{2}{9} = \frac{7}{9} となります。

リンゴ2つとバナナ4つの入ったかごから2つの果物を同時に取り出すとき、リンゴ1つとバナナ2つを取り出す確率を求めてください。

全事象は、果物6個の中から3個を選ぶ組合せなので、 \displaystyle _6 C _3 通りとなります。
リンゴ2個の中から1個、バナナ4個の中から2個を選ぶ組合せは、\displaystyle _2 C _1 \times _4 C _2 通りです。
よって確率は、\displaystyle p = \frac{_2 C _1 \times _4 C _2}{_6 C _3} = \frac{2 \times 6}{20} = \frac{12}{20} = \frac{3}{5}

男4人、女3人の計7人を並べるとき、両端が2人とも女子である確率を求めてください。

全事象は、7人の並べ方なので順列となり、\displaystyle _7 P _7 通りとなります。
両端が女子となる時、両端の二人の並べ方は、\displaystyle _3 P _2、間の5人の並べ方は \displaystyle _5 P _5
よって求める確率は、\displaystyle p = \frac{_3 P _2 \times _5 P _5}{_7 P _7} = \frac{3 \times 2 \times 5 \times 4 \times 3 \times 2 \times 1}{7 \times 6 \times 5 \times 4 \times 3 \times 2 \times 1} = \frac{6}{42} = \frac{1}{7}

最後に

今回はあくまで基礎の部分です。これすら自分的にはまだ勉強不足なところがありますが大まかな確率の概念が分かれば良しとします。
機械学習や統計で使っている確率はまた違った求め方になりますので、次回に説明していきます。

参照