行列演算 API(ND4J)の簡単整理

Javaディープラーニングライブラリー「Deeplearning 4 j(以下、DL4J)」で行列の演算を行う方法を整理します。

今まで数冊のディープラーニングや数学の本を読んで
ディープラーニングについて少しは分かった気はしますが、
数学が苦手なので、DL4で行列に関する引数及び戻り値の解析に難がありました。
なんというか、行列に関するAPIを呼び出した時、紙に書いてみないと、
頭の中に結果行列の姿がパット思い浮かべないです。

そこでここに行列演算のAPIについて書いて自分の理解を深めると同時に参考ドキュメントにしたいと思います。

DL4Jを使う時、行列の演算を行う時、依存ライブラリーとして、ND4Jという多次元配列のライブラリーのAPIを使用します。
以下はその説明です。
CSVやDBからDL4Jが自動で行列を作ってくれるので、以下のAPIを使うのはまれだと思いますが、
基礎となる部分ですので、理解して損はないと思います。

ND4Jのメソッドの結果はINDArrayで、多次元配列です。内部はC++で実装されているようです。

行列の作成

説明

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/

2番目の引数に行列を格納する多次元配列の形(shape)を指定します。
数字の数が次元の数(ここでは2つ)を表しており、要素は各次元の長さ(1次元 : 4つ, 2次元 : 2つ)です。
まず、長さ4の配列が作られ、それぞれの要素に長さ2の配列が割り当てられます。
2次元なので、まさに行・列ですね。

1番目の引数に指定された配列の要素が最後の次元(2次元)の長さずつ(ここでは2つずつ)、順に配列を埋めていきます。
ここではまず、[1, 2]と埋めて、次は[ 3, 4]、[5, 6]、[7, 8]と埋めていき、4x2の多次元配列、つまり、行列が完成します。

1番目の引数に指定される要素の数は2番目の引数に指定される要素同士の掛け算の結果と同じでなければなりません。
ここでは4 * 2 = 8ですから、8つの要素が必要です。

練習問題

以下の結果は何でしょうか。次元の数は3つです。答えは最後にあります。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, new int[]{ 3, 2, 2 });

前置行列(Transpose)

説明

縦の要素と横の要素を入れ替えた行列です。行列を表すINDArrayオブジェクトに対してtranpose()メソッドを呼び出すことで前置行列を求められます。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/
INDArray tnd = nd.transpose();
/*
結果2:  結果1の縦と横が変わりました。
[[1.00, 3.00, 5.00, 7.00],
 [2.00, 4.00, 6.00, 8.00]]
*/

変形(Reshape)

説明

元の行列の形(行と列の数)を変えます。例えば、4x2行列を2x4行列に変えるなどです。
注意すべきところは変形前後の行と列の掛け算の結果が同じでなければならないということです。
例えば、4x2行列は3x3行列に変えることはできません。掛け算の結果(8 != 9)が違うためです。

INDArray nd = Nd4j.create(new float[]{1, 2, 3, 4, 5, 6, 7, 8}, new int[]{ 4, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00],
 [5.00, 6.00],
 [7.00, 8.00]]
*/

INDArray rnd = nd.reshape(2, 4); // rows:2 , columns: 4
/*
結果2:
[[1.00, 2.00, 3.00, 4.00],
 [5.00, 6.00, 7.00, 8.00]]
*/

hstack(水平積み上げ)

引数に指定された複数のINDArrayを水平に埋めた行列を返します。
サンプルを見た方が早いでしょう。

INDArray nd1 = Nd4j.create(new float[]{1, 2, 3, 4}, new int[]{ 2, 2 });
/*
結果1:
[[1.00, 2.00],
 [3.00, 4.00]]
*/
INDArray nd2 = Nd4j.create(new float[]{5, 6, 7, 8}, new int[]{ 2, 2 });
/*
結果2:
[[5.00, 6.00],
 [7.00, 8.00]]
*/
INDArray hstack = Nd4j.hstack(nd1, nd2);
/*
結果3:
[[1.00, 2.00, 5.00, 6.00],
 [3.00, 4.00, 7.00, 8.00]]
結果1の行列と結果2の行列が水平に埋められた結果になりました。
*/

hstackの引数は可変長なので、複数のINDArrayが指定できます。

linspace(行ベクトル作成)

lower(下限値)、upper(上限値)、num(長さ)をそれぞれ順にlinspaceメソッドに指定して実行すると、
lowerからupperまでの範囲からnumだけの数を要素として持つX行1列の行列(行ベクトル)が返されます。
要素はnumに合わせてlower~upperの範囲を一定の間隔(つまり、一定のstepで)で分割し、求められます。
例えば、lower: 1, upper: 10, num: 3なら、3個の要素を持つ行列になりますが、
最初は1、2番目は真ん中の5.5, 3番目は10になります。

以下にサンプルを示します。

INDArray nd1 = Nd4j.linspace(1, 10, 3);
/*
結果1:
[1.00, 5.50, 10.00]
*/

INDArray nd2 = Nd4j.linspace(1, 10, 10);
/*
結果2:
[1.00, 2.00, 3.00, 4.00, 5.00, 6.00, 7.00, 8.00, 9.00, 10.00]
*/

INDArray nd3 = Nd4j.linspace(1, 10, 20);
/*
結果3:
[1.00, 1.47, 1.95, 2.42, 2.89, 3.37, 3.84, 4.32, 4.79, 5.26, 5.74, 6.21, 6.68, 7.16, 7.63, 8.11, 8.58, 9.05, 9.53, 10.00]
*/

今日はここまでです。思い出したら、vstack, diag, zeroも書きます。
恥ずかしい水準ではありますが、やはり書きながら、理解度が上がる気がします。

練習問題の解答

行列の作成

[[[1.00, 2.00],
  [3.00, 4.00]],

 [[5.00, 6.00],
  [7.00, 8.00]],

 [[9.00, 10.00],
  [11.00, 12.00]]]

2*2の行列が3つ生成された形です。