仮説推論エンジン"Phillip"を動かしてみた
昨今流行りのAIの影響か、推論エンジンを動かす機会があったので、構築メモ。
(※私はネットワークレイヤの人間なので推論エンジンって何?って聞かれても、あまり詳しくはわかりません。会社から依頼されたのですが、明らかに人選ミスな気がする。)
いくつかそれっぽいものがgithubに公開されているが、今回使ってみたのは「Phillip」という物。
github.com
githubのReadmeを見る限りでは、日本の方が作られた模様。WindowsやOS Xでも動くそうなのだが、Linux環境で動かすことを求められたのでLinux。
環境はみんな大好きAWSでAmazon Linuxのインスタンスを立てて試してみた。
1.ダウンロード
githubに書かれているインストール方法に従ってとにかくやってみる。
Getting Started · kazeto/phillip Wiki · GitHub
まずはサクッとgit clone。
$ git clone https://github.com/kazeto/phillip.git
次の手順は、
Move to the directory where Phillip is installed.
と書かれているが、とりあえずホームディレクトリで動けばいいのでこのままで。
2.Makefile生成
これも公式に従って実施するだけ。pythonは2.7指定だったのでyumでインストール済み。
聞かれるのは
- バイナリの配置場所
- LP-SOLVEを使うかどうか
- GUROBIを使うかどうか
の3つだけ。バイナリ配置場所はデフォルトで良しとし、ライブラリは二者択一で良いようなので、LP-SOLVEを使うこととする。
(GUROBIはダウンロードにアカウント作成などが必要で面倒くさかった。。。)
$ cd ./phillip $ python tools/configure.py *** Configuration of Phillip *** --> BINARY TARGET [default=bin/phil]: --> USE-LP-SOLVE [y/n]: y --> USE-GUROBI [y/n]: n
3.ライブラリのインストール&コンパイル
あとはライブラリをダウンロードしてコンパイルするだけらしい。LP-SOLVEの5.5系なら良いらしいので↓からダウンロード。
https://sourceforge.net/projects/lpsolve/files/lpsolve/5.5.2.5/
何がなんだかわからんが、とりあえず”lp_solve_5.5.2.5_exe_ux64.tar.gz”がそれっぽいので落として展開。公式にパスを通せと書いてあるのでパスを通す。そんでmake.(exportコマンドを打っているように見えますが、実際は.bash_profileに記載してます。)
$ mkdir lp_solve $ mv lp_solve_5.5.2.5_exe_ux64.tar.gz ./lp_solve $ tar xzvf ./lp_solve/lp_solve_5.5.2.5_exe_ux64.tar.gz $ export CPLUS_INCLUDE_PATH=$HOME/phillip/lp_solve $ make 〜省略〜 rc/././sol/ilp_solver.h:14:20: fatal error: lp_lib.h: No such file or directory #include <lp_lib.h> ^
はい。lp_lib.hが足りないらしい。多分ソースファイルにおいてありそうなので、ダウンロードのサイトに戻って今度は"lp_solve_5.5.2.5_source.tar.gz"をダウンロードして展開。パスも通す。
$ mkdir lp_solve_src $ mv lp_solve_5.5.2.5_source.tar.gz ./lp_solve_src $ tar xzvf ./lp_solve_src/lp_solve_5.5.2.5_source.tar.gz $ export CPLUS_INCLUDE_PATH=$HOME/phillip/lp_solve_src/lp_solve_5.5 $ make 〜省略〜 /usr/bin/ld: cannot find -llpsolve55 collect2: error: ld returned 1 exit status make: *** [all] Error 1
なるほどなるほど〜。こんどはlpsolve55というライブラリが足りないらしい。ライブラリだからdevかなという適当な推測の元、今度は"lp_solve_5.5.2.5_dev_ux64.tar.gz"をダウンロードして展開。ライブラリとしてパスを通す。
$ mkdir lp_solve_dev $ mv lp_solve_5.5.2.5_dev_ux64.tar.gz ./lp_solve_src $ tar ./lp_solve_dev/lp_solve_5.5.2.5_dev_ux64.tar.gz $ export LIBRARY_PATH=$LIBRARY_PATH:$HOME/phillip/lp_solve_dev $ make
無事に終わったらしい。
4.知識ベースのコンパイル
公式に従って、LISP形式のインプットを準備する。知識ベースが何かはわかってないよ。詳しい同僚はオントロジーがどうトリプルがどうとか親切に教えてくれた。(けど、基礎知識がなさすぎてあんまりよくわからんかった。あとで調べてみよう。)
- kb.lisb
(B (name kb01) (=> (steal-vb e1 x y) (criminal-jj e2 x))) (B (name kb02) (=> (criminal-jj e1 x) (arrest-vb e2 y x)))
- obs.lisp
; "Tom robbed jewels. Police arrested him." (O (name obs01) (^ (steal-vb E1 Tom Jewel) (arrest-vb E2 Police he)))
そして、この入力をつかって知識ベースをコンパイル。
$ bin/phil -m compile -c dist=basic -c tab=null -k compiled -P 8 kb.lisp # 04/05/2017 11:02:41] Phillip starts... # 04/05/2017 11:02:41] version: phil.3.19 〜省略
とりあえずできたっぽいね。
4.実行
これでいよいよ実行環境が整ったっぽい。
$ bin/phil -m infer -k compiled -c lhs=depth -c ilp=weighted -c sol=gurobi -P 8 -T 120 obs.lisp 〜省略 <phillip> <configure> <version>phil.3.19</version> <time_stamp compiled="Apr 5 2017 10:57:03" executed="Apr 5 2017 11:15:15"></time_stamp> <components lhs="DepthBasedEnumerator" ilp="weighted-converter" sol="gurobi-optimizer"></components> <knowledge_base path="compiled" size="2" max_distance="-1"></knowledge_base> <params timeout_lhs="-1" timeout_ilp="-1" timeout_sol="-1" timeout_all="120" verbose="1" gurobi_thread_num="8" kb_thread_num="8"></params> </configure> </phillip>
出力結果が何なのかは後でゆっくり見るとしてとりあえず動作しました。
大層なことをやったような書き方になってしまいましたが、公式のチュートリアルそのまんま走らせただけですね。。。
5.追記
どうも出力に結果が含まれていないと思ったら、オプションで指定しているプログラム指定がLP-SOLVEになっていなかった。
$ bin/phil -m infer -k compiled -c lhs=depth -c ilp=weighted -c sol=lpsolve -P 8 -T 120 obs.lisp <phillip> <configure> <version>phil.3.19</version> <time_stamp compiled="Apr 5 2017 10:57:03" executed="Apr 6 2017 03:39:53"></time_stamp> <components lhs="DepthBasedEnumerator" ilp="weighted-converter" sol="LP-Solve"></components> <knowledge_base path="compiled" size="2" max_distance="-1"></knowledge_base> <params timeout_lhs="-1" timeout_ilp="-1" timeout_sol="-1" timeout_all="120" verbose="1" gurobi_thread_num="8" kb_thread_num="8"></params> </configure> <proofgraph name="obs.lisp::obs01" state="optimal" objective="10"> <time lhs="0" ilp="0" sol="0.001" all="0.001"></time> <timeout lhs="no" ilp="no" sol="no" all="no"></timeout> <literals num="4"> <literal id="0" type="observable" depth="0" active="yes" paid-cost="yes" cost="10.000000">(steal-vb E1 Tom Jewel):0</literal> <literal id="1" type="observable" depth="0" active="yes" paid-cost="no" cost="10.000000">(arrest-vb E2 Police he):1</literal> <literal id="2" type="hypothesis" depth="1" active="yes" paid-cost="no" cost="12.000000">(criminal-jj _u1 he):2</literal> <literal id="3" type="hypothesis" depth="2" active="yes" paid-cost="no" cost="14.400000">(steal-vb _u2 he _u3):3</literal> </literals> <explanations num="2"> <explanation id="0" tail="0:{1}" head="1:{2}" active="yes" backward="yes" axiom="kb02" gap="">(arrest-vb E2 Police he):1 => BACKWARD(axiom=1) => (criminal-jj _u1 he):2</explanation> <explanation id="1" tail="1:{2}" head="2:{3}" active="yes" backward="yes" axiom="kb01" gap="">(criminal-jj _u1 he):2 => BACKWARD(axiom=0) => (steal-vb _u2 he _u3):3</explanation> </explanations> <unifications num="1"> <unification l1="0" l2="3" unifier="E1=_u2, Tom=he, Jewel=_u3" active="yes" gap="">(steal-vb E1 Tom Jewel):0 ^ (steal-vb _u2 he _u3):3 => UNIFY => (= E1 _u2):4 ^ (= Tom he):5 ^ (= Jewel _u3):6</unification> </unifications> </proofgraph> </phillip>