ND4J ユーザガイド

Takami Torao
  • このエントリーをはてなブックマークに追加
http://nd4j.org/userguide

イントロダクション

NDArray は本質的に \(n\) 次元の配列 - つまりいくつかの次元数を有する長方形の数列です。

NDArray に対して理解しておくべき概念は以下の通り:

  • rank は NDArray の次元を表します。2次元の NDArray の rank は 2、3次元の配列は 3、といったようにです。任意の rank で NDArray を作成することが出来ます。
  • shape は各次元のサイズを定義します。 3行5列の2次元配列があるとき、この NDArray の shape は [3, 5] です。
  • length は配列内の要素の総数を定義します。length は常に shape を構成する値の積に等しくなります。
  • stride は各次元の (根底にあるデータバッファ内の) 連続する要素の分離として定義されます。 stride は次元ごとに定義されるため、rank \(N\) NDArray には各次元ごとに1つの stride 値があります。ほとんどの場合、開発者が stride を知ったり気にかけるが必要がないことに注意。これが ND4J の内部でどのように動作するかだけ注意してください。次のセクションには stride の例があります。
  • data type は NDArray のデータ型 (例えば float または double 精度の) を指します。これは ND4J でグローバルに設定されているので、すべての NDArray が同じ data type を持つ必要があることに注意してください。data type の設定についてはこのドキュメントの後半で説明します。

インデックスについていくつか理解しておくことがあります。第一に、次元0の行、次元1の列、つまり INDArray.size(0) は行数、INDArray.size(1) は列数です。一般的なプログラミング言語の配列と同様にインデックスは 0 基準です。従って行はインデックスを 0 から INDArray.size(0)-1 まで持ち、その他の次元も同様です。

このドキュメントを通して我々は NDArray を \(n\) 次元配列の一般的な外苑を示すものを示します。また INDArray は特に ND4J が定義する Java インターフェースを指します。実際にはこの二つの単語は同じ意味で使用できます。

NDArrays: メモリ上にどう配置されるか?

続くいくつかの段落では ND4J のアーキテクチャについて説明します。ND4J を使用するためにこれらを知ることは必ずしも必要ありませんが舞台裏で何が起きているかを理解するのに役立つかも知れません。NDArray は数値の単一フラット配列 (より一般的にはメモリ上の単一連続ブロック) としてメモリ上に保存されます。このため Java で典型的な float[][]double[][] のような多次元配列とは異なります。

物理的には INDArray のバックアップはオフヒープ (JavaVM ヒープ外のネイティブヒープ) に格納されます。これはパフォーマンス、高性能 BLAS ライブラリとの相互運用性、高性能コンピューティングで JavaVM のいくつかの欠点 (Java の配列が int インデクシングにより \(2^{31-1}\) (21.4億) 要素に限定されている問題など) など、多くの利点を持っています。

エンコーディングの観点から NDArray は C (行優先) または Fortran (列優先) 順序のどちらかでエンコードすることが出来ます。行 vs 列順序の主要な詳細についてはウィキペディアを参照してください。 ND4J では C および F 順配列の組み合わせを同時に使用することが出来ます。ほとんどのユーザはデフォルトの配列順序を使用することができますが、必要に応じて特定の配列に対して特定の順序づけを使用することが可能であることに注意してください。

以下の図は単純な \(3\times 3\) (2次元) NDArray がメモリに格納される様子を示します。

上記の配列において:

  • shape = [3,3] (3行3列)
  • rank = 2 (2次元)
  • length = 9 (\(3 \times 3 = 9\))
  • stride
    • C順序 stride: [3,1]: 連続する行の値はバッファ内で3つごとに区切られ、連続する列は1つごとに区切られている。
    • F順序 stride: [1,3]: 連続する行の値はバッファ内で1つごとに区切られ、連続する列は3つごとに区切られている。

Views: 2つ以上の NDArray が同じデータを参照する場合

ND4J の重要な概念は 2 つの NDArray がメモリ上にある同じデータを参照できることです。通常、別の配列のサブセットを参照する 1 つの NDArray は INDArray.get()INDArray.transpose()INDArray.getRow() のような特別な操作でのみ発生します。これは強力な概念であり理解する価値があります。

これには 2 つの同期があります:

  1. パフォーマンス利点の考慮、特に配列コピーを回避すること
  2. NDArray 上の操作をどのように行うことが出来るかという点で多くの利点を得ること

巨大な行列 (\(10,000 \times 10,000)\) を転置するような簡単な操作を考える。ビューを使うことでこの行列転置はコピーを行うことなく一定時間内に (BigO表記で \(O(1)\)) 行うことができ、すべての要素のコピーコストを考慮しなくて済みます。もちろんコピーを作成する必要がある場合は INDArray.dup() を使ってコピーを取得することが出来ます。例えば転置行列のコピーが必要な場合は INDArray out = myMatrix.transpose().dup() を使えます。この dup() が呼び出された後、元の myArray と新しい行列 out に関連はなくなります (従って片方への変更は他方に影響しません)。

ビューが強力に出来るところを見てみましょう。巨大な配列 myArray の先頭行に 1.0 を加える簡単なタスクを考えます。これは myArray.getRow(0).addi(1.0) 行で簡単に行えます。ここで何が起きたかをブレイクダウンしましょう。最初に getRow(0) 操作は元のビューとなる INDArray を返します。myArraymyArray.getRow(0) はメモリ上の同じエリアを指している点に注意:

次に addi(1.0) が実行触れた後、以下の状態を得ることが出来ます。

このように myArray.getRow(0) によって返される NDArray の変更は元の配列 myArray に反映されます。同様に myArray に対する変更は行ベクトルに反映されるでしょう。

NDArray の生成

0, 1 またはスカラー値で初期化された配列

配列を作成するためによく使われる 2 つのメソッドがあります

  • Nd4j.zeros(int...)
  • Nd4j.ones(int...)

配列の shape は整数値で指定されます。例えば 3 行 5 列のゼロ埋めされた配列を生成するには Nd4j.zeros(3, 5) とします。

これらはしばしば別の値の配列を生成するために別の操作と共に使われます。例えば 10 で埋めた配列を作成する場合は ND4j.zeros(3, 5).addi(10) のように書けます。この初期化は最初に 0 埋めされた \(3\times 5\) の配列を作成し、次にそれぞれの値に 10 を加算します。

乱数配列

Nd4j は疑似乱数を値とする INDArray を生成するためのいくつかのメソッドを用意しています。

0 から 1 の範囲を持つ一様乱数を生成するには Nd4j.rand(int nRows, int nCols) (2次元配列の場合) または Nd4j.rand(int[]) (3次元以上の場合) を使用します。

同様に平均 0、分散 1 の正規乱数を生成するには Nd4j.randn(int nRows, int nCols) または Nd4j.randn(int[]) を使用します。

再現性のために Nd4j.getRandom().setSeed(long) つまり Nd4j の乱数生成器のシードを設定できます。

Java 配列から NDArray の作成

Nd4j は Java の float と double 配列から配列を生成するための便利なメソッドを用意しています。

1次元の Java 配列から 1 次元の NDArray を生成するには以下を使用します:

  • 行ベクトル: Nd4j.create(float[]) または Nd4j.create(double[])
  • 列ベクトル: Nd4j.create(float[], new int[]{length,1}) または Nd4j.create(double[], new int[]{length,1})

2次元配列は Nd4j.create(float[][]) または Nd4j.create(double[][])

3 次元以上の Java プリミティブ配列から NDArray を作成するには、一つの方法として以下を使用します。

double[] flat = ArrayUtil.flattenDoubleArray(myDoubleArray);
int[] shape = ...;
INDArray myArray = Nd4j.create(flat, shape, 'C');

他の NDArray から NDArray を生成

他の配列から配列を作る方法は 3 つあります。

  • 既存の NDArray から INDArray.dup() を使ってコピーを抽出する。
  • 既存の NDArray のサブセットとして配列を作成する。
  • 既存のいくつかの NDArray を連結して新しい NDArray を作成する。

NDArray を結合する 2 つのメソッド Nd4j.hstack(INDArray...)Nd4j.vstack(INDArray...) があります。

hstack (水平方向にスタック) は同じ行数を持つ複数の配列を引数に取ります。それらを水平方向に連結して新しい配列を生成します。ここで入力の NDArray は異なる列数を持つことが出来ます。

例:

int nRows = 2;
int nColumns = 2;
// Create INDArray of zeros
INDArray zeros = Nd4j.zeros(nRows, nColumns);
// Create one of all ones
INDArray ones = Nd4j.ones(nRows, nColumns);
//hstack
INDArray hstack = Nd4j.hstack(ones,zeros);
System.out.println("### HSTACK ####");
System.out.println(hstack);

出力:

### HSTACK ####
[[1.00, 1.00, 0.00, 0.00],
[1.00, 1.00, 0.00, 0.00]]

vstack (垂直方向にスタック) は hstack の縦方向版です。入力の配列は同じ列数を持つ必要があります。

例:

int nRows = 2;
int nColumns = 2;
// Create INDArray of zeros
INDArray zeros = Nd4j.zeros(nRows, nColumns);
// Create one of all ones
INDArray ones = Nd4j.ones(nRows, nColumns);
//vstack
INDArray vstack = Nd4j.vstack(ones,zeros);
System.out.println("### VSTACK ####");
System.out.println(vstack);

出力:

### VSTACK ####
[[1.00, 1.00],
 [1.00, 1.00],
 [0.00, 0.00],
 [0.00, 0.00]]

Nd4j.concat は次元に沿って配列を連結します。

例:

int nRows = 2;
int nColumns = 2;
//INDArray of zeros
INDArray zeros = Nd4j.zeros(nRows, nColumns);
// Create one of all ones
INDArray ones = Nd4j.ones(nRows, nColumns);
// Concat on dimension 0
INDArray combined = Nd4j.concat(0,zeros,ones);
System.out.println("### COMBINED dimension 0####");
System.out.println(combined);
//Concat on dimension 1
INDArray combined2 = Nd4j.concat(1,zeros,ones);
System.out.println("### COMBINED dimension 1 ####");
System.out.println(combined2);

出力:

### COMBINED dimension 0####
[[0.00, 0.00],
 [0.00, 0.00],
 [1.00, 1.00],
 [1.00, 1.00]]
### COMBINED dimension 1 ####
[[0.00, 0.00, 1.00, 1.00],
 [0.00, 0.00, 1.00, 1.00]]

Nd4j.pad は配列の埋めに使用されます。

例:

int nRows = 2;
int nColumns = 2;
// Create INDArray of all ones
INDArray ones = Nd4j.ones(nRows, nColumns);
// pad the INDArray
INDArray padded = Nd4j.pad(ones, new int[]{1,1}, Nd4j.PadMode.CONSTANT );
System.out.println("### Padded ####");
System.out.println(padded);

出力:

### Padded ####
[[0.00, 0.00, 0.00, 0.00],
 [0.00, 1.00, 1.00, 0.00],
 [0.00, 1.00, 1.00, 0.00],
 [0.00, 0.00, 0.00, 0.00]]

時に便利なメソッドとして Nd4j.diag(INDArrat in) があります。このメソッドは引数 in によって:

  • in がベクトルなら diag は配列 in に等しい対角を持つ \(N\times N\) 行列を生成します (ここで \(N\) は in の長さ)。
  • in が \(N\times N\) 行列なら diag は in の対角から取り出したベクトルを生成します。

様々な NDArray の生成方法

サイズ \(N\) の単位行列を作成するには Nd4j.eye(N) を使用します。

要素 [a, a+1, a+2, ..., b] の行ベクトルを作成するには linspace (隙間) コマンド Nd4j.linspace(a, b, b-a+1) を使用します。

linspace は他の shape を得るための reshape 操作と組み合わせることが出来ます。例えば 1 から 25 までの 5 行 5 列の 2 次元 NDArray が必要ならば Nd4j.linspace(1,25,25).reshape(5,5) と書けます。

個々の値の参照と設定

ある INDArray に対して get または set したい要素のインデックスを使用して get/set を行うことが出来ます。rank \(N\) 配列 (つまり \(N\) 次元の配列) に対して \(N\) 個のインデックスが必要です。

値を個別に get または set することはパフォーマンスの観点から悪い考えです (例えばループで1度に一つずつのように)。可能であれば一度に大量の数の操作を行う他の INDArray メソッドを試してください。

2次元配列から値を get するには INDArray.getDouble(int row, int column) を使用します。

任意次元の配列に対しては INDArray.getDouble(int...) を使用します。例えば i,j,k のインデックスから値を get するには INDArray.getDouble(i,j,k) とします。

値を set するには putScalar メソッドの一つを使用します:

  • INDArray.putScalar(int[],double)
  • INDArray.putScalar(int[],float)
  • INDArray.putScalar(int[],int)

ここで int[] はインデックスで double/float/int はそのインデックスに設定する値です。

特定の状況で有用な追加の機能は NDIndexIterator クラスです。NDIndexIterator は定義された順序でインデックスを参照することが出来ます (具体的には C 順序の rank 3 配列で: [0,0,0],[0,0,1],[0,0,2],...,[0,1,0],...)。

2次元配列で値を列挙するには以下を使用します:

NdIndexIterator iter = new NdIndexIterator(nRows, nCols);
while (iter.hasNext()) {
  int[] nextIndex = iter.next();
  double nextVal = myArray.getDouble(nextIndex);
  //do something with the value
}

NDArray の一部の参照また設定

getRow() と putRow()

INDArray から一つの行を取得するために INDArray.getRow(int) を使用することが出来ます。これは明示的に行ベクトルを返します。ここでこの行はビューです: 返された行への変更は元の配列にも影響します。これは非常に役に立つことがあります (例えば myArr.getRow(3).addi(1.0) は巨大な配列の 3 行目に 1.0 を加算します)。もし行のコピーが必要であれば getRow(int).dup() を使用します。

同様に、複数の行を取得するには INDArray.getRows(int...) を使用します。これは積み上げられた行を返します: ただし元の行のコピー (ビューではない) です。ビューは NDArray がメモリの格納方法のため不可能です。

単一の行への set に対して myArray.putRow(int rowIdx, INDArray row) が使用できます。このメソッドは myArrayrowIdx 行目に INDArray row に含まれている値を設定します。

Sub-Arrays: get(), put() NDArrayIndex

Tensor Along Dimension

Slice

NDArray 上の操作

スカラー操作

集約操作

インデックス集約操作

ブロードキャストとベクトル操作

Boolean Indexing: Selectively Apply Operations Based on a Condition

Advanced and Miscellaneous Topics

データ型の設定

Reshaping

Flattening

Permute

sortRows/sortColumns

Serialization

Quick Reference: A Summary Overview of ND4J Methods

FAQ: Frequently Asked Questions