読者です 読者をやめる 読者になる 読者になる

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

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

線形回帰で二乗和を2で割る理由について

機械学習 人工知能

はじめに

これは、機械学習に必要な高校数学やり直しアドベントカレンダー Advent Calendar 2016の10日目の記事です。

線形回帰 ー 誤差の和の式(データ個数 n 個の場合)

\displaystyle E(\theta)=\frac{1}{2}\sum_{i=1}^{n}(y^{(i)}-f\theta(x^{(i)}))^2

2乗することに関しては、以前書いた下記の記事にてピタゴラスの定理で距離を求めるという理解を得ることが出来ました。 yaju3d.hatenablog.jp

でも、全体を 2 で割ってる理由はなんだろうという疑問を持ちました。

経緯

「やる夫で学ぶ機械学習 - 単回帰問題 -」とgihyo.jpの「機械学習 はじめよう 第9回 線形回帰」も、全体を 2 で割る理由について、もやっとする感じな書き方なんです。

全体を 2 で割るのが、やる夫が言っているように「おまじない的なものは気持ち悪いお…、」なんです。本筋ではないので記事的に省略するのは仕方ないですけどね。

やる夫
全体を 2 で割ってるのは何だお?

やらない夫
後で E(θ)E(θ) を微分することになるんだが、微分した式をちょっとだけ簡単にするためのトリックだ。最適化問題は、何か定数が全体に掛かっていたとしても、求まる答えは変わらないからこういうことができる。まあ、あまり深く考えなくていいさ。

やる夫
おまじない的なものは気持ち悪いお…、うーん、全体像はわかったけど、ちょっと、まだピンときてないお。
出典:やる夫で学ぶ機械学習 - 単回帰問題 -

ただし一般的な記法にあわせて,二乗和を2で割ったものを使います(注1)。
注1
2で割っていることは今回の範囲では全く本質的ではないのですが,次回以降の話との整合性のためにこの形式を用います。
出典: 機械学習 はじめよう 第9回 線形回帰[後編]

調査

ネットで数時間かけて検索してみたのですが、ぜんぜん見つからないです(笑)。こういう時は人に頼るということで、 Yahoo!知恵袋に質問しました。

線形回帰の求め方で二乗和を2で割るの理由? - 数学 | Yahoo!知恵袋

2人から回答をいただきました。その内の1人はブログに書き起こしくれました。 勉強会で知り合った@K_Ryuichirouさんです。 線形回帰の求め方で二乗和を2で割る理由 - Qiita

では、解決したのかというとそうではなく、理解する知識がまだ自分にはなかったのです、まずは微分を理解する必要があります。

理解

Yahoo!知恵袋のzatsugakusanさんの回答を一部抜粋すれば

例えば

f(x)=(x-1)2

を最小にするxを求めればx=1となりますが、

f(x)=100(x-1)2

を最小にするxを求めてもx=1になります。

二乗和の式をこれから微分すると分かっているのですから、微分して「2」が出てくることも予めわかっています。

ですから、二乗和の損失関数に1/2を掛けていれば微分したときに消えてくれるのでそうしておく。ただそれだけのことです。そうして求まるパラメータ自体には何の影響もありません。
「2で割るのは、単に二乗和が微分したら2が出てくることが分かっているからです。」

導関数の定義では、二次関数 y=x^2微分すると y=2x になる。

参照:

この2を打ち消すということだが、まだピンとこない。

修正量による考え

修正された値をxとし、2つの計測値を a_1a_2とする。それぞれの計測値に修正を加える修正量を v_1v_2とした場合、これらの間には「計測値+修正量=修正された計測値」の関係が成り立ち、これを数式にしてみる。

a_1 + v_1 = x
a_2 + v_2 = x

これらを修正量=・・・の形式に書き換えます。

v_1 = x - a_1
v_2 = x - a_2

これを修正量の2乗の総和を S とすれば次のように計算できる。

S = v_1^2 + v_2^2
  = (x-a_1)^2 + (x-a_2)^2
  = 2x^2 - 2(a_1+a_2)x + a_1^2 + a_2^2

総和 S は、二次関数になるのでグラフが下向きの放物線になります。 そして、放物線の頂点が最小値ということになります。 総和 S を x で微分した上で「微分した値 = 0」とすれば目的の値を求めると次のような計算になります。

\displaystyle \frac{dS}{dx}=4x-2(a_1+a_2)=0

f:id:Yaju3D:20161209030620p:plain

よって、最小値は図の赤丸のところで下記の式となる。

\displaystyle x = \frac{(a_1+a_2)}{2}

これはa_1a_2の平均である。

でも、これは平均でたまたま2つだから2で割っただけで、3つになったら意味が違うかも知れない。

参照: わかりやすい測量の数学 ―行列と最小二乗法― 著者: 小白井 亮一 Kindle版

微分係数とは何か

f(x)=(x-1)2

を最小にするxを求めればx=1となりますが、

f(x)=100(x-1)2

を最小にするxを求めてもx=1になります。

つまり、最小値に微分係数は関係ないようだ。そこで微分係数で検索していたら下記サイトを見つけた。

微分係数は、ある点における接線の傾きともいうことができます。虫めがねの図を思い出してください。
最小値をとるところ、つまり、x=0では接線の傾き(=微分係数)はどうなりますか?
・・・接線は水平になりますね。
ということは、傾きは・・・ 0ですね。
だから、微分係数が0になる点が最小値(yの値ですよ。)になるのです。

参照:6.微分の応用

http://www.minc.ne.jp/~ryokan/bibun/bibun26.gif

結論

微分係数はある点における接線の傾きで最小値は水平となるので、微分係数は無視してx=0を代入した値が最小値となる。

二乗和を微分したら微分係数に2が付くけど、微分係数は無視するので求まる答えは変わらない。
微分係数2は数式上は1/2を掛けて(2で割る)相殺しておくことが、数学として一般的な記法なっているということです。

最後に

これで、やらない夫の「何か定数が全体に掛かっていたとしても、求まる答えは変わらない」とYahoo!知恵袋のzatsugakusanさんの「2で割るのは、単に二乗和が微分したら2が出てくることが分かっているからです。」が分かったような気がします。

ただ、理解が間違っていたらご教示ください。

TensorFlowがWindowsサポートしたのでインストールしてみた

機械学習 tensorflow 人工知能

はじめに

TensorFlow 0.12からWindowsをサポートするようになりました。これにより、VirtualBoxやDockerを使う必要がなくなります。

環境

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

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

下記サイトからAnaconda 4.2.0 For Windows PYTHON 3.5 64-BITをダウンロードしてください。
https://www.continuum.io/downloads#windows

自分は、「C:\Anaconda3」をインストール先にしました。

AnacondaのTensorflow用環境を作成

Anaconda3 (64-bit)メニュー配下から「Anaconda Prompt」を選択します。

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

(C:\Anaconda3) C:\Users\(ユーザー)> conda create --name=tensorenv python=3.5

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

Fetching package metadata .........
Solving package specifications: ..........

Package plan for installation in environment C:\Anaconda3\envs\tensorenv:

The following packages will be downloaded:

    package                    |            build
    ---------------------------|-----------------
    pip-9.0.1                  |           py35_0         1.7 MB

The following NEW packages will be INSTALLED:

    pip:            9.0.1-py35_0
    python:         3.5.2-0
    setuptools:     27.2.0-py35_1
    vs2015_runtime: 14.0.25123-0
    wheel:          0.29.0-py35_0

Proceed ([y]/n)? y

Fetching packages ...
pip-9.0.1-py35 100% |###############################| Time: 0:00:02 786.13 kB/s
Extracting packages ...
[      COMPLETE      ]|##################################################| 100%
Linking packages ...
[      COMPLETE      ]|##################################################| 100%
#
# To activate this environment, use:
# > activate tensorenv
#
# To deactivate this environment, use:
# > deactivate tensorenv
#
# * for power-users using bash, you must source
#

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

(C:\Anaconda3) C:\Users\(ユーザー)>conda info -e
# conda environments:
#
tensorenv                C:\Anaconda3\envs\tensorenv
root                  *  C:\Anaconda3

作成した環境を有効にするには下記のコマンドを入力します。
Windows版はsourceコマンドが無いため、activateからとなります。

(C:\Anaconda3) C:\Users\(ユーザー)>activate tensorenv

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

(tensorenv) C:\Users\(ユーザー)>

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

(tensorenv) C:\Users\(ユーザー)> deactivate

TensorFlowのインストール

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

参照:https://www.tensorflow.org/versions/r0.12/get_started/os_setup.html#pip-installation-on-windows

【追記】2016/12/10
コメントを頂きまして、2016/12/10に「0.12.0rc1」が出ています。
参照:https://pypi.python.org/pypi/tensorflow

(tensorenv) C:\Users\(ユーザー)>pip install --upgrade https://storage.googleapis.com/tensorflow/windows/cpu/tensorflow-0.12.0rc0-cp35-cp35m-win_amd64.whl

インストールされます。

Collecting tensorflow==0.12.0rc0 from https://storage.googleapis.com/tensorflow/windows/cpu/tensorflow-0.12.0rc0-cp35-cp35m-win_amd64.whl
  Downloading https://storage.googleapis.com/tensorflow/windows/cpu/tensorflow-0.12.0rc0-cp35-cp35m-win_amd64.whl (12.2MB)
    100% |################################| 12.2MB 27kB/s
Collecting six>=1.10.0 (from tensorflow==0.12.0rc0)
  Downloading six-1.10.0-py2.py3-none-any.whl
Collecting protobuf==3.1.0 (from tensorflow==0.12.0rc0)
  Downloading protobuf-3.1.0-py2.py3-none-any.whl (339kB)
    100% |################################| 348kB 475kB/s
Collecting numpy>=1.11.0 (from tensorflow==0.12.0rc0)
  Downloading numpy-1.11.2-cp35-none-win_amd64.whl (7.6MB)
    100% |################################| 7.6MB 38kB/s
Requirement already up-to-date: wheel>=0.26 in c:\anaconda3\envs\tensorenv\lib\site-packages (from tensorflow==0.12.0rc0)
Collecting setuptools (from protobuf==3.1.0->tensorflow==0.12.0rc0)
  Downloading setuptools-30.2.0-py2.py3-none-any.whl (472kB)
    100% |################################| 481kB 396kB/s
Installing collected packages: six, setuptools, protobuf, numpy, tensorflow
  Found existing installation: setuptools 27.2.0

TensorFlowのインストールエラー

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

Cannot remove entries from nonexistent file c:\anaconda3\envs\tensorenv\lib\site-packages\easy-install.pth

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

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

(tensorenv) C:\Users\(ユーザー)>pip install --upgrade -I setuptools
Collecting setuptools
  Using cached setuptools-30.2.0-py2.py3-none-any.whl
Installing collected packages: setuptools
Successfully installed setuptools-30.2.0


(tensorenv) C:\Users\(ユーザー)>pip install --upgrade https://storage.googleapis.com/tensorflow/windows/cpu/tensorflow-0.12.0rc0-cp35-cp35m-win_amd64.whl
Collecting tensorflow==0.12.0rc0 from https://storage.googleapis.com/tensorflow/windows/cpu/tensorflow-0.12.0rc0-cp35-cp35m-win_amd64.whl
  Using cached https://storage.googleapis.com/tensorflow/windows/cpu/tensorflow-0.12.0rc0-cp35-cp35m-win_amd64.whl
Collecting numpy>=1.11.0 (from tensorflow==0.12.0rc0)
  Using cached numpy-1.11.2-cp35-none-win_amd64.whl
Collecting protobuf==3.1.0 (from tensorflow==0.12.0rc0)
  Using cached protobuf-3.1.0-py2.py3-none-any.whl
Requirement already up-to-date: wheel>=0.26 in c:\anaconda3\envs\tensorenv\lib\site-packages (from tensorflow==0.12.0rc0)
Requirement already up-to-date: six>=1.10.0 in c:\anaconda3\envs\tensorenv\lib\site-packages (from tensorflow==0.12.0rc0)
Requirement already up-to-date: setuptools in c:\anaconda3\envs\tensorenv\lib\site-packages (from protobuf==3.1.0->tensorflow==0.12.0rc0)
Installing collected packages: numpy, protobuf, tensorflow
Successfully installed numpy-1.11.2 protobuf-3.1.0 tensorflow-0.12.0rc0

TensorFlowの動作確認

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

(tensorenv) C:\Users\(ユーザー)>python -c "import tensorflow; print(tensorflow.__version__);"
0.12.0-rc0

Jupyter Notebookのインストール

Web上の実行環境である「Jupyter Notebook」はAnacondaと同時にインストールされているのですが、自分はTensorFlow専用の環境(tensorenv)を作成しているため、インストールする必要があるようです。そうしないと、「ImportError: No module named tensoflow」となります。

(tensorenv) C:\Users\(ユーザー)>pip install jupyter 

Jupyterの起動

カレントフォルダを移動しておきます。

(tensorenv) C:\Users\(ユーザー)>cd C:\Anaconda3 
(tensorenv) C:\Anaconda3 >jupyter notebook

Webブラウザが起動しますので、そこからanaconda3/envs/tensorenvフォルダまで移動させます。 f:id:Yaju3D:20161206022332p:plain

更にtensorenvフォルダ配下に移動して、右横の「New」で新規ファイルを「Python 3」で作成します f:id:Yaju3D:20161206022344p:plain

それでは、下記サイトの演算を実際に実行してみましょう。 qiita.com

では、実際にTensorFlowを使って計算してみます。最初は、以下の式を書いてみます。

y = x^2 + b
import tensorflow as tf

def x2_plus_b(x, b):
    _x = tf.constant(x)
    _b = tf.constant(b)
    result = tf.square(_x)
    result = tf.add(result, _b)
    return result

print resultではエラーとなったため、print(result)に修正しました。

with tf.Session() as sess:
    result = sess.run([x2_plus_b(2., 3.)])
    print(result) 

実行結果(printの出力)は以下のようになります。

2^2 + 3 = 7

なので、きちんと計算できていることがわかります。

f:id:Yaju3D:20161206023119p:plain

TensorBoardについて

まだ確認中です。Linux版と違って「Terminal」が使用できないんですよね。
yaju3d.hatenablog.jp

最後に

ようやっとWindowsをサポートされることになりましたが、いろいろやっていくと不足しているところが出てくるかも知れませんね。

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

機械学習 tensorflow 人工知能

はじめに

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

環境

VirtualBoxUbuntu(64bit)をインストール

下記サイトを参考にVirtualBoxUbuntu 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」 これはVirtualBoxUbuntu 32bit用に64bit版をインストールしようとしたためです。 何故、VirtualBoxUbuntu 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で書かれたパッケージソフトウェアをインストール・管理するためのパッケージ管理システムです。RubyRubyGemsPerlCPANWindowsの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 tensorflow 人工知能 機械学習 自然言語

はじめに

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

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

小説のテキスト化

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

下記サイトの手順に従って、「君の名は。」をテキスト化しました。個人でデータマイニングする上で問題ないので。
it-sora.net

形態素解析

テキスト化された「君の名は。」ですが、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による自然言語処理を試す

Word2Vec tensorflow 人工知能 機械学習 自然言語

はじめに

以前、ベイジアンフィルタを実装して自然言語処理に興味を持ち始めたので、とりあえず「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

はじめに

久しぶりに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) ... 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

参照:ナイーブベイズを用いたテキスト分類 - 人工知能に関する断創録

ソースリスト

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
「機械学習 はじめよう」が始まりました. => 推定カテゴリ: 機械学習
検索エンジンや画像認識に利用されています. => 推定カテゴリ: 機械学

最後に

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