ネアンデルタールと遊ぼう
#clojure #Neanderthal #機械学習 #Advent_Calendar
このページは Clojure Advent Calendar 2017 9日目に向けたものです。
アイドルマスターシンデレラガールズ 黒川千秋黒川千秋.icon担当プロデューサー 兼 サイボウズスタートアップスで日々clojureを書くお仕事をしているuochanuochan.iconといいます。
今までclojureで行列計算などするときは core.matrix を使っていたのですが、超速いと噂のNeanderthalが気になったので機械学習も絡めて遊んでみました。
前提
遊ぶのが目的だったので他ライブラリとの比較は行いません
NeanderthalはCPUエンジンを使います
GPUエンジンの利用についてはまとめません
数学は得意ではないので間違いがあればご指摘ください
準備
Neanderthalは単純に leiningen や boot で依存関係を追加するだけでは使えません。
GPUを使わずにCPUを使ったエンジンで計算する場合でもintelが提供している MKL (要ユーザー登録、700MBくらい!!)をインストール(soやdllを所定の場所に配置)する必要があり、敷居はちょっとだけ高いです。
Ubuntuの場合
MKL は /opt/intel 配下に各種ファイルがインストールされます。
私の環境では以下のファイルを /usr/local/lib 配下にコピーして問題なく動作しました
libmkl_rt.so
libmkl_core.so
libmkl_intel_lp64.so
libmkl_avx2.so
libiomp5.so
libmkl_intel_thread.so
libmkl_vml_avx2.so
基本
ベクトル、行列計算方法は公式のドキュメントにチュートリアルがありますが、雰囲気を感じてもらうため、ベクトル・行列の作成とそれぞれの積だけ抜粋しておきます。
関数名が短いのはMKL譲りのようですが、xpy が vector x plus vector y の略なのはやり過ぎだと思います。
code:clj
(require 'uncomplicate.neanderthal.core :refer :all
'uncomplicate.neanderthal.native :refer :all)
;; ベクトル操作
(dv 1 2 3) ;; => 1.0 2.0 3.0
(dot (dv 1 2 3) (dv 4 5 6)) ;; => 32.0
;; 行列操作
(dge 2 3 (range 6)) ;; => 0.0 0.2 0.4] [1.0 3.0 5.0
(mm (dge 1 3 1 2 3) (dge 3 1 4 5 6)) ;; => 32.0
ELMを実装してみる
では本題です。ネアンデルタールと遊びましょう!
今回は機械学習と絡めるということで Extreme Learning Machine を Neanderthal で実装してみます。
ELMの詳細(?)は上記リンクを参照してもらいたいのですが、簡単に言うと速い易い上手いと三拍子揃ったニューラルネットの一種なので、遊ぶにはもってこいの材料です。
ソース
ソースの全体は以下を参照してください。
実装にミスなどあれば(ありそう!!)issueをあげてもらえるとありがたいです。
https://github.com/liquidz/neelm
以下、補足です
行列とベクトルの和
numpyだと何も考えずにできる行列とベクトルの和ですが、Neanderthalだと自分で実装する必要があります。(もしかしたら良いやり方が他にあるかも?)
ベクトル同士の和は関数が用意されているので、行列の各列をベクトルとして取り出して、それぞれ和を取って代入する作戦で実装したものが以下になります。
code:clj
(defn- mpv!
(mat v (mpv! 1 mat v))
(alpha mat v
(doseq r (rows mat)
(axpy! alpha v r))))
Neanderthalでは副作用のある関数を多く使うことになりますが、copy 関数が用意されているので、必要に応じて使うと良いでしょう。
擬似逆行列
ELMでは擬似逆行列の計算が必要なのですが、現状、MKLではまだ一発で計算できるような関数は提供されていません。
ただこうやれば計算できるよーという例は intelのページ上で紹介されている のでこれを参考にしました。
code:clj
(def shape (juxt mrows ncols))
(defn pinv [mat & {:keys alpha beta :or {alpha 1.0 beta 0.0}}]
(let [m n (shape mat)
k (min m n)
{:keys sigma u vt} (n.l/svd mat true true)
inva (dge n m)]
(dotimes i k
(let [si (entry sigma i i)
ss (double (if (> si 1.0e-9) (/ 1.0 si) si))]
(scal! ss (col u i))))
(mm! alpha (trans vt) (trans u) beta inva)
inva))
数値計算に長けた言語やライブラリと比べると融通がきかないことが多いので、ことあるごとに「これどうやればいいんだ?」という疑問は湧くのですが、そこは公式のドキュメントと気合で乗り切りましょう。
学習させてみる
実装したELMがちゃんと動作するか試してみましょう。
Neanderthalとイチャイチャし過ぎてあまり時間がとれなかったので、今回はなんの面白みもないsin曲線です。
他のデータセットでの学習結果・かかった時間などはあとで時間がとれたら公開できたらなぁと思っています。
実際のコードは以下です。
https://github.com/liquidz/neelm/blob/master/example/sin_curv.clj
赤い先が期待した結果、青い線が学習したモデルから予測した結果です。
隠れノード数が20だと完全に予測しきれてないことがわかります。
https://gyazo.com/b1402fed872d21f687740804f6b0da6d
次は隠れノード数を200まで増やすと予測が完全に一致することがわかります。正しく動いてますね。
https://gyazo.com/48b090ab253e1dcfc542b49b974d6e28
ちなみにDigitalOceanの一番安い500MメモリのVM上で学習させても、一瞬で終わるのでELMはお金にも優しいです。
最後に
正直、pythonや他の数値計算に特化した言語で明らかに簡単だとは思うので、わざわざclojureでこういったことをやる意味はあまりないかもしれません。
ただライブラリがもっと洗練されてclojureであっても高速な数値計算処理が簡単に書ける未来がくれば、それは浪漫に満ち溢れていると思うので、その浪漫に少しでも貢献できればいいなぁと思っています。
余談
今日12月9日は私の誕生日なので、バンダイナムコさんは是非、アイドルマスターシンデレラガールズ スターライトステージにて黒川千秋さんのSSRを実装していただければなと思います。
※追記
朝起きたら違う誕生日プレゼントきてました
http://blog.cognitect.com/blog/clojure19
#2017 #2017-12 #2017-12-09
#記事