Machine Learning/Basic

[6-3] 주성분 분석

jwjwvison 2021. 4. 13. 10:25

이 포스팅은 혼자 공부하는 머신러닝 + 딥러닝 책을 공부하고 정리한것 입니다.


 k-평균 알고리즘으로 업로드된 사진을 클러스터로 분류하여 저장할수 있게 되었다. 그런데 시간이 지나면서 너무 많은 사진이 등록되어 저장 공간이 부족해질 수 있다. 나중에 군집이나 분류에 영향을 끼치지 않으면서 업로드된 사진의 용량을 줄일 수 있을까?

 

 

차원과 차원 축소

 지금까지 우리는 데이터가 가진 속성을 특성이라 불렀다. 과일 사진의 경우 10000개의 픽셀이 있기 때문에 10000개의 특성이 있는 셈이다. 머신러닝에서는 이런 특성을 차원(dimension) 이라고도 부른다. 10000개의 특성은 결국 10000개의 차원이라는 건데 이 차원을 줄일 수 있다면 저장 공간을 크게 절약할 수 있을 것이다.

 

 

 이를 위해 비지도 학습 작업 중 하나인 차원 축소(dimension reduction) 알고리즘을 다루어 보겠다. 앞에서 특성이 많으면 선형 모델의 성능이 높아지고 훈련 데이터에 쉽게 과대적합 된다는것을 공부했다. 차원 축소는 데이터를 가장 잘 나타내는 일부 특성을 선택하여 데이터 크기를 줄이고 지도 학습 모델의 성능을 향상시킬 수 있는 방법이다.

 또한 줄어든 차원에서 다시 원본 차원으로 손실을 최대한 줄이면서 복원할 수도 있다. 이번 시간에는 대표적인 차원 축소 알고리즘인 주성분 분석(principal component analysis)을 공부할 것이다. 주성분 분석을 간단히 PCA 라고도 부른다.

 

 

주성분 분석 소개

 PCA는 데이터에 있는 분산이 큰 방향을 찾는 것으로 이해할 수 있다. 분산은 데이터가 널리 퍼져있는 정도를 말한다. 분산이 큰 방향을 데이터로 잘 표현하는 벡터로 생각할 수 있다. 이해하기 쉽도록 다음과 같은 2차원 데이터를 생각해보자.

 

 

 우리는 직관적으로 길게 늘어진 대각선 방향이 분산이 가장 크다고 알 수 있다. 위의 그림에서 화살표 위치는 큰 의미가 없다. 분산이 큰 방향을 찾는 것이 중요하다.

 앞에서 찾은 직선이 원점에서 출발한다면 두 원소로 이루어진 벡터로 쓸 수 있다.

 

 

 이 벡터를 주성분(principal component) 라고 부른다. 이 주성분 벡터는 원본 데이터에 있는 어떤 방향이다. 따라서 주성분 벡터의 원소 개수는 원본 데이터셋에 있는 특성 개수와 같다. 하지만 원본 데이터는 주성분을 사용해 차원을 줄일 수 있다. 예를 들면 다음과 같이 샘플 데이터 s(4,2)를 주성분에 직각으로 투영하면 1차원 데이터 p(4,5)를 만들 수 있다.

 

 

 주성분은 원본 차원과 같고 주성분으로 바꾼 데이터는 차원이 줄어든다는 점을 꼭 기억해야 한다. 주성분이 가장 분산이 큰 방향이기 때문에 주성분에 투영하여 바꾼 데이터는 원본이 가지고 있는 특성을 가장 잘 나타내고 있을 것이다.

 

 첫 번째 주성분을 찾은 다음 이 벡터에 수직이고 분산이 가장 큰 다음 방향을 찾는다. 이 벡터가 두 번째 주성분이다. 여기서는 2차원이기 때문에 두 번째 주성분의 방향은 다음처럼 하나 뿐이다.

 

 

 일반적으로 주성분은 원복 특성의 개수만큼 찾을 수 있다.

 

 

 

PCA 클래스

!wget https://bit.ly/fruits_300_data -O fruits_300.npy

import numpy as np
fruits=np.load('fruits_300.npy')
fruits_2d=fruits.reshape(-1,100*100)

 

 사이킷런은 sklearn.decomposition 모듈 아래 PCA 클래스로 주성분 분석 알고리즘을 제공한다. PCA 클래스의 객체를 만들 때 n_components 매개변수에 주성분의 개수를 지정해야 한다.

from sklearn.decomposition import PCA

pca=PCA(n_components=50)
pca.fit(fruits_2d)

 PCA 클래스가 찾은 주성분은 components_속성에 저장되어 있다.

print(pca.components_.shape)

 

 

  n_components=50 으로 지정했기 때문에 pca.components_ 배열의 첫 번째 차원이 50이다. 즉 50개의 주성분을 찾은 것이다. 두 번째 차원은 항상 원본 데이터의 특성 개수와 같은 10000이다.

def draw_fruits (arr,ratio=1):
  n=len(arr)

  rows=int(np.ceil(n/10))
  cols=n if rows<2 else 10
  fig, axs= plt.subplots(rows,cols,figsize=(cols*ratio,rows*ratio),squeeze=False)

  for i in range(rows):
    for j in range(cols):
      if i*10 + j <n:
        axs[i,j].imshow(arr[i*10 + j],cmap='gray_r')
        axs[i,j].axis('off')

  plt.show()
  
  draw_fruits(pca.components_.reshape(-1,100,100))

 

 

 이 주성분은 원본 데이터에서 가장 분산이 큰 방향을 순서대로 나타낸 것 이다. 한편으로는 데이터 셋에 있는 어떤 특징을 잡아낸 것처럼 생각할 수도 있다.

 

 주성분을 찾았으므로 원본 데이터를 주성분에 투영하여 특성의 개수를 10000개에서 50개로 줄일 수 있다. 이는 마치 원본 데이터를 각 주성분으로 분해하는 것으로 생각할 수 있다.

print(fruits_2d.shape)

 

 

fruits_pca=pca.transform(fruits_2d)
print(fruits_pca.shape)

 

 

 10000개의 픽셀(특성)을 가진 300개의 이미지가 50개의 특성을 가진 데이터가 되었다.

 

 

원본 데이터 재구성

 앞에서 10000개의 특성을 50개로 줄였다. 이로 인해 어느 정도 손실이 발생할 수밖에 없다. 하지만 최대한 분산이 큰 방향으로 데이터를 투영했기 때문에 원본 데이터를 상당 부분 재구성할 수 있다.

fruits_inverse=pca.inverse_transform(fruits_pca)
print(fruits_inverse.shape)

 

 

 예상대로 10000개의 특성이 복원되었다. 이 데이터를 100x100 크기로 바꾸어 100개씩 나누어 출력하겠다.

fruits_reconstruct=fruits_inverse.reshape(-1,100,100)
for start in [0,100,200]:
  draw_fruits(fruits_reconstruct[start:start+100])
  print('\n')

 

 

 

 

설명된 분산

 주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값을 설명된 분산(explained variance)라고 한다. PCA 클래스의  explained_variance_ratio_ 에 각 주성분의 설명된 분산 비율이 기록되어 있다. 첫 번째 주성분의 설명된 분산이 가장 크다. 이 분산 비율을 모두 더하면 50개의 주성분으로 표현하고 있는 총 분산 비율을 얻을 수 있다.

print(np.sum(pca.explained_variance_ratio_))

 

92% 가 넘는 분산을 유지하고 있다는 의미이다

 

plt.plot(pca.explained_variance_ratio_)

 

 

 그래프를 보면 처음 10개의 주성분이 대부분의 분산을 표현하고 있다. 그다음부터는 각 주성분이 설명하고 있는 분산은 비교적 작다. 이번에는 PCA로 차원 축소된 데이터를 사용하여 지도 학습 모델을 훈련하겠다.

 

 

다른 알고리즘과 함께 사용하기

 과일 사진 원본 데이터와 PCA로 축소한 데이터를 지도 학습에 적용해 보고 어떤 차이가 있는지 알아보겠다. 3개의 과일 사진을 분류해야 하므로 간단히 로지스틱 회귀 모델을 사용하겠다.

from sklearn.linear_model import LogisticRegression

lr=LogisticRegression()
target=np.array([0]*100 + [1]*100 + [2]*100)
from sklearn.model_selection import cross_validate

scores=cross_validate(lr,fruits_2d,target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))

 

 

 교차 검증의 점수는 0.997 정도로 매우 높다. 특성이 10,000개나 되기 때문에 300개의 샘플에서는 금방 과대적합된 모델을 만들기 쉽다.

scores=cross_validate(lr,fruits_pca,target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))

 

 

 50개의 특성만 사용했는데도 정확도가 100% 이고 훈련시간은 20배 이상 감소했다. PCA로 훈련 데이터의 차원을 축소하면 저장 공간뿐만 아니라 머신러닝 모델의 훈련 속도도 높일 수 있다.

 

 앞서 PCA 클래스를 사용할 때 n_components 매개변수에 주성분의 개수를 지정했다. 이 대신 원하는 설명된 분산의 비율을 입력할 수도 있다. PCA 클래스는 지정된 비율에 도달할 때까지 자동으로 주성분을 찾는다. 설명된 분산의 50%에 달하는 주성분을 찾도록 PCA 모델을 만들어 보겠다.

pca=PCA(n_components=0.5)
pca.fit(fruits_2d)
print(pca.n_components_)
print(pca.components_.shape)

 

 

 2개의 특성만으로 원본데이터에 있는 분산의 50%를 표현할 수 있다.

 

 이 모델로 원본 데이터를 변환하겠다. 주성분이 2개이므로 변환된 데이터의 크기는 (300,2)가 될 것이다.

fruits_pca=pca.transform(fruits_2d)
print(fruits_pca.shape)
draw_fruits(pca.components_.reshape(-1,100,100))

 

 

scores=cross_validate(lr,fruits_pca,target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))

 

 

 2개의 특성을 사용했을 뿐인데 99%의 정확도를 달성했다.

 

 이번에는 차원 축소된 데이터를 사용해 k-평균 알고리즘으로 클러스터를 찾아보겠다.

from sklearn.cluster import KMeans

km=KMeans(n_clusters=3,random_state=42)
km.fit(fruits_pca)
print(np.unique(km.labels_,return_counts=True))

 

 

 이 결과는 앞에서 원본 데이터를 사용했을 때와 거의 비슷한 결과이다.

for label in range(0,3):
  draw_fruits(fruits[km.labels_==label])
  print('\n')

 

 

 훈련 데이터의 차원을 줄이면 또 하나 얻을 수 있는 장점은 시각화 이다. 3개 이하로 차원을 줄이면 화면에 출력하기 비교적 쉽다. fruits_pca 데이터는 2개의 특성이 있기 때문에 2차원으로 표현 할 수 있다.

for label in range(0,3):
  data=fruits_pca[km.labels_==label]
  plt.scatter(data[:,0],data[:,1])
  
plt.legend(['apple','banana','pineapple'])
plt.show()

 

 

 2개의 특성만을 사용했는데 로지스틱 회귀 모델의 교차 검증 점수가 99%에 달하는 이유를 볼 수 있다.

 

 

결론


 이번에는 대표적인 비지도 학습 문제 중 하나인 차원 축소에 대해 알아보았다. 차원 축소를 사용하면 데이터셋의 크기를 줄일 수 있고 비교적 시각화하기 쉽다. 또 차원 축소된 데이터를 지도 학습 알고리즘이나 다른 비지도 학습 알고리즘에 재사용하여 성능을 높이거나 훈련 속도를 빠르게 만들 수 있다.

 

 사이킷런의 PCA클래스를 사용해 과일 사진 데이터의 특성을 50개로 줄였다. 특성 개수는 작지만 변환된 데이터는 원본 데이터에 있는 분산의 90% 이상을 표현한다. 이를 설명된 분산이라 부른다.

 

 PCA 클래스는 자동으로 설명된 분산을 계산하여 제공해 준다. 또한 주성분의 개수를 명시적으로 지정하는 대신 설명된 분산의 비율을 설정하여 원하는 비율만큼 주성분을 찾을 수 있다.

 PCA 클래스는 변환된 데이터에서 원본 데이터를 복원하는 메서드도 제공한다. 변환된 데이터가 원본 데이터의 분산을 모두 유지하고 있지 않다면 완벽하게 복원되지 않는다. 하지만 적은 특성으로도 상당 부분의 디테일을 복원할 수 있다.