最近のNetworkx関係の記事でよく行列の固有ベクトルを求めていますが、
そこで使っているNumPyの関数について紹介します。
最初に行列の固有値と固有ベクトルの定義について復習しておきます。
$\mathbf{A}$を正方行列とします。
この時、スカラー$\lambda$と、零でないベクトル$\mathbf{x}$が、
$$
\mathbf{A}\mathbf{x} = \lambda \mathbf{x}
$$
という関係を満たす時、
$\mathbf{x}$を$\mathbf{A}$の固有ベクトル、$\lambda$を$\mathbf{A}$の固有値と呼びます。
最近のネットワーク分析系の記事でも頻出しているだけでなく、
数学やデータ分析の各所に登場する非常に重要な概念です。
NumPyでは、
numpy.linalg.eig と、 numpy.linalg.eigh として実装されています。
早速、適当な行列に対して使ってみます。
import numpy as np
a = np.array(
[[-2, -1, 2],
[1, 4, 3],
[1, 1, 2]]
)
print(a)
"""
[[-2 -1 2]
[ 1 4 3]
[ 1 1 2]]
"""
# 固有値のリストと、固有ベクトルを列に持つ行列のタプルが戻る
values, vectors = np.linalg.eig(a)
print(values)
# [-2.37646808 4.92356209 1.452906 ]
print(vectors)
"""
[[ 0.97606147 0.04809876 0.4845743 ]
[-0.05394264 -0.95000852 -0.73987868]
[-0.21069932 -0.30849687 0.46665542]]
"""
eig一発で、固有値と固有ベクトルをまとめて返してくれるのでとても手軽ですね。
上のサンプルコードのように、それぞれ別の変数で受け取るのがオススメです。
なお、一つの変数で受け取ることもできます。
結果を見ていただければ若干使いにくそうな雰囲気が伝わると思います。
eig_result = np.linalg.eig(a)
print(eig_result)
"""
(array([-2.37646808, 4.92356209, 1.452906 ]), array([[ 0.97606147, 0.04809876, 0.4845743 ],
[-0.05394264, -0.95000852, -0.73987868],
[-0.21069932, -0.30849687, 0.46665542]]))
"""
さて、固有値の方はvalues に入っている値がそれぞれ求めたかった値になりますが、
固有ベクトルの方は少し注意が必要です。 というのもサンプルコードの、コメントに書いている通り、
固有ベクトルは、結果の行列の列ベクトルとして格納されています。
つまり、 vectors[0], vectors[1], vectors[2] は固有ベクトルではありません。
正しい固有ベクトルは、 vectors[:, 0], vectors[:, 1], vectors[:, 2] です。
それぞれ、values[0], values[1], values[2] に対応します。
なお、固有ベクトルを0でないスカラー倍したものはそれもまた同じ固有値の固有ベクトルになりますが、
このeigの戻り値は、単位ベクトル(長さが1)になるように正規化されて戻されます。
一応、固有値と固有ベクトルの定義の両辺をそれぞれ計算して、
これらの値が本当に固有値と固有ベクトルなのか見ておきましょう。
for i in range(3):
print(values[i] * vectors[:, i])
print(a @ vectors[:, i])
"""
[-2.31957893 0.12819296 0.5007202 ]
[-2.31957893 0.12819296 0.5007202 ]
[ 0.23681725 -4.67742593 -1.5189035 ]
[ 0.23681725 -4.67742593 -1.5189035 ]
[ 0.70404091 -1.07497418 0.67800646]
[ 0.70404091 -1.07497418 0.67800646]
"""
バッチリですね。
もう一つのeighについての紹介です。
eigは一般の正方行列に対して利用できますが、 eighは、実対称行列と、エルミート行列に対してのみ利用できます。
なお、eighは行列の下三角行列部分だけ使って計算するので、
どちらでもない行列を渡しても普通に動いてしまいます。結果は不正確なので注意が必要です。
ついでに紹介しますが、
numpy.linalg.eigvals と、 numpy.linalg.eigvals というメソッドで、
固有値のみを得ることもできます。
固有ベクトルが不要なら、eig の戻り値の該当部分を捨てれば済むのであまり使ったことはないのですが、
メモリの節約や計算速度等のメリットがあるのかもしれません。