3次元以上の配列の内積をMATLABで計算する方法（Pythonでのnp.dot関数）

# はじめに

MATLABでは配列を`*`演算子で掛け算させると「行列の内積」になってしまう、これはMATLAB入門でよく注意事項として挙げられることですね。
```matlab
ar1 = [1 2 3
       4 5 6];
ar2 = [1 2
       3 4
       5 6];
ar = ar1*ar2
```
```:結果
ar =

    22    28
    49    64
```

そのため普通の要素ごとの掛け算が欲しい場合は`.*`を書けなければならないのは厄介なことです。

詳しくはこの基本の記事にも書いてあります。

https://qiita.com/phyblas/items/3a24b0e3745156a78d6b

でも逆にいうと、内積の計算をしたい時にただ`*`でけかけばよくて簡潔で書くことができて便利です。

Pythonではただの演算子ではこのような計算ができず、`np.dot`などの関数を使わなければならないのだから。
```python
import numpy as np
ar1 = np.array([[1,2,3],
                [4,5,6]])
ar2 = np.array([[1,2],
                [3,4],
                [5,6]])

ar = np.dot(ar1,ar2)
print(ar)
```


だからMATLABで行列の計算を書きやすい、とい思っていたのですが……どうやらそうでもないようです。

なぜならこの便利そうな`*`演算子は2次元の行列の間でしか使えないのですから。

書きやすそうに見えるのに、案外使い勝手がよくなくて使い所が少ないのです。

では3次元の行列の内積が欲しい時はどうしたらいいでしょう？

Pythonなら`np.dot`は3次元行列でも簡単にできるから便利です。
```python
import numpy as np

ar1 = np.ones([5,4,3])
ar2 = np.ones([3,2])
ar = np.dot(ar1,ar2)
print(ar.shape) # (5, 4, 2)
```

MATLABでも一応`dot`という関数があって内積の計算に使うのですが、動作はPythonの`np.dot`と違って、同じような計算をしようとしたらエラーになります。
```matlab
ar1 = rand(5,4,3);
ar2 = rand(3,2);
ar = dot(ar1,ar2)
```
```:結果
次を使用中のエラー: dot (行 37)
A と B は同じサイズでなければなりません。
```

だから同じ名前だからって使い方が同じではないので、無闇に同じような使い方をしようとしても失敗するから注意が必要です。

ではMATLABで同じようなことはどうしたらいいか、次に紹介していきます。


# 3次元以上の配列の内積を計算するMATLABの関数

次は早速本題に入ります。

実は昨日数時間をかけてgoogleなどで3次元の配列の内積を簡単に計算できる方法を調べてみたのですが、満足できる方法はありませんでした。

結局自分で思いついた限りの一番いい方法で書くことになりました。それはそのような関数を自分で作ることです。
```matlab
function ar = npdot(ar1,ar2)
    s1 = size(ar1);
    s2 = size(ar2);
    ar = reshape(reshape(ar1,[],s1(end))*ar2,[s1(1:end-1) s2(end)]);
end
```

考え方としては、「`*`演算子は2次元配列にしか使えないから、まず2次元に変換して、計算した後戻せばいい」です。

結局簡潔とは言い難いですが、3行でできます。

そしてこうやって使えるようになりました。
```matlab
ar1 = rand(5,4,3);
ar2 = rand(3,2);
ar = npdot(ar1,ar2);
size(ar) % 5     4     2

ar1 = rand(4,5,6,7);
ar2 = rand(7,8);
ar = npdot(ar1,ar2);
size(ar) % 4     5     6     8
```

しかしその後、Twitterでこれについて呟いたら@eigsさんからアドバイスを頂いて、もっと簡潔な書き方を教えていただきました。

それは[`tensorprod`](https://jp.mathworks.com/help/matlab/ref/tensorprod.html)を使うという方法です。

この関数は指定した軸の間の内積を計算してくれる関数です。

以上の例では`tensorprod`に書き換えればこうなります。
```matlab
ar1 = rand(5,4,3);
ar2 = rand(3,2);
ar = tensorprod(ar1,ar2,3,1);
size(ar) % 5     4     2

ar1 = rand(4,5,6,7);
ar2 = rand(7,8);
ar = tensorprod(ar1,ar2,4,1);
size(ar) % 4     5     6     8
```

内積を行う2つの配列の軸を指定しなければならないのはちょっとわかりにくいかもしれませんが、`np.dot`でやる計算は基本的に1つ目の配列の最後の軸と2つ目の配列の最初の軸が対象なので、`tensorprod`を使って新しい`npdot`関数を書き直したらいいです。
```matlab
function ar = npdot(ar1,ar2)
    ar = tensorprod(ar1,ar2,ndims(ar1),1);
end
```

これは上述で定義した`npdot`と同じ結果になりますが、簡潔で書けますね。


# 使い所

結局こんな計算ができて何が嬉しいの？そのような疑問をする人もいるでしょう。

まず今一番重要な例はやはり深層学習でしょう。ニューラルネットワークの中の計算は内積が不可欠です。普段深層学習の実装はライブラリーで行われるのは殆どであまり意識していないかもしれませんが、そのライブラリーの中身の計算は沢山の内積で構成されています。

その他にも例えば3Dアニメーションで「回転行列」による物体の回転です。

例えばこのようなサーフェスがあります。
```matlab
[mx,my] = meshgrid(-1:0.05:1);
mz = mx.^2*0.3-my.^2*0.3+rand(size(mx))*0.02;
surf(mx,my,mz)
light(Position=[2 4 2])
axis equal
```

![meshia.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/886b6c1e-6485-d83a-625c-d14453e7f2da.jpeg)

これを回転行列と内積することで回転してアニメーションを作ることができます。

```matlab
% 内積計算の関数
function ar = npdot(ar1,ar2)
    ar = tensorprod(ar1,ar2,ndims(ar1),1);
end

theta = -10; % 回転する角度
% x軸の回転行列
rotx = [1           0            0
        0 cosd(theta) -sind(theta)
        0 sind(theta)  cosd(theta)];
% z軸の回転行列
rotz = [cosd(theta) -sind(theta) 0
        sind(theta)  cosd(theta) 0
                  0            0 1];

[mx,my] = meshgrid(-1:0.05:1);
mz = mx.^2*0.3-my.^2*0.3+rand(size(mx))*0.02;
mc = my/2+0.5;
mxyz = cat(3,mx,my,mz);

for i = 1:18
    surf(mxyz(:,:,1),mxyz(:,:,2),mxyz(:,:,3),mc)
    axis equal
    xlim([-1,1])
    ylim([-1,1])
    zlim([-1,1])
    light(Position=[2 1 2])
    light(Position=[-4 3 -2])
    exportgraphics(gcf,'meshanim.gif',Append=i~=1,Resolution=100);
    % 内積計算で回転する
    if i<8 % まずはx軸
        mxyz = npdot(mxyz,rotx);
    else % 後はz軸
        mxyz = npdot(mxyz,rotz);
    end
end
```

![meshanim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/58e7e99c-4569-2602-1a70-9856de944cb2.gif)

本当にちゃんと回転しています！そのための内積計算です。

MATLABにおける3Dに関して興味あればこの記事を読んでください。

https://qiita.com/phyblas/items/150a34293df36b8da313


# 終わりに

以上、このような機械学習や3Dアニメーションでもよく使われる内積計算の実装の方法でした。

MATLABはよく使われる数学計算の関数が整っていて数学計算としては優秀なプログラミング言語です。Pythonと比べて同じような関数があったりなかったりしますが、もし同じような関数がなかったとしても、欲しいなら自分で簡単に作ることができるでしょう。そうすることで色々勉強にもなりますし。