Clojure機械学習勉強 - Gorilla REPLと線形回帰 (その2)
昨日のコードを2点修正:
- 前回は独立変数を一つだけ用意したが、今回は二つ。さらに、今後独立変数の数の増減はX値と母数のθ値だけアップデートすれば、他のコードは変更なしで機能するようリファクタした。
- core.matrixの実装を、標準実装のndarray、vectorz-cljそしてclatrixの三種類から選べるようにした。
そのコードがこれ:
一番重い処理が、30000回更新したθ値を求める以下のコード:
(doall (theta-after-n-iteration 30000))
まずはndarrayで実行してみた場合:
"Elapsed time: 16456.788619 msecs" [2.0190228761901188 5.246421586567178 2.879450293240761] (抜粋)
vectorz:
"Elapsed time: 2122.331857 msecs" [1.881619902630865,4.979437022948518,2.8889278718090208]
頭の方で
(m/set-current-implementation :vectorz)
と指定するだけで十倍近く早くなっている。
そしてclatrix。なんと全く同じコードで:
Exception thrown: clojure.lang.ExceptionInfo (Can't broadcast to a lower dimensional shape)
と例外投げて死んでくれる。
多分clatrixだと(* A B)
といった処理はAの次元数がB以下(というかAがスカラー値かベクトルかBと全く同じ次元である)必要があるのに対して、他の実装ではBがスカラーかベクトルで他の次元が合えば、BをAに合わせてbroadcastしてくれていたようだ。(多分。逆かな?)
同じcore.matrixのAPIを実装しているはずなのに、こういうところで違いが出るのは面白くもあり、めんどくさくもあり。少なくとも「APIが同じだから入れ替えは一行で済むよ!」というのは盛りすぎなようである。
Clojure機械学習勉強 - Gorilla REPLと線形回帰
まあ何も言わずこのGorilla-replのOnline Viewerを見てくれ。
コードの常であるが、「どうやって正しく計算するか」よりも「どうやって正しく読み手にコードの意図を伝えるか」が最も難しかった。
一番腐心したところの割には、正直あまり成功しているとは言い難いが・・・ とりあえずLaTeXで苦心したりplotの機能を探ったりしながら作ってみた。今後はもうちょっと何とかなりそうな気がする。
次はcore.matrixのAPIを実装した高速行列計算ライブラリであるVectorzとClatrixの話をしたい。
追記:うっかり乱数のシードを同じにしてしまった。いかんいかん。というか、正直な話、乱数のシードはグローバルで一括管理したいよなぁ・・・
Clojure機械学習勉強 - core.matrix(その1)
前置き
Clojure Advent Calendarで機械学習ネタで記事を書くことを宣言してしまった。どうしよう。
C++、Python、Matlab\Octaveあたりではちょこちょこと(業務も含めて)機械学習はやったことがあるが、Clojureでは「JVMだし数値計算はしたくないよねー」とlisp.meetupの懇親会でネタにしまくっていたというのに。
機械学習なんて大量のデータを行列に投げ込んで出来るだけ早くぶん回すような世界なんだから、C++(かFortran!)コードを呼び出して行列データ構造で計算するのが正しいだろうし、それってJVMでしかも関数型な言語でやるのは筋悪なんじゃないか?というのが直感的な意見だし、lisp.meetupの他の参加者からも概ね同意を得ていた。*1
まあ多分実際にその通りなのだが、そこはほら、ものは試しということで、Clojureでどこまでやれるのかを調べてみたい。
あと他言語で覚えたアルゴリズムをClojureで実装してみると作法が大きく違うので理解が進む(気がする)ので、機械学習の勉強にもなるはず。
というわけで、まずはClojureにおける行列計算のAPIであるcore.matrixを見てみたい。
core.matrix
Clojureには複数の行列計算用ライブラリがある。純粋なJVM実装であるvectorz、JavaからBLASを呼び出すjblasを使っているclatrix、そしてJNIで直接BLAS系のライブラリであるATLASを呼び出しているNeanderthalあたりが強いようだ。NeanderthalはとくにGPUなども非常に簡単にアクセスできるっぽい。
それらのライブラリの上に共通のAPIとして存在するのがcore.matrixである。Neanderthalは以前は対応してなかったみたいだが、core.matrixの対応済みライブラリのリストが最近アップデートされた時に加えられたので対応したみたいだ。
とりあえずvectorzなどのちゃんとした行列計算用ライブラリを使わず、core.matrixの標準実装(clojure自体のデータ構造をwrapしているだけなので遅い)だけでAPIに慣れ親しんでみる。
まずproject.clj:
(defproject corem "0.1.0-SNAPSHOT" :dependencies [[org.clojure/clojure "1.8.0"] [net.mikera/core.matrix "0.56.0"]])
そしてとりあえずインポート:
(ns corem.core (:refer-clojure :exclude [* - + == / < <= > >= not= = min max]) (require [clojure.core.matrix :as m] [clojure.core.matrix.operators :refer :all] ))
*
や+
などの演算子をclojure.core.matrix.operatorsのものでオーバーライドしている!が普通の数字もちゃんとオーバーライドされたもので計算できるので大丈夫(らしい)。実行速度に影響出ないんだろうか。後々試してみたい。とりあえずこれで一般的な行列計算的なことができる。
(def a (m/matrix [[1 2 3] [4 5 6]])) a ;; [[1 2 3] [4 5 6]] (m/shape a) ;; [2 3] (m/ecount a) ;; 6 (m/esum a) ;; 21 (m/transpose a) ;; [[1 4] [2 5] [3 6]]
行列の行数・列数を返すshape、成分の数を返すecount、成分の和を返すesumそして転置行列を返すtransposeあたりは基礎中の基礎だろう。
関数型ならではの高階関数もある:
(m/emap inc a) ;; [[2 3 4] [5 6 7]] (m/ereduce * a) ;; 720
行列とスカラー値の四則演算は簡単:
(+ a 1) ;; [[2 3 4] [5 6 7]] (- a 1) ;; [[0 1 2] [3 4 5]] (* a 2) ;; [[2 4 6] [8 10 12]] (/ a 2) ;; [[1/2 1N 3/2] [2N 5/2 3N]] (/ a 2.0) ;; [[0.5 1.0 1.5] [2.0 2.5 3.0]]
最後、割り算のところだけは、とくに速度重視の場合は気をつけないといけない・・・ 浮動小数点と合理数では効率があまりにも違いすぎる。
同じ形の行列とも四則演算は簡単:
(def b (m/matrix [[2 2 2] [2 2 2]])) (+ a b) ;; [[3 4 5] [6 7 8]] (- a b) ;; [[-1 0 1] [2 3 4]] (* a b) ;; [[2 4 6] [8 10 12]] (/ a b) ;; [[1/2 1N 3/2] [2N 5/2 3N]]
行列と、列の数が一致する行ベクトルとの四則演算では(本当はスカラー値の場合も)broadcastingという計算が働き、行列にある行の数だけ同じ行ベクトルが並んでいるものと看做される。
(def c (m/matrix [-1 0 1])) (+ a c) ;; [[0 2 4] [3 5 7]] (- a c) ;; [[2 2 2] [5 5 5]] (* a c) ;; [[-1 0 3] [-4 0 6]]
割り算は0が入っていなければ大丈夫。
行の数が一致する列ベクトルとは残念ながらbroadcastは作動しない。(transpose (+ (transpose a) (transpose d)))
などと二重に転置をしてやる必要がある。
ここらへんで一旦終わる。次は線形代数的な機能と、簡単に線形最小二乗法でもやってみたい。
参考文献的なもの
公式wiki - Home · mikera/core.matrix Wiki · GitHub
API Guide - Core.matrix 0.44.0
core.matrixの作者であるMichael Andersonによる2014年のClojure Conjでのトークは今回書いた内容あたりも含めていろいろと説明していてなかなか良かった。
Clojure Web Development勉強 - Hiccup (その2) XSSの恐怖
なにやら五島勉のようなタイトルをつけて恐縮だが、Hiccupでも良く指摘される重大な問題として、自動的に文字列をエスケープしないことによるXSS脆弱性の導入がある。
つまり、今まで紹介してきたコードにもあるように、「フォームなどでユーザインプットを受け取りそれをHTMLに埋め込んで返す」というような挙動の場合、そのインプットに任意のJavaScriptコードなどが入っていると、それをユーザのブラウザ上で自動的に走らせてしまう恐れがある。恐ろしいのは「あるユーザの入力したスクリプトが別のユーザのブラウザで走る場合」そして「そのスクリプトが与えられている権限が、サイト全体の権限と同一で、一般的な権限よりも大きい場合」である。
それを避けるために「文字列をエスケープする」、つまり「タグやコードとして認識されるような部分を無理やり出力するべき文字として認識するようエスケープ記号などを挿入する」ことが重要になる。
そういえばこの話題はJoel on Softwareでも言及されてたな。
Let’s put it in pseudocode. Imagine that
s = Request("name")
reads input (a POST argument) from the HTML form. If you ever write this code:
Write "Hello, " & Request("name")
your site is already vulnerable to XSS attacks. That’s all it takes.
ということで、今までのHTMLベタ打ちなコードではこの脆弱性が存在していた。
前述の通りHiccupは自動的には文字列をエスケープしてくれない。
(html [:div "<script>alert()</script>"])
のようなコードは
"<div><script>alert()<script></div>"
と変換されてしまう。
ただしくエスケープするためには、Hiccupが提供しているh関数で文字列を変換してやる必要がある。例えば
(html [:div (h "<script>alert()</script>")])
だと結果は
"<div><script>alert()</script></div>"
で、ブラウザでもちゃんと文字列として表示されるようになる。
前回のコードで動的にユーザインプットを表示していたのはviews.cljのdisplay-result関数である。GET/POSTフォームのパラメータからnameを取り出してHTMLに埋め込んでいた。
(defn display-result [req] (let [{:keys [params uri]} req param-name (get params "name") req-type (if (= uri "/get-submit") "GET" "POST")] (html [:div [:h1 "Hello " (h param-name) "!"] [:p "Submitted via a " req-type " request."] [:p [:a {:href ".."} "Return to main page"]]])))
ちゃんと(h param-name)
となっているので、例えば入力された値が</h1><script>"Hello XSS!"</script><h1>
などというJavaScriptが含まれたものでも、ちゃんとウェブページ上で
Hello </h1><script>alert("Hello XSS!")</script><h1>!
と表示される。
ちなみに(h param-name)
をparam-name
にして再実行すると、Chromeの場合JavaScriptは実行されず、Console Logでこんなメッセージが出る:
The XSS Auditor refused to execute a script in 'http://localhost:8080/post-submit' because its source code was found within the request. The auditor was enabled as the server sent neither an 'X-XSS-Protection' nor 'Content-Security-Policy' header.
モダンブラウザは偉大である。が、とりあえずモダンじゃないブラウザもまだ稼働してそうなことだし、やはりXSS対策は開発者側のほうでできる限り対応するべきだろう。ということでh関数はユーザインプットを受け取るところではいたるところで使うべきだろう。
最後に参考文献的なものを。
Clojure Web Security全般についてはこの発表が面白かった。
このどちらかというと問題山積みな状況から二年半でClojureのセキュリティ関連のエコシステムがどう変わったのか、とても興味がある。Web開発の勉強を進めていく上で気をつけたいポイントである。
XSSそのものではないが、似たようなセキュリティ問題について:
セキュリティは大きな問題なので体系的かつ早期に学ぶことが望ましい(多分)。というわけでずっと積ん読していたこれをぼちぼち読んでいる。
いろんな脆弱性があるなぁ・・・(遠い目 ウェブ開発は大変である。
追記: このエントリ書いていて知ったんだけど、Hatena BlogってユーザインプットからJavaScript実行するのな・・・ Preview機能を使ったら突然Alertポップアップが開いて驚いた。(修正済み)
Clojure Web Development勉強 - Hiccup (その1)
今まで愚直にHTMLタグを文字列として書いてきたが、読むほうも辛かったと思うが書くほうはもっと辛かった。
まあ、ただこの時点では大して動的な機能もなく(唯一あったのはフォームから受け取った文字列を表示するくらい(ただしこれが「くらい」というには問題のありすぎることなのは後述する))、複雑なテンプレート機能を使う必要があったわけじゃないのも事実である。
ここから「すべてのページで共通の部分を表示させたい」「同じようなエレメントを複数回表示させたい」などといったニーズが生じていくにつれ、さらに「HTMLタグを文字列で」というアプローチは辛くなっていく。
ということで、より動的にHTMLを作成するテンプレートDSLを提供するライブラリHiccupを使ってみる。
HiccupはHTMLテンプレートとしてもかなり特殊で、Clojureの標準データ構造であるVectorとMapでタグとプロパティを表現する。
例えば
<div> <h1>Hello</h1> <p>My HTML document</p> </div>
はHiccupだと
[:div [:h1 "Hello"] [:p "My HTML document"]]
となる。
いちいち</h1>
と閉じなくて良くなった、というのはまあ小さなメリットである。より重要なメリットとして、Clojureデータ構造として表現されている以上、Clojureの豊富なデータ変換関数を使って非常に高い自由度で以って変換したり組み合わせたりすることが可能となっている。
Hiccupを使って今までのコードを書き直してみた。
基本的に変更はview.cljの部分。hiccup.core
からhtmlマクロとh関数を使っている。
htmlはClojureデータ構造で定義されたhtml構造を正しいHTML文字列に変換するhiccupの根本となるマクロ。
REPLで試してみるとこんな感じ:
user=> (use 'hiccup.core) nil user=> (html [:div #_=> [:h1 "Hello"] #_=> [:p "My HTML document"]]) "<div><h1>Hello</h1><p>My HTML document</p></div>"
インデントは(当然といえば当然)再現されない。
なのでview.cljに定義されている関数は今までどおりHTMLタグ入りの文字列を返している。関数内部の表記がhiccupなだけである。
user=> (ns simple-hiccup.views) nil simple-hiccup.views=> (get-form {}) "<div><h1>Hello GET Form!</h1><p>Submit a message with GET</p><form action=\"get-submit\" method=\"get\"><input name=\"name\" type=\"text\" /><input type=\"submit\" value=\"submit\" /></form><p><a href=\"..\">Return to main page</a></p></div>"
個人的には、ここまで使った範疇だけでもHTMLの記法としてかなり良くできていて、HTMLタグを打つより断然Hiccupで書きたいと感じているのだが、これは個人の好みの問題かもしれない。近いうちにhiccupのより動的な機能や再利用性の向上を例を挙げて書きたい。
しかし、次の話題はhiccup.core
から呼び出しているもう一つの名称であるh関数とXSS攻撃についてである。
Clojure Web Development勉強 - Compojure (その2)
ちょっと前回の説明で書いていなかったことを補足。
Compojureで使ったdefroutes
マクロとGET
やnot-found
といった個別のルート作成マクロ・関数の引数と戻り値について。
まず、GETやnot-foundは普通に関数を返す。返された関数のシグネチャは当然「リクエストマップを引数に受け取り、レスポンスマップを戻り値に返す」というハンドラの標準のものだ。
(def main-handler (GET "/" [] "Hello Main"))
というようにマクロを実行し、関数を作成して特定の名前に束縛することも当然できる。
(main-handler request)
と関数を実行すると、引数のrequestがマップでrequest-methodがgetでuriが"/"であれば
{:status 200, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Hello"}
こんな感じのマップが返ってくる。
ちなみに先ほど「引数のrequestがマップでrequest-methodがgetでuriが"/"であれば」と書いた。実際、引数がマップでこの二つのフィールドが存在しその値が正しければ、他のフィールドが何であれ(むしろ他のフィールドは存在しなくても)GET
で作った関数は普通に適用される。REPL上でやってみると
=> (main-handler {:request-method :get :uri "/"}) {:status 200, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Hello"}
といった按配である。
not-found
の返す関数はさらにゆるくて、どんな引数でも
=> ((not-found "Not Found") {:uri "/random-stuff" :request-method :get}) {:status 404, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Not Found"} => ((not-found "Not Found") {}) {:status 404, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Not Found"} => ((not-found "Not Found") nil) {:status 404, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Not Found"} => ((not-found "Not Found") "a") {:status 404, :headers {"Content-Type" "text/html; charset=utf-8"}, :body "Not Found"}
のように、同じ404エラーを返す。引数がマップである必要すらない。
ではdefroutes
の方はどうだろう。defroutes
の定義に使われる関数は基本的にリクエストマップを受け取ってレスポンスマップを返すわけだが、戻り値に関してdefroutes
の方で何らかの制限を設けているだろうか?
こういうルーティングをdefroutes
で定義してみる:
(defroutes routes (GET "/" [] "Main") (fn [req] (if (= (:uri req) "/test1") {:a "b"})) (fn [req] (if (= (:uri req) "/test2") {})) (fn [req] (if (= (:uri req) "/test3") "b")) (fn [req] (if (= (:uri req) "/test4") nil)) (not-found "Not Found"))
それで:uriを"/test1"から"/test4"まで投げてみると、戻り値はnil
でなければ何でもいいことがわかる。nil
の場合はマッチしなかったと看做されてさら次の関数(この場合はnot-found)から順次適用されることになる。
とまあGET
、not-found
、defroutes
と全てがかなり薄く単純なデータと関数をベースにラップしている機能だということがわかった。例えばバリデーションなどはやってないに等しい。そしてそれは例えばdefroutes
を使っているからといってGET
を使う必要は必ずしもなく、逆もまた然り、ということになる。
こういう簡単な関数を、推奨される方法はあるにしろある程度自由に組み合わせていってアプリを作成する、という文化はなかなか好きかもしれない。
Clojure Web Development勉強 - Compojure (その1)
さて、Ringに続いてClojure Web Developmentでメジャーなライブラリ/フレームワークであるCompojureを使ってみる。
ちなみにここらへんも含めてCompojureに関しては@_ayato_pさんにいろいろご教授いただいた。あとウェブ開発の資料も大変参考にさせていただいている。
@zehnpaard 出来ます(笑)
— お金稼ぐのに飽きた (@_ayato_p) 2016年10月25日
@zehnpaard この話はこれで一度書いたんですよ。https://t.co/V8mcFDKFGN
— お金稼ぐのに飽きた (@_ayato_p) 2016年10月25日
多謝。
Compojureはたまにフレームワークとして紹介されているが、現時点ではただのライブラリだと思う。とにかく責務がかなり限られている。が、限られているようで微妙に複数の異なることをやっていて、それが個人的にはちょっと鼻につく。
githubのドキュメントの最小例を見てみるとこんな感じになっている:
(ns hello-world.core (:require [compojure.core :refer :all] [compojure.route :as route])) (defroutes app (GET "/" [] "<h1>Hello World</h1>") (route/not-found "<h1>Page not found</h1>"))
こんな小さな例なんだから:refer :all
じゃなくて[defroutes GET]
してほしいとか思ったり思わなかったり。とにかくこのGET
やらroute/not-found
やらで「requestがこんなパターンにマッチしたらこういうresponseを返す」というロジックを作り、それらをまるでcond
のようにつなげているのがdefroutes
。ちなみにdefroutes
とGET
はマクロ、route/not-found
は関数。
今まで書いてきたようにringでこれを実現するとしたら:
(ns hello-world.core (:require [ring.util.response :as res])) (defn app [req] (cond (= (:uri req) "/") (-> (res/response "<h1>Hello World</h1>") (res/content-type "text/html")) :else (-> (res/not-found "<h1>Page not found</h1>") (res/content-type "text/html"))))
という形になるので、確かにCompojureを使うと綺麗になっていて書きやすい。
気をつけないといけないのは、GETが同時に二つのことをしている点で、
- uri(以外にもparameterなどでもできる)でパターンマッチングをしている
- ring.util.responseのように文字列を装飾してレスポンスマップの形式に変換している
と、一粒で二度美味しいとみるか、責務が違うものが同じマクロで同時に展開されていてちょっと分かり難いとみるか。
個人的には、ここにさらにuriとparameterのdestructuringでローカル変数束縛の機能が入ると、ちょっと異なる責務が同じマクロに集中しすぎている印象を持ってしまう。
それを踏まえて、前回のコードをcompojureを使った仕様で書いてみた:
前回handler.clj
と呼んでいたファイルをroutes.clj
とviews.clj
に分割。viewsにはリクエストマップを受け取り(not-foundの場合は引数なし)、レスポンスのbodyとなるテキストファイルを返す関数を集めてある。routesが今回追加のcompojureの部分で、ライブラリで定義されているdefroutes
、GET
、POST
、not-found
とviews.clj
の関数を使って最終的なringハンドラ関数app
を作っている。
これをcore.clj
でmiddlewareに包んでringのjetty adapterに渡しているのは従来の通り。
現在の悩みどころとしては、views.clj
のほぼ全ての関数をreqをとってresを返すというシグネチャに統一しているデザインでいいかどうか。特に、display-result
の引数をより分割されたものにして、ルーティングのGET
マクロが持つdestructuring機能をより有効に活用すべきかどうか。
(defn display-result [req] (let [{:keys [params uri]} req param-name (get params "name") req-type (if (= uri "/get-submit") "GET" "POST")] (str "<div> <h1>Hello " param-name "!</h1> <p>Submitted via a " req-type " request.</p> <p><a href='..'>Return to main page</p> </div>")))
見ての通り、現在だとdestructuringはこのviewの関数の中で全てやっている。個人的にはこっちの方がいい気がするのだが、いろいろ書いているうちに意見が変わるのかもしれない。
次回はHiccupというClojure特有のHTML Templateライブラリを使って、HTML文字列を直接コードに書き込まずに済むようにしたい。