1. remore

    Posted

    remore
Changes in title
+RubyからPythonやJuliaを呼び出す方法(実験的な実装)
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,91 @@
+RubyからPythonやJuliaのパッケージを呼び出せる`virtual_module`というgemを作っています。下記の例では、いくつかのコマンドのmanpageをドキュメントとして読み込むところまでをRubyで書いて、doc2vecの処理にかける部分はPythonを呼び出してgensimに任せるということをやっています。
+
+```ruby:doc2vec.rb
+require 'natto'
+manpages={}
+natto = Natto::MeCab.new
+%w"ps ls cat cd top df du touch mkdir".each do |cmd|
+ list = []
+ natto.parse(`man #{cmd} | col -bx | cat`) do |n|
+ list << n.surface
+ end
+ manpages[cmd] = list
+end
+
+require 'virtual_module'
+py = VirtualModule.new(:methods=><<EOS, :python=>["gensim"])
+class LabeledListSentence(object):
+ def __init__(self, words_list, label_list):
+ self.words_list = words_list
+ self.label_list = label_list
+
+ def __iter__(self):
+ for i, words in enumerate(self.words_list):
+ yield gensim.models.doc2vec.LabeledSentence(words, [self.label_list[i]])
+
+EOS
+model = py.gensim.models.doc2vec.Doc2Vec(py.LabeledListSentence(manpages.values, manpages.keys), min_count:0)
+p model.docvecs.most_similar(["ps"]) # [["top", 0.5594387054443359], ["cat", 0.46929454803466797], ["df", 0.3900265693664551], ["mkdir", 0.38811227679252625], ["du", 0.23663029074668884], ["ls", 0.15436093509197235], ["cd", -0.1965409815311432], ["touch", -0.38958919048309326]]
+```
+
+これを使って自分のブログ(Sinatra製)にこのRubyで動くdoc2vecを使った関連記事抽出の機能を追加したのですがちょっと便利でした。僕以外に嬉しくなれる人がどれだけいるか分かりませんが、(かなり不自由ですが一応)scikit-learnも使えるようになりますし活用の仕方次第では面白いと思うので、想定する使い方などを書いていきます。
+
+なお、doc2vec以外にscikit-learn使った例とかを[個人のブログの方にまとめてある](http://rimuru.lunanet.gr.jp/notes/post/handling-doc2vec-or-scikit-learn-from-ruby/)ので気になる人がいたらそちらもどうぞ。
+
+## REPLを使ったVirtualModuleの動作の簡単な紹介
+
+ここではREPLを使ってVirtualModuleが内部的にどのように動いているのかを書いてみようと思います。下記が既にシステム上にインストールされていることを想定しています:
+
+- `virtual_module` gem (v0.3.0以上)
+- Pythonの実行環境
+
+まずirbを立ち上げます。
+
+```ruby
+debussy:~ remore$ irb -r virtual_module
+irb(main):001:0> po = VirtualModule.new(:python=>["sklearn"=>"datasets"])
+=> #<Module:0x007fb7e1aee818>
+```
+
+`VirtualModule#new`を呼び出すと、裏側でPython(またはJulia)のプロセスが起動します。バックグランドジョブが正しく起動し終わると、VirtualModuleは新しいModuleインスタンスを返却します。今後はこのModuleインスタンスを介して(≒このインスタンスがプロキシのように動作することで)バックグラウンドとの通信を行っていきます。ここでは簡便のために、これをプロキシオブジェクトと呼ぶことにします。
+
+```ruby
+irb(main):002:0> py.int(2.3)
+=> 2
+irb(main):003:0> po.unknown_method(2.3)
+RuntimeError: An error occurred while executing the command in python process: ,name 'unknown_method' is not defined
+```
+
+プロキシオブジェクトの動作は非常に単純です。上記の例では、プロキシオブジェクトは`int(2.3)`というメソッド呼び出しを受取、これをそのままバックグラウンドジョブに伝えます(この時、msgpackを使って値の変換を行います)。結果としてFixnum型の値である`2`がターミナルに出力されていますが、これはバックグラウンドジョブから返却されたものです。データの変換はmsgpackを使っているだけなので、相互に変換できる値も[msgpackの仕様](https://github.com/msgpack/msgpack/blob/master/spec.md)に準じます。もし`po.unknown_method(2.3)`の例のようにバックグラウンドジョブ側で未定義のメソッドが呼び出されると、エラーが表示されます。基本的には以上がVirtualModuleの動作の全てです。
+
+これだけだと腑に落ちないところもあると思うので、もう少し書き加えます。
+
+```ruby
+irb(main):004:0> po.datasets
+=> #<Module:0x007ffd0906c030>
+irb(main):005:0> po.datasets.load_iris(:_)
+=> #<Module:0x007ffd09074500>
+irb(main):006:0> po.datasets.load_iris(:_).vclass
+=> "<class 'sklearn.datasets.base.Bunch'>"
+irb(main):007:0> po.datasets.load_iris(:_).data[1].to_a
+=> [4.9, 3.0, 1.4, 0.2]
+```
+
+実際msgpackで変換できない値が利用されてるとどう動作するかについては、こちらの例を参照してください。この例では初めにプロキシオブジェクト(ここでいうローカル変数の`po`)が`#datasets`メソッドの呼び出しに対して新しいプロキシオブジェクト(`#<Module:0x007ffd0906c030>`)を返していますが、その後の`#load_iris(:_)`も別のプロキシオブジェクト(`#<Module:0x007ffd09074500>`)を返しています。datasetsはPython上でmodule型のオブジェクトですし、load_iris(:_)は`'sklearn.datasets.base.Bunch'`クラスのインスタンスということでどちらもmsgpackを経由して変換不能なため、Moduleインスタンスが生成されています。このようにmspgackが変換できないような呼び出しに対しては、VirtualModuleには値の実体は渡されることがなく、その値へのポインタのみが渡されるような内部動作をしています。
+
+```ruby
+irb(main):008:0> po.datasets.vclass
+=> "<type 'module'>"
+irb(main):009:0> iris = po.datasets.load_iris(:_)
+=> #<Module:0x007ffd09057568>
+irb(main):010:0> iris.target.vclass
+=> "<type 'numpy.ndarray'>"
+irb(main):011:0> iris.target.vmethods
+=> ["T", "__abs__", "__add__", "__and__", "__array__", "__array_finalize__", "__array_interface__", "__array_prepare__", "__array_priority__", "__array_struct__", "__array_wrap__", "__class__", "__contains__", "__copy__", "__deepcopy__", "__delattr__", "__delitem__", "__delslice__", "__div__", "__divmod__", "__doc__", "__eq__", "__float__", "__floordiv__", "__format__", "__ge__", "__getattribute__", "__getitem__", "__getslice__", "__gt__", "__hash__", "__hex__", "__iadd__", "__iand__", "__idiv__", "__ifloordiv__", "__ilshift__", "__imod__", "__imul__", "__index__", "__init__", "__int__", "__invert__", "__ior__", "__ipow__", "__irshift__", "__isub__", "__iter__", "__itruediv__", "__ixor__", "__le__", "__len__", "__long__", "__lshift__", "__lt__", "__mod__", "__mul__", "__ne__", "__neg__", "__new__", "__nonzero__", "__oct__", "__or__", "__pos__", "__pow__", "__radd__", "__rand__", "__rdiv__", "__rdivmod__", "__reduce__", "__reduce_ex__", "__repr__", "__rfloordiv__", "__rlshift__", "__rmod__", "__rmul__", "__ror__", "__rpow__", "__rrshift__", "__rshift__", "__rsub__", "__rtruediv__", "__rxor__", "__setattr__", "__setitem__", "__setslice__", "__setstate__", "__sizeof__", "__str__", "__sub__", "__subclasshook__", "__truediv__", "__xor__", "all", "any", "argmax", "argmin", "argpartition", "argsort", "astype", "base", "byteswap", "choose", "clip", "compress", "conj", "conjugate", "copy", "ctypes", "cumprod", "cumsum", "data", "diagonal", "dot", "dtype", "dump", "dumps", "fill", "flags", "flat", "flatten", "getfield", "imag", "item", "itemset", "itemsize", "max", "mean", "min", "nbytes", "ndim", "newbyteorder", "nonzero", "partition", "prod", "ptp", "put", "ravel", "real", "repeat", "reshape", "resize", "round", "searchsorted", "setfield", "setflags", "shape", "size", "sort", "squeeze", "std", "strides", "sum", "swapaxes", "take", "tobytes", "tofile", "tolist", "tostring", "trace", "transpose", "var", "view"]
+irb(main):012:0> iris.target.to_a
+=> [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
+```
+
+Rubyでは`Object#class`や`Object#methods`でオブジェクトの様々な状態の情報を取得することができますが、VirtualModuleもこれにならって似たようなメソッド(`#vclass`と`#vmethods`)を用意しています。なんとなく想像できるかもしれませんが、`#vclass`はその値の型をバックグラウンドジョブに訪ねて返し、`#vmethods`はそのオブジェクトについて利用可能なメソッドを返します。
+
+今のところ用意している解説は以上ですが、もし更に他の例も見たい人がいれば[GitHubにいくつか他の例も置いてある](https://github.com/remore/virtual_module/tree/master/example)ので参照可能です。実験的な実装なので大分使いづらいところも多いと思いますが、もし使いたい人がいたら使ってみた感想など教えてもらえると嬉しいです。