はじめに
MATLABでは配列を*
演算子で掛け算させると「行列の内積」になってしまう、これはMATLAB入門でよく注意事項として挙げられることですね。
ar1 = [1 2 3
4 5 6];
ar2 = [1 2
3 4
5 6];
ar = ar1*ar2
ar =
22 28
49 64
そのため普通の要素ごとの掛け算が欲しい場合は.*
を書けなければならないのは厄介なことです。
詳しくはこの基本の記事にも書いてあります。
でも逆にいうと、内積の計算をしたい時にただ*
でけかけばよくて簡潔で書くことができて便利です。
Pythonではただの演算子ではこのような計算ができず、np.dot
などの関数を使わなければならないのだから。
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次元行列でも簡単にできるから便利です。
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
と違って、同じような計算をしようとしたらエラーになります。
ar1 = rand(5,4,3);
ar2 = rand(3,2);
ar = dot(ar1,ar2)
次を使用中のエラー: dot (行 37)
A と B は同じサイズでなければなりません。
だから同じ名前だからって使い方が同じではないので、無闇に同じような使い方をしようとしても失敗するから注意が必要です。
ではMATLABで同じようなことはどうしたらいいか、次に紹介していきます。
3次元以上の配列の内積を計算するMATLABの関数
次は早速本題に入ります。
実は昨日数時間をかけてgoogleなどで3次元の配列の内積を簡単に計算できる方法を調べてみたのですが、満足できる方法はありませんでした。
結局自分で思いついた限りの一番いい方法で書くことになりました。それはそのような関数を自分で作ることです。
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行でできます。
そしてこうやって使えるようになりました。
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
を使うという方法です。
この関数は指定した軸の間の内積を計算してくれる関数です。
以上の例ではtensorprod
に書き換えればこうなります。
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
関数を書き直したらいいです。
function ar = npdot(ar1,ar2)
ar = tensorprod(ar1,ar2,ndims(ar1),1);
end
これは上述で定義したnpdot
と同じ結果になりますが、簡潔で書けますね。
使い所
結局こんな計算ができて何が嬉しいの?そのような疑問をする人もいるでしょう。
まず今一番重要な例はやはり深層学習でしょう。ニューラルネットワークの中の計算は内積が不可欠です。普段深層学習の実装はライブラリーで行われるのは殆どであまり意識していないかもしれませんが、そのライブラリーの中身の計算は沢山の内積で構成されています。
その他にも例えば3Dアニメーションで「回転行列」による物体の回転です。
例えばこのようなサーフェスがあります。
[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
これを回転行列と内積することで回転してアニメーションを作ることができます。
% 内積計算の関数
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
本当にちゃんと回転しています!そのための内積計算です。
MATLABにおける3Dに関して興味あればこの記事を読んでください。
終わりに
以上、このような機械学習や3Dアニメーションでもよく使われる内積計算の実装の方法でした。
MATLABはよく使われる数学計算の関数が整っていて数学計算としては優秀なプログラミング言語です。Pythonと比べて同じような関数があったりなかったりしますが、もし同じような関数がなかったとしても、欲しいなら自分で簡単に作ることができるでしょう。そうすることで色々勉強にもなりますし。