本文章はStan Modeling Language
Stan Development Team. 2015. Stan Modeling Language Users Guide and Reference Manual, Version 2.7.0.の4章: Containers: Arrays, Vectors, and Matricesの翻訳となります。
訳者は勉強がてら翻訳しようと思っただけで、プログラムの専門家でもデータ解析の専門家でもありません。そのため誤訳・意味の取り違えなどあると思いますが、その際はご指摘いただけると幸いです。5章はやりたいと思っていますが、その先は未定です。また、翻訳ペースも気分しだいです。ご了承ください。
##4.データ格納形式: Arrays, Vectors, and Matrices
Stanにはarrays, vectors, matrices3種のデータ格納型が用意されており、内部のでデータを互いに入れ替えることはできません。これは、たとえデータの次元が一致していても、同様です。Stanにおいて、3 × 4次元のmatrixは3 × 4次元のarrayとは全く別種類のオブジェクトなのです。
###4.1. Vectors and Matrices
Vectors, MatricesはArraysに比べると、単純なデータ構造になっています。Vectorsは1次元の実数を、Matricesは2次元の実数を取り扱うためのデータ格納型です。Matrices型の用途として、コード内でMatrices内のデータを読み出すことが挙げられます。Stanにおいて、Vectors, Matrices型を使うのはおそらく以下の3つの場合のみでしょう。
- 行列演算 (行列の乗算など)
- 線形代数周りの機能 (固有値, 行列式など)
- 多変量パラメータ, アウトカムの取り扱い (多変量正規分布の引数など)
Vectors, Matricesは整数値を返すことはできず、取り扱えるのは実数値に限られています。
###4.2. Arrays
Arraysは、基本的には1次元のデータを扱うためのオブジェクトです。Arraysには単純な実数や整数値、Vectors、Matrices、別のArraysのような、あらゆるデータ型の値を格納することができます。ArraysはStanにおいて唯一、整数のシーケンスを代入可能なデータ型です。整数のシーケンスが必要な状況として、離散分布の指定などが挙げられます。
2次元のArraysはいわばArraysを並べたものとして扱うことができます。インデックスをArrayに与えると、Arrayはそのインデックスの値を返します。複数のインデックスをArrayに与えると、連鎖的にインデックス付けが行われます。例えばこれを利用して、"a[m][n]"というアレイを、"a[m,n]"と簡単に記述することができます。
###4.3. 上手い使い分け方
Stanの目指す基本設計の方向性の1つとして、効率の良さが挙げられます。
Stanにおいて、行列、線形代数の操作に関わる動作はC++のEigenライブラリのデータ型をベースに実装されています。このため、行列、線形代数の機能を使用する際Vectors, Matricesを型としていれば、データ型を変換する必要はありません。
一方、ArraysはC++の一般的なvectorクラスのインスタンスとして実装されています。(EigenライブラリのEigen**Vectorクラス, StanのVectorsと混同しないように注意しましょう)。Arraysはこのように実装されているため、値を参照して返すことができます。このため、値をコピーすることに比べて非常に効率よくインデックス付けを実行できます。
####Matrices vs. 2次元Arrays
Stanモデルにおける2次元アレイとMatricesの使い分けについて考慮すべき点を以下に示します。
第1に、Matricesのメリットとして2次元Arraysよりもメモリの使用量が少ないことが挙げられます。これは、これらのデータ型が、Arraysのシーケンスではなく、データと2次元の形式を保存しているためです。第2のメリットとして、Matricesは列データを優先して格納する仕組みであることが挙げられます。またMatrices内のすべてのデータはメモリ内で隣接することが保証されます。一般的に、メモリからキャッシュへのデータの移動は、現代のCPUで実行される算術演算よりも時間がかかります。このため、データ型の仕組みを理解して使い分けをすることは、コードを最適化する上で重要な事項となります。
一方、Arraysはプリミティブ型の値をメモリ内で隣接することを保証しており、それ以外の場合はその値のコピーを保持するように設計されています(可能な限り値を参照するように返す)。
3に、いずれのデータ構造もそれらが保存されている順序でトラバースするのが最も高速です。これはメモリ参照の局所性を有効活用する際に重要です。Matricesでは列が優先されるため、以下のような記述が適切です。
matrix[M,N] a;
//...
for (n in 1:N) //列が先
for (m in 1:M) //行が後
// ... a[m,n]を使った計算...
Arraysでは行が優先されるため、以下のような記述が適切です。
real a[M,N];
// ...
for (m in 1:M) //行が先
for (n in 1:N) //列が後
// ... a[m,n]を使った計算...
最初にa[m,n]を使う際には、a[m]をメモリに受け渡すようにしましょう。一般的に、Matricesをトラバースする方が、Arraysをトラバースするよりも効率が良いです。これはMatricesのArraysについても同様です。以下にMatricesの2次元Arraysの理想的な記述例を示します。
matrix[M,N] b[I,J];
// ...
for (i in 1:I) //Arrayなので行が先
for (j in 1:J) //Arrayなので列が後
for (n in 1:N) //Matrixなので列が先
for (m in 1:M) //Matrixなので行が後
//... b[i,j,m,n] を使った計算...
Matricesの場合、a[m]と表記するとそのMatricesの行mが抽出されますが、これはMatricesを取り扱う上では非効率な操作です。ベクトルのインデックスを作る必要があるなら、以下のようにベクトルのArraysを宣言する方がはるかに効率的です。
row_vector[N] b[M];
// ...
for (m in 1:M)
//... row vector b[m]を使った計算 ...
これは以下のmatrixを使った例よりも圧倒的に優れています。
matrix b[M,N];
// ...
for (m in 1:M)
// ... row vector b[m]を使った計算 ...
同様に、列ベクトルのArrayを作成することで、matrixの列を選択するcol関数を使うよりも効率よく列を指定することができます。対照的に、matrixを使うと純粋に線形代数の計算を解くことになるため、演算を高速化することができます。もし予測係数のドット積の列を作成する場合、以下のように記述することで、
matrix[N,K] x; // predictors (aka covariates)
// ...
vector[K] beta; // coeffs
// ...
vector[N] y_hat; // linear prediction
// ...
y_hat <- x * beta;
以下のように書くよりも効率よく列を作ることができます。
row_vector[K] x[N]; // predictors (aka covariates)
// ...
vector[K] beta; // coeffs
...
vector[N] y_hat; // linear prediction
...
for (n in 1:N)
y_hat[n] <- x[n] * beta;
####列Vectors vs. 1次元Arrays
純粋にコンテナとして使用する上では、列Vectorsと1次元Arraysの間に大きな違いはありません。EigenライブラリのVectorテンプレートと、C++一般ライブラリに含まれるVectorテンプレートクラスは、double型の値のコンテナとして非常に近い形で実装されています (Stanでは実数型)。(Stanでは実数型)。StanにおいてはArraysだけが整数値を持つことができます。
####参考資料
Stan Development Team. 2015. Stan Modeling Language Users Guide and Reference Manual, Version 2.7.0.
Stan のデータ型についてまとめてみた