Python
numpy
線形代数

Numpyで行列同士の内積を計算するには

はじめに

pythonのNumpyは便利です。数学系や科学系計算はnumpyの関数やscipyの関数を良く使います。
そのため、numpy.arrayやnumpy.ndarrayを直接使って線形代数の演算をすることがなかったのですが、いざ自前で演算しようとすると、なんか変な感じがします。

結論

  • 行列同士の内積を計算する関数は用意されていない。
  • numpy.dotは数学における内積の計算と等価ではないので注意が必要。
  • 行列同士の積はアダマール積や、ベクトル同士の内積を集約した結果のいずれかで、どちらになるかは型と演算子に依存する。

行列

numpy.ndarrayは行列ではなく多次元配列のようです。Numpyにおける真の行列はnumpy.matrixのようです。

ベクトル

numpy.arrayは一次元配列です。ベクトルのように扱えます。numpy.vectorなんて型は存在しません。
代わりにかどうかわかりませんが、おそらく、numpy.matrixの行か列のいずれかが1の場合をベクトルとしてみなして扱えるようです。

行列同士の積

積は「*」「numpy.multiply」の二種類あります。
numpy.ndarray同士の演算結果はnumpy.ndarrayになります。
また、numpy.matrix同士の演算結果はnumpy.matrixになります。
一方で、numpy.ndarrayとnumpy.matrixの演算結果はnumpy.matrixになるようです。

さらに、numpy.ndarrayの積は「*」「numpy.multiply」はどちらもアダマール積になります。行列の積にはなりません。

一方、numpy.matrixの場合、「*」は行列の積、「numpy.multiply」はアダマール積になります。

表にまとめます。

* numpy.multiply
numpy.ndarray アダマール積 アダマール積
numpy.matrix 行列同士の積 アダマール積

行列とベクトルの積

numpy.ndarrayとnumpy.arrayの「 * 」及び「numpy.multiply」演算は、やはりアダマール積になります。
一方、ベクトル表現されたnumpy.matrixとの「*」演算は、行列とベクトルの積になり、結果はnumpy.matrixです。ここで、二項演算子の左辺値がかける数、右辺値がかけられる数になっているようで、状況に応じてベクトル側の転置が必要になります。
また、「numpy.multiply」演算は、やはりアダマール積になります。

行列同士の内積

numpy.dotが内積計算のはずですが、numpy.ndarrayに対するnumpy.dot演算は、行列同士の積になります。
また、numpy.matrix行列に対するnumpy.dot演算も、行列同士の積になり、結果は行列です。内積の結果はスカラーになると思っていたのですが、何か変な感じです。

ベクトル同士の内積

numpy.arrayに対するnumpy.dot演算は、確かに内積になっているようです。値の型もnumpy.float64やnumpy.int64で返ってきます。
一方、numpy.matrixベクトルの場合は、いわゆる縦ベクトルと横ベクトルの場合にだけ演算が成功し、内積を返します。ただし、内積の結果がスカラーではなく、1x1の行列で得られます。

Numpyの内積演算が何をしているのか?

numpy.dotはどうやらベクトル同士の内積を計算しているだけのようです。そのため、行列同士の内積をnumpy.dotで求めることはできません。
行列同士の積は、横ベクトルと縦ベクトルの内積の結果を並べた結果になるので、numpy.dotがベクトル同士の内積を求めているだけだと解釈すれば、numpy.matrix行列に対するnumpy.dotの結果が行列同士の内積ではなく行列同士の積になる事に合点が行きます。

つまり、積の演算が行なっている本当の処理は、以下の表の通りです。

numpy.dot * numpy.multiply
numpy.ndarray ベクトルの内積 アダマール積 アダマール積
numpy.matrix ベクトルの内積 ベクトルの内積 アダマール積

まとめてみると、型によって意味が異なるのは「*」演算のみで、内積ならnumpy.dot、アダマール積ならnumpy.multiplyを使っておけば間違いなさそうですね。

行列同士の内積を求めるには?

行列同士の内積はベクトルの内積同様、対応する各要素同士の積の総和です。
つまり、アダマール積であるnumpy.multiplyと総和であるnumpy.sumを組み合わせて求めることができます。

おわりに

行列同士の内積を求める、より良い方法があれば教えてください。

追記

「@」演算子

Python 3.5から@演算子が行列積の演算子として追加されたみたいです。
「@」演算子はnumpy.dotと同じ演算を行います。「@」演算子を加えた表は以下のとおりです。

numpy.dot @ * numpy.multiply
numpy.ndarray ベクトルの内積 ベクトルの内積 アダマール積 アダマール積
numpy.matrix ベクトルの内積 ベクトルの内積 ベクトルの内積 アダマール積

numpy.arrayであれば、ベクトルの内積なら「@」、アダマール積なら「*」を使えば記述も簡略できて良いですね。

numpy.matrixはいらない子だったのでは……。

型について

numpy.ndarray型は存在しないので、前述の内容でnumpy.ndarrayと説明している箇所は、すべてnumpy.arrayの誤りです。

なお、numpy.arrayは多次元配列を扱うことができますが、numpy.matrixはあくまで二次元配列(つまり平易な行列)しか扱えません。
そのため、numpy.arrayの方がnumpy.matrixより汎用性があります。

やはりnumpy.matrixはいらない子だったのでは……。

行列のN乗

分野によっては行列をN乗したい事があります。例えば漸化式を計算する時とかです。
pythonでは「**」演算子でZ乗を計算できます。一方で、「@@」という演算子は定義されていないようです。

前述の通り、「*」演算子はnumpy.arrayにおいてはアダマール積、numpy.matrixにおいては行列積です。
そのためかどうかわかりませんが、numpy.arrayにおける「**」演算子は要素のZ乗、numpy.matrixにおいては行列積によるZ乗です。
また、numpy.powerを使った場合、numpy.arrayとnumpy.matirxのいずれに対しても、要素のR乗になるようです。

ここで、Zは整数、Rは実数です。
そのため、numpy.matrixで定義した行列Mに対するM ** -1という演算は、行列Mの逆行列を計算する事になります。

なお、numpy.arrayに対する「**」演算子ですが、numpy.arrayの要素が整数型の場合、負数による累乗を計算できないようです。単なるスカラーの整数型であれば計算できるのですが……。

おそらくですが、numpy.arrayは型の同一性を保つために、あえて負値の累乗を禁止しているのではないでしょうか。numpy.matrixにはそんな制約は無いようですので、2次元配列までの行列を扱う場合は、numpy.matrixを使う方が記述しやすいかもしれません。

numpy.matrixの面目躍如ですね!