Pythonの計算を高速化するためにCythonに書き換えている。このとき、numpy配列の扱いにつまずいたのでメモした。
環境
ubuntu 18.04 LTS
jupyter lab 0.35.4
python 3.7.5
CUIでCythonをやるよりも、jupyterの方が楽だと思っている。setupファイルを作らなくていいので。
Cythonでnumpyを使おうとする
まず、jupyter上では
%%cython
をつける。次に下準備。
import numpy as np
cimport numpy as np
cimport cython
numpy配列を使うために、cのtypedef宣言と同じようなことをしてから、配列を作る。ここでは、次元3のaという配列を作る。
ctypedef np.float64_t DTYPE_t
cdef np.ndarray[DTYPE_t, ndim=3] a
問題点
ここまでが、色々なサイトを見ると書いてあることだが、このまま実行すると以下の様なエラーが出る。
Error compiling Cython file:
------------------------------------------------------------
...
cimport cython
ctypedef np.float64_t DTYPE_t
ctypedef np.int_t INT_t
cdef np.ndarray[DTYPE_t, ndim=3] a
^
------------------------------------------------------------
/home/qcmp/.cache/ipython/cython/_cython_magic_043176e78a21b81e31740451e1fe277a.pyx:9:33: Buffer types only allowed as function local variables
Error compiling Cython file:
------------------------------------------------------------
...
import numpy as np
^
------------------------------------------------------------
/home/qcmp/.cache/ipython/cython/_cython_magic_043176e78a21b81e31740451e1fe277a.pyx:2:0: Buffer vars not allowed in module scope
モジュールレベルの変数を使えず、ローカル変数だったら使えるという。
確かに、以下のようなローカル変数は、エラーが出ない。
cdef int x
解決法1
ローカル変数にすればよい。つまり、関数内で定義すればローカルになる。例えば、
def hoge():
ctypedef np.float64_t DTYPE_t
cdef np.ndarray[DTYPE_t, ndim=3] a
解決法2
解決法1のようにローカル変数にしたくないときもある。モジュールレベルの変数でどうしても扱いたいなと思っていたら、このstackoverflowの回答に手がかりが書いてあった。
Typically if I want to have an array as a module level variable (i.e not local to a method), I define a typed memoryview and then set it within a method
memoryviewを使うらしい。
メモリービューオブジェクトとは
変数をメモリとして参照するために使う。
公式ページの型付きメモリービューページを参照すると、こう書いてある。
型付きメモリビューを使用すると、Pythonのオーバーヘッドを発生させることなく、基になるNumPy配列などのメモリバッファに効率的にアクセスできます。 メモリービューは、現在のNumPy配列バッファーのサポート(np.ndarray [np.float64_t、ndim = 2])に似ていますが、より多くの機能とより簡潔な構文があります。
基本的な構文としては[:]
を使うらしい。例えば一次元のintバッファを作るなら、
cdef int[:] view1D = exporting_object
三次元なら
cdef int[:,:,:] view3D = exporting_object
という具合である。
exporting_object
には、
- numpy array
- C array
- Cython array
のメモリービューオブジェクトを指定する。
元々numpyだけ知ればいいのだが、折角なので簡単に全部紹介する。
1. exporting_object が numpy arrrayの場合
np.arange(NUMBER , dtype) #NUMBERは任意の数
detypeはdtype=np.dtype("i")
のように指定する。("i"はint)
次元を増やしたければ、reshape
すればよい。
2. exporting_object が C arrayの場合
3次元なら
cdef int a[NUM1][NUM2][NUM3] #NUM1,2,3は任意の数。もちろんaも任意。
3 exporting_object が Cython array の場合
まず、
from cython.view cimport array as cvarray
のようにcython.view
からarray
をcimport
しなければならない。
3次元なら
cvarray(shape=(NUM1,NUM2,NUM3), itemsize, format)
itemsize, formatはitemsize=sizeof(int)
, format="i"
のように指定する。
解決法2に戻る
memoryviewの紹介でnumpy以外も紹介したが、本来の問題に戻ると結局以下のように書けばよいだろう。
cimport numpy as np
ctypedef np.float64_t DTYPE_t
cdef DTYPE_t [:, :, :] a
あとはa = np.arange(27, dtype=np.double).reshape((3, 3, 3))
などを代入すればいいだろう。
dtypeについてまたつまずいたので、別記事に書く。
まとめ
Cythonでnumpy使うときは、1.関数内でローカル定義する か 2.memoryviewを使う のどちらかで解決。
参考
- Cythonで使える配列バッファオブジェクト
- Cython公式ページより Typed Memoryviews