ボールを蹴りたいシステムエンジニア

ボール蹴りが大好きなシステムエンジニア、ボールを蹴る時間確保の為に時間がある時には勉強する。

初心者がナイーブベイズ分類器を作成する為の備忘録

やりたい事

ナイーブベイズ分類器を用いてツイートの内容が修造BOTイチローBOTのどちらに分類されるかを識別する。

自分用の備忘録として纏めていますので、若干分かりづらい所があると思いますので悪しからず。
間違いがあれば指摘頂けると嬉しいです。

学習データ(訓練用データ)

修造BOTイチローBOTのツイートを150件ずつ合計300件

教師データ作成

Ci(クラス)のパターンを考える

今回は2パターンだけなので2つ

C1=修造BOT
C2=イチローBOT

Bag of Wordsを作成する

Bag of Wordsとは文書から抽出した単語の集合。

訓練用データを読み込んで、mecab分かち書きし、
[ツイート][単語]の2次元配列を作成する。

こんな感じなる

[
 [自分,絶対,駄目,人,全て],
 [自分,ない,人,目,バット]
]

訓練用データのBag of Wordsから教師データ(モデル)を作成する

教師データ作成では以下の情報を取得する。

P(Ci)=事前確率

教師データ作成の為に利用した全データのうちクラスCiの発生する確率。
各クラスの生起確率とも言う。

今回の識別器で扱うクラスは

C1=修造BOT
C2=イチローBOT

それぞれ150件、合計300件を利用しているので事前確率は等しい事となる。

P(C1) = 150 / 300 = 1/2
P(C2) = 150 / 300 = 1/2

全ての単語の、「単語の条件付き確率」の分子・分母

単語の条件付き確率は、識別時のベイズの定理での尤度で利用し、その際に計算しても良いが、
訓練時に予め算出しておく事で、識別処理を高速化する。

「単語の条件付き確率」の分子
ex)
イチローBOT」クラスで「野球」という単語は20回出現

という感じで全ての単語の値を求める。

「単語の条件付き確率」の分母
ex)
イチローBOT」クラスで出現した全ての単語の件数+ツイート全体(イチローBOT、修造BOT)での単語ユニーク件数
※何故後者を加算するかよく分かっていない・・

識別対象データのクラス識別する

識別対象データの特徴ベクトルを取得

今回の例だと識別対象のツイートから頻出単語上位5件を取得する。

取得した特徴ベクトルを特徴ベクトルxと呼ぶ事とする。

例)
[自分,明日,人,耳,バット]

観測データをベイズの定理に当てはめてそれぞれのクラスの事後確率を求める

P(Ci|x)=p(x|Ci)/p(x) * P(Ci)

以下の様に変形できる

P(x|Ci) * P(Ci)

p(x)とは特徴ベクトルxの生起確率、全てのクラスで共通なので無視できる。
例えば、特徴ベクトルxのC1クラスとC2クラスそれぞれの事後確率を比較する場合、

p(x|C1)/p(x) * P(C1)

p(x|C2)/p(x) * P(C2)

を比較する事となるが、p(x)はどちらでも共通の値(全文書で特徴ベクトルxの生起確率)なので無視できる。

なので
P(x|Ci) * P(Ci)
の式に当てはめて事後確率を求める。

P(Ci)=事前確率

訓練時に取得済み。

p(x|Ci)=尤度

特徴ベクトルが連続的なデータ(単語の出現回数)なので、多項分布モデルで分類する。
※単語が出たか出なかったか(0 or 1)のような離散的データならベルヌーイ分布モデル

観測データをx、分類クラスをCi(i=1,...,N)とし、書き直すと
=P(x|Ci)
=クラスCiに分類されたデータxの確率分布
=Ci の中でxが発生する確率(xがCiであることが尤もらしい度合い)。

イチローBOTの例で考えると、
イチローBOTのツイート全ての中で、特徴ベクトル[自分,ない,人,目,バット]が発生する確率。

P(x|Ci) = P( word1, word2, word3, ... | Ci)
と考えられる。

P(word_i | Ci)=単語の条件付き確率
各単語がイチローBOTのツイート全ての中で発生する確率「単語の条件付き確率」を求める。

P( word1, word2, word3, ... | Ci)
 =P(word1 | Ci) * P(word2 | Ci) * P(word3 | Ci) ...

 =特徴ベクトルxの単語の条件付き確率の積が尤度となる。


※注意
上記の式の変形では単語の出現確率の間に独立性を仮定して同時確率をそれぞれの確率の積で表しているが、
本来、単語の出現に独立性は成り立りたたない。
たとえば、「人工」と「知能」は共起しやすいし、「機械」と「学習」は共起しやすい。
これを無視して単語の出現は独立と無理矢理仮定して文書の確率を単語の確率の積で表して
単純化するのがナイーブベイズのナイーブたる所以。

P(word_i | Ci)=単語の条件付き確率

クラスCiでword_iが出現する確率。
例として、
イチローBOTでword_iが出現した単語数 / (イチローBOTで出現した全ての単語数 + 全文書のユニーク単語数)

ラプラススムージングとして、分母に全文書のユニーク単語数を加算してる。

尤度の対数を取る

ここまでの計算結果でベイズの定理の事後確率を求める事もできそうですが、
尤度=P(x|Ci)はというのは非常に小さい数な上に文書中にはたくさんの単語が含まれるので
かけ算部分がアンダーフローを起こす可能性がある。
ex)
P(word1 | Ci) * P(word2 | Ci) * P(word3 | Ci) ...
の積がアンダーフローを起こす可能性がある。

その対策として、対数をとってかけ算を足し算化する。
事後確率の大小関係は対数をとっても変化しないので問題無い。

スムージング

識別対象データの単語が教師データに含まれない場合、
単語の条件付き確率P(word_i|Ci)は0となり、尤度(単語の条件付き確率の積)も0となってしまい計算ができなくなる。
これをゼロ頻度問題と言う。
ゼロ頻度問題は、スムージングという方法で対処できる。
よく使われるのが単語の出現回数に1を加えるラプラススムージング(LaplaceSmoothing)。
新しい単語が出てくると確率は低くなりますが、0にはなりません。

今回の例だと、単語の条件付き確率の算出時に、
分子である、Ciでの単語(word_i)の出現回数に+1
分母である、Ciでの全ての単語数に+全文書のユニーク単語数

クラスの識別

各クラスの事後確率を比較して、最も事後確率が大きいクラスが該当する。
(Map推定)

おわりに

  • 修造BOTイチローBOTでは発言の傾向が似ているので若干識別がしずらい感じがするので、もっと発言が異なるBOTで識別器を作成した方が良いかも。
  • ナイーブベイズ分類器の理論を理解するにあたって、様々な数学の知識が必要。周辺情報の勉強にかなり時間かかった。
  • 本記事は誤りが多数ある可能性がある為、都度間違いは修正していく。

mecabにmecab-ipadic-neologd辞書を追加する

環境

CentOS6

mecab-ipadic-neologdとは?

mecab標準のシステム辞書の拡張の為の新語辞書。
辞書は月に数回更新されており定期的に新語が追加されている。
※ipadicは2007年を最後に更新が止まっている

更新された辞書を反映する為には都度以下の作業(mecab-ipadic-NEologdのダウンロードと辞書変換)が必要になると思われる。

mecabのインストールはこちらから
toriaezu-engineer.hatenablog.com

手順

mecab-ipadic-NEologdをダウンロードする

git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git

解凍する。
解凍後のCSVファイル名はmecab-ipadic-NEologdの最終更新日みたい。

xz -dkv mecab-ipadic-neologd/seed/mecab-user-dict-seed.*.csv.xz

mecab-ipadic-neologd/seed/mecab-user-dict-seed.20160915.csv.xz (1/1)
  100.0 %                33.6 MiB / 422.0 MiB = 0.080    89 MiB/s         0:04

mecab用辞書に変換する。
ファイル名の日付は適宜変更する事

/usr/local/libexec/mecab/mecab-dict-index -d /usr/local/lib/mecab/dic/ipadic -u mecab-user-dict-seed.20160915.dic -f utf-8 -t utf-8 mecab-ipadic-neologd/seed/mecab-user-dict-seed.20160915.csv 

辞書を移動
※別に移動しなくても後述の設定で辞書パスを指定すれば大丈夫です

cp -p ./mecab-user-dict-seed.20160915.dic /usr/local/lib/mecab/dic/   

MeCabの設定ファイルにユーザー辞書を設定する。

vi /usr/local/etc/mecabrc

ユーザー辞書追記

userdic = /usr/local/lib/mecab/dic/mecab-user-dict-seed.20160915.dic

辞書が反映されている事を確認

追加前

[root@localhost src]# echo "なのはちゃんかわいい" | mecab

な      助動詞,*,*,*,特殊・ダ,体言接続,だ,ナ,ナ
の      名詞,非自立,一般,*,*,*,の,ノ,ノ
は      助詞,係助詞,*,*,*,*,は,ハ,ワ
ちゃん  名詞,一般,*,*,*,*,ちゃん,チャン,チャン
かわいい        形容詞,自立,*,*,形容詞・イ段,基本形,かわいい,カワイイ,カワイイ
EOS

追加語

[root@localhost src]# echo "なのはちゃんかわいい" | mecab

なのは  名詞,固有名詞,人名,一般,*,*,なのは,ナノハ,ナノハ
ちゃん  名詞,接尾,人名,*,*,*,ちゃん,チャン,チャン
かわいい        形容詞,自立,*,*,形容詞・イ段,基本形,かわいい,カワイイ,カワイイ
EOS

設定ファイルに追記しなくてもmecabのオプションでユーザー辞書の指定が可能

echo "なのはちゃんかわいい" | mecab -u /usr/local/lib/mecab/dic/mecab-user-dict-seed.20160915.dic
なのは  名詞,固有名詞,人名,一般,*,*,なのは,ナノハ,ナノハ
ちゃん  名詞,接尾,人名,*,*,*,ちゃん,チャン,チャン
かわいい        形容詞,自立,*,*,形容詞・イ段,基本形,かわいい,カワイイ,カワイイ
EOS

python3でTwitter APIからデータを取得

環境

CentOS6
python3.5

手順

Twitter API Keyを取得する。

Twitterアカウント作成後、以下にアクセス。
※アカウント登録には電話番号のひも付けが必要
https://apps.twitter.com/

「Create New App」をクリック

web siteには「http://127.0.0.1」を入力しても大丈夫だった。

登録後、「Keys and Access Tokens」タブでAPI Keyが確認できる。


requests-oauthlibインストール

pip install requests-oauthlib

API Keyの管理は設定ファイルで管理する。

twitter_api.ini

[twtter_api]
consumer_key=*****
consumer_key_secret=*****
access_token=*****
access_token_secret=*****

下記サイトを参考にAPI Key管理方法だけ変更して動いた。

http://umanotsubuyaki.blog.fc2.com/blog-entry-15.html?sp

linuxでnkfコマンド使って日本語をURLエンコード

ワンライナーコマンドで日本語をURLエンコードしてHTTPリスエストパラメータに設定。
nkfのインストールが必要です
複数パラメータには非対応・・・

key=`echo ピカチュウ | nkf -WwMQ | tr = %` ; curl http://localhost:8000/?key=${key}

【python】gunicornとfalconを使ってWSGIサーバを作成してみる

環境

CentOS6
python3.5.1
gunicorn19.6.0
falcon-1.0.0

はじめに

gunicornはPython製のWSGIサーバ。
WSGIサーバーとはWEBサーバーとWebアプリケーションをつなぐサーバ。
今回はwebアプリケーションにpythonのWEBフレームワークfalconを利用する。

前回の記事で導入したfalconアプリケーションを今回は利用する
toriaezu-engineer.hatenablog.com


手順

gunicornインストール

pip install gunicorn

インストール確認。

gunicorn -v
gunicorn (version 19.6.0)

gunicorn起動
バックグラウンドプロセスで起動する。

gunicorn falcon_test:api &

起動時の引数は

gunicorn モジュール名:falcon.APIインスタンスの変数名


モジュール名は、pythonスクリプトファイル名の拡張子を省いたもの。
falcon_test.py

falcon.APIインスタンスの変数名

api = falcon.API()

起動してみる
※BGプロセスで起動
何かログ出た

gunicorn falcon_test:api

[2016-09-15 20:09:08 +0900] [62160] [INFO] Starting gunicorn 19.6.0
[2016-09-15 20:09:08 +0900] [62160] [INFO] Listening at: http://127.0.0.1:8000 (62160)
[2016-09-15 20:09:08 +0900] [62160] [INFO] Using worker: sync
[2016-09-15 20:09:08 +0900] [62234] [INFO] Booting worker with pid: 62234


curlでアクセスしてみる
gunicornで起動するとfalconアプリケーションで指定したポートは無効になるみたい。
gunicornのデフォルトのポート8000でアクセスする。

curl http://localhost:8000/test_api?key=TEST 

{'USER-AGENT': 'curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2', 'HOST': 'localhost:8000', 'ACCEPT': '*/*'}
{"title": "WebAPIテスト", "tags": [{"バージョン": [], "name": "テスト"}, {"name": "request", "TEST": []}]}

アクセス出来た。

停止するには、プロセス番号を特定してkill
2つプロセスあるけど・・

 ps ax|grep gunicorn
 
 62676 pts/0    S      0:00 /root/.pyenv/versions/3.5.0/bin/python3.5 /root/.pyenv/versions/3.5.0/bin/gunicorn falcon_test:api
 62750 pts/0    S      0:00 /root/.pyenv/versions/3.5.0/bin/python3.5 /root/.pyenv/versions/3.5.0/bin/gunicorn falcon_test:api

pkill

 pkill gunicorn
[2016-09-15 20:13:47 +0900] [62676] [INFO] Handling signal: term
[2016-09-15 20:13:47 +0900] [62750] [INFO] Worker exiting (pid: 62750)
[2016-09-15 20:13:47 +0900] [62676] [INFO] Shutting down: Master

停止してる事を確認

[root@localhost falcon]# ps ax|grep gunicorn
 63106 pts/0    S+     0:00 grep gunicorn

パット見、falconだけの起動時と何も変わらない。

と思ったら、起動時のオプションで色々指定できるみたい。

参考
http://docs.gunicorn.org/en/stable/settings.html


ポート番号指定して起動

gunicorn -b localhost:4000 falcon_test:api &

workerプロセス4つで起動
2~4の間で指定

gunicorn -w 4 falcon_test:api &

4プロセス起動してる

pstree -a|grep gunicorn
  |           |-grep gunicorn
  |           |-gunicorn /root/.pyenv/versions/3.5.0/bin/gunicorn -w 4 falcon_test:api
  |           |   |-gunicorn /root/.pyenv/versions/3.5.0/bin/gunicorn -w 4 falcon_test:api
  |           |   |-gunicorn /root/.pyenv/versions/3.5.0/bin/gunicorn -w 4 falcon_test:api
  |           |   |-gunicorn /root/.pyenv/versions/3.5.0/bin/gunicorn -w 4 falcon_test:api
  |           |   `-gunicorn /root/.pyenv/versions/3.5.0/bin/gunicorn -w 4 falcon_test:api

デーモンプロセスで起動
これ指定すれば&はいらないや。

gunicorn -D  falcon_test:api

workerのスレッド数
2-4 x CPUコア数の間で指定

gunicorn --threads 2 falcon_test:api 

workerの最大コネクション数

gunicorn --worker-connections 100 falcon_test:api

タイムアウト値、ワーカーが指定した秒数応答無い場合、自動的に再起動されます。
合ってるか怪しい

gunicorn -t 30 falcon_test:api

カレントディレクトリを指定
webアプリケーションのpythonがある場所を指定したりする

gunicorn --chdir /work/falcon/ falcon_test:api 

pidファイルを作成

gunicorn -p /tmp/gunicorn.pid falcon_test:api

こんな感じでプロセスIDが出力される

cat /tmp/gunicorn.pid 
64695

アクセスログ出力

gunicorn --access-logfile /tmp/gunicorn_access.log falcon_test:api 
cat /tmp/gunicorn_access.log 
127.0.0.1 - - [15/Sep/2016:22:35:13 +0900] "GET /test_api?key=TEST HTTP/1.1" 200 117 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.21 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2"

エラーログ出力

gunicorn --error-logfile /tmp/gunicorn_error.log falcon_test:api 
cat /tmp/gunicorn_error.log

[2016-09-15 22:36:16 +0900] [65318] [INFO] Starting gunicorn 19.6.0
[2016-09-15 22:36:16 +0900] [65318] [INFO] Listening at: http://127.0.0.1:8000 (65318)
[2016-09-15 22:36:16 +0900] [65318] [INFO] Using worker: sync
[2016-09-15 22:36:16 +0900] [65332] [INFO] Booting worker with pid: 65332

ログレベル指定

gunicorn --log-level debug falcon_test:api 

他も色々設定あるみたい。

後でヘルプで確認してみる

gunicorn -h