1. Home
  2. プログラマーのための線形代数
  3. 行列の分解
  4. 固有値分解

固有値分解

固有値分解は、ある行列を固有値と固有ベクトルに分解するものであり、機械学習では主成分分析で重要な役割を担っています。当ページでは、この固有値分解について知っておきたいことをまとめて確認することができます。具体的には以下のことがわかります。

当ページでわかること

  • 固有値分解とは
  • 固有値分解のやり方
  • Python で固有値分解

固有値分解とは

固有値分解とは、ある行列 \(A\) を、固有ベクトルを列ベクトルとした行列 \(V\) と、固有値 \(\lambda\) を対角線分とした対角行列 \(\Lambda\) として、以下のように分解することを言います。

\[A=V\Lambda V^{-1}\]

このように行列 \(A\) の固有値を値を対角成分とした対角行列 \(\Lambda\) を得るのが固有値分解です。たとえば以下の行列 \(A\) はご覧のように固有値分解することができます。

\[\begin{eqnarray}
\overset{A}{
\begin{bmatrix}
2 & 1 \\
1 & 2
\end{bmatrix}}
=
\overset{V}{
\begin{bmatrix}
\frac{1}{\sqrt{2}} & -\frac{1}{\sqrt{2}}\\
\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}}
\end{bmatrix}}
\overset{\Lambda}{
\begin{bmatrix}
3 & 0 \\
0 & 1
\end{bmatrix}}
\overset{V^{-1}}{
\begin{bmatrix}
\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}}\\
-\frac{1}{\sqrt{2}} & \frac{1}{\sqrt{2}}
\end{bmatrix}}
\end{eqnarray}\]

プログラマーにとって固有値分解はなぜ重要なのか?

結論から言うと、プログラマーにとって固有値分解が重要な理由は、それによって計算量を大きく削減することができるからです。

線形変換やドット積などの行列の演算は、計算量が非常に大きくなるためコンピューターで行うにしても高くつきます。特に機械学習では、何千、何万もの次元を持つデータを扱うことになるので尚更です。何百万もの次元を持つ行列で線形変換を行うとしたら、たとえ世界最高のコンピュータだとしても、すぐに限界を超えてしまいます。しかし対角行列の場合は、演算は非常にシンプルになります。そのためコストがかかる行列の演算を行う前に、その行列を対角行列に変換してしまえば、コンピュータの計算も遥かに速くなり、安価になります。

具体例としては機械学習における主成分分析があります。

固有値分解のやり方

固有値分解は、次の 2 つのステップで行います。

  • 固有方程式で固有値 \(\lambda\) を求める
  • 固有値から固有ベクトルを求める

それぞれ見ていきましょう。

固有値を求める

固有値は固有方程式と呼ばれる以下の行列式を解くことで求められます。

\[|A-\lambda I|=0\]

例として、以下の行列 \(A\) の固有値を求めてみましょう。

\[\begin{eqnarray}
A=
\begin{bmatrix}
3 & 1 \\
0 & 2
\end{bmatrix}
\end{eqnarray}\]

次のようになります。

\[\begin{eqnarray}
|A−\lambda I|
&=&
\left|
\left[\begin{array}{cc} 3 & 1 \\ 0 &2 \end{array} \right]

\lambda
\left[\begin{array}{cc} 1 & 0 \\ 0 & 1 \end{array}\right]
\right|\\
&=&
\left|
\left[\begin{array}{cc} 3 & 1 \\ 0 &2 \end{array} \right]

\left[\begin{array}{cc} \lambda & 0 \\ 0 & \lambda \end{array}\right]
\right|\\
&=&
\left|\begin{array}{cc} 3−\lambda & 1 \\ 0 & 2−\lambda \end{array} \right|\\
&=&
(3-\lambda)(2-\lambda)-(1)(0) \\
&=&(3-\lambda)(2-\lambda) \\
&=&0
\end{eqnarray}\]

このように、最終的に \((3-\lambda)(2-\lambda)=0\) となり、\(\lambda= 3, 2\) と求まります。この固有値を対角成分としたものが固有値行列 \(\Lambda\) です。

\[
\Lambda
=
\begin{bmatrix}
3 & 0 \\
0 & 2
\end{bmatrix}
\]

固有ベクトルを求める

続いて固有値を使って固有ベクトルを求めます。これは先ほどの固有方程式を用いた以下の連立方程式を解くことで求められます。

\[
[A-\lambda I]
\left[ \begin{array}{cc} x \\ y \end{array} \right]
=
0
\]

まず、\(\lambda=3\) の場合を解いてみましょう。

\[\begin{eqnarray}
A-\lambda I \left[\begin{array}{cc} x \\ y \end{array} \right]
=
\left[ \begin{array}{cc} 0 \\ 0 \end{array} \right] \\
\rightarrow
\left[ \begin{array}{cc} 3−\lambda & 1 \\ 0 &2−\lambda \end{array} \right]
\left[ \begin{array}{cc} x \\ y \end{array} \right]
=
\left[ \begin{array}{cc} 0 \\ 0 \end{array} \right]\\
\rightarrow
\left[ \begin{array}{cc} 3−3 & 1 \\ 0 & 2−3 \end{array} \right]
\left[ \begin{array}{cc} x \\ y \end{array} \right]
=
\left[ \begin{array}{cc} 0 \\ 0 \end{array} \right]\\
\rightarrow
\left[ \begin{array}{cc} 0 & 1 \\ 0 &-1 \end{array} \right]
\left[ \begin{array}{cc} x \\ y \end{array} \right]
=
\left[ \begin{array}{cc} 0 \\ 0 \end{array} \right]\\
\end{eqnarray}\]

これによって以下の解が導かれます。

\[
\begin{cases}
0x+1y = 0 \\
0x-1y = 0 \\
\end{cases}
\rightarrow
y = 0
\]

これは、つまり \(y=0\) 上に存在するすべてのベクトル、わかりやすく単位ベクトルで表すと \(\begin{bmatrix} 1 \\ 0\end{bmatrix}\) が行列 \(A\) の固有値 \(3\) の固有ベクトルであるということを意味しています。

次に \(\lambda=2\) の場合を解いてみましょう。

\[\begin{eqnarray}
A-\lambda I \left[\begin{array}{cc} x \\ y \end{array} \right]
=
\left[ \begin{array}{cc} 0 \\ 0 \end{array} \right] \\
\rightarrow
\left[ \begin{array}{cc} 3−\lambda & 1 \\ 0 &2−\lambda \end{array} \right]
\left[ \begin{array}{cc} x \\ y \end{array} \right]
=
\left[ \begin{array}{cc} 0 \\ 0 \end{array} \right]\\
\rightarrow
\left[ \begin{array}{cc} 3−2 & 1 \\ 0 & 2−2 \end{array} \right]
\left[ \begin{array}{cc} x \\ y \end{array} \right]
=
\left[ \begin{array}{cc} 0 \\ 0 \end{array} \right]\\
\rightarrow
\left[ \begin{array}{cc} 1 & 1 \\ 0 &0 \end{array} \right]
\left[ \begin{array}{cc} x \\ y \end{array} \right]
=
\left[ \begin{array}{cc} 0 \\ 0 \end{array} \right]\\
\end{eqnarray}\]

これによって以下の解が導かれます。

\[
\begin{cases}
1x+1y = 0 \\
0x+0y = 0 \\
\end{cases}
\rightarrow
y = −x
\]

つまり、\(y = −x\) 上にあるすべてのベクトルが、わかりやすく単位ベクトルで表すと \(\begin{bmatrix} -\frac{\sqrt{2}}{2} \\ \frac{\sqrt{2}}{2} \end{bmatrix}\) が行列 \(A\) の固有値 \(2\) の固有ベクトルであるということを意味しています。

以上のことから、行列 \(V\) はこれら 2 つのベクトルを合わせた \(\begin{bmatrix} 1 & -\frac{\sqrt{2}}{2} \\ 0 & \frac{\sqrt{2}}{2} \end{bmatrix}\) になります。

これで固有値分解問題を解くことができました。

\[\begin{eqnarray}
\overset{A}{
\begin{bmatrix}
3 & 1 \\
0 & 2
\end{bmatrix}}
=
\overset{V}{
\begin{bmatrix}
1 & -\frac{1}{\sqrt{2}}\\
0 & \frac{1}{\sqrt{2}}
\end{bmatrix}}
\overset{\Lambda}{
\begin{bmatrix}
3 & 0 \\
0 & 1
\end{bmatrix}}
\overset{V^{-1}}{
\begin{bmatrix}
1 & 1 \\
0 & \sqrt{2}
\end{bmatrix}}
\end{eqnarray}\]

Pythonで固有値分解

Python では NumPy の linalog.eig() 関数を使って固有値と固有ベクトルを求めます。

In [1]:
# NumPy と SciPy のインポート
import numpy as np
from numpy.linalg import eig

# 正方行列を定義
A = np.array([
    [1,2,3],
    [4,5,6],
    [7,8,9]])
print(A)
[[1 2 3]
 [4 5 6]
 [7 8 9]]
In [2]:
# 固有分解
values, vectors = eig(A)
In [3]:
# 固有値
print(values)
[ 1.61168440e+01 -1.11684397e+00 -3.38433605e-16]
In [4]:
# 固有ベクトル
print(vectors)
[[-0.23197069 -0.78583024  0.40824829]
 [-0.52532209 -0.08675134 -0.81649658]
 [-0.8186735   0.61232756  0.40824829]]

固有ベクトルと固有値の確認

そのベクトルが本当に固有ベクトルであるのかどうかを確認することができます。それは候補の固有ベクトルを、バリューベクトルと掛けて、結果を固有値と比べることで行えます。

In [1]:
# NumPy と SciPy のインポート
import numpy as np
from numpy.linalg import eig

# 正方行列を定義
A = np.array([
    [1,2,3],
    [4,5,6],
    [7,8,9]])
print(A)
[[1 2 3]
 [4 5 6]
 [7 8 9]]
In [2]:
# 固有分解
values, vectors = eig(A)
In [3]:
# 一つ目の固有ベクトルの確認
B = A @ vectors[:, 0]
print(B)
[ -3.73863537  -8.46653421 -13.19443305]
In [4]:
C = vectors[:, 0] * values[0]
print(C)
[ -3.73863537  -8.46653421 -13.19443305]

行列の再構築

固有ベクトルと固有値があれば、もともとの行列を再構築することができます。まずは、それぞれの固有ベクトルを行にした行列を作成します。そして、固有値は対角行列にしておく必要があります。

In [1]:
# NumPy と SciPy のインポート
import numpy as np
from numpy.linalg import inv
from numpy.linalg import eig

# 正方行列を定義
A = np.array([
    [1,2,3],
    [4,5,6],
    [7,8,9]])
print(A)
[[1 2 3]
 [4 5 6]
 [7 8 9]]
In [2]:
# 固有分解
values, vectors = eig(A)
# 固有ベクトルから行列を作成
Q=vectors
# 固有ベクトル行列の逆行列を作成
R = inv(Q)
# 固有値から対角行列を作成
L = np.diag(values)
# もともとの行列の再構築
B = Q @ L @ R
print(B)
[[1. 2. 3.]
 [4. 5. 6.]
 [7. 8. 9.]]