SVD
SVD 역시 PCA와 유사한 행렬 분해 기법을 이용한다.
PCA의 경우 정방행렬 만을 고유벡터로 분해 할 수 있지만, SVD는 정방행렬 뿐만 아니라 행과 열의 크기가 다른 행렬 에도 적용할 수 있다.
일반 적으로 SVD는 m x n 크기의 행렬 A를 다음과 같이 분해하는 것을 의미한다.
SVD는 특이값 분해로 불리며, 행렬 U와 V에 속한 벡터는 특이 벡터이며, 모든 특이 벡터는 서로 직교하는 성질을 가진다.
∑는 대각행렬이며, 행렬의 대각에 위치한 값만 0이 아니고 나머지 위치의 값은 모두 0이다. ∑가 위치한 0이 아닌 값이 바로 행렬 A의 특이값이다.
하지만 일반적으로 ∑ 의 비대각인 부분과 대각 원소중에 특이값이 0 인 부분도 모두 제거 하고 제거된 ∑에 대응되는 U와 V원소도 함께 제거해 차원을 줄인 형태로 SVD를 적용한다.
이렇게 컴팩트한 형태로 SVD를 적용하면 A의 차원이 m x n 일때, U의 차원을 m x p , ∑의 차원을 p x p , V^T 의 차원을 p x n 으로 분해 한다.
Trucated SVD는 ∑의 대각 원소 중에 상위 몇 개만 추출해서 여기에 대응하는 U와 V의 원소도 함께 제거해 더욱 차원을 줄인 형태로 분해하는 것이다.
일반적인 SVD 는 보통 넘파이나 사이파이 라이브러리를 이용해서 구현 한다.
넘파이의 SVD를 이용해 SVD 연산을 수행하고, SVD로 분해가 어떤식으로 되는지 간단한 예제를 통해 살펴보자.
새로운 주피터 노트북에 넘파이의 SVD 모듈을 로딩하고, 랜덤한 4 x 4넘파이 행렬을 생성한다.
랜덤 행렬을 생성하는 이유는 행렬의 개별 로우끼리의 의존성을 없애기 위해서다.
SVD 개요
# numpy의 svd 모듈 import
import numpy as np
from numpy.linalg import svd
# 4X4 Random 행렬 a 생성
np.random.seed(121)
a = np.random.randn(4,4)
print(np.round(a, 3))
[[-0.212 -0.285 -0.574 -0.44 ]
[-0.33 1.184 1.615 0.367]
[-0.014 0.63 1.71 -1.327]
[ 0.402 -0.191 1.404 -1.969]]
이렇게 생성된 a 행렬에 SVD를 적용해 U, Sigma, Vt를 도출해 보자.
U, Sigma, Vt = svd(a)
print(U.shape, Sigma.shape, Vt.shape)
print('U matrix:\n',np.round(U, 3))
print('Sigma Value:\n',np.round(Sigma, 3))
print('V transpose matrix:\n',np.round(Vt, 3))
(4, 4) (4,) (4, 4)
U matrix:
[[-0.079 -0.318 0.867 0.376]
[ 0.383 0.787 0.12 0.469]
[ 0.656 0.022 0.357 -0.664]
[ 0.645 -0.529 -0.328 0.444]]
Sigma Value:
[3.423 2.023 0.463 0.079]
V transpose matrix:
[[ 0.041 0.224 0.786 -0.574]
[-0.2 0.562 0.37 0.712]
[-0.778 0.395 -0.333 -0.357]
[-0.593 -0.692 0.366 0.189]]
U 행렬이 4 x 4, Vt행렬이 4 x 4 로 반환됐고, Sigma의 경우 1차원 행렬인 (4,) 으로 반환됐다.
분해 된 U,Sigma, V_t를 이용해 다시 원본 행렬로 정확히 복원되는지 확인해 보자.
# Sima를 다시 0 을 포함한 대칭행렬로 변환
Sigma_mat = np.diag(Sigma)
a_ = np.dot(np.dot(U, Sigma_mat), Vt)
print(np.round(a_, 3))
[[-0.212 -0.285 -0.574 -0.44 ]
[-0.33 1.184 1.615 0.367]
[-0.014 0.63 1.71 -1.327]
[ 0.402 -0.191 1.404 -1.969]]
U, Sigma, V_t를 이용해 a_는 원본 행렬 a와 동일하게 복원됨을 알 수 있다.
이번에는 데이터 셋이 로우 간 의존성이 있을 경우 어떻게 Sigma값이 변하고 , 이에 따른 차원 축소가 진행될 수 있는지 알아보자.
일부러 의존성을 높이기 위해 a 행렬을 각각의 로우로 재 조합하자.
a[2] = a[0] + a[1]
a[3] = a[0]
print(np.round(a,3))
[[-0.212 -0.285 -0.574 -0.44 ]
[-0.33 1.184 1.615 0.367]
[-0.542 0.899 1.041 -0.073]
[-0.212 -0.285 -0.574 -0.44 ]]
이제 a행렬은 이전과 다르게 로우 간 관계가 높아졌다. 이데이터를 SVD로 다시 분해해 보자.
# 다시 SVD를 수행하여 Sigma 값 확인
U, Sigma, Vt = svd(a)
print(U.shape, Sigma.shape, Vt.shape)
print('Sigma Value:\n',np.round(Sigma,3))
(4, 4) (4,) (4, 4)
Sigma Value:
[2.663 0.807 0. 0. ]
이전과 차원은 같지만 Sigma 값 중 2개가 0으로 변했다.
즉 , 선형 독립인 로우 벡터의 개수가 2개라는 의미이다.
이렇게 분해된 U, Sigma, V_t를 이용해 다시 원본 행렬로 복원해보자.
이번에는 U, Sigma, V_t의 전체 데이터를 이용하지 않고 Sigma의 0에 대응되는 U, Sigma, V_t의 데이터를 제외하고 복원해 보자.
즉 ,Sigma의 경우 앞의 2개 요소만 0이 아니므로 U 행렬 중 선행 두 개의 열만 추출하고, V_t의 경우는 선행 두 개의 행만 추출해 복원하는 것이다.
# U 행렬의 경우는 Sigma와 내적을 수행하므로 Sigma의 앞 2행에 대응되는 앞 2열만 추출
U_ = U[:, :2]
Sigma_ = np.diag(Sigma[:2])
# V 전치 행렬의 경우는 앞 2행만 추출
Vt_ = Vt[:2]
print(U_.shape, Sigma_.shape, Vt_.shape)
# U, Sigma, Vt의 내적을 수행하며, 다시 원본 행렬 복원
a_ = np.dot(np.dot(U_,Sigma_), Vt_)
print(np.round(a_, 3))
(4, 2) (2, 2) (2, 4)
[[-0.212 -0.285 -0.574 -0.44 ]
[-0.33 1.184 1.615 0.367]
[-0.542 0.899 1.041 -0.073]
[-0.212 -0.285 -0.574 -0.44 ]]
본문 내용은 파이썬 머신러닝 완벽 가이드 (권철민 저)을 요약정리한 내용입니다.
'머신러닝' 카테고리의 다른 글
NMF(Non - Negative Matrix Factorization) (0) | 2020.08.23 |
---|---|
Truncated SVD (0) | 2020.08.23 |
차원축소 / PCA(주성분 분석) (0) | 2020.08.22 |
회귀 트리 (0) | 2020.08.21 |
로지스틱 회귀 (0) | 2020.08.21 |