Pythonにはnumpyという数学的な処理を行ってくれる拡張モジュールがあり、AIや技術計算、統計処理などを行う場合にはよく使う。
利用するには、importが必要で、通常 as np をつけて、numpyと書く代わりにnpで済ませることが多く、以下の説明でも np を用いることがある。
import numpy as np
内積の計算は、numpy.dot関数を使って次のように行っているのではないだろうか。最後は、要素毎の積の和の計算で、ベタな確認である。
>>> import numpy as np
>>> a = [1,2,3]
>>> b = [4,5,6]
>>> np.dot(a,b)
32
>>> 1*4+2*5+3*6
32
何次元でも可能なので、もうちょっと一般的なのを示す。
>>> c = list(range(1,10)); c
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> d = list(range(11,20)); d
[11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> np.dot(c,d)
735
>>> sum([x*y for x,y in zip(c,d)])
735
科学技術計算などでは、数は実数とは限らず、複素数のこともある。
とりあえず、複素数の場合の内積をnumpy.dot関数で求めて、検算もしておこう。
虚数単位は数学ではiで表現することが普通だが、Pythonではjを用いる。虚部は実数の直後にjをつけて示す。単独で j と書いた場合は虚数単位と解釈されずエラーとなるので、虚数単位を示すには 1j と書く。
>>> e = [1+2j,3+4j]
>>> f = [5+6j,7+8j]
>>> np.dot(e,f)
(-18+68j)
>>> (1+2j)*(5+6j)+(3+4j)*(7+8j)
(-18+68j)
numpy.vdot関数があり、dotとvdotは同じという説明を見かけるが、そうだろうか。
>>> np.vdot(a,b)
32
>>> np.vdot(c,d)
735
>>> np.vdot(e,f)
(70-8j)
aとb, cとdの内積は一致したが、eとfの内積は異なってしまった。
どうやら、以下のように、eの共役複素数とfのnumpy.dot演算が行われているようである。
>>> (1-2j)*(5+6j)+(3-4j)*(7+8j)
(70-8j)
numpyには、共役複素数を求める関数numpy.conjがあるので、これを使って確認しておこう。
>>> e
[(1+2j), (3+4j)]
>>> np.conj(e)
array([1.-2.j, 3.-4.j])
>>>np.dot(np.conj(e),f)
(70-8j)
eは正確には複素数のリストであるが、np.conjの変換結果はarrayがついているので、配列になっている。
だから、np.dotの第1パラメータは配列で、第2パラメータはリストとタイプが異なっているが、良きに計らってくれて、ちゃんと内積が求まる。
つまり、numpy.vdot関数は、次の内積が計算されているようだ。
$$ \boldsymbol{a\cdot b} = \sum_{i=1}^{n} \overline{a}_{i} b_{i}$$
ここで $\overline{a}_{i}$は複素数$a_{i}$の共役複素数である。
しかし、複素ベクトルの内積は、線型代数の教科書では
$$ \boldsymbol{a\cdot b} = \sum_{i=1}^{n} a_{i} \overline{b}_{i}$$
となっているはずだ。
これに従って計算すると、
>>> np.dot(e,np.conj(f))
(70+8j)
>>> np.dot(e,np.conj(f)).conj()
(70-8j)
となって、虚部の符号が逆になる。
複素ベクトルの内積が、数学の世界とnumpyの世界で異なっているが、どうしてだろう。
つづく