특이값 분해란 무엇인가?
특이값 분해(Singular Value Decomposition, SVD)란 $m x n$ 차원의 행렬을 대각화해 세 개의 행렬로 분해하는 방법이다. 고유값 분해와 비슷하지만, 고유값 분해는 정사각 행렬에만 사용 가능한 반면, 특이값 분해는 직사각 행렬일 때도 사용 가능해 활용도가 높다.
특이값 분해를 수식으로 표현하면 다음과 같다.
$$\mathbf{X} = \mathbf{U} \mathbf{\Sigma} \mathbf{V}^T$$
여기서 각 기호는 다음과 같다.
$\mathbf{X}$ : $m \times n$ 행렬.
$\mathbf{U}$ : $m \times m$ 정사각 행렬로, $\mathbf{X}$의 좌특이 벡터(Left Singular Vectors)로 구성돼 직교 행렬이 된다
$\mathbf{\Sigma}$: $m \times n$ 대각 행렬
$\mathbf{V}$: $n \times n$ 정사각 행렬로, $\mathbf{X}$의 우특이 벡터(Left Singular Vectors)로 구성돼 직교 행렬이 된다.
특이값 분해 하는 방법
다음 행렬 $\mathbf{X}$에 대해 특이값 분해를 수행하면서 특이값 분해를 하는 방법을 알아본다.
$$\begin{bmatrix}
1 & 2 \\
-1 & 2 \\
0 & 1
\end{bmatrix}$$
$\mathbf{U}$ 구하기
$\mathbf{U}$ 를 구하기 위해서는 $\mathbf{X}\mathbf{X^T}$의 고유 벡터를 구해야 한다. 여기서 나오는 고유 벡터들이 $\mathbf{U}$가 된다.
A = np.array([[1, 2], [-1, 2], [0, 1]])
eigenValues, U = np.linalg.eig(np.dot(A,A.T))
print("U: ",U)
U: [[-6.66666667e-01 -7.07106781e-01 -2.35702260e-01]
[-6.66666667e-01 7.07106781e-01 -2.35702260e-01]
[-3.33333333e-01 -5.78537465e-17 9.42809042e-01]]
$\mathbf{\Sigma}$ 구하기
$\mathbf{\Sigma}$ 는 $\mathbf{X}\mathbf{X^T}$나 $\mathbf{X^T}\mathbf{X}$의 고유값이다. 둘은 같은 고유값을 가지기 때문에 같은 값이 된다. 앞서 구한 eigenValue에 루트를 씌워 구해보자.
A = np.array([[1, 2], [-1, 2], [0, 1]])
eigenValues, U = np.linalg.eig(np.dot(A,A.T))
print("U: ",U)
print("eigenValuesRoot", np.sqrt(eigenValues))
그러면 다음과 같은 값이 나온다. 여기서 세번째 값이 매우 작은 값으로 0인 것을 볼 수 있으니, 고유값에 루트를 취한 값은 3.0과 1.414인 것을 볼 수 있다.
eigenValuesRoot [3.00000000e+00 1.41421356e+00 3.22909121e-09]
$\mathbf{V}$ 구하기
$\mathbf{V}$ 를 구하기 위해서는 $\mathbf{X^T}\mathbf{X}$의 고유 벡터를 구해야 한다. 여기서 나오는 고유 벡터들이 $\mathbf{V}$가 된다.
eigenValues2, V = np.linalg.eig(np.dot(A.T,A))
print("V:", V)
print("eigenValuesRoot", np.sqrt(eigenValues2))
그러면 결과값은 다음과 같은 것을 볼 수 있다.
V: [[1. 0.]
[0. 1.]]
eigenValuesRoot [1.41421356 3. ]
여기서 보면 고유값에 루트를 취한 값(eigenValuesRoot)이 위에서 구한 3.0과 1.414과 같은 값인 것을 볼 수 있다.
구한 값으로 원래 행렬 계산해보기
이제 $\mathbf{U}$, $\mathbf{\Sigma}$, $\mathbf{V}$ 를 구했으니, 구한 값으로 원래 행렬을 계산해보자.
$$\mathbf{X} = \mathbf{U} \mathbf{\Sigma} \mathbf{V}^T$$
위의 수식을 계산해야 하므로, 다음과 같이 파이썬 코드를 만들 수 있다.
Sigma = np.concatenate((np.diag(np.sqrt(eigenValues)[:-1]), [[0, 0]]), axis=0)
np.matmul(np.matmul(U, Sigma), VT)
그러면 다음과 같은 결과가 나온다.
결과의 행렬이 우리가 특이값 분해를 실행한 행렬과 같은 것을 볼 수 있다.
$$\begin{bmatrix}
1 & 2 \\
-1 & 2 \\
0 & 1
\end{bmatrix}$$
하지만, 위와 같이 특이값 분해를 실행하면 너무 복잡하다. 머신러닝 라이브러리들에서는 이를 위한 간단한 방법을 제공한다. 이어서 그 방법들을 알아보자.
Numpy, TensorFlow, PyTorch 사용해 특이값 분해하기
Numpy 사용해 특이값 분해하기
Numpy 사용해 특이값 분해를 하려면 linalg패키지의 svd 함수를 사용하면 된다.
import numpy as np
A = np.array([[1, 2], [-1, 2], [2, 3]])
U, d, VT = np.linalg.svd(A)
print("U:",U)
print("V:",VT.T)
그러면 다음과 같은 값이 나온다.
U: [[-0.50395811 0.0600165 -0.86164044]
[-0.32223601 -0.93862263 0.12309149]
[-0.80136769 0.33968453 0.49236596]]
V: [[-0.40266324 0.91534819]
[-0.91534819 -0.40266324]]
이 값들은 $\mathbf{A}\mathbf{A^T}$ 에 대한 고유 값 분해를 한 것과 같은 비슷한 결과를 내는 것을 볼 수 있다.
eigenValues, V3 = np.linalg.eig(np.dot(A,A.T))
print("Eigen V3: ",V3)
eigenValues, V2 = np.linalg.eig(np.dot(A.T,A))
print("Eigen V2:", V2)
Eigen V3: [[ 0.50395811 0.86164044 0.0600165 ]
[ 0.32223601 -0.12309149 -0.93862263]
[ 0.80136769 -0.49236596 0.33968453]]
Eigen V2: [[-0.91534819 -0.40266324]
[ 0.40266324 -0.91534819]]
TensorFlow 사용해 특이값 분해하기
같은 연산을 TensorFlow를 사용해 하려면 linalg.svd 함수를 사용하면 된다.
import tensorflow as tf
A = tf.constant([[1, 2], [-1, 2], [2, 3]], dtype=tf.float32)
S, U, VT = tf.linalg.svd(A, full_matrices=True)
print("V:", tf.transpose(VT))
print("U:", U)
그러면 같은 결과가 나오는 것을 볼 수 있다.
V: tf.Tensor(
[[ 0.40266326 0.91534823]
[ 0.91534823 -0.40266326]], shape=(2, 2), dtype=float32)
U: tf.Tensor(
[[ 0.50395805 0.06001655 -0.86164033]
[ 0.322236 -0.9386227 0.12309146]
[ 0.8013677 0.33968455 0.49236602]], shape=(3, 3), dtype=float32)
PyTorch 사용해 특이값 분해하기
같은 연산을 PyTorch를 사용해 하려면 svd 함수를 사용하면 된다.
import torch
A = torch.tensor([[1, 2], [-1, 2], [2, 3]], dtype=torch.float32)
U, S, VT = torch.svd(A)
print("V:", VT.T)
print("U:", U)
그러면 같은 결과가 나오는 것을 볼 수 있다.
V: tensor([[-0.4027, -0.9153],
[ 0.9153, -0.4027]])
U: tensor([[-0.5040, 0.0600],
[-0.3222, -0.9386],
[-0.8014, 0.3397]])