libsvm (2)
とあるWebのサービスを使いたくなった。それには会員登録して下さいとな。何か有効な情報を 得ようとすると、こちらの情報と引き換えにという作戦。裏では、機械学習がカラカラと 音を立てて動いているに違いない。そして、隙あらば余計な物を買わせようと、虎視眈々と 狙ってる。消費者心理学とか、考慮してるんだろうな。
で、idを決めてパスワードも決める必要がある。初めにの解説を読むと、idは半角英数字と記号 で、8桁から19桁。パスワードもやはり英数字と記号で、6桁から8桁でお願いしますとの事。 更に使える記号は、ハイフン、アンダーバー、ドット、アットマークの4種に限定ですって。
これって、idはメアドを推奨しますって暗に言ってるな。パスワードは8桁じゃ短すぎないか。 どれも、作る側の都合で決めた仕様っぽいぞ。まるで、 なぜマイナポータルはJava必須なのか、開発者側の理屈でユーザー体験がおざなりにと同じじゃんと思ったぞ。
まあ、文句を言っても始まらないで、ユーザー登録にかかったよ。パスワードは英大文字を 混ぜるのが常識と思って、混ぜたら撥ねられた。何、この仕様。セキュリティを知らない馬鹿者 が、自分の都合だけで作ったな。それでいて、パスワードは生年月日は危険ですとか、偉そうに いってるよ。
ええい、こうなったら、idとパスワードを含めて、超いやらしくランダムな文字列にしてあげるかな。それとも、19桁と8桁なら全27桁になるから、これで、好きな英語の歌のフレーズを入力出来そうだな。
ランダム文字なら、とある座右の銘をmd5にかけて、それをsha256ぐらい にかけて、それをbase64エンコードしてやるか。ログインidとパスワードは、計算して算出させましょう。
[ob: ~]$ echo trump | openssl sha512 | openssl base64 | cut -c 13-21 YThmOGFmY N2VkNjk4Y ZGZkYTIzO
idは上の2行を連結、パスワードは最後の行を採用とかね。ああ、大文字が混じっているから trでもかませる必要が有るな。
我ながら、馬鹿な事を考えるものだ。
libsvmを使い倒す
と、キャッチャーな見出しで初めてみる。それには、libsvmが喰えるファイルが必要だな。 なんせ、独自のフォーマットですからね。
$ cat train-images.txt |\ awk '{ for (i = 1; i <= NF; i++) if ($i > 0) printf("%d:%f%s", i, $i / 255, i < NF ? " " : ""); printf("\n"); }' |\ paste -d' ' train-labels.txt - >train.svm
何方かが書かれた、MNISTの手書き数字をサポートベクタマシン用データに変換する例。 ラベルファイルとフィーチャーファイルが別になってて、それをpasteで合体させてる。 pasteの上手い使い方だな。
オイラーが持ってる、例の血圧データはCSVファイル。これをSVM形式に変換するやつを 書いてみた。CSVファイル、そんなのpythonに任せますってのは無しの方向です。 上のコードを再利用させていただきました。
sakae@debian:~/ML/svm$ cat conv.awk # conv csv-file to svm-file BEGIN { FS = "," } { printf("%d ", ($1 % 100) < 12 ? -1 : 1); for (i = 2; i <= NF; i++) { printf("%d:%d%s", i - 1, $i, i < NF ? " " : ""); } printf("\n"); }
下記のように使います。
sakae@debian:~/ML/svm$ cat z.csv 15111204,126,78,53 15111221,120,64,61 15111304,130,73,55 15111321,128,73,59 15111405,135,71,55 sakae@debian:~/ML/svm$ cat z.csv | awk -f conv.awk -1 1:126 2:78 3:53 1 1:120 2:64 3:61 -1 1:130 2:73 3:55 1 1:128 2:73 3:59 -1 1:135 2:71 3:55
スケーリングをやってくれる、svm-scaleってコマンドは、ラベルには手を付けない 仕様になってる。(当たり前と言えば当たり前な事)
sakae@debian:~/ML/svm$ cat z.csv | awk -f conv.awk > t.txt sakae@debian:~/ML/svm$ svm-scale t.txt > t.svm sakae@debian:~/ML/svm$ cat t.svm -1 1:-0.2 2:1 3:-1 1 1:-1 2:-1 3:1 -1 1:0.333333 2:0.285714 3:-0.5 1 1:0.0666667 2:0.285714 3:0.5 -1 1:1 3:-0.5
変換されたデータを良く見ると、最後の行の2カラム目のデータが省略されてる。これ、 変換した結果がZEROになったから。いわゆる疎な行列として扱い、メモリーとかを 節約したい作戦なんだろうな。
sakae@debian:~/ML/svm$ svm-scale -s xx t.txt > t.svm sakae@debian:~/ML/svm$ cat xx x -1 1 1 120 135 2 64 78 3 53 61
スケールさせた時の範囲をファイルに記録しておける。後で、テストデータをスケールさせる 時、このファイルを使う必要がある。トレーニングとテストと同じスケールになっていないと 意味ないからね。
debianと言う緩い環境から離れて、OpenBSD上でやってみるかな。libsvmに同梱されてる toolsの中のpythonファイル類を/usr/local/binの中に移動。後、python-dirの中にある ファイルは、/usr/local/lib/python3.4/site-packageの中に移動。これで、普通に 扱えるpythonコマンドに昇格だな。
それから、svm-xxxってコマンドも/usr/local/binの下へ。ライブラリィーファイルは/usr/local/libへ配置。なんせ、インストーラーが付いていませんから、手動管理してくださいって いう自由度が高いものになってます。
手始めに、パラメータのチューチングでもやってみるか。
[ob: svm]$ easy.py z.svm Traceback (most recent call last): File "/usr/local/bin/easy.py", line 28, in <module> assert os.path.exists(svmscale_exe),"svm-scale executable not found" AssertionError: svm-scale executable not found
早速怒られた。
[ob: svm]$ sudo vi +28 /usr/local/bin/easy.py : if not is_win32: svmscale_exe = "../svm-scale" svmtrain_exe = "../svm-train" svmpredict_exe = "../svm-predict" grid_py = "./grid.py" gnuplot_exe = "/usr/bin/gnuplot"
こんなに良い物をlibsvmの中に閉じ込めておくのは、いかがなものかと。まあ、煩雑に バージョンアップが有るなら、相応の対処をしなきゃならんだろうけど、世界の目が集中 してるんで、十分に安定してると思われ。上記の相対PATHを、/usr/local/binに書き換えて おいた。
[ob: svm]$ easy.py z.svm Scaling training data... Cross validation... Best c=32.0, g=0.5 CV rate=96.0219 Training... Output model: z.svm.model
これ、クロスバリディーションって言ってるんで、トレーニングデータ、テストデータに 分割して、過学習にならないように配慮しながら、cとgのパラメータを変えて、正解率が 上がる点を見つけているんだな。
最良のパラメータの時、正解率は96%になりましたとな。上記の結果が出た後も暫くは 等高線グラフが出てるけど、やがて終了して消えてしまう。
[ob: svm]$ ls z* z.svm z.svm.range z.svm.scale.out z.svm.model z.svm.scale z.svm.scale.png [ob: svm]$ gpicview z.svm.scale.png
各種のログを残してくれていて、その中に等高線グラフも含まれているんで、後でじっくりと 観察出来るね。
なお、供試データは欲張らない事。データが大きすぎると、探索に膨大な時間がかかってしまう。せいぜい1000行ぐらいまでにしておけ。大きなデータから データの特徴に配慮して切り出してくれる、subset.pyが用意されてるぞ。
[ob: svm]$ easy.py z.svm Scaling training data... Cross validation... Best c=2048.0, g=0.03125 CV rate=93.0059 Training... Output model: z.svm.model
3217行のデータで探索したら、こんな結果になった。正解率が落ちているぞ。 subset.pyを使って抽出したデータでやってみたら、
[ob: svm]$ easy.py z.svm Scaling training data... Cross validation... Best c=32768.0, g=0.00048828125 CV rate=93.0556 Training... Output model: z.svm.model
こんな結果が。データに捉えどころが無いって事かな。なんかcが振り切っている予感が するぞ。このデータは、3年半分のデータだから、体調が徐々に変化してるのかな。
wine
統計的機械学習入門を見つつ、ワイン でも楽しみましょ。
とか言ってるけど、この機械学習を始める機会になった、node.jsでなんたらってのが回を 重ねて、 機械にワインの味は判定できるか? その2で、node.js用のsvmを使い速いだろうってやってたんだ。svmが何たるか知らなかったけど、そのまま Wine Quality Data Set から落としてきたデータで試そうも、フォーマットが変わっていて、面倒だったので、そのまま うっちゃっていたんだ。
時は流れ、周囲を見渡せるようになった今、改めてワインに酔ってみましょとなった訳。
機械学習の取っ掛かりは、データの調査から。落としてきたデータは、赤ワインと白ワインの 2本だて。面倒なので、両者を混ぜ合わせて、ロゼワインにしちゃおうかと思ったけど、 ワインソムリエの田崎真也さんに、馬鹿な事をするなと窘めるられそうなので、すんでの所で 思いとどまったよ。
[ob: wine]$ head -n 3 red.csv "fixed acidity";"volatile acidity";"citric acid";"residual sugar";"chlorides";"free sulfur dioxide";"total sulfur dioxide";"density";"pH";"sulphates";"alcohol";"quality" 7.4;0.7;0;1.9;0.076;11;34;0.9978;3.51;0.56;9.4;5 7.8;0.88;0;2.6;0.098;25;67;0.9968;3.2;0.68;9.8;5
冒頭にフィーチャの説明が書いてある。ワインのフレーバーとか品質がどの列にあるかの説明だな。機械学習で使う教師データに相当するものは、一番最後になるんだな。 2行目からは、セミコロン区切りのデータが並んでいる。各ワイナリーから供試されたものだな。
これをsvmで扱えるように変換する必要が有るな。前に作ったawkスクリプトをチョイ変更。
[ob: wine]$ cat conv.awk BEGIN { FS = ";" } { printf("%d ", $NF); for (i = 1; i < NF; i++) { printf("%d:%f%s", i , $i, i < NF ? " " : ""); } printf("\n"); }
これをそのまま使うと、
0 1:0.000000 2:0.000000 3:0.000000 4:0.000000 5:0.000000 6:0.000000 7:0.000000 8:0.000000 9:0.000000 10:0.000000 11:0.000000 5 1:7.400000 2:0.700000 3:0.000000 4:1.900000 5:0.076000 6:11.000000 7:34.000000 8:0.997800 9:3.510000 10:0.560000 11:9.400000 5 1:7.800000 2:0.880000 3:0.000000 4:2.600000 5:0.098000 6:25.000000 7:67.000000 8:0.996800 9:3.200000 10:0.680000 11:9.800000
ヘッダー行が数値ではないのに、awkは何のエラー(警告)も出さずに変換したよ。文字列は、 数値のZEROになるんか。ちょいと怖い仕様だな。ユーザーが気を付けるしかない。
wine for octave
後は、お決まりの手法。スケールしてレーニングして、鑑定するってなるんだけど、 どうも鑑定で良いスコアが得られない。ワインは嗜好品だから、万人が一致する事は無い だろうって言う論法を持ち出すか。
[ob: wine]$ awk -f conv.awk red.csv > t [ob: wine]$ svm-scale t > w.svm [ob: wine]$ octave-cli -q octave:1> [y, x] = libsvmread('w.svm'); octave:2> cv = svmtrain(y, x, '-c 1 -g 0.07'); * optimization finished, #iter = 574 nu = 0.726641 obj = -911.830280, rho = -1.127341 nSV = 966, nBSV = 953 octave:3> [p, a, d] = svmpredict(y, x, cv); Accuracy = 58.474% (935/1599) (classification)
それとも、下記のoctave用のパラメータチューニングスクリプトで、舌を鍛えるか。
bestcv = 0; for log2c = -5:2:15, for log2g = -15:2:3, cmd = ['-q -v 5 -c ', num2str(2^log2c), ' -g ', num2str(2^log2g)]; cv = svmtrain(y, x, cmd); if (cv >= bestcv), bestcv = cv; bestc = 2^log2c; bestg = 2^log2g; end fprintf('%g %g %g (best c=%g, g=%g, rate=%g)\n', log2c, log2g, cv, bestc, bestg, bestcv); end end
結果は、随分吟味した末に、下記のようになった。全数検査してたんで時間がかかったんだな。 こういう場合は、抜き取り検査でいいのかな。
Cross Validation Accuracy = 42.5891% -5 -15 42.5891 (best c=0.03125, g=3.05176e-05, rate=42.5891) Cross Validation Accuracy = 42.5891% -5 -13 42.5891 (best c=0.03125, g=0.00012207, rate=42.5891) : Cross Validation Accuracy = 63.6023% 15 3 63.6023 (best c=2, g=8, rate=66.0413)
それにしても、node.js下のsvmで、98%のスコアだったってのが信じられないな。
on scikit-learn
色々試験するには、ちゃんとしてる実験室がいいな。設備も揃っているし。Debianに戻りました。 そして、ワインのデータを取り込むpython pandaさんです。
def get_wine(file): a = pd.read_csv(file, delimiter=';') return a.ix[:,:-1], a.ix[:,-1]
一応、動作確認。
In [27]: x,y=get_wine('red.csv') In [28]: !head -n 2 red.csv "fixed acidity";"volatile acidity";"citric acid";"residual sugar";"chlorides";"\free sulfur dioxide";"total sulfur dioxide";"density";"pH";"sulphates";"alcohol\";"quality" 7.4;0.7;0;1.9;0.076;11;34;0.9978;3.51;0.56;9.4;5
冒頭にビックリを付けると、本物もshellを呼べるのは良い兆候です。
In [29]: x.ix[0,:] Out[29]: fixed acidity 7.4000 volatile acidity 0.7000 citric acid 0.0000 residual sugar 1.9000 chlorides 0.0760 free sulfur dioxide 11.0000 total sulfur dioxide 34.0000 density 0.9978 pH 3.5100 sulphates 0.5600 alcohol 9.4000 Name: 0, dtype: float64 In [30]: y[0] Out[30]: 5
どうやら正しく読み込んでくれてます。ワインのpHが3.5って、随分と酸性なんだな。他の ワインはどうなんだろう? 一番酸っぱいのは、2.7だった。これって硫酸だか塩酸ぐらいでは なかろうか? こんなの飲んで大丈夫か? あの人に聞いてみたいぞ。
tips
なんか、新しいのが出たみたい。 Python向けのグラフィカルなシェル「IPython 5.3」リリース 良く使うんで、重いのは御免こうむりたいぞ。
で、コピペしたのを実行出来るとな。某Webに出てたpythonとのセッション記録。 これをコピペしようとすると、冒頭のプロンプトが邪魔になって、一行づつしか出来ない。 最後の結果はいいけど、入力した部分をペターとコピーして貼り付けたい。
>>> import random >>> mylist=['apple','orange','passion fruit','banana'] >>> random.shuffle(mylist) >>> mylist ['orange', 'passion fruit', 'banana', 'apple']
そんな時は、cpasteっていうのが便利。
In [7]: cpaste Pasting code; enter '--' alone on the line to stop or use Ctrl-D. : >>> import random >>> mylist=['apple','orange','passion fruit','banana'] >>> random.shuffle(mylist) >>> mylist::: :-- Out[7]: ['apple', 'banana', 'orange', 'passion fruit']
張り付けてて、最後に、ダッシュを2つ入力するか、Ctrl-Dすればおk。プロンプトを 取り除いて、実行してくれる。