고유값 분해란 무엇인가?
고윳값 분해(Eigen Decomposition)는 정방행렬(square matrix)을 고유값(eigenvalues)과 고유벡터(eigenvectors)를 사용해 분해하는 방법이다. 고윳값 분해를 통해 행렬의 구조와 성질을 분석하고 계산을 단순화할 수 있다.
고윳값 분해 수식은 다음과 같다.
$$\mathbf{A} = \mathbf{V} \mathbf{\Lambda} \mathbf{V}^{-1}$$
$\mathbf{A}$: n x n 정방행렬
$\mathbf{V}$: 고유 벡터를 열벡터로 표현한 행렬
$\mathbf{\Lambda}$: 고유값을 대각 행렬로 표현한 행렬
수식을 사용한 고유값 분해
이곳에서는 다음 $\mathbf{A}$ 행렬에 대한 고유값 분해를 수행한다.
$$\mathbf{A} =
\begin{pmatrix}
4 & 1 \\
2 & 3
\end{pmatrix}$$
1. 고유값 구하기
고유값 분해를 위해서는 먼저 고유값을 구해야한다. 이를 위해 다음 식을 만족하는 $\lambda$ 값을 찾아보자.
$$\det(\mathbf{A} - \lambda \mathbf{I}) = 0$$
계산 과정은 다음과 같다.
$$\det
\begin{pmatrix}
4 - \lambda & 1 \\
2 & 3 - \lambda
\end{pmatrix} = 0$$
$$(4 - \lambda)(3 - \lambda) - 2 = \lambda^2 - 7\lambda + 10 = 0$$
그러면 고유값이 다음과 같이 나온다.
$$\lambda_1 = 5, \quad \lambda_2 = 2$$
2. 고유 벡터 구하기
이제 각 고유값에 대한 고유 벡터를 계산해보자. 먼저 5에 대한 고유벡터는 다음과 같이 계산된다.
$$(\mathbf{A} - 5 \mathbf{I}) \mathbf{v} = 0$$
$$\begin{pmatrix}
4 - 5 & 1 \\
2 & 3 - 5
\end{pmatrix}
\begin{pmatrix}
v_1 \\
v_2
\end{pmatrix} =
\begin{pmatrix}
-1 & 1 \\
2 & -2
\end{pmatrix}
\begin{pmatrix}
v_1 \\
v_2
\end{pmatrix} = 0$$
그러면 고유 벡터는 다음과 같다.
$$\mathbf{v}_1 = \begin{pmatrix} 1 \\ 1 \end{pmatrix}$$
이번에는 고유값 2에 대한 고유 벡터를 구해보자. 위와 같은 방식으로 구하면 된다.
$$(\mathbf{A} - 2 \mathbf{I}) \mathbf{v} = 0$$
$$\begin{pmatrix}
4 - 2 & 1 \\
2 & 3 - 2
\end{pmatrix}
\begin{pmatrix}
v_1 \\
v_2
\end{pmatrix} =
\begin{pmatrix}
2 & 1 \\
2 & 1
\end{pmatrix}
\begin{pmatrix}
v_1 \\
v_2
\end{pmatrix} = 0$$
그러면 고유 벡터는 다음과 같아진다.
$$\mathbf{v}_2 = \begin{pmatrix} 1 \\ -2 \end{pmatrix}$$
3. 고유값 분해 실행하기
위에서 계산한 고유 벡터들로 행렬을 만들면 다음과 같아진다.
$$\mathbf{V} =
\begin{pmatrix}
1 & 1 \\
1 & -2
\end{pmatrix}$$
그 다음 고유값을 대각 행렬로 만들면 다음과 같아진다.
$$\mathbf{\Lambda} =
\begin{pmatrix}
5 & 0 \\
0 & 2
\end{pmatrix}$$
그러면 이제 고유값 분해가 완료됐다. 다음 수식을 계산해보면 좌변과 우변이 같아지는 것을 볼 수 있다.
$$\mathbf{A} = \mathbf{V} \mathbf{\Lambda} \mathbf{V}^{-1}$$
파이썬을 사용한 고유값 분해
Numpy를 사용한 고유값 분해
Numpy를 사용해 고유값 분해를 하기 위해서는 이전 시간에 다뤘던 linalg.eig 함수를 사용하면 된다.
이 함수를 사용해 고유값과 고유 벡터를 계산한 후, 고유값은 np.diag 함수를 통해 대각행렬로 만들면 이것이 바로 $\mathbf{\Lambda}$ 가 된다.
import numpy as np
# 행렬 정의
A = np.array([[4, 1],
[2, 3]])
# 고유값과 고유벡터 계산
eigenvalues, V = np.linalg.eig(A)
# 대각 행렬 Λ 생성
Lambda = np.diag(eigenvalues)
# 고유벡터의 역행렬 계산
V_inv = np.linalg.inv(V)
# A 재구성
A_reconstructed = V @ Lambda @ V_inv
print("원래 행렬 A:\n", A)
print("재구성된 행렬 A:\n", A_reconstructed)
이제 이 코드를 실행해보면 다음과 같은 결과를 얻을 수 있다.
TensorFlow를 사용한 고유값 분해
TensorFlow를 사용해 고유값 분해를 할 때도 tensorflow 패키지의 linalg.eig 함수를 사용하면 된다.
이 함수를 사용해 고유값과 고유 벡터를 계산한 후, linalg.diag 함수를 통해 고유값들(eigenvalues)을 대각행렬로 만들면 이것이 바로 $\mathbf{\Lambda}$ 가 된다.
import tensorflow as tf
# 행렬 정의
A = tf.constant([[4, 1],
[2, 3]], dtype=tf.float32)
# 고유값과 고유벡터 계산
eigenvalues, V = tf.linalg.eig(A)
# 대각 행렬 Λ 생성
Lambda = tf.linalg.diag(eigenvalues)
# 고유벡터의 역행렬 계산
V_inv = tf.linalg.inv(V)
# A 재구성
A_reconstructed = tf.matmul(tf.matmul(V, Lambda), V_inv)
print("원래 행렬 A:\n", A)
print("재구성된 행렬 A:\n", A_reconstructed)
코드를 실행해보면 같은 결과가 나오는 것을 볼 수 있다.
PyTorch를 사용한 고유값 분해
PyTorch를 사용해 고유값 분해를 할 때는 torch.linalg.eig 함수를 사용하면 된다. 1.9버전 미만에서는 torch.eig 함수를 사용했던 것 같은데, 최신 버전에서는 torch.linalg.eig을 사용하면 되도록 바꼈다.
이 함수를 사용해 고유값과 고유 벡터를 계산한 후, diag 함수를 통해 고유값들(eigenvalues)을 대각행렬로 만들면 이것이 바로 $\mathbf{\Lambda}$ 가 된다.
import torch
# 행렬 정의
A = torch.tensor([[4, 1],
[2, 3]], dtype=torch.float)
# 고유값과 고유벡터 계산
eigenvalues, V = torch.linalg.eig(A)
# 대각 행렬 Λ 생성
Lambda = torch.diag(eigenvalues)
# 고유벡터의 역행렬 계산
V_inv = torch.inverse(V)
# A 재구성
A_reconstructed = torch.mm(torch.mm(V, Lambda), V_inv)
print("원래 행렬 A:\n", A)
print("재구성된 행렬 A:\n", A_reconstructed)
코드를 실행해보면 예상한 것과 같은 결과가 나오는 것을 볼 수 있다.