Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 3 years have passed since last update.
はじめに
PyCallは mrkn さんが開発されている、Python のライブラリを Ruby や Julia で利用するためのブリッジライブラリです。
https://github.com/mrkn/pycall.rb/
PyCallの解説やインストール方法は mrkn さん始め諸賢のドキュメントが多く有りますので、ここでは私がRuby版PyCallを使ってきてのTipsを書いていきます。(2018年2月時点の最新版 PyCall 1.0.3 に基づく)
Tips
Python ライブラリのロード
Python のライブラリを使う場合、次の2行を書いておく
require'pycall/import'includePyCall::ImportPython のライブラリは次のようにロードする
# Pythonだと、 import matplotlibpyimport:matplotlibpyimport'matplotlib'# 上ではシンボルを使ったが、文字列でも同じ# Python だと import matplotlib.pyplot as pltpyimport'matplotlib.pyplot',as: :pltplt=PyCall.import_module('matplotlib.pyplot')# 上はこのような記法でも同じ# Python だと from janome.tokenizer import Tokenizerpyfrom'janome.tokenizer',import: :Tokenizer# Python だと from keras.layers import Dense, Dropoutpyfrom'keras.layers',import:['Dense','Dropout']全般的に気をつけること
- Pythonから返ってきたオブジェクトの型には気をつけましょう。 - PythonのいろいろなライブラリからRubyに戻されるオブジェクトには、一見数値やArrayやHashのように思えるものが有りますが、実は違っている場合が有ります。
# RubyのArrayといくつかのArray風のオブジェクトの違いを調べますr_array=[0.0,1.0,2.0]# RubyのArray(配列)を作るp_list=PyCall.eval("[0.0, 1.0, 2.0]")# Python のリストを作るrequire'numpy'# numpy は配列を扱うPythonの定番ライブラリnp_array=Numpy.array([0.0,1.0,2.0])# numpy のarrayを作るpyfrom('collections',import: :deque)# dequeはappendやpopを高速に行えるリスト風のコンテナp_deque=deque.([0.0,1.0,2.0])# deque を作る# 上で作ったArray風のオブジェクトが [] でアクセスできることを確認します。またクラスを調べますpr_array[1],r_array.class# -> 1.0, Arraypp_list[1],p_list.class# -> 1.0, <class 'list'>pnp_array[1],np_array.class# -> 1.0, <class 'numpy.ndarray'>pp_deque[1],p_deque.class# -> 1.0, Object# Array風のオブジェクトで each が使えるかどうかの確認r_array.each{|x|px}# OKp_list.each{|x|px}# OKnp_array.each{|x|px}# OKp_deque.each{|x|px}# エラー! PyCall::List.(p_deque)とすればリストにキャストできる。でも計算コストを考えると高速性がウリのdequeをリストにする意味はないだろう# Array風のオブジェクトで delete_if が使えるかどうかの確認r_array.delete_if{|x|x>1.5}# OKp_list.delete_if{|x|x>1.5}# エラー! - p_list.to_a とすればArrayにキャストできるnp_array.delete_if{|x|x>1.5}# エラー! - PyCall::List.(np_array).to_aとすればArrayにキャストできるp_deque.delete_if{|x|x>1.5}# エラー! - PyCall::List.(p_deque).to_aとすればArrayにキャストできる- Pythonオブジェクトに渡す引数の()の前にはピリオドを入れますが、よく使うオブジェクトでは不要です。
("よく使う" というのはPythonの基本的なオブジェクトやnumpy のようにPyCall側でラッパーメソッドが用意されているもの、という意味です)
ピリオドが抜けた場合、"ArgumentError" か "NoMethodError" が返ってきます。
# 引数の()の前にピリオドを入れる例pyfrom'collections',import: :dequep_deque=deque.([0.0,1.0,2.0,3.0])# deque と ( の間に . を入れる# 引数の()の前にピリオドを入れない例require'numpy'a=Numpy.array([1,2,3],dtype=np.float32)# array と ( の間に . を入れないNumpy の使用
numpyは PyCall と同じ mrkn さんの作成によるgemがあります。 gem install numpy でインストールしましょう。
Rubyプログラムから使う時は、
require'numpy'np=Numpya=np.array([1,2,3],dtype=np.float32)などとして使用します。 pyimport :numpy, as: :np とするより良いです。
# pyimport で numpy をロードする時の落とし穴pyimport:numpy,as: :np# これでも良いように思えるが...a=np.array([1,2],dtype=np.float32)b=a[0]+a[1]pb# 3.0 が表示される。当たり前じゃないかpb.class# Object と表示される。 float じゃないんだp"bは3だよ"ifb==3.0# "bは3だよ" と表示される。当たり前じゃないかp"bは3だよ"ifb==5.0# "bは3だよ" と表示される!? why!? (b==5.0) は false じゃなくFalseだから。Rubyはfalseとnil以外は真ですnumpy の : は 代わりに範囲演算子 .. / ... を使います
# 配列へのアクセス例m=np.arange(0,100).reshape(10,10)#まずnumpyの配列を作ります=>array([[0,1,2,3,4,5,6,7,8,9],[10,11,12,13,14,15,16,17,18,19],[20,21,22,23,24,25,26,27,28,29],...[90,91,92,93,94,95,96,97,98,99]])# 特定の行、列へのアクセスm[2,1]=>21# m が RubyのArrayだったら [20, 21, ... 28, 29] となる# 最後の行へのアクセス pythonだと m[-1,:] と書くところm[-1]=>array([90,91,92,93,94,95,96,97,98,99])# 最後の列へのアクセス pythonだと m[:,-1] と書くところm[0..-1,-1]=>array([9,19,29,39,49,59,69,79,89,99])# 部分へのアクセス python だと m[5:7,2:4] と書くところm[5...7,2...4]=>array([[52,53],[62,63]])# python のスライスを使うこともできます# 6行目から最後の行までの偶数行の、3列目から5列目までを選択する例sl=PyCall::Slice.(6,-1,2)# 6行目から最後の行まで2行ごとに行を選択するためのスライスを作成m[sl,3..5]=>array([[63,64,65],[83,84,85]])Pythonモジュールを読むディレクトリの指定
PythonがデフォルトでサーチしないディレクトリにPythonモジュールが書かれたファイルを置く場合、次のような感じで置いたディレクトリを指定する
PyCall.sys.path.append(__dir__)# Rubyプログラムと同じディレクトリにある場合hoge=PyCall.import_module('hoge')PyCall.sys.path.append('/home/boo/foo')# ディレクトリを指定する場合woo=PyCall.import_module('woo')Ruby のクラスの中で PyCall を使う
クラスの中で、あるメソッドに "pyimport :tensorflow, as: :tf" のように書いても "tf" は別のメソッドの名前空間には存在しない。
次の2つの例のいずれかが使える
classBoomodulePyextendPyCall::Importpyimport:tensorflow,as: :tfend...tf=Py.tf# 記法1 クラスの中で"tf"のインスタンスをたくさん作るならこの記法が見やすい?...endclassFoo...tf=PyCall.import_module(:tensorflow)# 記法2...endRuby プログラムの中に Python の関数を作る
2種の記法例で、引数を倍にする関数を示します
#記法 1PyCall.exec(<<PYTHON)def double(object): return object * 2PYTHONa=5pPyCall.eval("double(#{a})")# -> 10c="'!!'"pPyCall.eval("double(#{c})")# -> "!!!!"#記法 2double=PyCall.eval(<<PYTHON) lambda object: object *2PYTHONa=5pdouble.(a)# -> 10c='!!'pdouble.(c)# -> "!!!!"Ruby プログラムの中に Python のクラスを作る
次のサイコロのクラスを参考にしてください。PyCall.exec(script) はPythonのscript を実行します。
でもRuby プログラムの中に大きなクラスを作るのはデバッグが面倒なのでおすすめしません。大きなPythonのクラスを作る時は別ファイルに作って、Pythonでデバッグするのが良いと思います。
PyCall.exec(<<PYTHON)import randomclass Die(): def __init__(self,x): # サイコロを作るよ if x == 0: self.val = self.roll() # 0 が与えられたら最初の目はランダムに決める else: self.val = x # 最初の目を指定された値にする def roll(self): self.val = random.randint(1,6) # サイコロを転がすよ return self.val def look(self): return self.val # サイコロの目を確認するよ def change(self, x): self.val = x # サイコロの目を指定された値にするよ return self.valPYTHONdie=PyCall.eval('Die').(4)# PyCall.eval('Die').new(4) とも書けるdie.look# -> 4die.roll# -> 1i=5die.change(i)# -> 5set
Python のsetオブジェクトに対する演算子、 &, |, -, ^ , > をRubyでも使うことができる
# Python の set オブジェクトを作るset1=PyCall.eval("{1,2,3,4}")set2=PyCall.eval("{0,2,4,6}")# set オブジェクトの演算例set1&set2# -> {2, 4}set1|set2# -> {0, 1, 2, 3, 4, 6}set1-set2# -> {1, 3}set1^set2# -> {0, 1, 3, 6}set1>set2# -> falseTensorFlow Eager Execution
TensorFlow の バージョン1.5からデフォルトで使えるようになったEager Executionは python コードの冒頭に次のようなおまじないを書きます。
# Eager Execution を使う python のコード冒頭部importtensorflowastfimporttensorflow.contrib.eagerastfetfe.enable_eager_execution()私には理屈がわからないのですが、これを単純にpyimportに置き換えてもrubyでは動きません。次のように書く必要があるようです。
# Eager Execution を使う ruby のコード冒頭部pyimport:tensorflow,as: :tftf.contrib.eager.enable_eager_execution()余談になりますが、 Eager Execution を使うとグラフを動的に変えることができるし、irbやpryのデバッグ中にtensorflow オブジェクトの中身を簡単に確認できてよさげです。
# Eager Execution でtensorflow オブジェクトを操作する例x=[[1.0,-1.0],[-1.0,1.0]]y=[[0.25,0.5],[0.75,1.0]]# xとyはrubyのArraytm=tf.nn.tanh(tf.matmul(x,y))# rubyのArrayを何も考えずにtensorflowの計算に使えるptm# 結果は随時確認できる# <tf.Tensor: id=8, shape=(2, 2), dtype=float32, numpy=array([[-0.46211717,-0.46211717],[ 0.46211717, 0.46211717]], dtype=float32)>numpy_m=tm.numpy()# numpy へのキャストも簡単with
PyCall.with() を使う
# Python の io.open と with を使って、ファイルの各行を空行が出てくるまで読む例a=[]# ファイルの行を格納するArrayfile_name='hoge.txt'PyCall.with(io.open(file_name,"r")){|fp|# fp は Pythonのファイルポインタですwhile(line=fp.readline())!=""# readline()はPythonの io::readline ですa<<lineend}# with を使ってるのでファイルをcloseしなくて良いキーワード引数
キーワードで引数をわたす Pyrhon のメソッドには、ハッシュで引数を渡す
# Python では w2v.Word2Vec(st, size=200, sg=1) と書くところw2v.Word2Vec.(st,{size:200,sg:1})キャスト 型変換
Python のオブジェクトから Ruby のオブジェクトへ
- タプルから: to_a で RubyのArray(配列)が作られる
- リストから: to_a で RubyのArray(配列)が作られる
- 辞書(dict)から: to_h で Rubyのハッシュが作られる
Ruby のオブジェクトを Pythonの タプル、リスト、辞書へ
- タプルへ: PyCall.eval("tuple(hoge)") もしくは PyCall::Tuple.(hoge)
- リストへ: PyCall.eval("list(hoge)") もしくは PyCall::List.(hoge)
- 辞書(dict)へ: PyCall.eval("dict(hoge)") もしくは PyCall::Dict.(hoge)
# numpy オブジェクトを Python の リストにする例PyCall::List.(np_array)# numpy オブジェクトを Ruby の Array にする例PyCall::List.(np_array).to_a- Ruby から Python のオブジェクトを作る
PyCall::List.([0,1,2])# Ruby の Array から Pythonのリストを作るPyCall::Tuple.([0,1,2])# Array から タプルを作るPyCall::Dict.({zero:0,one:1,two:2})# ハッシュから 辞書(dict)を作る# Python の Slice オブジェクトの作成と使用例sl=PyCall::Slice.(1,3,nil)# slice(1,3,None) ができる。 nilは省略可np.array([0,1,2,3,4],dtype=np.float32)[sl]# -> array([1., 2.], dtype=float32)辞書(dict)にイテレータを使う
each は辞書で使える。使えないイテレータは to_h して使う
python_dict.to_h.delete_if{|k,v|v>1.5}タプルにイテレータを使う
タプルに each や inject や zip みたいなRubyのArrayのメソッドが使いたい!って時は、to_a でArrayにする
python_tuple.to_a.each{|x|px}タプルの戻り値
Python のオブジェクトがタプルで戻り値を返す場合、to_a で配列に変換して受け取る
a,b=pythonObj.tuple_return_method.(arg).to_a内包表記を使う
PyCall.eval() を使う
size=3x=PyCall.eval("[ i*2 for i in range(#{size}) ]")# -> [0,2,4]# ここで x は to_a しないとRubyのArrayではなくPythonのリストであることに注意Ruby と Python を結びつけるその他のアプローチ
メインメモリ上にデータベースを作成して Python, Ruby, R などの言語で共用する、 Apache Arrow プロジェクトがあります。
- https://arrow.apache.org/ :公式
- https://www.slideshare.net/kou/datasciencerb :プロジェクトメンバーの須藤功平さんによるスライド
PyCallとは逆にPythonからRubyのメソッドを使うためのライブラリとして、yohmさんによる rb_call があります。
なぜPyCall
tensorflow を使うため初めて Python に触れ、tensorflow みたいなフレームワークには Python がぴったりだよな、と思いつつ Python でいろいろプログラムを書いてみるものの、 Ruby のほうが書いてて楽しいし、慣れてるせいもあって効率も良い。機械学習のプログラムを書くと言っても、tensorflow のグラフまわりのコードは全体の1割か2割位で、データを収集したり加工するコードのほうが遥かに多いんだ、ということが判って私は機械学習のプログラムを基本は Ruby で書いて tensorflow まわりのコードだけ Python で書くことにしました。でもその場合 Ruby と Python での間でデータを どのようにやり取りするかが問題だよな。 という経緯で PyCall を使ってみたのですがなかなか具合が良いです。
謝辞
素晴らしいライブラリをご提供くださっている mrkn さんに改めて感謝の意を表します
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme
