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

アラフィフプログラマーが数学と物理と英語を基礎からやり直す。https://qiita.com/yaju

VirtualBoxにUbuntu16.04 LTS と TensorFlow をインストール

はじめに

日本語形態素解析システム「JUMAN++」をインストールするために、久しぶりにVirtualBoxのUbuntuを使ったのですが、各システムのバージョンも古くなってきているので新規にインストールし直します。
以前書いた下記の記事(2016年3月27日)を基にインストールをしたのですが、バージョンが新しくなって記事内容があわなくなってきたので書き直しました。 yaju3d.hatenablog.jp

環境

  • Windows 10 Home 64bit
  • Oracle VM VirtualBox 5.1.10

VirtualBoxにUbuntu(64bit)をインストール

下記サイトを参考にVirtualBoxにUbuntu 16.04 LTSをインストールしました。 サイトとの違いとして、メモリは2048MByte、仮想ハードドライブサイズは20GByteにしています。 qiita.com 仮想ハードドライブサイズは後で変更することが出来ますが、面倒なので最初からそれなりの大きさにしておきましょう。 qiita.com

画面サイズは1024x768になっています。

インストール時のエラー

Ubuntu(64bit)を入れようとするとPCによっては下記のエラーが出ます。 「This kerner requures an X86-64 CPU, but only detected an i686 CPU」 これはVirtualBoxのUbuntu 32bit用に64bit版をインストールしようとしたためです。 何故、VirtualBoxにUbuntu 64bit用がリストに表示されないのかというと、PCのBIOSの仮想化機能が有効になっていないためです。有効にするためには、BIOSの設定(Lenovoでは再起動時にF1キーを押す等)で、Security→Vitualizationを「Enabled」にして保存後に再起動する必要があります

Guest Additionsのインストール

Guest Additionsは、ビデオ性能の向上、共有フォルダ、クリップボード共有等の機能を提供するVirtualBoxの追加コンポーネントです。
Guest Additionsインストール前だとUbuntu 16.04のディスプレイ解像度は2種類だけですが、インストール後だと任意の解像度に変更できます。

VirtualBoxのメニューのデバイスにある「Guest Additions CD イメージの挿入」をクリックします。インストールが成功したら、Ubuntu 16.04を再起動します。

Ubuntu 16.04: VirtualBox上のUbuntu 16.04にGuest Additionsをインストールする - Narrow Escape

クリップボードの共有

Ubuntuのインストールが完了したら、VirtualBoxメニューのデバイスの「クリップボードの共有」を双方向にしておくとWindowsのクリップボードを貼り付け出来たりして便利です。また、「ドラッグ&ドロップ」も同様に双方向にするといいでしょう

ターミナルの起動

先ずはWindowsのコマンドプロンプトのようなのを起動する必要があります。
Ubuntuの左横のパネルメニューから「コンピューターとオンラインリソースの検索」をクリックします。
検索ボックスで「terminal」を入力すると「端末」(ターミナル)が出てきますのでクリックします。

pipのインストール

ターミナルが起動したら、最初に「pip」をインストールして下さい。
pipとは、Pythonで書かれたパッケージソフトウェアをインストール・管理するためのパッケージ管理システムです。RubyのRubyGemsやPerlのCPANやWindowsのNuGetといったところです。
ターミナル上で下記のコマンドを入力します。

$ sudo apt-get install python-pip
$ pip install --upgrade pip

※$より前の部分は自分の環境では「yaju@yaju-VirtualBox:~$」です。これはインストール環境によって変わります。説明する上で便宜上省略しています。

Python数値計算環境「Anaconda」のLinux版をインストール

AnacondaとはPythonの数値計算環境を構築するために様々なパッケージをまとめた無料のディストリビューションです。 Anacondaをインストールをすると、NumPy,SciPy,matplotlib、scikit-learn等のパッケージがインストールされます。

Ubuntu上のWebブラウザ「FireFox」で下記サイトからAnaconda for Linux PYTHON 3.5 LINUX 64-BITをダウンロードしてください。ファイルの保存先は「ダウンロード」にしました。 https://www.continuum.io/downloads

※Python2.7ではなくPython3.5にしたのは、サイト「http://learningtensorflow.com/lesson1/」を参考にしたためです。

ターミナル上で「bash 」と入力したら、Ubuntuの左横のパネルメニューから「ファイル」からダウンロードフォルダにある「Anaconda3-4.2.0-Linux-x86_64.sh」をドラッグ&ドロップしました。引用符は付いたままでもいいかも知れませんが、一応消しました。

$ bash /home/yaju/ダウンロード/Anaconda3-4.2.0-Linux-x86_64.sh

AnacondaのTensorflow用環境を作成

Anacondaのインストールが終わったら、一旦ターミナルを閉じて下さい。
これはTensorflow用環境を作成する際に使用する「conda」コマンドがまだ有効になっていないためです。

Tensorflow用環境を作成するため、ターミナル上で下記のコマンドを入力します。
名前は「tensorenv」にしました。envは環境(environment)の略称

$ conda create --name=tensorenv python=3.5

環境が作成されていきます、途中のProceedの確認は「y」を入力します。

The following NEW packages will be INSTALLED:

    openssl:    1.0.2j-0     
    pip:        9.0.1-py35_0 
    python:     3.5.2-0      
    readline:   6.2-2        
    setuptools: 27.2.0-py35_0
    sqlite:     3.13.0-0     
    tk:         8.5.18-0     
    wheel:      0.29.0-py35_0
    xz:         5.2.2-0      
    zlib:       1.2.8-3      

Proceed ([y]/n)? y

Fetching packages ...
pip-9.0.1-py35 100% |################################| Time: 0:00:02 742.12 kB/s
Extracting packages ...
[      COMPLETE      ]|###################################################| 100%
Linking packages ...
[      COMPLETE      ]|###################################################| 100%
#
# To activate this environment, use:
# > source activate tensorenv
#
# To deactivate this environment, use:
# > source deactivate tensorenv
#

以下のコマンドで作った環境の一覧を表示できます。

$ conda info -e

Using Anaconda Cloud api site https://api.anaconda.org
# conda environments:
#
tensorenv                /home/yaju/anaconda3/envs/tensorenv
root                  *  /home/yaju/anaconda3

作成した環境を有効にするには下記のコマンドを入力します。

$ source activate tensorenv

これで先頭に「(tensorenv)」が付くようになります。

(tensorenv)$

ちなみに作成した環境から戻るには下記のコマンドを入力します。

(tensorenv)$ source deactivate

TensorFlowのインストール

TensorFlowには、「CPU only」と「GPU enabled」の2種類が用意されています。今回は「CPU only」版をインストールします。
ファイル名はPython35にしているので「tensorflow-(version No)-cp35」となります。ちなみにPython27は「cp27」です。
バージョンは現時点(2016/11/27)で最新の「0.11.0」を入れます。
入力が長いのでクリップボードにコピーして貼り付ければいいです。

参照:https://www.tensorflow.org/versions/r0.11/get_started/os_setup.html

(tensorenv)$ pip install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.11.0-cp35-cp35m-linux_x86_64.whl

Building wheels for collected packages: numpy あたりで10分程度かかりました。

Collecting tensorflow==0.11.0 from https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.11.0-cp35-cp35m-linux_x86_64.whl
  Downloading https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.11.0-cp35-cp35m-linux_x86_64.whl (39.8MB)
    100% |████████████████████████████████| 39.8MB 9.8kB/s 
Requirement already up-to-date: wheel>=0.26 in ./anaconda3/envs/tensorenv/lib/python3.5/site-packages (from tensorflow==0.11.0)
Collecting protobuf==3.0.0 (from tensorflow==0.11.0)
  Downloading protobuf-3.0.0-py2.py3-none-any.whl (342kB)
    100% |████████████████████████████████| 348kB 258kB/s 
Collecting six>=1.10.0 (from tensorflow==0.11.0)
  Downloading six-1.10.0-py2.py3-none-any.whl
Collecting numpy>=1.11.0 (from tensorflow==0.11.0)
  Downloading numpy-1.11.2-cp35-cp35m-manylinux1_x86_64.whl (15.6MB)
    100% |████████████████████████████████| 15.6MB 22kB/s 
Collecting setuptools (from protobuf==3.0.0->tensorflow==0.11.0)
  Downloading setuptools-29.0.1-py2.py3-none-any.whl (472kB)
    100% |████████████████████████████████| 481kB 290kB/s 
Installing collected packages: setuptools, six, protobuf, numpy, tensorflow
  Found existing installation: setuptools 27.2.0

TensorFlowのインストールエラー

途中で下記のエラーが発生してインストールが止まります。

Cannot remove entries from nonexistent file /home/yaju/anaconda3/envs/tensorenv/lib/python3.5/site-packages/easy-install.pth

パッケージの依存関係のコンフリクトが原因のようで、下記サイトの「2. pip で setuptools を TensorFlow と互換性があるものに upgrade する」で対応します。※下記サイトは、mac版です。
datalove.hatenadiary.jp

よって、下記コマンドを入力して再インストールします。

(tensorenv)$ pip install --upgrade -I setuptools
Collecting setuptools
  Using cached setuptools-29.0.1-py2.py3-none-any.whl
Installing collected packages: setuptools
Successfully installed setuptools-29.0.1

(tensorenv)$ pip install --upgrade https://storage.googleapis.com/tensorflow/linux/cpu/tensorflow-0.11.0-cp35-cp35m-linux_x86_64.whl
 ︙
Successfully installed numpy-1.11.2 protobuf-3.0.0 six-1.10.0 tensorflow-0.11.0

TensorFlowの動作確認

バージョンが表示されればインストール成功です。

(tensorenv)$python -c "import tensorflow; print(tensorflow.__version__);"

0.11.0

最後に

これでやっとTensorFlowを始められます。

これ以降は下記サイトを参考にして下さい。Web上の実行環境である「Jupyter Notebook」はAnacondaと同時にインストールされています。
yaju3d.hatenablog.jp

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を使う前に作成したVirtualBoxにUbuntu(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版) - デジタル・デザイン・ラボラトリーな日々

環境

  • Docker
  • Jupyter
  • Python 2.7
  • Janome

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}}

よって求める確率は(※\frac{5}{5}は分母125にして打ち消すため)
\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

活用

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

最後に

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

参照

スポンサーリンク