numpyの内積(5)テンソル積


2023年 10月 12日

テンソル

前回3次元配列について書いたが、一般にn次元配列というのが考えられる。

そして、こういう多次元配列のことをテンソルといい、次元数を階数(order)という。つまり、

  • スカラー:0階テンソル
  • ベクトル:1階テンソル
  • 行列  :2階テンソル
  • n次元配列:n階テンソル

という風になり、階数の違いだけとなる。要するに、一般化である。

そして、これらのテンソル間でいろいろな演算ができる。

テンソルはいろいろな分野で使われている。TensorFlowのテンソルがそうであり、数理物理学の分野では頻繁にお目にかかるものであるが、ここでは深入りはしない。

実験の続き

さて、numpyの実験に戻ろう。
np.dot()で、多次元の配列(テンソル)の積が求まったが、np.tensordot()が用意されているので、これで同じ計算ができないか試してみよう。

>>> np.dot(M64,M342)
array([[[  50,   60],
        [ 130,  140],
        [ 210,  220]],

       [[ 114,  140],
        [ 322,  348],
        [ 530,  556]],

       [[ 178,  220],
        [ 514,  556],
        [ 850,  892]],

       [[ 242,  300],
        [ 706,  764],
        [1170, 1228]],

       [[ 306,  380],
        [ 898,  972],
        [1490, 1564]],

       [[ 370,  460],
        [1090, 1180],
        [1810, 1900]]])
>>> np.tensordot(M64,M342,axes=((1),(1)))
array([[[  50,   60],
        [ 130,  140],
        [ 210,  220]],

       [[ 114,  140],
        [ 322,  348],
        [ 530,  556]],

       [[ 178,  220],
        [ 514,  556],
        [ 850,  892]],

       [[ 242,  300],
        [ 706,  764],
        [1170, 1228]],

       [[ 306,  380],
        [ 898,  972],
        [1490, 1564]],

       [[ 370,  460],
        [1090, 1180],
        [1810, 1900]]])

np.dot()と同じ結果がnp.tensordot()でも得られた。

np.tensordot()には、2つのテンソルの指定に続いて積和を計算するを指定するようになっている。
軸とは、n階テンソルの何階目を積和の計算に使うかの指定のことである。

上の例では、M64(6×4のテンソル)とM345(3×4×5のテンソル)の積和を取るため、どちらも同じサイズの4の軸を指定するため、axis=((1),(1))と指定した。

3階テンソル同士のnp.tensordot()をやってみよう。

>>> N432 = np.array(range(0,24)).reshape(4,3,2); N432
array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5]],

       [[ 6,  7],
        [ 8,  9],
        [10, 11]],

       [[12, 13],
        [14, 15],
        [16, 17]],

       [[18, 19],
        [20, 21],
        [22, 23]]])
>> np.tensordot(M342,N432,axes=((0),(1)))
array([[[[  86,  113],
         [ 248,  275],
         [ 410,  437],
         [ 572,  599]],

        [[  92,  122],
         [ 272,  302],
         [ 452,  482],
         [ 632,  662]]],


       [[[  98,  131],
         [ 296,  329],
         [ 494,  527],
         [ 692,  725]],

        [[ 104,  140],
         [ 320,  356],
         [ 536,  572],
         [ 752,  788]]],


       [[[ 110,  149],
         [ 344,  383],
         [ 578,  617],
         [ 812,  851]],

        [[ 116,  158],
         [ 368,  410],
         [ 620,  662],
         [ 872,  914]]],


       [[[ 122,  167],
         [ 392,  437],
         [ 662,  707],
         [ 932,  977]],

        [[ 128,  176],
         [ 416,  464],
         [ 704,  752],
         [ 992, 1040]]]])
>>> np.shape(np.tensordot(M342,N432,axes=((0),(1))))
(4, 2, 4, 2)

上の例では、M342(3×4×2のテンソル)とN432(4×3×2のテンソル)の積和を取るため、どちらも同じサイズの3の軸を指定するため、axis=((0),(1))と指定した。

結果は、サイズが(4, 2, 4, 2)の4階テンソルになった。

np.tensordot()では、積和は何度も行える。それぞれのテンソルの軸のサイズが同じ同士なら計算可能である。

2つの3階テンソル(M342,N432)に対して、2つ軸を指定してnp.tensordot()を行ってみた。

サイズ4とサイズ3の軸を指定したので、2つのテンソルの残りの軸のサイズはいずれも2なので、結果は2×2のテンソル(行列)になった。

>>> np.tensordot(M342,N432,axes=((0,1),(1,0)))
array([[1892, 2036],
       [2024, 2180]])
>>> np.tensordot(M342,M432,axes=((1,0),(0,1)))
array([[2036, 2180],
       [2180, 2336]])

上記は、軸の順序を入れ替えて行っている。結果は、異なっている。

軸の指定順序を変更したら、結果は一致しなくなる。

ちょっと深堀りし過ぎてテンソルの話を書いたが、通常のプログラミングで使うことは稀だと思うので、この辺で終わりにする。

(完)