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
>>>