boost-python ではじめる大規模機械学習(4)- 配列アクセス
あらすじ
boost-python を使用して、Python と C 言語両方を活用する方法を説明しています。
前記事では、任意の Python モジュールを C 言語から呼び出す方法を解説しました。
本記事では、SciPy の配列に C 言語からアクセスする方法を解説します。
配列オブジェクトの取得
色々な方法がありますが、メモリに直接アクセスするのが最も高速と考えられます。
はじめに、PyObject* object.ptr() を使用して PyObject のポインタを取得します。
次に、PyArray_FromObject で配列の構造体へのポインタを得ます。
static inline PyArrayObject* py_array(PyObject* ptr, int ndim) { return (PyArrayObject*)PyArray_FromObject(ptr, NPY_FLOAT64, ndim, ndim); } void pick_double(object src, int index) { PyArrayObject* psrc = py_array(src.ptr(), 1); Py_DECREF(psrc); }
PyArray_FromObject の引数は、順にポインタ(PyObject*)、データ型(NPY_FLOAT64, NPY_INT32 など)、最小次元数、最大次元数です。最小次元数に 2、最大次元数に 3 を指定して 1 次元配列を入力するとエラーになる、という仕掛けです。
本シリーズでは、最小次元数・最大次元数には常に同じ値を指定することにします。
PyArray_FromObject 関数は Python 内部の参照カウンタの値を 1 増やすので、処理が終わったら Py_DECREF 関数を呼び出し、参照カウンタの値を減らす必要があります。
値の読み出し
SciPy の配列はメモリ上に連続に並んでいるとは限らないので、添字ごとのアドレスの移動量(ストライド)を取得します。
次に、char* PyArrayObject.data を使用して値を読み出せば完成です。
static inline int str1(PyArrayObject* p) { return p->strides[0]; } template<class T> static inline T* valof(char* x, size_t i, int stride) { return (T*)&x[i * stride]; } double pick_double(object src, int index) { PyArrayObject* psrc = py_array(src.ptr(), 1); char* pbuf = psrc->data; double ret = *valof<double>(pbuf, index, str1(psrc)); Py_DECREF(psrc); return ret; }
動作テスト
hello.cpp
#include <boost/python.hpp> #include <numpy/arrayobject.h> using namespace boost::python; // // Helpers // static inline PyArrayObject* py_array(PyObject* ptr, int ndim) { return (PyArrayObject*)PyArray_FromObject(ptr, NPY_FLOAT64, ndim, ndim); } static inline PyArrayObject* py_int_array(PyObject* ptr, int ndim) { return (PyArrayObject*)PyArray_FromObject(ptr, NPY_INT32, ndim, ndim); } static inline int str1(PyArrayObject* p) { return p->strides[0]; } template<class T> static inline T* valof(char* x, size_t i, int stride) { return (T*)&x[i * stride]; } // // Implementations // double pick_double(object src, int index) { PyArrayObject* psrc = py_array(src.ptr(), 1); char* pbuf = psrc->data; double ret = *valof<double>(pbuf, index, str1(psrc)); Py_DECREF(psrc); return ret; } int pick_int(object src, int index) { PyArrayObject* psrc = py_int_array(src.ptr(), 1); char* pbuf = psrc->data; int ret = *valof<int>(pbuf, index, str1(psrc)); Py_DECREF(psrc); return ret; } BOOST_PYTHON_MODULE(hello) { numeric::array::set_module_and_type("numpy", "ndarray"); def("pick_double", pick_double); def("pick_int", pick_int); import_array(); }
実行結果
>>> import hello >>> from scipy import float64, int32, rand >>> x = rand(10) >>> x array([ 0.38193373, 0.24639094, 0.02470218, 0.25281427, 0.41856296, 0.52292722, 0.7646326 , 0.04793535, 0.41331719, 0.88596331]) >>> y = int32(x * 10) >>> y array([3, 2, 0, 2, 4, 5, 7, 0, 4, 8], dtype=int32) >>> hello.pick_double(x, 2) 0.02470218244761957 >>> hello.pick_int(y, 3) 2 >>>