머신러닝

차원 축소 (Dimension Reduction) - PCA, LDA

jeongpil 2021. 7. 14. 16:18

*해당 포스팅은 파이썬 머신러닝 완벽 가이드(권철민 지음) 교재를 공부하며 작성한 글입니다.

 

안녕하세요! 오늘은 차원 축소 알고리즘 중 PCA 와 LDA에 대해 알아보겠습니다.

 

 

1. 차원 축소란?

2. PCA

3. LDA

 

1. 차원 축소란?

 

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

 

일반적으로 차원이 증가할수록 데이터 간의 거리가 기하급수적으로 증가하기 때문에 희소한 구조를 가지게 되고 모델의 예측 신뢰도가 떨어지게 됩니다.

 

차원 축소를 할 경우 학습 데이터의 크기가 줄어들어서 학습에 필요한 처리 능력도 줄일 수 있습니다.

 

 

일반적으로 차원 축소는 피처 선택피처 추출로 나눌 수 있습니다.

 

피처 선책은 말 그대로 특정 피처에 종속성이 강한 불필요한 피처는 아예 제거하는 것이고 피처 추출은 기존 피처를 저차원의 중요 피처로 압축해서 추출하는 것입니다.

 

이렇게 새롭게 추출된 중요 피처는 기존의 피처가 압축된 것이므로 기존의 피처와는 완전히 다른 값이 됩니다.

 

 

피처 선택장점은 선택한 피처의 해석이 용이하다는 점이고 단점은 피처간 상관관계를 고려하기 어렵다는 점입니다.

 

 

피처 추출장점은 피처 간 상관관계를 고려하기 용이하고 피처의 개수를 많이 줄일 수 있다는 점이고 단점은 추출된 변수의 해석이 어렵다는 점입니다.

 

 

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

 

차원 축소 알고리즘은 이미지 분류 등의 분류 수행 시에 과적합을 방지할 수 있고 텍스트 문서의 숨겨진 의미를 추출할 수 있습니다. 

 

 

2. PCA

 

PCA는 가장 대표적인 차원 축소 기법입니다.

 

PCA는 여러 변수 간에 존재하는 상관관계를 이용해 이를 대표하는 주성분을 추출해 차원을 축소하는 기법입니다.

 

PCA 기법의 핵심은 데이터를 축에 사영했을 때 가장 높은 분산을 가지는 데이터의 축을 찾아 그 축으로 차원을 축소하는 것인데, 이 축을 주성분이라고 말합니다.

 

높은 분산을 가지는 축을 찾는 이유는 정보의 손실을 최소화하기 위함입니다.

 

사영했을 때 분산이 크다는 것원래 데이터의 분포를 잘 설명할 수 있다는 것을 뜻하고 정보의 손실을 최소화 할 수 있다는 것을 뜻합니다. 

 

 

 

출처: https://www.youtube.com/watch?v=FhQm2Tc8Kic&list=PLpIPLT0Pf7IoTxTCi2MEQ94MZnHaxrP0j&index=11

 

 

이해를 돕기 위해 김성범 교수님의 "[핵심 머신러닝] Principal Component Analysis" 강의에서 그림을 가져왔습니다.

 

한눈에 봐도 데이터를 좌측 축에 사영시키는 것이 데이터의 원래 분포를 잘 설명한다고 볼 수 있습니다.

 

 

출처 : https://blog.bioturing.com/2018/06/14/principal-component-analysis-explained-simply/

 

 

PCA는 제일 먼저 가장 큰 분산을 기반으로 첫 번째 축을 생성하고,

 

두 번째 축은 이 벡터 축에 직각이 되는 벡터를 축으로 합니다.

 

세 번째 축은 다시 두 번째 축과 직각이 되는 벡터를 축으로 설정하는 방식으로 축을 생성합니다.

 

이렇게 생성된 벡터 축에 원본 데이터를 투영하면 벡터 축의 개수만큼의 차원으로 원본 데이터가 차원 축소됩니다.

 

 

직각이 되는 벡터를 다음 축으로 설정하는 이유는 예를 들어 설명해 보겠습니다.

 

첫 번째 축에 데이터를 사영했는데 같은 점으로 겹쳐서 사영된 데이터가 10개가 있다고 가정하겠습니다.

 

첫 번째 축으로는 이 10개의 데이터를 모두 설명할 수 없기 때문에 다른 축이 필요합니다.

 

이때, 직교하는 축으로 사영하게 되면 겹쳤던 10개의 데이터는 절대 같은 점으로 사영될 수 없고 두 번째 축이 이 10개의 데이터를 설명할 수 있게 됩니다.

 

이런 원리로 계속 직교하는 축을 다음 축으로 설정하는 방식으로 축을 생성하게 되는 것입니다.

 

그렇다면 이제 이 축을 어떻게 찾는지 알아보겠습니다.

 

 

입력 데이터의 공분산 행렬을 고유값 분해했을 때 구해진 고유벡터 PCA의 주성분 벡터로서 입력 데이터의 분산이 큰 방향을 나타냅니다.

 

고유값고유벡터의 크기를 나타내고 입력 데이터의 분산을 나타냅니다.

 

공분산은 두 변수 간의 변동을 의미하고 고유벡터는 n x n 행렬 A에 대해서 Ax=ax (a는 스칼라값) 을 만족하는 x를 고유벡터라고 합니다. 

 

이때, a를 고유값이라고 합니다.

 

공분산 행렬은 정방행렬이며 대칭행렬인데, 대칭행렬은 항상 고유벡터를 직교행렬로, 고유값을 정방 행렬로 대각화할 수 있는 특성을 가지고 있습니다.

 

이에 공분산 행렬을 A라고 했을 때,

 

 

 

 

이와 같이 A를 고유값 분해할 수 있습니다.

 

이때 v1, v2 ... vn이 고유벡터에 해당하고 λ1, λ2 ... λn이 고유값해당합니다.

 

v1이 가장 분산이 큰 방향을 가진 고유벡터이고 v2는 v1에 수직이면서 다음으로 가장 분산이 큰 방향을 가진 고유벡터입니다.

 

정방행렬을 위처럼 분해하는 것이 고유값 분해인데, 분해하는 수학적인 방법은 다음에 포스팅을 해보도록 하겠습니다.

 

지금까지 설명이 매우 길었는데 결국 PCA에서 기억해야 할 것은

 

"입력 데이터의 공분산 행렬이 고유벡터와 고유값으로 분해될 수 있으며, 이렇게 분해된 고유벡터를 이용해 입력 데이터를 선형 변환하는 방식이 PCA라는 것" 입니다.

 

PCA가 진행되는 순서를 정리해 보겠습니다.

 

 

① 입력 데이터 세트의 공분산 행렬을 구합니다.

 

② 공분산 행렬을 고유값 분해해서 고유벡터와 고유값을 구합니다.

 

③ 고유값이 가장 큰 순으로 K개 (변환 차수) 만큼 고유벡터를 추출합니다.

 

④ 고유벡터에 입력 데이터를 선형 변환합니다.

 

 

사이킷런에 내장되어 있는 iris 데이터 세트는 4개의 속성 (sepal length, sepal width, petal length, petal width) 로 되어 있는데,

 

이를 2개의 속성으로 차원 축소하여 원래 데이터 세트와 축소된 데이터 세트가 어떻게 달라지는지 확인해 보겠습니다.

 

 

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']
df_iris=pd.DataFrame(iris.data,columns=columns)
df_iris['target']=iris.target
df_iris.head()

 

 

PCA를 적용하기 전에 개별 속성을 함께 스케일링해야 합니다.

 

PCA는 여러 속성의 값을 연산해야 하기 때문에 속성의 스케일에 영향을 받기 때문입니다.

 

 

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

for i, marker in enumerate(markers):
    x_axis_data=df_iris[df_iris['target']==i]['sepal_length']
    y_axis_data=df_iris[df_iris['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()

 

 

원래 데이터는 versicolor와 virginica가 분류가 어려운 것을 확인할 수 있습니다.

 

그럼 PCA를 통해 속성을 2개로 축소해보고 어떻게 변화되는지 확인해 보겠습니다.

 

 

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

scaler=StandardScaler()
iris_scaled=scaler.fit_transform(df_iris.iloc[:,:-1])

#2차원으로 차원 축소
pca=PCA(n_components=2)
pca.fit(iris_scaled)
iris_pca=pca.transform(iris_scaled)

pca_columns=['pca_component_1','pca_component_2']
df_iris_pca=pd.DataFrame(iris_pca,columns=pca_columns)
df_iris_pca['target']=iris.target
df_iris_pca.head()

 

 

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

for i, marker in enumerate(markers):
    x_axis_data=df_iris_pca[df_iris_pca['target']==i]['pca_component_1']
    y_axis_data=df_iris_pca[df_iris_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()

 

versicolor와 virginica가 완벽히 분류되지는 않았지만 원래 데이터에 비해 훨씬 분류가 잘 되어 있는 것을 확인할 수 있습니다.

 

그래프를 보면 데이터가 pca_component_1 축을 기반으로 서로 겹치는 부분이 별로 존재하지 않게 잘 분류되어 있는 것을 확인할 수 있습니다.

 

이는 첫 번째 주성분인 pca_component_1이 원본 데이터의 변동성을 잘 반영했기 때문입니다.

 

PCA 주성분 별로 원본 데이터의 주성분을 얼마나 반영하는지 알아보겠습니다.

 

 

pca.explained_variance_ratio_

 

 

첫 번째 주성분이 약 73%, 두 번째 주성분이 약 23%로 두 주성분이 원본 데이터의 96%를 설명할 수 있는 것을 확인할 수 있습니다.

 

분류 예측 정확도는 PCA 변환 차원 개수에 따라 떨어질 수밖에 없습니다. 

 

모델은 RandomForestClassifier를 이용하고 교차 검증 세트로 정확도를 비교해 보겠습니다.

 

 

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

#원본 데이터
rf_clf=RandomForestClassifier(random_state=156)
scores=cross_val_score(rf_clf,iris.data,iris.target,scoring='accuracy',cv=3)
print('원본 데이터 교차 검증 평균 정확도:{0:.4f}'.format(np.mean(scores)))

#PCA변환 후 데이터
pca_X=df_iris_pca[['pca_component_1','pca_component_2']]
scores_pca=cross_val_score(rf_clf,pca_X,iris.target,scoring='accuracy',cv=3)
print('PCA 변환 후 교차 검증 평균 정확도:{0:.4f}'.format(np.mean(scores_pca)))

 

 

PCA를 통해 2차원으로 축소한 결과 예측 성능이 8% 하락했습니다.

 

하지만 속성 개수가 50% 감소한 것을 고려하면 PCA 변환 후에도 원본 데이터의 특성을 상당 부분 유지하고 있음을 알 수 있습니다.

 

하지만, PCA의 주요 한계점으로 최대의 분산의 각 축이 반드시 클래스 간의 구별을 잘하는 좋은 피처를 뽑아준다는 보장이 없다는 점이 있습니다.

 

 

출처: http://www.kwangsiklee.com/2017/12/%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D%EC%97%90-%ED%95%84%EC%9A%94%ED%95%9C-pcalda-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0/

 

 

위의 그림을 보면 가장 데이터가 안 겹치게 사영되는 것은 3번째 축이지만 사실 사영했을 때 분류가 제일 잘 되는 것은 2번째 축입니다.

 

그래서, 차원을 축소하는 데 있어 클래스 간의 차별성을 최대화할 수 있는 방향으로 수행하는 것이 LDA입니다.

 

 

3. LDA

 

LDA는 선형 판별 분석법으로 불리며 PCA와 매우 유사합니다.

 

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

 

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

 

 

PCA는 입력 데이터의 변동성(분산)이 가장 큰 축을 찾았지만,

 

LDA는 입력 데이터의 결정 값 클래스를 최대한으로 분리할 수 있는 축을 찾습니다.

 

즉, LDA는 클래스 분리를 최대화하는 축을 찾기 위해 클래스 간 분산을 최대화하고, 클래스 내부 분산은 최대한 작게 가져가는 방식입니다.

 

클래스의 값들은 모여있고 클래스끼리는 서로 많이 떨어져 있음을 의미합니다.

 

LDA는 공분산 행렬이 아닌 클래스 내부와 클래스 간 분산 행렬에 기반해 고유벡터를 구하고 입력 데이터를 투영합니다.

 

LDA가 진행되는 순서를 정리해 보겠습니다.

 

 

① 클래스 내부와 클래스 간 분산 행렬을 구합니다.

 

② 클래스 내부 분산 행렬의 역치 행렬과 클래스 간 분산 행렬의 곱을 분해하여 고유벡터와 고유값을 구합니다.

 

③ 고유값이 가장 큰 순으로 K개 추출합니다.

 

④ 추출된 고유벡터를 이용해 입력 데이터를 선형 변환합니다.

 

 

첫 번째 과정과 두 번째 과정이 PCA와 다르고 나머지는 같습니다.

 

변환하는 코드는 PCA와 매우 유사하므로 넘어가겠습니다.

 

한 가지 유의해야 할 점은 LDA는 실제로는 PCA와 다르게 지도학습이기 때문에 클래스의 target 값을 변환 시에 넣어줘야 합니다.

 

 

lda=LinearDiscriminantAnalysis(n_components=2)
lda.fit(iris_scaled,iris.target)

 

 

위와 같이 iris.target 값을 넣어줘야 합니다.

 

 

 

이렇게 차원 축소의 PCA와 LDA에 대해 알아봤습니다.

 

이해되지 않는 부분이 없도록 자세하게 적다 보니 글이 매우 길어졌네요...ㅎ

 

그래도 저도 포스팅을 하면서 예전에 전공 수업에서 배웠던 고유값 분해에 대해 더 자세히 공부하고

 

여러 곳에서 차원 축소에 대해 찾아서 공부해보면서 차원 축소에 대해 더욱 자세히 알게 되고 이해할 수 있게 되었습니다.

 

여러분도 잘 이해되었기를 바라며 포스팅 마치겠습니다 :)