파이썬 넘파이 특이값 분해(SVD) 차원 축소 : LA.svd()
이번 글에서는 파이썬 넘파이에서 특이값 분해(SVD)를 이용하여
\(U, \Sigma, V\)의 3가지 행렬로 분리된 결과를 가져와보고,
이를 이용하여 차원 축소를 진행하는 예시까지 간단하게 살펴보도록 하겠습니다.
우선, 오늘 예제에서 사용할 간단한 2 * 3 행렬을 선언해 보겠습니다.
import numpy as np
A = np.array([[-1, 1, 0],
[0, -1, 1]])
파이썬 full SVD vs truncated SVD 예제
오늘 설명할 파이썬의 np.linalg.svd (혹은 import numpy.linalg as LA 선언 시 LA.svd)
함수는 full SVD와 truncated SVD의 두 가지 형태를 모두 지원합니다.
full SVD와 truncated SVD의 차이는 아래 그림처럼 요약이 가능합니다.
이제 각 경우에 대해서 \(U, \Sigma, V\)를 각각 찾아보도록 하겠습니다.
먼저, full SVD 형태입니다. 기본적으로 svd 함수 실행 시 full SVD로 지정됩니다.
np.linalg.svd(A)
# 결과
(array([[-0.70710678, 0.70710678],
[ 0.70710678, 0.70710678]]),
array([1.73205081, 1. ]),
array([[ 4.08248290e-01, -8.16496581e-01, 4.08248290e-01],
[-7.07106781e-01, -2.78976253e-16, 7.07106781e-01],
[ 5.77350269e-01, 5.77350269e-01, 5.77350269e-01]]))
3가지 output이 튜플 형태로 묶여서 등장하고 있습니다.
순서대로 \(U, \Sigma\)의 대각 성분(0인 부분 제외), \(V^T\)를 의미하고 있습니다.
따라서, \(U, \Sigma, V\)는 위 결과에서 다음과 같이 추출해낼 수 있습니다.
U, Sigma, V = np.linalg.svd(A)
Sigma = np.diag(list(Sigma) + [0])[:2] # full rank에서 빠진 rank 만큼 0을 채우고 대각화 -> rank 만큼 행 추출
V = V.T # 실제 결과는 V.T 형태였으므로 다시 전치하여 원래 V를 구함
이번에는 truncated SVD 형태입니다. full_matrices 인자의 값을 False로 지정하면 됩니다.
np.linalg.svd(A, full_matrices = False)
# 결과
(array([[-0.70710678, 0.70710678],
[ 0.70710678, 0.70710678]]),
array([1.73205081, 1. ]),
array([[ 4.08248290e-01, -8.16496581e-01, 4.08248290e-01],
[-7.07106781e-01, -2.78976253e-16, 7.07106781e-01]]))
\(U, \Sigma\)의 대각 성분까지는 동일하게 추출되었으나, \(V^T\)가 간소화 되었습니다.
3 * 2 행렬과 같이 위아래로 긴 형태라면 \(U\) 부분이 간소화된 형태가 나타나게 됩니다.
여기서 \(U, \Sigma, V\)를 추출하는 방법은 full SVD보다는 더 간단합니다.
U, Sigma, V = np.linalg.svd(A, full_matrices = False)
Sigma = np.diag(Sigma)
V = V.T
\(\Sigma\)가 대각 행렬 형태이기 때문에 더 쉽게 추출이 가능했습니다.
파이썬 SVD 이용 차원 축소 예제
이제 위에서 얻어낸 SVD의 결과를 바탕으로 차원 축소를 진행해보겠습니다.
만일, 위의 2 * 3 행렬 A의 데이터를 \(\Sigma\)의 첫 번째 성분까지만 이용하여
차원 축소를 진행하려면 아래 그림과 같은 원리가 적용될 것입니다.
full SVD의 결과를 기준으로 차원 축소 행렬 A'의 결과를 만들어 보겠습니다.
(truncated SVD에서도 마찬가지 수행이 가능합니다.)
U, Sigma, V = np.linalg.svd(A)
U_Sigma = U[:, 0:1] * Sigma[0:1]
A_prime = U_Sigma @ v[0:1, :]
A_prime
# 결과
array([[-0.5, 1. , -0.5],
[ 0.5, -1. , 0.5]])
2차, 3차 주성분까지 사용하는 경우에는 0:1 부분을 0:2, 0:3 식으로 바꿔주시면 됩니다.
차원 축소한 결과의 설명력은 \(\Sigma\)의 값을 이용해서 쉽게 구할 수 있습니다.
U, Sigma, V = np.linalg.svd(A)
Sigma[0:1].sum() / Sigma.sum()
# 0.6339745962155613
마찬가지로 2번째, 3번째 주성분까지 사용한 경우, 0:1 부분을 0:2, 0:3 처럼 바꾸시면 됩니다.
첫 번째 주성분만 사용한 경우, 약 63% 정도의 설명력을 가지고 있었습니다.
'Python > Numpy' 카테고리의 다른 글
[Numpy] np.sum() 사용법, axis와 keepdims 의미 (1) | 2022.02.02 |
---|---|
[Numpy] 행렬곱 함수 np.matmul 사용법, np.dot과의 차이 (0) | 2022.01.19 |
[Numpy] np.squeeze 함수 사용법과 의미 (0) | 2022.01.14 |