머신러닝
[파이썬 머신러닝] 6장.차원 축소 - SVD, NMF
오월&절미
2021. 2. 1. 23:41
In [1]:
from IPython.core.display import display, HTML
display(HTML("<style>.container { width:1200px !important; }</style>"))
SVD¶
정방행렬뿐만 아니라 행과 열의 크기가 다른 행렬에도 적용할 수 있다.
SVD는 특이값 분해 > U, Sigma(대각행렬), V전치행렬로 분해
SVD는 넘파이의 SVD 또는 사이파이 라이브러리를 이용해 수행
Turncated SVD는 Sigma의 대각원소 중에 상위 몇개만 추출해서 여기에 대응하는 U와 V의 원소도 함께 젝해 더욱 차원을 줄인 형태로 분해
In [1]:
# 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]]
In [2]:
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)) #0이 아닌 부분만 1차행렬로 표시
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]]
In [3]:
# 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]]
In [4]:
# 로우 간 의존성이 있을 경우 Sigma 변화와 차원 축소진행 확인
# 읜존성을 부여하기 위해 3번째 로우 = 1번째 로우 + 2번쨰 로우 // 4번째 로우 = 1번째로우 로 업데이트
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 ]]
In [5]:
# 다시 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. ]
In [6]:
# Sigma Value: [2.663 0.807 0. 0. ] > Sigma=0 == 선형 독립인 로우 벡터의 개수가 2개
In [9]:
# U, Sigma, Vt 전체 데이터가 아닌 Sigma의 0에 대응되는 U, Sigma, Vt를 제거하고 복원
# > U의 2개 열, Vt의 2개 행만 추출해 복원
# 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("원본 : \n", 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 ]]
Turncated SVD > 사이파이만 지원
In [10]:
import numpy as np
from scipy.sparse.linalg import svds
from scipy.linalg import svd
# 원본 행렬을 출력하고, SVD를 적용할 경우 U, Sigma, Vt 의 차원 확인
np.random.seed(121)
matrix = np.random.random((6, 6))
print('원본 행렬:\n',matrix)
U, Sigma, Vt = svd(matrix, full_matrices=False)
print('\n분해 행렬 차원:',U.shape, Sigma.shape, Vt.shape)
print('\nSigma값 행렬:', Sigma)
# Truncated SVD로 Sigma 행렬의 특이값(k)을 4개로 하여 Truncated SVD 수행.
num_components = 4
U_tr, Sigma_tr, Vt_tr = svds(matrix, k=num_components)
print('\nTruncated SVD 분해 행렬 차원:',U_tr.shape, Sigma_tr.shape, Vt_tr.shape)
print('\nTruncated SVD Sigma값 행렬:', Sigma_tr)
matrix_tr = np.dot(np.dot(U_tr,np.diag(Sigma_tr)), Vt_tr) # output of TruncatedSVD
print('\nTruncated SVD로 분해 후 복원 행렬:\n', matrix_tr)
원본 행렬: [[0.11133083 0.21076757 0.23296249 0.15194456 0.83017814 0.40791941] [0.5557906 0.74552394 0.24849976 0.9686594 0.95268418 0.48984885] [0.01829731 0.85760612 0.40493829 0.62247394 0.29537149 0.92958852] [0.4056155 0.56730065 0.24575605 0.22573721 0.03827786 0.58098021] [0.82925331 0.77326256 0.94693849 0.73632338 0.67328275 0.74517176] [0.51161442 0.46920965 0.6439515 0.82081228 0.14548493 0.01806415]] 분해 행렬 차원: (6, 6) (6,) (6, 6) Sigma값 행렬: [3.2535007 0.88116505 0.83865238 0.55463089 0.35834824 0.0349925 ] Truncated SVD 분해 행렬 차원: (6, 4) (4,) (4, 6) Truncated SVD Sigma값 행렬: [0.55463089 0.83865238 0.88116505 3.2535007 ] Truncated SVD로 분해 후 복원 행렬: [[0.19222941 0.21792946 0.15951023 0.14084013 0.81641405 0.42533093] [0.44874275 0.72204422 0.34594106 0.99148577 0.96866325 0.4754868 ] [0.12656662 0.88860729 0.30625735 0.59517439 0.28036734 0.93961948] [0.23989012 0.51026588 0.39697353 0.27308905 0.05971563 0.57156395] [0.83806144 0.78847467 0.93868685 0.72673231 0.6740867 0.73812389] [0.59726589 0.47953891 0.56613544 0.80746028 0.13135039 0.03479656]]
사이킷런 TruncatedSVD 클래스를 이용한 변환
- 원본 행렬을 분해항 U, Sigma, Vt 를 반환하지는 않는다. fit() 와 transform()을 호출해 원본 데이터를 몇 개의 주요 컴포넌트로 차원을 축호새 변환한다.
In [11]:
from sklearn.decomposition import TruncatedSVD, PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
%matplotlib inline
iris = load_iris()
iris_ftrs = iris.data
# 2개의 주요 component로 TruncatedSVD 변환
tsvd = TruncatedSVD(n_components=2)
tsvd.fit(iris_ftrs)
iris_tsvd = tsvd.transform(iris_ftrs)
# Scatter plot 2차원으로 TruncatedSVD 변환 된 데이터 표현. 품종은 색깔로 구분
plt.scatter(x=iris_tsvd[:,0], y= iris_tsvd[:,1], c= iris.target)
plt.xlabel('TruncatedSVD Component 1')
plt.ylabel('TruncatedSVD Component 2')
Out[11]:
Text(0, 0.5, 'TruncatedSVD Component 2')
사이킷런의 TruncatedSVD 와 PCA 모두 SVD를 이용해 행렬을 분해. 붓꽃 데이터를 스케일링으로 변환한 뒤에 TruncatedSVD와 PCA 클래스 변환을 해보면 두 개가 거의 동일하다.
In [12]:
from sklearn.preprocessing import StandardScaler
# iris 데이터를 StandardScaler로 변환
scaler = StandardScaler()
iris_scaled = scaler.fit_transform(iris_ftrs)
# 스케일링된 데이터를 기반으로 TruncatedSVD 변환 수행
tsvd = TruncatedSVD(n_components=2)
tsvd.fit(iris_scaled)
iris_tsvd = tsvd.transform(iris_scaled)
# 스케일링된 데이터를 기반으로 PCA 변환 수행
pca = PCA(n_components=2)
pca.fit(iris_scaled)
iris_pca = pca.transform(iris_scaled)
# TruncatedSVD 변환 데이터를 왼쪽에, PCA변환 데이터를 오른쪽에 표현
fig, (ax1, ax2) = plt.subplots(figsize=(9,4), ncols=2)
ax1.scatter(x=iris_tsvd[:,0], y= iris_tsvd[:,1], c= iris.target)
ax2.scatter(x=iris_pca[:,0], y= iris_pca[:,1], c= iris.target)
ax1.set_title('Truncated SVD Transformed')
ax2.set_title('PCA Transformed')
Out[12]:
Text(0.5, 1.0, 'PCA Transformed')
In [13]:
# 즉, 데이터 세트가 스케일링으로 데이터 중심이 동일해지면 사이킷런의 SVD와 PCA는 동일한 변환을 수행
NMF¶
낮은 랭크를 통한 행렬 근사 방식의 변형. 원본 행렬 내의 모든 원소 값이 모두 양수라는 게 보장되면 좀 더 간단하게 기반 양수 행렬로 분해될 수 있는 기법.
V(4 6) = W(4 2) H(2 6)
이렇게 분해된 행렬은 잠재 요소를 특성으로 가지게 된다.
W 는 원본 행에 대해서 잠재 요소 값이 얼마나 되는지 H 는 잠재 요소가 원본 열(원본 속성)로 어떻게 구성됐는지
In [14]:
from sklearn.decomposition import NMF
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
%matplotlib inline
iris = load_iris()
iris_ftrs = iris.data
nmf = NMF(n_components=2)
nmf.fit(iris_ftrs)
iris_nmf = nmf.transform(iris_ftrs)
plt.scatter(x=iris_nmf[:,0], y= iris_nmf[:,1], c= iris.target)
plt.xlabel('NMF Component 1')
plt.ylabel('NMF Component 2')
Out[14]:
Text(0, 0.5, 'NMF Component 2')
In [ ]: