6장_차원축소

12 minute read

본 포스팅은 [파이썬 머신러닝 완벽 가이드 _ 권철민 저] 도서를 기반으로 하고 있으며, 본인이 직접 요약, 정리한 내용입니다.

차원 축소 참고 링크

[https://ratsgo.github.io/from%20frequency%20to%20semantics/2017/04/06/pcasvdlsa/]

## 1 : 차원 축소 개요

  • 차원 축소는 매우 많은 feature로 구성 된 다차원 datasets의 차원을 축소해 새로운 차원의 데이터 세트를 생성하는 것이다.

  • 일반적으로 차원이 증가할수록 데이터 포인트 간의 거리가 기하급수적으로 멀어지게 되고, 희소(sparse)한 구조를 가지게 된다.

  • 수백 개 이상의 feature로 구성된 데이터 sets의 경우 상대적으로 적은 차원에서 학습된 모델보다 예측 신뢰도가 떨어진다.

  • 또한 feature가 많을 경우 개별 feature 간에 상관 관계가 높을 가능성이 크다. (다중 공선성)

  • 일반적으로 차원 축소는 feature selection과 feature extraction으로 나눌 수 있다.

feature selection : 특정 feature에 종속성이 강한 불필요한 feature은 아예 제거하고, 데이터의 특징을 잘 나타내는 주요 feature만 선택하는 것이다.

feature extraction : 기존 feature을 저 차원의 중요 feature으로 압축해서 추출하는 것이다. 이렇게 새롭게 추출된 중요 특성은 기존의 feature가 압축된 것이므로 기존의 feature와는 완전히 다른 값이 된다.

feature extraction은 기존 feature을 단순 압축이 아닌, feature을 함축적으로 더 잘 설명할 수 있는 또다른 공간으로 mapping하여 추출하는 것이다. 이러한 함축적인 feature extraction은 기존 feature가 전혀 인지하기 어려웠던 잠재적인 요소(latent factor)를 추출하는 것을 의미한다.

  • 이처럼 차원 축소는 단순히 데이터의 압축을 의미하는 것이 아닌, 차원 축소를 통해 좀 더 데이터를 잘 설명할 수 있는 잠재적인 요소를 추출하는 데에 있다.

  • PCA, SVD, NMF 는 이처럼 잠재적인 요소를 찾는 대표적인 차원 축소 알고리즘이다.

  • 차원 축소 알고리즘으로 적은 차원으로 변환 후 분류 수행 시에 과적합 영향력이 작아져서 오히려 원본 데이터로 예측하는 것보다 예측 성능을 더 끌어올릴 수 있다.

## 2 : PCA(Principal Component Analysis) - 주성분 분석

### ※PCA의 수리적 이해에 대한 참고 링크※

  • https://www.youtube.com/watch?v=FhQm2Tc8Kic&list=PLpIPLT0Pf7IoTxTCi2MEQ94MZnHaxrP0j&index=8

PCA는 여러 변수 간에 존재하는 상관 관계를 이용하여 이를 대표하는 주성분을 추출해 차원을 축소하는 기법이다. PCA로 차원을 축소할 때는 기존 데이터의 정보 유실이 최소화되는 것이 당연하다. 이를 위해 PCA는 가장 높은 분산을 가지는 데이터의 ‘축’(X 축, Y축 같은)을 찾아 이 축으로 차원을 축소하는데, 이것이 PCA의 주성분이 된다.(즉, 분산이 데이터의 특성을 가장 잘 나타내는 것으로 간주한다.)

PCA는 제일 먼저 가장 큰 데이터 변동성(Variance)을 기반으로 첫 번째 벡터 축을 생성하고, 두 번째 축은 이 벡터 축에 직각이 되는 벡터(직교 벡터)를 축으로 한다.세 번째 축은 다시 두 번째 축과 직각이 되는 벡터를 설정하는 방식으로 축을 생성한다.이렇게 생성된 벡터 축에 원본 데이터를 투영하면 벡터 축의 개수만큼의 차원으로 원본 데이터가 차원 축소된다.

PCA를 선형대수적 관점에서 해석해보면, 입력 데이터의 Covariance Matrix를 고윳값 분해하고, 이렇게 구한 고유 벡터에 입력 데이터를 선형 변환하는 것이다. 이 고유 벡터가 PCA의 주성분 벡터로서 입력 데이터의 분산이 큰 방향을 나타낸다.

PCA는 다음과 같은 스텝으로 수행된다.

  1. 입력 데이터 세트의 공분산 행렬을 생성한다.
  2. 공분산 행렬의 고유벡터와 고유값을 계산한다.
  3. 고유값이 가장 큰 순으로 K개(PCA 변환 차수만큼)만큼 고유벡터를 추출한다.
  4. 고유값이 가장 큰 순으로 추출된 고유 벡터를 이용해 새롭게 입력 데이터를 변환한다.
from sklearn.datasets import load_iris
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

# 사이킷런 내장 데이터 셋 API 호출
iris = load_iris()

# 넘파이 데이터 셋을 Pandas DataFrame으로 변환
columns = ['sepal_length','sepal_width','petal_length','petal_width']
irisDF = pd.DataFrame(iris.data , columns=columns)
irisDF['target']=iris.target
irisDF.head(3)
sepal_length sepal_width petal_length petal_width target
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
#setosa는 세모, versicolor는 네모, virginica는 동그라미로 표현
markers=['^', 's', 'o']

#setosa의 target 값은 0, versicolor는 1, virginica는 2. 
# 각 target별로 다른 모양으로 산점도 표시하겠다.

for i, marker in enumerate(markers) :
    x_axis_data = irisDF[irisDF['target']==i]['sepal_length']
    y_axis_data = irisDF[irisDF['target']==i]['sepal_width']
    plt.scatter(x_axis_data, y_axis_data, marker = marker, label = iris.target_names[i])
    
plt.legend()
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.show()

output_11_0

PCA는 여러 속성의 값을 연산해야 하므로 속성의 스케일에 영향을 받는다. 따라서 여러 속성을 PCA로 압축하기 전에 각 속성값을 동일한 스케일로 변환하는 것이 필요하다.

from sklearn.preprocessing import StandardScaler

iris_scaled = StandardScaler().fit_transform(irisDF.iloc[:, :-1])
iris_scaled.shape
(150, 4)
from sklearn.decomposition import PCA

pca = PCA(n_components = 2)

#fit()과 transform()을 호출하여 PCA 변환 데이터 변환
pca.fit(iris_scaled)
iris_pca = pca.transform(iris_scaled)
print(iris_pca.shape)
(150, 2)
# PCA 변환된 데이터의 컬럼 명을 각각 pca_compoent_1, pca_component_2로 명명
pca_columns = ['pca_component_1', 'pca_component_2']
irisDF_pca = pd.DataFrame(iris_pca, columns = pca_columns)
irisDF_pca['target'] = iris.target
irisDF_pca.head(3)
pca_component_1 pca_component_2 target
0 -2.264703 0.480027 0
1 -2.080961 -0.674134 0
2 -2.364229 -0.341908 0

PCA로 차원 축소된 피처들로 데이터 산포도 시각화

for i, marker in enumerate(markers) :
    x_axis_data = irisDF_pca[iris['target'] == i]['pca_component_1']
    y_axis_data = irisDF_pca[iris['target'] == i]['pca_component_2']
    plt.scatter(x_axis_data, y_axis_data,marker = marker, label = iris.target_names[i])

plt.legend()
plt.xlabel('pca_component_1')
plt.ylabel('pca_component_2')
plt.show()

output_18_0

위 결과에서, PCA의 첫 번째 새로운 축인 pca_component_1이 원본 데이터의 변동성을 잘 반영했음을 확인할 수 있다.

다음으로 PCA Component별로 원본 데이터의 변동성을 얼마나 반영하고 있는지 알아보겠다.

각 PCA Component별 변동성 비율 - pca.explained_variance_ratio_

print(pca.explained_variance_ratio_)
[0.72962445 0.22850762]

위 결과에서, 첫 번째 PCA 변환 요소인 pca_component_1이 전체 변동성의 약 72.9%를 차지하며, 두 번째인 pca_component_2가 약 22.8%를 차지한다. 따라서 PCA를 2개 요소로만 변환해도 원본 데이터의 변동성을 95% 설명할 수 있는 것이다.

이를 바탕으로, 원본 feature 데이터와 pca.feature 데이터로 활용한 정확도 결과를 cross_val_score()으로 비교하겠다.

원본 데이터와 PCA 변환된 데이터 기반에서 예측 성능 비교

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

rcf = RandomForestClassifier(random_state=156)
scores = cross_val_score(rcf, iris.data, iris.target,scoring='accuracy',cv=3)
print(scores)
print(np.mean(scores))
[0.98039216 0.92156863 0.97916667]
0.960375816993464


C:\Users\Oh Won Jin\Anaconda3\lib\site-packages\sklearn\ensemble\forest.py:245: FutureWarning: The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.
  "10 in version 0.20 to 100 in 0.22.", FutureWarning)
C:\Users\Oh Won Jin\Anaconda3\lib\site-packages\sklearn\ensemble\forest.py:245: FutureWarning: The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.
  "10 in version 0.20 to 100 in 0.22.", FutureWarning)
C:\Users\Oh Won Jin\Anaconda3\lib\site-packages\sklearn\ensemble\forest.py:245: FutureWarning: The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.
  "10 in version 0.20 to 100 in 0.22.", FutureWarning)
pca_X = irisDF_pca[['pca_component_1', 'pca_component_2']]
scores_pca = cross_val_score(rcf, pca_X, iris.target, scoring='accuracy', cv=3 )
print(scores_pca)
print(np.mean(scores_pca))
[0.8627451  0.84313725 0.89583333]
0.8672385620915034


C:\Users\Oh Won Jin\Anaconda3\lib\site-packages\sklearn\ensemble\forest.py:245: FutureWarning: The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.
  "10 in version 0.20 to 100 in 0.22.", FutureWarning)
C:\Users\Oh Won Jin\Anaconda3\lib\site-packages\sklearn\ensemble\forest.py:245: FutureWarning: The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.
  "10 in version 0.20 to 100 in 0.22.", FutureWarning)
C:\Users\Oh Won Jin\Anaconda3\lib\site-packages\sklearn\ensemble\forest.py:245: FutureWarning: The default value of n_estimators will change from 10 in version 0.20 to 100 in 0.22.
  "10 in version 0.20 to 100 in 0.22.", FutureWarning)

위 결과에서 보듯이, 원본 데이터 세트 대비 예측 정확도는 PCA 변환 차원 개수에 따라 예측 성능이 떨어질 수 밖에 없다. 그래도 feature의 개수가 50% 감소한 것에 비하면 PCA 변환된 데이터 또한 원본 데이터 세트의 특성을 상당 부분 유지하고 있음을 알 수 있다.

## 3 : LDA(Linear Discriminant Analysis) ### LDA 개요

LDA는 선형 판별 분석법으로 불리며, PCA와 매우 유사하다. LDA는 PCA와 유사하게 입력 데이터 세트를 저차원 공간에 투영하여 차원을 축소하는 기법이지만, 중요한 차이는 LDA는 지도학습의 분류(classification)에서 사용하기 쉽도록 개별 클래스를 분별할 수 있는 기준을 최대한 유지하면서 차원을 축소한다. PCA는 입력 데이터의 변동성의 가장 큰 축을 찾았지만, LDA는 입력 데이터의 결정 값 클래스를 최대한으로 분리할 수 있는 축을 찾는다.

LDA는 특정 공간 상에서 클래스 분리를 최대화하는 축을 찾기 위해 클래스 간 분산(between-class scatter)과 클래스 내부 분산(within-class scatter)의 비율을 최대화하는 방식으로 차원을 축소한다. 즉, 클래스 간 분산을 최대한 크게 가져가고, 클래스 내부의 분산은 최대한 작게 가져가는 방식이다.

일반적으로 LDA를 구하는 스텝은 PCA와 유사하나, 가장 큰 차이점은 공분산 행렬이 아니라 위에 설명한 클래스 간 분산과 클래스 내부 분산 행렬을 생성한 뒤, 이 행렬에 기반해 고유 벡터를 구하고 입력 데이터를 투영한다는 점이다.

LDA를 구하는 스텝

  1. 클래스 내부와 클래스 간 분산 행렬을 구한다. 이 두 개의 행렬은 입력 데이터의 결정값 클래스 별로 개별 feature의 mean vector을 기반으로 구한다.

  2. 클래스 내부 분산 행렬을 $S_w$, 클래스 간 분산 행렬을 $S_B$ 라고 하면 다음 식으로 두 행렬을 고유 벡터로 분해할 수 있다.

$S_W^T$$S_B$ = $[e_1 … e_n ]$ $\begin{pmatrix} \lambda_1 & …& 0 \ … & … & … \ 0 & … & \lambda_n \end{pmatrix}$ $\begin{pmatrix} e_1^T \ … \ e_n^T \end{pmatrix}$

  1. 고유값이 가장 큰 순으로 K개(LDA 변환 차수만큼) 추출한다.

  2. 고유값이 가장 큰 순으로 추출된 고유벡터를 이용하여 새롭게 입력 데이터를 변환한다.

붓꽃 데이터 셋에 LDA 적용하기

from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris

iris = load_iris()
iris_scaled = StandardScaler().fit_transform(iris.data)
lda = LinearDiscriminantAnalysis(n_components = 2)
# fit() 호출 시 target 값 입력
lda.fit(iris_scaled, iris.target)
iris_lda = lda.transform(iris_scaled)
print(iris_lda.shape)

(150, 2)
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

lda_columns=['lda_component_1','lda_component_2']
irisDF_lda = pd.DataFrame(iris_lda,columns=lda_columns)
irisDF_lda['target']=iris.target

#setosa는 세모, versicolor는 네모, virginica는 동그라미로 표현
markers=['^', 's', 'o']

#setosa의 target 값은 0, versicolor는 1, virginica는 2. 각 target 별로 다른 shape으로 scatter plot
for i, marker in enumerate(markers):
    x_axis_data = irisDF_lda[irisDF_lda['target']==i]['lda_component_1']
    y_axis_data = irisDF_lda[irisDF_lda['target']==i]['lda_component_2']

    plt.scatter(x_axis_data, y_axis_data, marker=marker,label=iris.target_names[i])

plt.legend(loc='upper right')
plt.xlabel('lda_component_1')
plt.ylabel('lda_component_2')
plt.show()

output_35_0

from sklearn.datasets import load_iris
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

# 사이킷런 내장 데이터 셋 API 호출
iris = load_iris()

# 넘파이 데이터 셋을 Pandas DataFrame으로 변환
columns = ['sepal_length','sepal_width','petal_length','petal_width']
irisDF = pd.DataFrame(iris.data , columns=columns)
irisDF['target']=iris.target
irisDF.head(3)
from sklearn.preprocessing import StandardScaler

iris_scaled = StandardScaler().fit_transform(irisDF.iloc[:, :-1])
from sklearn.decomposition import PCA

pca = PCA(n_components=2)

#fit( )과 transform( ) 을 호출하여 PCA 변환 데이터 반환
pca.fit(iris_scaled)
iris_pca = pca.transform(iris_scaled)
print(iris_pca.shape)
pca_columns=['pca_component_1','pca_component_2']
irisDF_pca = pd.DataFrame(iris_pca,columns=pca_columns)
irisDF_pca['target']=iris.target
#setosa를 세모, versicolor를 네모, virginica를 동그라미로 표시
markers=['^', 's', 'o']

#pca_component_1 을 x축, pc_component_2를 y축으로 scatter plot 수행. 
for i, marker in enumerate(markers):
    x_axis_data = irisDF_pca[irisDF_pca['target']==i]['pca_component_1']
    y_axis_data = irisDF_pca[irisDF_pca['target']==i]['pca_component_2']
    plt.scatter(x_axis_data, y_axis_data, marker=marker,label=iris.target_names[i])

plt.legend()
plt.xlabel('pca_component_1')
plt.ylabel('pca_component_2')
plt.show()
(150, 2)

output_36_1

## 4 : SVD(Singular Value Decomposition) - 특이값 분해

### ※ SVD의 수리적 이해에 대한 참고 링크※

  • SVD의 차원 축소 개념적 설명 링크

    https://www.youtube.com/watch?v=VUUIrbMKUT4

  • SVD의 기하적 접근 설명 링크

    https://www.youtube.com/watch?v=cq5qlYtnLoY

  • 기타 링크

https://ratsgo.github.io/from%20frequency%20to%20semantics/2017/04/06/pcasvdlsa/

https://yjjo.tistory.com/10

SVD 개요

SVD 역시 PCA와 유사한 행렬 분해 기법을 이용한다. PCA의 경우 정방 행렬만을 고유 벡터로 분해할 수 있지만, SVD는 정방 행렬뿐만 아니라 행과 열의 크기가 다른 행렬에도 적용할 수 있다. 일반적으로 SVD는 m x n 크기의 행렬 A를 다음과 같이 분해하는 것으 의미한다.

$ A = U \sum V^2$

행렬 U와 V는 특이벡터(singular vector)이며, 모든 특이 벡터는 서로 ‘직교’하는 성질을 가진다.

#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]]

SVD 행렬 분해

  • 사이킷런에서는 numpy.linalg 클래스에서 svd를 제공하며 반환값은 U, sigma 리스트 값, V 행렬이다.

다음은 행렬의 개별 row 끼리의 선형 종속성을 없애 Rank 값이 최대가 되도록 랜덤 난수를 생성하여 차원 축소가 아닌, 그저 분해 형태를 보이고 다시 원복하는 과정이다.

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]]

분해된 행렬들을 이용하여 다시 원행렬로 원복

# Simga를 다시 0 을 포함한 대칭행렬로 변환
Sigma_mat = np.diag(Sigma)
# np.dot은 행렬 곱을 의미한다.
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]]

위 결과에서 보듯이 원 행렬과 동일함을 확인할 수 있다.

데이터 의존도가 높은 원본 데이터 행렬 생성

다음으로 row 간 선형 종속성이 강하여 선형 종속인 행렬을 생성한 후, SVD를 적용해보겠다.

# 의존성을 부여하기 위해 a 행렬의 3번째 row를 '첫 번째 row + 두 번째 row'로 업데이트
# 하고 4번 째 row는 첫 번째 row와 같다고 업데이트 하겠다.
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 ]]
# 다시 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.   ]
# 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 ]]

Truncated SVD 를 이용한 행렬 분해

  • Truncated SVD는 numpy가 아닌 scipy에서만 지원한다. 물론 scipy는 svd도 지원한다. Truncated SVD는 희소 행렬로만 지원돼서 scipy.sparse.linalg.svds를 이용해야 한다.
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 행렬의 특이값을 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]]

위 결과에서 Truncated SVD로 분해된 행렬로 다시 복원할 경우 완벽하게 복원되지 않고 근사적으로 복원됨을 알 수 있다.

사이킷런 TruncatedSVD 클래스를 이용한 변환

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')
Text(0, 0.5, 'TruncatedSVD Component 2')

output_56_1

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')
Text(0.5, 1.0, 'PCA Transformed')

output_57_1

위와 같은 결과에서 확인할 것은, 데이터 세트가 스케일링으로 데이터 중심이 동일해지면 사이킷런의 SVD와 PCA는 동일한 변환을 수행한다. 이는 PCA가 SVD 알고리즘으로 구현됐음을 의미한다. 하지만 PCA는 밀집 행렬(Dense Matrix)에 대한 변환만 가능하며 SVD는 희소 행렬에 대한 변환도 가능하다는 점에서 차이가 있다.

5 : NMF (Non - Negative Matrix Factorization)

NMF는 Truncated SVD와 같이 낮은 랭크를 통한 행렬 근사 방식의 변형이다.

NMF는 원본 행렬 내의 모든 원소 값이 모두 양수라는게 보장되면 다음과 같이 더 간단하게 두 개의 기반 양수 행렬로 분해될 수 있는 기법이다.

V(m x n) = W (m x k ) x H ( k x n)

W와 H는 각각 잠재 요소를 특성으로 가지는데, W는 원본 행에 대해서 이 잠재 요소(Latent Factor)의 값이 얼마나 대응하며, H는 이 잠재 요소가 원본 열(원본 속성)로 어떻게 구성됐는지를 나타내는 행렬이다.

따라서 feature가 column에 있는 경우, 원본 데이터 대신 분해 행렬 H(잠재 요소가 있는) 을 사용하여 차원 축소를 한다.

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')

Text(0, 0.5, 'NMF Component 2')

output_64_1


Tags:

Categories:

Updated: