CythonでRRLを実装
目次
はじめに
前回の記事では、再帰型強化学習(RRL)を使ってFXトレードしたらどうなるか試してみました。
Pythonで作ったコードでは、学習に非常に時間がかかってしまい、パラメータの最適化ができなくて困ってしまいました。
今回は前々から気になっていた爆速Cythonを試してみたいと思います。
前回Pythonで作成したRRLのコードをCython化してみようという試みです。
準備するもの
Cythonは、ほぼ文法がPythonなCythonスクリプトを自動でCに書き換えてくれます。
Cythonが自動で書き換えたCコードをコンパイルする必要がある為、事前にCのコンパイラが必要になってきます。
コンパイルされたバイナリファイルをPythonからモジュールとしてimportして使うという流れです。
Windows環境ではMinGWでgccを入れるのがオススメみたいです。
こちらのページを参考にさせていただきました。
MinGWのページに行って右上にダウンロードボタンがあるのでそこからインストーラをダウンロードします。
一応インストーラのリンクを貼っておきます。
MinGWのインストールは、全部Yesで特に問題なく入ると思います。
インストール後は、MinGWの管理画面が自動で立ち上がります。(デフォルトでは、C:\MinGW\libexec\mingw-get\guimain.exe
が管理画面表示のプログラムです。)
右上のリストのmingw32-base
とmingw32-gcc-g++
にチェックを入れて、左上のツールバー⇒Instlation⇒Apply Changesでgccがインストールされます。
gccのインストール後は、C:\MinGW\bin
にパスを通してやります。
Cython自体はpipで入れてやります。
pip install -U cython
また、CythonのコンパイラがMinGWのgccであることを設定するため、C:\Python27\Lib\distutils
にdistutils.cfg
を作成して以下を記述します。
[build] compiler = mingw32 [build_ext] compiler = mingw32
Linux環境であればgccは既に入っていると思うので、MinGWやdistutils.cfg
の設定はいらないです。
テストコード
速くしたい部分は基本的にはforループになります。
numpyの関数を使って(例えばdotでベクトルの内積等)演算する分には、既に高速化されているみたいなのでCython化してもあまり速くならないみたいです。
Pythonで書いたクラスとCythonで書いた全く同じクラスを実装したモジュールをテスト用に作ってみました。
以下Pythonでfor文を回し、累積和を求めるスクリプト(py_test_sum.py)です。
上と全く同じものをCythonで書き換えたスクリプト(cy_test_sum.pyx)が以下になります。
Python⇒Cython書き換えのポイントです。
- Cython用のnumpyをcimport (2行目)
- Cythonをcimport (3行目)
- np.float64_t をDOUBLE_tとしてctypedef (5行目)
- クラス定義の前にcdefを付ける (7行目)
- クラス内のメンバ変数をクラスの最初にcdef宣言する (9,10行目)
- 変数は使う前にcdefで宣言する (17行目)
- for文内のnumpy配列は別途cdefで(データ型と配列次元)を宣言した物を使う (18行目)
ちょっとめんどくさいですが、for文で回す前にメンバ変数を別途cdefで宣言した変数に入れて、for文を回した後はまた戻します。(19,22行目)
Cythonのファイルは、.pyxという拡張子にしておきます。
Cythonファイルのコンパイル
cy_test_sum.pyx
ファイルをコンパイルする為にsetup.py
を用意する必要があります。以下コンパイル用のsetup.py
になります。
9行目のExtention(
の次はスクリプトと同じ名前のcy_test_sum
を入れておき、sources=[" "]
には、cy_test_sum.pyx
を指定しておきます。
以下のコマンドでコンパイルを実行します。
python setup.py build_ext -i
コンパイルに成功すると、cy_test_sum.c
とcy_test_sum.pyd
ファイルが出来上がります。
cy_test_sum.c
の方はCythonがcy_test_sum.pyx
内のコードをCのコードに書き換えた物で、cy_test_sum.pyd
はそのCコードをgccでコンパイルしたライブラリファイルになります。
Pythonスクリプト内でcy_test_sum.pyd
ファイルをモジュールとしてimportして使えます。
実行スピード比較
以下実行スピード比較用のスクリプトになります。
Pythonモジュール(py_test_sum.py
)とCythonモジュール(cy_test_sum.pyd
)を読み込んで、5行目のn_iter = 10000000
までの累積和を計算して、それぞれ実行スピードを出力します。
実行結果です。
Python Sum: 4.9999995e+13. Elapsed time: 13.8376375448 sec. Cython Sum: 4.9999995e+13. Elapsed time: 0.139662261603 sec.
すごいですね。Cythonは100倍近く速いですね!
RRLをCythonで実装
では、前回Pythonで書いたRRLのスクリプトをCython化して見ようと思います。
Cythonスクリプト
Cythonでコンパイルする部分は、RRLエージェントを実装した、TradingRRLクラスの記述になります。
メンバ変数を全部cdefする必要はないと思いますが、機械的に全部やってしまいました。
基本的にfor文を回しているのは、とを求める111行目のset_x_F()
と、を求める149行目のcalc_dSdw()
なので、その部分だけCython化してあります。
2~4行目の記述はよくわかりません。これをすると速くなるらしいので取りあえずやっときました。
#cython: boundscheck=False #cython: wraparound=False #cython: nonecheck=False
setup.py
以下がコンパイル用のsetup.py
です。
上記のコードをtradingrrl.pyx
という名前で保存して、python setup.py build_ext -i
でコンパイルできます。
学習実行スクリプト
では、早速学習を実行してみましょう。
Cython実装のtradingrrl.pyd
をインポートして学習を実施します。前回のPython実装コードのmain関数部分そのままです。
学習に使用するUSDJPYデータはここからダウンロードしてUSDJPY30.csvという名前で一緒のフォルダに入れてください。
学習結果
前回同様10000回のエポックを回しており、100エポック毎にシャープレシオと経過時間を出力する様にしています。
Epoch loop start. Initial sharp's ratio is -0.0135567256889. Epoch: 100/10000. Shape's ratio: 0.0343913902492. Elapsed time: 1.82266770672 sec. Epoch: 200/10000. Shape's ratio: 0.0554093410986. Elapsed time: 3.60496513464 sec. ... ... Epoch: 9900/10000. Shape's ratio: 0.165284734361. Elapsed time: 177.458381119 sec. Epoch: 10000/10000. Shape's ratio: 0.164346202522. Elapsed time: 179.214963948 sec. Epoch loop end. Optimized sharp's ratio is 0.164346202522. Epoch: 10000/10000. Shape's ratio: 0.164346202522. Elapsed time: 179.215753807 sec.
前回のPython実装での結果が1321秒なので、179秒だと7.4倍ぐらい速くなりましたが、、
思ったより速くなってない。。
基本的にset_x_F()
とcalc_dSdw()
以外の関数では、numpy演算しかしていないのでいじりようがないのですが、どこか律速になっている部分が他にもあるんでしょうか?
ん~。いずれにせよ179秒だとまだ時間がかかりすぎですね。いっそCで書いてみようかな。