こしあん
2020-05-17

テンソル同士の行列積を計算するにはnp.dot?np.matmul?np.tensordot?

Pocket
LINEで送る
Delicious にシェア

1.2k{icon} {views}



テンソルと行列、テンソルとテンソルの積について、どの使えばいいのか(np.dot, np.matmul, np.tensordot)わからなくなることがあります。アフィン変換の例を通じてどの関数を使えばいいのか見ていきます。

背景

アフィン変換同士の合成は行列積で表されます。しかし、複数のアフィン変換の行列(アフィン変換のテンソル)を、同時に合成したいときはどの関数を使って合成すればよいのでしょうか?

関連:tf.tensordotで行列積を表現するための設定

アフィン変換

アフィン変換の行列について振り返ると、xy座標におけるアフィン変換の行列は3×3行列で表されます。これは変換が1個しかないようなケースです。

例えば、画像をx軸方向にa倍, y軸方向にb倍拡大する処理は、

$$A_1 = \begin{bmatrix} a & 0 & 0 \\ 0 & b & 0 \\ 0 & 0 & 1\end{bmatrix}$$

という行列で表されます。この変換を任意の点の集合(3xN 行列)

$$P=\begin{bmatrix} x_1 & x_2 & \cdots & x_N \\ y_0 & y_1 & \cdots & y_N \\ 1 & 1 & \cdots & 1 \end{bmatrix}$$

というに対して行うときは、変換後の点は、

$$P’=A_1P$$

という行列積を取れば良いです。P’は3xN行列になります。変換後のx,y座標を見たいときは、3行目の1の羅列を無視してください。この1は行列積を取る都合上入れているものです。

行列の概念を使うことで、複数の点に対して同一の変換を一気にかけることができるのがアフィン変換の強みです(これはGPUを使った処理では非常に有利に機能します)。

アフィン変換の合成

アフィン変換は拡大・縮小だけではありません。平行移動、せん断、回転もできます。x方向にc、y方向にdだけ平行移動したいときは、

$$A_2 = \begin{bmatrix} 1 & 0 & c \\ 0 & 1 & d \\ 0 & 0 & 1\end{bmatrix}$$

という変換の行列になります。変換後の点の求め方は拡大のケースと同じです。

アフィン変換のすごいところは、複数の変換の合成を行列積で簡単にできるということです(詳細は割愛しますが逆変換も逆行列を取るだけでいいのでとても楽です)。例えば$A_1$と$A_2$の合成変換は、

$$A=A_1A_2$$

という行列積を取ると、合成されたアフィン変換の行列になります。

もしこれをアフィン変換に使わないなら、「x方向に2倍拡大して、横に50ピクセル移動して……あれ、何やってたんだっけ?」とバグの温床になりがちです。つまり、コードを簡潔にするためにも行列計算一本で統一できるのならそちらのほうが良いというメリットがあります。

複数のアフィン変換を同時に計算できるように拡張

今アフィン変換の行列が1種類でしたが、アフィン変換とはただの行列積なので、複数のアフィン変換を複数の点に対して同時に行って計算するということができます。

複数のアフィン変換とはどういうことかというと、shapeは何でもよいですが、x方向に5個、y方向に5個異なるアフィン変換があったとします。つまり、3×3の行列が5×5個あるということになります。全体のアフィン変換のテンソル(行列)を「$T_1$:(5, 5, 3, 3)」というshapeにします。

点の集合Pは「(3, N)」という行列のままです。

また、アフィン変換のテンソル同士の合成をしたいので、$T_1$と同じshapeの「$T_2$:(5, 5, 3, 3)」というテンソルを用意します。

ここで問題です。

  1. (5, 5, 3, 3)と(5, 5, 3, 3)のshapeのテンソル同士の積を取り、(5, 5, 3, 3)というテンソルを返すにはどういう関数を使えばよいでしょう?
  2. (5, 5, 3, 3)と(3, N)のshapeのテンソル同士の積を取り、(5, 5, 3, N)というテンソルを返すにはどういう関数を使えばよいでしょう?

一般化して考える

アフィン変換を離れて一般的な4階テンソル同士、テンソルと行列の積の問題として考えてみます。1, 2は、

  1. (a, b, c, d)と(a, b, d, e)の積を取り、(a, b, c, e)を返すには?
  2. (a, b, c, d)と(d, N)の積を取り、(a, b, c, N)を返すには?

愚直にforループで書くと次のようになります。

# t1 : (a, b, c, d), t2: (a, b, d, e), p:(d, N)
output1 = np.zeros((a, b, c, e))
output2 = np.zeros((a, b, c, N))

for i in range(a):
    for j in range(b):
        output1[i, j, :, :] = np.dot(t1[i, j, :, :], t2[i, j, :, :])
        output2[i, j, :, :] = np.dot(t1[i, j, :, :], p)

np.matmulで良い

結論を先にいうとnp.matmulを使うのが最もわかりやすく単純です。$a=6, b=5, c=4, d=3, e=2, N=10$としてみましょう。アフィン変換の前提を無視してt1, t2, pを全て乱数で初期化して計算すると、

t1 = np.random.randn(6, 5, 4, 3)
t2 = np.random.randn(6, 5, 3, 2)
p = np.random.randn(3, 10)
print(np.matmul(t1, t2).shape) # (6, 5, 4, 2)
print(np.matmul(t1, p).shape) # (6, 5, 4, 10)

となり、期待通りの結果になりました。np.matmulを使えばよいのです。

np.dotの場合

np.matmulではなく、np.dotを使った場合どうかというと、テンソルの場合の計算結果が変わります。

t1 = np.random.randn(6, 5, 4, 3)
t2 = np.random.randn(6, 5, 3, 2)
p = np.random.randn(3, 10)
print(np.dot(t1, t2).shape) # (6, 5, 4, 6, 5, 2)
print(np.dot(t1, p).shape) # (6, 5, 4, 10)

matmulの場合はテンソル同士の積は4階テンソルだったのに、dotの場合は6階テンソルになってしまいました。これは、

  • (6, 5, 4, 3)の3に注目
  • (6, 5, 3, 2)の3に注目

それぞれの積和を取って(6, 5, 4) + (6, 5, 2)→(6, 5, 4, 6, 5, 2)となったわけです。テンソルと行列の場合は同じことをやってもmatmulと同じ結果になります。

np.tensordotの場合

np.tensordotはdotの拡張版です、先程のdotのケースをtensordotで書くと次のようになります。

t1 = np.random.randn(6, 5, 4, 3)
t2 = np.random.randn(6, 5, 3, 2)
p = np.random.randn(3, 10)
print(np.tensordot(t1, t2, [-1, -2]).shape) # (6, 5, 4, 6, 5, 2)
print(np.tensordot(t1, p, [-1, -2]).shape) # (6, 5, 4, 10)

「-1, -2」は消す(和を取る)軸の指定です。tensordotの場合、消す軸を指定することができます。np.dotなら転置しないといけないケース、例えば、

t1 = np.random.randn(6, 5, 4, 3)
t2 = np.random.randn(6, 5, 2, 3) 
print(np.dot(t1, t2.swapaxes(-1, -2)).shape) # (6, 5, 4, 6, 5, 2)
print(np.tensordot(t1, t2, [-1, -1]).shape) # (6, 5, 4, 6, 5, 2)

は同じ結果で、tensordotならswapaxesなしでもdotを取れます。swapaxesなしでdotを取るとエラーになります。また、軸が複数のケースもOKで、

t1 = np.random.randn(6, 5, 3, 4)
t2 = np.random.randn(6, 5, 4, 3)
print(np.tensordot(t1, t2, [[-1, -2], [-2, -1]]).shape) # (6, 5, 6, 5)

となります。「具体的にどう使うんだ?」というのはピンとこないですけどね。

まとめ

「アフィン変換をテンソルで計算したい場合はとりあえずnp.matmulを覚えておけばよさそう」ということでした。



Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

【新刊】インフィニティNumPy――配列の初期化から、ゲームの戦闘、静止画や動画作成までの221問

「本当の実装力を身につける」ための221本ノック――
機械学習(ML)で避けて通れない数値計算ライブラリ・NumPyを、自在に活用できるようになろう。「できる」ための体系的な理解を目指します。基礎から丁寧に解説し、ディープラーニング(DL)の難しいモデルで遭遇する、NumPyの黒魔術もカバー。初心者から経験者・上級者まで楽しめる一冊です。問題を解き終わったとき、MLやDLなどの発展分野にスムーズに入っていけるでしょう。

本書の大きな特徴として、Pythonの本でありがちな「NumPyとML・DLの結合を外した」点があります。NumPyを理解するのに、MLまで理解するのは負担が大きいです。本書ではあえてこれらの内容を書いていません。行列やテンソルの理解に役立つ「従来の画像処理」をNumPyベースで深く解説・実装していきます。

しかし、問題の多くは、DLの実装で頻出の関数・処理を重点的に取り上げています。経験者なら思わず「あー」となるでしょう。関数丸暗記では自分で実装できません。「覚える関数は最小限、できる内容は無限大」の世界をぜひ体験してみてください。画像編集ソフトの処理をNumPyベースで実装する楽しさがわかるでしょう。※紙の本は電子版の特典つき

モザイク除去から学ぶ 最先端のディープラーニング

「誰もが夢見るモザイク除去」を起点として、機械学習・ディープラーニングの基本をはじめ、GAN(敵対的生成ネットワーク)の基本や発展型、ICCV, CVPR, ECCVといった国際学会の最新論文をカバーしていく本です。
ディープラーニングの研究は発展が目覚ましく、特にGANの発展型は市販の本でほとんどカバーされていない内容です。英語の原著論文を著者がコードに落とし込み、実装を踏まえながら丁寧に解説していきます。
また、本コードは全てTensorFlow2.0(Keras)に対応し、Googleの開発した新しい機械学習向け計算デバイス・TPU(Tensor Processing Unit)をフル活用しています。Google Colaboratoryを用いた環境構築不要の演習問題もあるため、読者自ら手を動かしながら理解を深めていくことができます。

AI、機械学習、ディープラーニングの最新事情、奥深いGANの世界を知りたい方にとってぜひ手にとっていただきたい一冊となっています。持ち運びに便利な電子書籍のDLコードが付属しています。

「おもしろ同人誌バザールオンライン」で紹介されました!(14:03~) https://youtu.be/gaXkTj7T79Y?t=843

まとめURL:https://github.com/koshian2/MosaicDeeplearningBook
A4 全195ページ、カラー12ページ / 2020年3月発行

Shikoan's ML Blog -Vol.1/2-

累計100万PV超の人気ブログが待望の電子化! このブログが電子書籍になって読みやすくなりました!

・1章完結のオムニバス形式
・機械学習の基本からマニアックなネタまで
・どこから読んでもOK
・何巻から読んでもOK

・短いものは2ページ、長いものは20ページ超のものも…
・通勤・通学の短い時間でもすぐ読める!
・読むのに便利な「しおり」機能つき

・全巻はA5サイズでたっぷりの「200ページオーバー」
・1冊にたっぷり30本収録。1本あたり18.3円の圧倒的コストパフォーマンス!
・文庫本感覚でお楽しみください

北海道の駅巡りコーナー

日高本線 車なし全駅巡り

ローカル線や秘境駅、マニアックな駅に興味のある方におすすめ! 2021年に大半区間が廃線になる、北海道の日高本線の全区間・全29駅(苫小牧~様似)を記録した本です。マイカーを使わずに、公共交通機関(バス)と徒歩のみで全駅訪問を行いました。日高本線が延伸する計画のあった、襟裳岬まで様似から足を伸ばしています。代行バスと路線バスの織り成す極限の時刻表ゲームと、絶海の太平洋と馬に囲まれた日高路、日高の隠れたグルメを是非たっぷり堪能してください。A4・フルカラー・192ページのたっぷりのボリュームで、あなたも旅行気分を漫喫できること待ったなし!

見どころ:日高本線被災区間(大狩部、慶能舞川橋梁、清畠~豊郷) / 牧場に囲まれた絵笛駅 / 窓口のあっただるま駅・荻伏駅 / 汐見の戦争遺跡のトーチカ / 新冠温泉、三石温泉 / 襟裳岬

A4 全192ページフルカラー / 2020年11月発行


Pocket
LINEで送る
Delicious にシェア

Add a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です