テンソル積を理解する（numpy.tensordot）

numpyのtensordot（テンソル積）が壊滅的に分からなかったのですが、いろいろ調べてそれなりに理解できた感があるので、メモしておこうと思います。

# 今回の題材

```python
>>> import numpy as np
>>> a = np.arange(12).reshape(2,3,2)
>>> b = np.arange(48).reshape(3,2,8)
>>> c = np.tensordot(a,b, axes=([1,0], [0,1]))

>>> a
array([[[ 0,  1],
        [ 2,  3],
        [ 4,  5]],

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

>>> b
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],
        [24, 25, 26, 27, 28, 29, 30, 31]],

       [[32, 33, 34, 35, 36, 37, 38, 39],
        [40, 41, 42, 43, 44, 45, 46, 47]]])

>>> c
array([[ 800,  830,  860,  890,  920,  950,  980, 1010],
       [ 920,  956,  992, 1028, 1064, 1100, 1136, 1172]])

```

# np.tensordotでやっていること１：　行列aから中間行列を生成する

![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/c6dd373b-d06d-fbcc-36cc-e571eab8778b.jpeg)

行列aの第２次元、第１次元の順番で要素を拾い、演算用の中間行列をつくる、というイメージです。
その中間行列の形状は、第２次元数　×　第１次元数　となります。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3435353431382f64393430643066642d636365652d373531322d623936652d6638383065613963376462662e6a706567.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/3c750366-1531-63ed-e960-39e941d6db7a.png)

今回の場合は、３（第２次元数）×２（第１次元数）　の形状の、中間行列がつくられるイメージです。

![3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/a92123c1-cece-c238-74b9-d6eb91d63d89.jpeg)

![4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/97ef5377-cbba-8a13-d239-4a1227633b7d.jpeg)

どんな場合でも、左上の要素を最初に拾います。

この次にどこの要素を拾うか、となるわけですが
第２次元、第１次元の順番に拾うわけですので、
まずは第２次元の順番に拾っていきます。

![5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/423b458a-1988-3c07-4e46-c30bae2295cc.jpeg)

第２次元の順番に拾うとこうなります。

さらに、この次にどこの要素を拾うか、となるわけですが
第２次元の順番に拾いきりましたので、
次は第１次元の順番に沿って、「下」の島にジャンプします。

![6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/8187a831-ccaf-61c4-08fa-8107b7b02589.jpeg)

さきほどと同じように、「下」の島でも第２次元の順番で要素を拾います。

これで、３×２の行列ができました。

![7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/a35f69a5-41d2-66d2-8c50-74aee767cef0.jpeg)

「右」側に対しても、同様のことをやっていきます。

これで、３×２の行列の二つ目ができました。

結果、以下のような構造ができます。

![8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/ef9cba14-d3f7-2db9-c199-818624f7c2b4.jpeg)

# np.tensordotでやっていること２：　行列bから中間行列を生成する

![9.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/e2c3a263-0ece-3a36-e95b-2d7777bfefb5.jpeg)

行列bの第１次元、第２次元の順番で要素を拾い、演算用の中間行列をつくる、というイメージです。

その中間行列の形状は、第１次元数　×　第２次元数　となります。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3435353431382f62613436636630632d326366352d323535362d313130312d3830383662343962366331612e6a706567.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/3a89abd5-9526-d75e-9ffc-305c7fc531cc.png)

今回の場合は、３（第１次元数）×２（第２次元数）　の形状の、中間行列がつくられるイメージです。

![11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/523eee38-deb1-813f-815b-fc211eb8e785.jpeg)

![12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/b2105d0f-ffda-51a5-ee5c-4d14b130ff2a.jpeg)

どんな場合でも、左上の要素を最初に拾います。

この次にどこの要素を拾うか、となるわけですが
第１次元、第２次元の順番に拾うわけですので、
次は、「下」の島にジャンプしなければなりません。

![13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/c3e4f4bd-7f35-08f3-ce65-ba94efaa1d56.jpeg)

第１次元の順番に拾うとこうなります。

さらに、この次にどこの要素を拾うか、となるわけですが
第１次元の順番に拾いきりましたので、
次は第２次元の順番に沿って、「一番上」の島に戻ります。

![14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/8345197d-c7d5-e430-7c9f-4ce169fa0c1e.jpeg)

④から再出発しつつも、さきほどと同じように第１次元の順番で要素を拾います。

これで、３×２の行列ができました。

残りに対しても、同様のことをやっていきます。

３×２の行列が８つできるはずです。

結果、以下のような構造ができます。

![15.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/e4a79512-97bc-f572-0d95-d5ea136bf36f.jpeg)

# np.tensordotでやっていること３：　行列a、行列b から得られた２つの中間行列について「積」をとる

![16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/9d0556c5-8628-af38-771e-8a3f44b788a1.jpeg)

![17.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/a5c55d55-a7f7-911e-ed65-b9de6992d12b.jpeg)

![18.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/ee4bab11-9f85-569e-a52c-11c976309fc4.jpeg)

２次元　×　８次元の行列にコンパクト化されました。

# 概念的には何をやっているのか

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3435353431382f31656164613036652d663366612d393762392d363333392d3166666134353338336339342e6a706567.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/346f6127-1d3d-5a19-d54e-5bfa4a0b194f.png)

これらの次元について計算することで、これらの次元の情報を保ちつつ、これらの次元を削減し、

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3435353431382f66363639666637352d303037632d316534322d396333322d3035653630386663636638302e6a706567.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/455418/1bf8c8f7-98ab-04f8-c655-1c32d0861958.png)

残ったこれらの次元（つまり２次元　×　８次元）の形状に、コンパクト化しているわけです。

こういった意味で、このような操作を縮約と呼ぶようですね。
「縮小　＋　要約」　ということだと解釈してます。

テンソル積というのはこういうことみたいですね。