Pythonと機械学習

Pythonも機械学習も初心者ですが、頑張ってこのブログで勉強してこうと思います。

CythonでRRLを実装

目次

はじめに

前回の記事では、再帰型強化学習(RRL)を使ってFXトレードしたらどうなるか試してみました。

Pythonで作ったコードでは、学習に非常に時間がかかってしまい、パラメータの最適化ができなくて困ってしまいました。

今回は前々から気になっていた爆速Cythonを試してみたいと思います。

前回Pythonで作成したRRLのコードをCython化してみようという試みです。

準備するもの

Cythonは、ほぼ文法がPythonなCythonスクリプトを自動でCに書き換えてくれます。

Cythonが自動で書き換えたCコードをコンパイルする必要がある為、事前にCのコンパイラが必要になってきます。

コンパイルされたバイナリファイルをPythonからモジュールとしてimportして使うという流れです。

Windows環境ではMinGWgccを入れるのがオススメみたいです。

こちらのページを参考にさせていただきました。

MinGWのページに行って右上にダウンロードボタンがあるのでそこからインストーラをダウンロードします。

f:id:darden:20170314200609p:plain

一応インストーラのリンクを貼っておきます。

MinGWのインストールは、全部Yesで特に問題なく入ると思います。

インストール後は、MinGWの管理画面が自動で立ち上がります。(デフォルトでは、C:\MinGW\libexec\mingw-get\guimain.exeが管理画面表示のプログラムです。)

右上のリストのmingw32-basemingw32-gcc-g++にチェックを入れて、左上のツールバー⇒Instlation⇒Apply Changesでgccがインストールされます。

gccのインストール後は、C:\MinGW\binにパスを通してやります。

Cython自体はpipで入れてやります。

pip install -U cython

また、CythonのコンパイラMinGWgccであることを設定するため、C:\Python27\Lib\distutilsdistutils.cfgを作成して以下を記述します。

[build]
compiler = mingw32

[build_ext]
compiler = mingw32

Linux環境であればgccは既に入っていると思うので、MinGWdistutils.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.ccy_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文を回しているのは、{\mathbf{x_t}}{F_t}を求める111行目のset_x_F()と、{\frac{dS_T}{d\mathbf{w}}}を求める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で書いてみようかな。

参考にさせて頂いたサイト