boost-python ではじめる大規模機械学習(6)- boost::multi_array のエクスポート・前編

あらすじ

boost-python を使用して、Python と C 言語両方を活用する方法を説明しています。

本記事では、boost::multi_array を Python 側にエクスポートする方法を解説します。

こんなものがつくりたい

Python でデータを読み込み、C で計算を行い、Python で途中経過を出力し、Python で結果を出力するプログラムを目標とします。
これは、たとえば次のようなコードになります。

import mylib, wrap
from matplotlib import pyplot as pl
from scipy import array, float64

# データを読み込む
x = array(..., dtype = float64)

# boost::multi_array 配列を確保
# x は入力、y は出力とします
wrap_x = wrap.make_vector(len(x))
wrap_y = wrap.make_vector(5)

# データを C 言語側に転送
wrap.copy_vector(x, wrap_x)

# (Python では不可能なほど大変な処理を) 実行します
for i in xrange(100):
    # 処理をおこなう
    mylib.execute(wrap_x, wrap_y)

    # 途中結果を表示
    y = wrap.scipy_vector(wrap_y)
    pl.plot(y)

# 結果をとりだす
y = wrap.scipy_vector(wrap_y)

mylib の中身は C 言語なので並列化などの最適化が容易です。
しかも、mylib の execute 関数にあたえる引数は boost::multi_array なので、実装もとても簡単です。
はじめに、wrap モジュールの概要を説明し、これを実装する方法を順番に紹介していきます。

おもな機能

wrap モジュールは大きく分けて、make (配列の確保)、reset (配列の初期化)、copy (SciPy -> C のデータ転送)、scipy (C -> SciPy のデータ転送) の 4 種類の機能を持ちます。
各関数はさらに、データ型と配列タイプをサフィックスとして持ちます。たとえば、reset_bool_matrix であれば、boolean 型の 2 次元配列を初期化する関数です。
さらに簡単のため、データ型が double の場合のみ型を省略することにします。
今回・次回合わせて、以下の関数の実装方法を説明します。

  • make_vector
  • make_matrix
  • make_bool_vector
  • make_bool_matrix
  • reset_vector
  • reset_matrix
  • reset_bool_vector
  • reset_bool_matrix

お急ぎの方は、ページ最後のソースコードをコピペして貼り付けるでも OK です。

共通モジュール

C 言語側でひろく利用する機能を、共通モジュールとして作り込みます。
ソースコードの名前は cmath.h とします。

インクルード、using 宣言
#include <boost/multi_array.hpp>
#include <boost/shared_ptr.hpp>
using boost::extents;
using boost::multi_array;
using boost::multi_array_ref;
using boost::shared_ptr;
型宣言

以下の型は、C 言語モジュールで繰り返し使うので、短縮名を与えておきます。
Python とのやりとりには multi_array に対するスマートポインタを使用するため、こちらも同時に定義しておきます。

typedef multi_array<double, 1> _wrap_vector;
typedef multi_array<double, 2> _wrap_matrix;
typedef multi_array<bool, 1> _wrap_bool_vector;
typedef multi_array<bool, 2> _wrap_bool_matrix;

typedef shared_ptr<_wrap_vector> wrap_vector;
typedef shared_ptr<_wrap_matrix> wrap_matrix;
typedef shared_ptr<_wrap_bool_vector> wrap_bool_vector;
typedef shared_ptr<_wrap_bool_matrix> wrap_bool_matrix;
dim, rdim

multi_array, multi_array_ref の次元サイズを取得するための補助関数を定義します。

template<class T>
static inline size_t dim1(T x)
{
    return x->size();
}

template<class T>
static inline size_t dim2(T x)
{
    return (*x)[0].size();
}

template<class T>
static inline size_t rdim1(T x)
{
    return x.size();
}

template <class T>
static inline size_t rdim2(T x)
{
    return x[0].size();
}
rnelem

multi_array_ref が何要素のデータ領域を指しているかを取得する補助関数を定義します。

template<class T>
static inline size_t rnelem(multi_array_ref<T, 1> x)
{
    return rdim1(x);
}

template<class T>
static inline size_t rnelem(multi_array_ref<T, 2> x)
{
    return rdim1(x) * rdim2(x);
}
ref 関数

C 言語側で、スマートポインタをはがすための関数を実装します。

template<class T>
multi_array_ref<T, 1> ref(shared_ptr<multi_array<T, 1> > x)
{
    multi_array_ref<T, 1> r(x->data(), extents[dim1(x)]);
    return r;
}

template<class T>
multi_array_ref<T, 2> ref(shared_ptr<multi_array<T, 2> > x)
{
    multi_array_ref<T, 2> r(x->data(), extents[dim1(x)][dim2(x)]);
    return r;
}

ソースコード

今回はここまで。
cmath.h はこれで完成です。次回は wrap モジュールを組み立てます。

cmath.h
#include <boost/multi_array.hpp>
#include <boost/shared_ptr.hpp>

#ifndef PYLIB_CMATH_INCLUDED
#define PYLIB_CMATH_INCLUDED
namespace cmath {
using boost::extents;
using boost::multi_array;
using boost::multi_array_ref;
using boost::shared_ptr;

typedef multi_array<double, 1> _wrap_vector;
typedef multi_array<double, 2> _wrap_matrix;
typedef multi_array<bool, 1> _wrap_bool_vector;
typedef multi_array<bool, 2> _wrap_bool_matrix;

typedef shared_ptr<_wrap_vector> wrap_vector;
typedef shared_ptr<_wrap_matrix> wrap_matrix;
typedef shared_ptr<_wrap_bool_vector> wrap_bool_vector;
typedef shared_ptr<_wrap_bool_matrix> wrap_bool_matrix;

// ========== ========== ========== ==========
//
//          dim
//
// ========== ========== ========== ==========
template<class T>
static inline size_t dim1(T x)
{
    return x->size();
}

template<class T>
static inline size_t dim2(T x)
{
    return (*x)[0].size();
}

// ========== ========== ========== ==========
//
//          rdim
//
// ========== ========== ========== ==========
template<class T>
static inline size_t rdim1(T x)
{
    return x.size();
}

template <class T>
static inline size_t rdim2(T x)
{
    return x[0].size();
}

// ========== ========== ========== ==========
//
//          rnelem
//
// ========== ========== ========== ==========
template<class T>
static inline size_t rnelem(multi_array_ref<T, 1> x)
{
    return rdim1(x);
}

template<class T>
static inline size_t rnelem(multi_array_ref<T, 2> x)
{
    return rdim1(x) * rdim2(x);
}

// ========== ========== ========== ==========
//
//          ref
//
// ========== ========== ========== ==========
template<class T>
multi_array_ref<T, 1> ref(shared_ptr<multi_array<T, 1> > x)
{
    multi_array_ref<T, 1> r(x->data(), extents[dim1(x)]);
    return r;
}

template<class T>
multi_array_ref<T, 2> ref(shared_ptr<multi_array<T, 2> > x)
{
    multi_array_ref<T, 2> r(x->data(), extents[dim1(x)][dim2(x)]);
    return r;
}
}
#endif