14
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Cythonでnumpyを扱う (memoryviewによる方法)

Last updated at Posted at 2020-01-10

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には、

  1. numpy array
  2. C array
  3. 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からarraycimportしなければならない。

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を使う のどちらかで解決。

参考

14
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?