Data Science

차원 축소

Gauss1 2020. 10. 13. 02:08

차원축소

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

 

차원 축소가 필요한 이유

차원이 증가할수록 데이터 포이트 간의 거리가 기하급수적으로 멀어지게되고 희소한 구조를 가지게 됨

수백개 이상의 피처로 구성된 데이터 세트의 경우 상대적으로 적은 차원에서 학습된 모델에 비해 예측 신뢰도가 떨어짐
=> 이유는?
피쳐가 많을 경우 개별 피쳐간에 상관관계가 높을 가능성이 크다.
3차원 이하로 축소할 경우 시각적으로 표한가능
학습 데이터의 크기가 줄어드는 효과

 

피처 선택(feature selection)

종속성이 강한 불필요한 피처는 제거하고 데이터의 특징을 잘 나타내는 주요 피처만 선택

 

피처 추출(feature extraction)

기존 피처를 저차원의 중요 피처로 압축해서 추출하는 것

 

 

PCA(Principal Component Analysis)

여러 변수간에 존재하는 상관관계를 이용해 이를 대표하는 주성분(Principal Component)을 추출해 차원을 축소하는 기법 

차원을 축소할 때는 기본 데이터의 정보 유실이 최소화 되는 것이 당연

가장 높은 분산을 가지는 데이터의 축을 찾아 이 축으로 차원을 축소하는데 이것이 PCA의 주 성분이 된다.

분산이 데이터의 특성을 가장 잘 나타내는 것으로 간주한다.

 

선형대수 관점에서 해석해 보면 입력데이터의 공분산 행렬을 고유값 분해하고 이렇게 구한 고유 벡터에 입력데이터를 선형변환하는 것입니다.

선형 변환은 특정 벡터에 행렬 A를 곱해 새로운 벡터로 변환하는 것을 의미한다.

이를 특정벡터를 하나의 공간에서 다른 공간으로 투영하는 개념을 볼 수 있으며 공분산은 두 변수 간의 변동을 의미

 

Cov(X, Y) > 0    X가 증가 할 때 Y도 증가한다.
Cov(X, Y) < 0    X가 증가 할 때 Y는 감소한다.
Cov(X, Y) = 0    공분산이 0이라면 두 변수간에는 아무런 선형관계가 없으며 두 변수는 서로 독립적인 관계에 있음을 알 수 있다.  그러나 두 변수가 독립적이라면 공분산은 0이 되지만, 공분산이 0이라고 해서 항상 독립적이라고 할 수 없다.

 

붓꽃 데이터 세트에 PCA 적용

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

iris = load_iris()
columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
irisDF = pd.DataFrame(iris.data,columns=columns)
irisDF['target']=iris.target
irisDF.head()

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

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

PCA로 4개 속성을 2개로 압축해보자

PCA를 적용하기 전에 개별 속성을 동일한 스케일로 변환해야 함

from sklearn.preprocessing import StandardScaler
from  sklearn.decomposition import PCA

iris_scaled = StandardScaler().fit_transform(irisDF.iloc[:,:-1])
pca = PCA(n_components=2)

pca.fit(iris_scaled)
iris_pca = pca.transform(iris_scaled)
print(iris_scaled.shape)
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
irisDF_pca.head(3)

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

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

 

 

첫번째 축인 pca_component_1이 원본 데이터의 변동성을 잘 반영했기 때문에 구분이 더욱 명확해짐

PCA component 별로 원본 데이터의 변동성을 얼마나 잘 반영하고 있는지 비율을 구해보면

print(pca.explained_variance_ratio_)

=> 2개의 요소로만 변환해도 원본 데이터의 변동성을 95% 설명 가능

 

원본 세트와 변환된 세트로 각각 분류를 적용하고 결과를 비교

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

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('PCA 변환 데이터 교차 검증 개별 정확도:',scores_pca)
print('PCA 변환 데이터 평균 정확도:',np.mean(scores_pca))

PCA 변환 차원 개수에 따라 예측 성능이 떨어질 수 있지만 속성 개수의 감소를 고려할 때 원본 데이터의 특성을 상당 부분 유지하고 있음을 알수 있다.

 

LDA(Linear Discriminant Analysis)

PCA와 유사하게 입력 데이터 세트를 저차원 공간에 투영해 차원을 축소하는 기법이지만,

LDA는 지도학습의 분류에서 사용하기 쉽도록 개별 클래스를 분별할 수 있는 기준을 최대한 유지하면서 차원을 축소

즉, PCA는 입력 데이터의 변동성의 가장 큰 축을 찾았지만 LDA는 입력 데이터의 결정값 클래스를 최대한으로 분리할 수 있는 축을 찾음

이걸 구현하는 방법은 클래스간 분산은 최대한 크게 가져가고, 클래스 내부의 분산은 최대한 작게 가져가면됨

붓꽃 데이터 세트에 LDA 적용

LDA는 PCA와 다르게 지도학습이기 때문에 클래스의 결정값이 변환시에 필요함

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)
# fitting시 결정값 입력
lda.fit(iris_scaled,iris.target)
iris_lda=lda.transform(iris_scaled)
print(iris_lda.shape)

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

markers=['^','s','o']

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

SVD(Singular Value Decomposition)

PCA와 유사한 행렬 분해 기법을 이용하지만

PCA의 경우 정방행렬만을 고유벡터로 분해할 수 있지만, SVD는 정방행렬뿐만 아니라 행과 열의 크기가 다른 행렬에도 적용할 수 있다.

import numpy as np
from numpy.linalg import svd

np.random.seed(121)
a = np.random.randn(4,4)
print(np.round(a,3))


U, Sigma, Vt = svd(a)
print(U.shape,Sigma.shape,Vt.shape)
print('U matrix:\n',np.round(U,3))
print('Sigma matrix:\n',np.round(Sigma,3))
print('V tanspose matrix:\n',np.round(Vt,3))

# 복원 되는지 확인
Sigma_mat=np.diag(Sigma)
a_=np.dot(np.dot(U,Sigma_mat),Vt)
print(np.round(a_,3))

# 일부로 의존성 부여
a[2] = a[0]+a[1]
a[3] = a[0]
print(np.round(a,3))

U, Sigma, Vt = svd(a)
print(U.shape,Sigma.shape,Vt.shape)
print('U matrix:\n',np.round(U,3))
print('Sigma matrix:\n',np.round(Sigma,3))
print('V tanspose matrix:\n',np.round(Vt,3))

U_=U[:,:2]
Sigma_=np.diag(Sigma[:2])
Vt_=Vt[:2]
print(U_.shape,Sigma_.shape,Vt_.shape)
a_=np.dot(np.dot(U_,Sigma_),Vt_)
print(np.round(a_,3))

Truncated SVD

import numpy as np
from scipy.sparse.linalg import svds
from scipy.linalg import svd

#SVD
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
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)

print('\nTruncated SVD로 분해 후 복원 행렬:\n',matrix_tr)



from sklearn.decomposition import TruncatedSVD,PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
%matplotlib inline

iris = load_iris()
iris_ftrs = iris.data

# Truncated SVD
tsvd = TruncatedSVD(n_components=2)
tsvd.fit(iris_ftrs)
iris_tsvd=tsvd.transform(iris_ftrs)


plt.scatter(x=iris_tsvd[:,0],y=iris_tsvd[:,1],c=iris.target)
plt.xlabel('TruncatedSVD Component 1')
plt.ylabel('TruncatedSVD Component 2')





from sklearn.decomposition import TruncatedSVD,PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
%matplotlib inline

iris = load_iris()
iris_ftrs = iris.data

scaler = StandardScaler()
iris_scaled = scaler.fit_transform(iris_ftrs)

# Truncated SVD
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)


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

NMF(Non-Negative Matrix Factorization)

NMF는 Truncated SVD와 같이 낮은 랭크를 통한 행렬 근사(Low-Rank Approximation) 방식의 변형

NMF는 원본 행렬의 모드 원소 값이 모두 양수(0 이상)라는 게 보장되면 다음과 같이 좀 더 간단하게 두 개의 기반 양수 행렬로 분해될 수 있는 기법을 지칭한다.

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.xlabel('NMF Component 2')

 

PCA는 입력 데이터의 변동성이 가장 큰 축을 구하고, 다시 이 축에 직각인 축을 반복적으로 축소하려는 차원 개수만 큼 구한 뒤 입력데이터를 이 축들에 투영해 차원을 축소하는 방식

이를 위해 공분산 행렬을 기반으로 고유 벡터를 생성하고 이렇게 구한 고유 벡터에 입력데이터를 선형변환하는 ㅂ아식

LDA는 PCA와 매우 유사한망식이며 PCA가 입력 데이터의 변동성의 가장 큰 축을 찾는데 반해 LDA는 입력데이터의 결정값 클래스를 최대한으로 분리할 수 있는 축을 찾는 방식으로 차원을 축소한다.

SVD와 NMF는 매우 많은 피처데이터를 가진 고차원 행렬을 두개의 저 차원행렬로 분리하는 행렬 분해 기법

이러한 행렬 분해를 수행하면서 원본 행렬에서 잠재된 요소를 추출하기 때문에 토픽 모델링이나 추천 시스템에서 활발하게 활용합니다.

'Data Science' 카테고리의 다른 글

회귀  (0) 2020.10.07
분류  (0) 2020.10.06
데이터 가공  (0) 2020.08.19
사이킷런(scikit-learn)  (0) 2020.08.14
Numpy, Pandas  (0) 2020.08.14