NumPyは高度な科学技術計算をPythonで容易にできるようにしているライブラリなので、基本的な行列やベクトル演算は自分で実装することなく標準ライブラリのように使用することができます。

ドット積もそのうちの1つです。np.dot関数を使用することで、簡単にベクトルの内積や行列の積を計算することができます。

この記事を通して

  • ベクトルの内積や行列の積について
  • np.dot関数の使い方

を理解できるように解説していきます。

数学のおさらい

ベクトルの内積

ベクトルの内積は、各要素の積を全て足し合わせたスカラー量に相当するものです。以下のような2つのベクトル \vec{a}, \vec{b} を設定した時、内積は \vec{a} \cdot \vec{b} と表すことができます。

\vec{a} = \left( \begin{array}{c} a_1 \\ a_2 \\ \vdots \\ a_n \end{array} \right) ,\vec{b} = \left( \begin{array}{c} b_1 \\ b_2 \\ \vdots \\ a_n \end{array} \right ) \\ \\ \vec{a} \cdot \vec{b} = a_1*b_1 + a_2*b_2 + \cdots + a_n*b_n

行列の積

行列の積を計算すると、行列の中にある行ベクトルと列ベクトルとの内積を要素とする行列が新たに作り出されます。行列の積を計算する条件として、左側からかける行列の列数(横方向の要素数)と右側からかける行列の行数(縦方向の要素数)とが一致している必要があります。

行ベクトルと列ベクトルとの内積

\left(\begin{array}{c}a_1, a_2 , \cdots, a_n\end{array}\right) \cdot \left( \begin{array}{c} b_1 \\ b_2 \\ \vdots \\ a_n \end{array} \right ) = (a_1*b_1 + a_2*b_2 + \cdots + a_n*b_n)

行列の積

A = \left( \begin{array}{cc} a_{11} & a_{12} \\ a_{21} & a_{22} \end{array} \right)\\ B = \left( \begin{array}{cc} b_{11} & b_{12} \\ b_{21} & b_{22} \end{array} \right ) \\ \\ A \cdot B = \left( \begin{array}{cc} a_{11}*b_{11} + a_{12}*b_{21} & a_{11}*b_{12} + a_{12}*b_{22} \\ a_{21}*b_{11} + a_{22}*b_{21} & a_{21}*b_{12} + a_{22}+b_{22} \end{array} \right)

詳細の解説は以下のサイトを参照してください。

*参考
高校数学の基本問題

np.dot

それでは、np.dotを使って上記のベクトルの内積や行列同士の積を行う方法を解説します。np.dotのAPIドキュメントは以下の通りです。

numpy.dot(a, b, out = None)

params:

パラメータ名 概要
a array_like(結果的に配列として出力されるもの) 左からかけるベクトルまたは行列(2次元配列)を指定します。
b array_like 右からかけるベクトルまたは行列(2次元配列)を指定します。
out ndarray 結果を代入する配列を指定します。このとき、もとの配列のデータ型や行数列数などが合致している必要があります。

returns:

ベクトルの内積の結果や、行列の積の結果がndarrayやmatrixで返されます。

dotの第一、第二引数には内積や積を求めたいベクトルや行列を指定します。まずはndarrayを指定して計算してみます。

In [1]: import numpy as np

In [2]: a = np.array([1, 2])

In [3]: b = np.array([4, 3])

In [4]: np.dot(a, b) # まずは2次元ベクトル同士の内積から。
Out[4]: 10

In [5]: np.dot(a, a) # これでベクトルのノルムの2乗が出る。
Out[5]: 5

1次元のベクトルの内積計算が計算されました。1 * 4 + 2 * 3 = 10になります。同じ値を指定して計算すると、ノルムの二乗になります。

np.dotにはスカラー値も入れて計算することができます。

In [6]: np.dot(4, 5) # ただの数字を入れてもその積が返される。
Out[6]: 20

また、複素数も計算することができます。

In [7]: c = np.array([1j, 2j]) # 複素数でやってみる。

In [8]: d = np.array([4j, 3j])

In [9]: np.dot(c, d)
Out[9]: (-10+0j)

In [10]: np.dot(a, d)
Out[10]: 10j

このとき、np.arrayだけでなくnp.matrixも使うことができます。np.matrixnp.arrayの二次元配列を再定義したものになっていますが挙動が行列の演算に比較的近いものになっています。 np.arrayからnp.matrixに変えてやってみましょう。列ベクトルか行ベクトルかで計算できるかどうかが変わってくるので注意が必要です。

In [11]: e = np.matrix([1,2])

In [12]: f = np.matrix([4, 3])

In [13]: np.dot(e, f) # np.matrixで同じようにやろうとするとエラーが返ってくる。
---------------------------------------------------------------------------
(エラーメッセージが表示される)
ValueError: shapes (1,2) and (1,2) not aligned: 2 (dim 1) != 1 (dim 0)

In [14]: f = np.matrix([[4], [3]]) # fを列ベクトルに変換すれば

In [15]: np.dot(e, f) # 同じ結果を得ることができる。
Out[15]: matrix([[10]])

次は行列同士の積をみていきます。

In [20]: a = np.array([[1,2], [3,4]])

In [21]: b = np.array([[4, 3], [2,1]])

In [22]: np.dot(a, b) # 2×2の行列同士の積
Out[22]:
array([[ 8,  5],
       [20, 13]])

In [23]: np.dot(b, a) # aとの順番を入れ替えると返ってくる行列も違ってくる。
Out[23]:
array([[13, 20],
       [ 5,  8]])


In [24]: c = np.arange(9).reshape((3,3))

In [25]: d = np.ones((3,3)) # 要素が1の3×3の配列を生成。

In [26]: np.dot(c, d) # 3×3の行列同士でももちろんできる。
Out[26]:
array([[  3.,   3.,   3.],
       [ 12.,  12.,  12.],
       [ 21.,  21.,  21.]])

左からかける行列の列数(axis = 1)と右からかける行列の行数(axis = 0)とが一致していないとうまく積を求めることができません。

In [27]: a = np.arange(12).reshape((4, 3))

In [28]: b = np.arange(16).reshape((4, 4))

In [29]: np.dot(a, b) # aのaxis = 1とbのaxis = 0が異なると演算ができない。
---------------------------------------------------------------------------
(エラーメッセージが表示される)
ValueError: shapes (4,3) and (4,4) not aligned: 3 (dim 1) != 4 (dim 0)

np.matrixでも行列同士の積を計算することができます。

In [30]: d = np.matrix([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) # cとdをmatrixに変換したものを作る。

In [31]: e = np.matrix([[1, 1, 1], [1, 1, 1], [1, 1, 1]])

In [32]: np.dot(d, e)
Out[33]:
matrix([[ 3,  3,  3],
        [12, 12, 12],
        [21, 21, 21]])

引数のoutの使い方をみていきましょう。 これを使うには演算の結果を代入する配列をあらかじめ作っておく必要があります。

In [34]: result = np.zeros((2, 2)) # 受け皿をあらかじめ作っておく。

In [35]: a = np.arange(4).reshape(2, 2)

In [36]: b = np.ones((2,2))

In [37]: a
Out[37]:
array([[0, 1],
       [2, 3]])

In [38]: np.dot(a, b, out = result)
Out[38]:
array([[ 1.,  1.],
       [ 5.,  5.]])

In [39]: result # ちゃんと代入されているか確認。
Out[39]:
array([[ 1.,  1.],
       [ 5.,  5.]])

In [40]: np.dot(b, a, out = result) # 入力するaとbの順番を入れ替えると値が変わることも反映されている。
Out[40]:
array([[ 2.,  4.],
       [ 2.,  4.]])