한글NewsletterND4JのフルインストールテンソルとND4JND4JユーザーガイドND4Jとはこのユーザーガイドは、ND4Jの主な機能性をご説明するものです(例もご紹介しています)。一部準備中のセクションもございます。はじめにNDArrayはどのようにしてメモリに格納されるのか。様々な高度なトピックデータ型の設定Reshaping(再整形)Flattening並べ替え(Permute)sortRows/sortColumnsBLAS演算に直接アクセスクイック参照:ND4Jメソッドのまとめよくある質問:(FAQ)はじめにNDArrayは、基本的にn次元の配列のことを指します。いくつかの次元を持った長方形状の数字配列のことです。知っておくべきいつかの概念は以下の通りです。NDArrayの*ランク(rank)とは、次元数を指します。2次元NDArrayはランクが2で、3次元配列はランクが3です。NDArrayを任意のランクで作成することができます。NDArrayの*シェイプ(shape)は各次元のサイズを示します。3行5列の2次元配列があるとしましょう。このNDArrayのシェイプは[3,5]となります。NDArrayの*長さ(length)は配列の全要素数を設定します。長さは常にシェイプを構成する値の積と等しくなります。NDArrayの*ストライド(stride)は各次元にある連続的要素を区切るもの(下部にあるデータバッファにおいて)と定義されます。ストライドは次元ごとに設定されます。したがってランクNのNDArrayのストライド値は全部でNとなります。ほとんどの場合、ストライドを知っている(あるいは気にする)必要はありません。ND4Jが内部でどのようにして働いているのかさえ理解していればいいのです。ストライドの例は次のセクションでご紹介しましょう。これはND4J全体に設定されるので、すべてのNDArrayは同じデータ型でなければなりません。データ型の設定については後にご説明しましょう。インデックス付けに関しては、知っておくべきことがいくつかあります。行は次元0で、列は次元1です。INDArray.size(0)は行数、INDArray.size(1)は列数を表します。ほとんどのプログラミング言語の正常な配列のように、インデックス付けはゼロで始まります。したがって例えば、列は0からINDArray.size(0)-1まであります。ここでは、NDArrayとはN次元配列の一般的な概念を指し、INDArrayとはND4Jが指定するJavaインターフェースを指します。いえ、実際はこれら二語は同意語として使用されます。NDArraysはどのようにしてメモリに格納されるのか。ここでND4Jの背景にある構造をご説明しましょう。ND4Jを使用するにはこれは理解しておかねばならないというわけではありませんが、コンテクストを理解するのに役立ちます。NDArrayは単一の平らな数字の配列(より一般的には、連続した単一のメモリーブロック)としてメモリーに格納されます。このため、float[][]やdouble[][][]などのごく一般的なJavaの複数次元配列とはずいぶん異なります。データはオフ・ヒープで格納されます。Java仮想マシン(JVM)の外部に格納されています。これには数多くの利点があります。(21.4億)要素に限定されるなどの問題)。エンコードに関しては、NDArrayはC(行優先)順またはFortran(列優先)順のいずれかでエンコードすることができます。行と列の順序に関する詳細は、[Wikipedia(英語)](https://en.wikipedia.org/wiki/Row-major_order)をご参照ください。Nd4JはCとFの順序を同時に両方使用することができます。ほとんどのユーザは、デフォルトの配列順序を使用するだけでいいのですが、必要な場合は配列に特定の順序を使用することも可能です。次の画像はシンプルな3x3(2次元)NDArrayがどのようにしてメモリに格納されるのかを表したものです。上記の配列には、以下のものがあります。(3x3=9)ストライドC順ストライド:[3,1]:連続した行の値はバッファーにより3つで区切られており、連続した列の値はバッファーにより1つで区切られています。F順ストライド:[1,3]:連続した行の値はバッファーにより1つで区切られており、連続した列の値はバッファーにより3つで区切られています。:2つ、またはそれ以上のNDArrayが同じデータを指す場合ND4Jのカギとなるコンセプトは、2つのNDArrayはメモリ内にある同じデータを指し示すことができるということです。通常、ある一つのNDArrayが別の配列のサブセットを参照しますが、これが発生するのは特定の演算でのみです。INDArray.get()、INDArray.transpose()、INDArray.getRow()などです。これはパワフルなコンセプトなので、理解しておくといいでしょう。これには主に2つの動機があります。1.かなりのパフォーマンス上の利点があります。配列の複製が防止できることです。2.自分のNDArrayでどのように演算を実行するかについて、多くのパワーを得ることができる。10,000)での行列転置のようなシンプルな演算について考えてみましょう。ビューを使用して、行列転置を一定時間で複製をせずに行うことができます(つまり、ランダウの記号のO(1))。これにより、配列の要素をすべて複製するというかなりのコストを回避できます。時には複製が欲しいこともあります。このときは、複製を得るためにINDArray.dup()を使用することができます。myMatrix.transpose().dup()を使用します。このdup()を呼び出した後、オリジナルの配列myMatrixと配列outの間の繋がりはなくなります(片方に行われた変更はもう片方に影響しません)。ビューがどれほどパワフルかを知るには、単に1.0を大きめの配列myArrayの最初の行に追加してみてください。これは1行で簡単にできます。myArray.getRow(0).addi(1.0)ここで何が起こっているのか詳しく見てみましょう。getRow(0)により、オリジナルのビューであるINDArrayが返されます。myArraysとmyArray.getRow(0)の両方がメモリ内の同域を指し示すことに注意してください。それから、addi(1.0)が実行されると、以下のようになります。myArray.getRow(0)によって返されたNDArrayへの変更はオリジナルの配列のmyArrayに反映されます。同様に、myArrayへの変更は行ベクトルに反映されます。一般的に使用される配列を作成する二つの方法は次の通りです。Nd4j.zeros(int...)Nd4j.ones(int...)配列のシェイプは整数で指定されます。ゼロで埋められた3行と5列の配列はNd4j.zeros(3,5)を使用します。これらは、しばしば他の演算と組み合わせて他の値の配列を作成することができます。10で埋められた配列を作成するには、以下のようにします。Nd4j.zeros(3,5).addi(10)上記の初期化作業は2段階で行われます。ゼロで埋められた3x5の配列を割り当て、次に各値に10を追加します。疑似乱数の配列Nd4jは疑似乱数のINDArrayを発生させるためにいつかの方法を提供しています。(2次元配列)またはNd4j.rand(int[])(3次元配列)を使用します。nCols)またはNd4j.randn(int[])を使用します。Nd4jの乱数発生シードの設定)、Nd4j.getRandom().setSeed(long)を使用します。method、便利なメソッド)を提供し、Javaのfloat配列及びdouble配列からの配列作成に使用しています。1次元のJava配列から1次元のNDArrayを作成するには以下を使用します。int[]{length,1})2次元配列にはNd4j.create(float[][])またはNd4j.create(double[][])を使用します。NDArraysを3次元以上のJavaプリミティブ配列(double[][][]など)で作成するには、以下を使用するのも一つのアプローチ方法です。...;//配列のシェイプをここに入れます。他の配列から配列を作成するには3つの基本的な方法があります。既存のNDArrayと全く同じ複製をINDArray.dup()を使って作成する。既存のNDArrayのサブセットとして配列を作成する。複数の既存のNDArrayを組み合わせて新しいNDArrayを作成する。二つ目の方法については、getRow()やget()などを使用することができます。詳細については、NDArraysのパーツを入手、設定)をお読みください。NDArrayを組み合わせる二つの方法は、Nd4j.hstack(INDArray...)とNd4j.vstack(INDArray...)です。stack(横方向に積み重ねる)」こと)は、同じ行数を持つ多くの行列を引数として使用し、新しい配列を生み出すために水平方向に積み上げます。入力するNDArrayの列は異なる数でも問題ありません。stack(縦方向に積み重ねる)こと」)です。入力配列は列数が同じでなければなりません。この方法には、引数のinによって2つの利用方法があります。inがベクトルである場合は、diagは配列inと等しい対角のNxNの行列を出力します(Nはinの長さ)。inがNxN行列である場合は、diagはinの対角から取ったベクトルを出力します。作成するには、Nd4j.eye(N)を使用します。b]によって作成するには、以下のようなlinspaceコマンドが使用できます。b-a+1)Linspaceはreshape演算と組み合わせて、その他のシェイプを得ることができます。値が1から25を含めた5行5列の2次元NDArrayが欲しい場合は、以下を使用できます。Nd4j.linspace(1,25,25).reshape(5,5)個々の値を入手し、設定INDArrayについては、欲しい、または設定したい要素のインデックスを使用して値を入手または設定することができます。ランクNの配列(つまり、N次元配列)については、インデックスがN個必要です。注意:個々に値を入手、または設定するのは一般的にパフォーマンス面から言うと望ましくはありません。可能な場合は、数多くの要素を使って同時に演算をするその他のINDArrayメソッドを使用するのがいいでしょう。2次配列から値を得るには、以下を使用します。column)すべての次元の配列にINDArray.getDouble(int...)を使用することができます。インデックスが値i,j,kを入手するには、INDArray.getDouble(i,j,k)を使用します。値を設定するには、次の複数のputScalarメソッドのうちどれか一つを使用します。INDArray.putScalar(int[],double)INDArray.putScalar(int[],float)INDArray.putScalar(int[],int)ここにおいて、int[]はインデックスで、double/float/intはそのインデックスに配置される値です。その他の機能性である特定の状況において役に立つものにNDIndexIteratorクラスがあります。[0,1,0]などのランク3配列など)。2次元配列で値を繰り返し処理するには、以下を使用します。//値に何かを設定します。}NDArraysのパーツを入手、設定getRow()とputRow()INDArrayから一行を入手するには、INDArray.getRow(int)を使用します。これにより、明らかに行ベクトルが返されます。ここで注目に値することは、行がビューであるということです。返された行への変更はオリジナルの行列に影響します。これが非常に役に立つことがあります(例えば、myArr.getRow(3).addi(1.0)によって、1.0を大型の配列に3行目に追加します。行の複製が欲しい場合はgetRow(int).dup()を使用します。同様に、複数の行を入手するには、INDArray.getRows(int...)を使用します。これにより、行が積み重なった配列が返されます。ここで注意しなければならないことは、これはオリジナルの行の複製(ビューではない)であり、このNDArrayがメモリに格納されている方法ではビューは可能ではありません。row)を使用することができます。これにより、第rowIdx行目のmyArrayにINDArrayrowに含まれた値が設定されます。get()、put()、NDArrayIndexGet:よりパワフルで一般的なメソッドは、INDArray.get(NDArrayIndex...)を使用することです。この機能性により、特定のインデックスを基にした任意のサブ配列を得ることができます。例を使うと一番分かりやすいので、ここで例をご紹介しましょう。一行を入手する(そして全列)には、以下を使用します。NDArrayIndex.all())行の範囲(行a(含まれる)から行b(含まれない))と全列を得るには、以下を使用します。NDArrayIndex.all())全行と隔列を入手するには、以下を使用します。myArray.get(NDArrayIndex.all(),NDArrayIndex.interval(0,2,nCols))上記の例は2次元配列のみの場合ですが、NDArrayIndexのアプローチ方法は3次元以上も可能です。3次元については、上記のように2つだけでなく、3つのINDArrayIndexオブジェクトを提供します。NDArrayIndex.interval(...)、.all()、.point(int)のメソッドは、常に下部にある配列のビューを返すということにご注意ください。したがって、.get()が返す配列はオリジナルの配列に反映されます。Put:同じNDArrayIndexアプローチ方法は、要素を別の配列に入れるためにも使用されます。toPut)メソッドを使用します。明らかに、NDArrayのtoPutのサイズは提供されたインデックスが示唆するサイズと一致していなければなりません。other)と同じであるということにご注意ください。これについても理由は.get(INDArrayIndex...)は下部にある配列の複製でなくビューを返すからです。dimension(次元に沿ったテンソル)メソッドに少し異なる結果を返します。dimensionはパワフルなテクニックですが、最初は理解するのに時間が掛かるかもしれません。このメソッド(以下、TAD)の背景にある考えは、オリジナルの配列のビューである低いランクのサブ配列を得ることです。このメソッドは以下の二つの引数を使用します。返すテンソルのインデックス(0からnumTensors-1までの範囲)TAD演算を行う次元(1以上の値)最もシンプルなケースは、2次元配列の1行または1列に沿ったテンソルです。以下の図で考えてみましょう。(次元0(行)はインデックスが縦に並び、次元1(列)はインデックスが横に並んでいます。)ここで、次元が1つであるtensorAlongDimensionの呼び出しによる出力は、すべての場合において行ベクトルであることに注意してください。このような出力なのかを理解するには、上図の最初のケースについて考えてみましょう。0番目(最初)のテンソルを次元0に沿って取っていきます(次元0は行)。値の(1,5,2)は、次元0に沿って移動すると一直線に並んでいます。これが出力となります。同様に、tensorAlongDimension(1,1)は、次元1に沿った2番目の(インデックス=1)テンソルです。値の(5,3,5)は次元1に沿って移動すると一直線に並んでいます。TAD演算は複数の次元でも実行可能です。TAD演算を行う2つの次元を指定すると、3次元(または4次元、5次元)配列から2次元のサブ配列を得ることができます。同様に、3次元を指定すると、4次元やそれ以上の配列から3次元を得ることができます。TAD演算を役立てるには、出力について知っておくべきことが2つあります。最初に、対象とする次元から得られるテンソルの数を知る必要があります。dimensions)を使用することができます。このメソッドは、単に指定された次元に沿ったテンソル数を返すものです。上記の例では、以下のようになります。1(後者の2つの例の場合は、次元に沿ったテンソルは中にあるオリジナルの配列と同じ配列を出します。2次元配列からは2次元の出力があります)。一般的には、テンソルの数は残りの次元の積、テンソルのシェイプは元のシェイプにある指定された次元のサイズです。以下にその例をご紹介しましょう。入力シェイプ[a,b,c]は、tensorssAlongDimension(0)によりテンソル数b*cが得られ、tensorAlongDimension(i,0)はシェイプ[1,a]のテンソルを返します。入力シェイプ[a,b,c]は、tensorssAlongDimension(1)によりテンソル数a*cが得られ、tensorAlongDimension(i,1)はシェイプ[1,b]のテンソルを返します。入力シェイプ[a,b,c]は、tensorssAlongDimension(0,1)によりテンソル数cが得られ、tensorAlongDimension(i,0,1)はシェイプ[a,b]のテンソルを返します。入力シェイプ[a,b,c]は、tensorssAlongDimension(1,2)によりテンソル数aが得られ、tensorAlongDimension(i,1,2)はシェイプ[b,c]のテンソルを返します。入力シェイプ[a,b,c,d]は、tensorssAlongDimension(1,2)によりテンソル数a*dが得られ、tensorAlongDimension(i,1,2)はシェイプ[b,c]のテンソルを返します。入力シェイプ[a,b,c,d]は、tensorssAlongDimension(0,2,3)によりテンソル数bが得られ、tensorAlongDimension(i,0,2,3)はシェイプ[a,c,d]のテンソルを返します。スライス[ただいま準備中です。]NDArrayでの演算Nd4JにはINDArrayを使って(またはINDArrayに対して)行いたい多くのことを実行するための演算の概念があります。双曲正接演算、スカラーの追加、要素ごとの演算のように、演算によって何かを適用します。ND4Jは5つの種類の演算を定義します。累積インデックス累積ブロードキャストそして以下の2つを行う方法も定義します。copy(複製)演算について考えてみましょう。多くの演算は、in-placeとcopyの両方があります。2つの配列を追加したいとしましょう。Nd4jはこれをするのに2つの方法を設定しています。INDArray.add(INDArray)とINDArray.addi(INDArray)です。前者(add)は、copy演算です。後者はin-place演算です。iは「in-place」を意味します。無いのはcopyを意味します)は、INDArrayインターフェイスでアクセス可能なその他の演算にも適用されます。x.addi(y)を行ったとしましょう。これらの演算の結果は以下の通りになります。x.add(y)演算の場合、オリジナルの配列xは変更されていません。それに対して、in-place演算のx.addi(y)の場合は配列xは変更されています。add演算の両方のバージョンにおいて、結果を含むINDArrayが返されます。addi演算の場合は、結果の配列はオリジナルの配列xに過ぎません。ops)とは、スカラー(つまり数字)も取る要素ごとの演算です。あります(上のリンクにすべてのリストしたものがあります)。INDArray.addi(Number)やINDArray.divi(Number)などの数多くの方法は、実際にスカラー演算を行います。したがって利用可能な場合はこれらの方法を使うほうが便利です。スカラー演算をより直接的に行うには、例えば以下を使用するといいでしょう。ScalarAdd(myArray,1.0))この演算でmyArrayが変更されていることに注意してください。使用したいのがこれではない場合は、myArray.dup()を使用してください。