본문 바로가기
Machine Learning

[혼자 공부하는 머신러닝+딥러닝] 주성분 분석

by 가론노미 2023. 5. 28.
[혼자 공부하는 머신러닝+딥러닝] 책의 내용을 정리한 글입니다.

개념

차원

머신러닝에서는 특성을 차원이라고도 부른다.

→ 10,000개의 특성은 10,000개의 차원이라고 볼 수 있다.

  • 다차원 배열에서 차원은 배열의 축 개수
  • 1차원 배열에서 차원은 원소의 개수

차원 축소

: 원본 데이터의 특성을 적은 수의 새로운 특성으로 변환하는 비지도 학습의 한 종류

  • 저장 공간을 줄이고 시각화하기 쉬워짐
  • 다른 알고리즘의 성능을 높이고 모델의 훈련 속도도 높일 수 있음

주성분 분석

: 차원 축소 알고리즘의 하나로 데이터에서 가장 분산이 큰 방향을 찾는 방법

  • 분산이 가장 큰 방향이란?
    • 데이터를 잘 표현하는 어떠한 벡터(크기와 방향을 갖는 물리량을 의미)
    • 이 벡터를 주성분이라고 부름
  • 원본 데이터를 주성분에 투영하여 새로운 특성을 만들 수 있음
  • 일반적으로 주성분은 원본 데이터에 있는 특성 개수보다 작음

설명된 분산

: 주성분 분석에서 주성분이 얼마나 원본 데이터의 분산을 잘 나타내는지 기록한 값

 

→ 사이킷런의 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)

사이킷런의 PCA 클래스를 사용하여 50개의 주성분 확인

from sklearn.decomposition import PCA

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

# pca.components_ 배열의 첫 번째 차원은 주성분의 개수, 두 번쨰 차원은 원본 데이터의 특성 개수
print(pca.components_.shape)
# => (50, 10000)

import matplotlib.pyplot as plt

# 이전 장에서 아용했던 이미지 출력 함수
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))

50개의 주성분을 이미지로 출력

원본 데이터를 찾은 주성분에 투영하여 특성의 개수를 10,000개에서 50개로 줄여보기

print(fruits_2d.shape)
# => (300, 10000)

# transform() 메서드를 사용해 원본 데이터를 각 주성분으로 분해
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)
# => (300, 50)

축소한 데이터를 다시 원본 데이터로 재구성

fruits_inverse = pca.inverse_transform(fruits_pca)
print(fruits_inverse.shape)
# => (300, 10000)

fruits_reconstruct = fruits_inverse.reshape(-1, 100, 100)

for start in [0, 100, 200]:
    draw_fruits(fruits_reconstruct[start:start+100])
    print("\n")

재구성한 데이터들의 이미지

주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 나타내는 설명된 분산 확인

# 각 주성분의 설명된 분산 비율을 모두 더하면 총 분산 비율 확인 가능
print(np.sum(pca.explained_variance_ratio_))
# => 0.9215967336068237

# 설명된 분산 비율 그래프를 보면 적절한 주성분의 개수를 찾는 데 도움이 됨
plt.plot(pca.explained_variance_ratio_)

각 주성분별 설명된 분산 비율

차원 축소된 데이터를 로지스틱 회귀 모델에 적용

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.9966666666666667
# => 1.0502480030059815

### 50개의 특성으로 축소한 데이터를 사용했을 때의 정확도와 훈련 시간
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
# => 1.0
# => 0.029622936248779298

### 설명된 분산의 50%에 달하는 주성분 개수만큼의 특성 데이터를 사용했을 때의 정확도와 훈련 시간
pca = PCA(n_components=0.5)
pca.fit(fruits_2d)

# 50%를 표현하기 위해 몇개의 주성분을 찾았는지 확인
print(pca.n_components_)
# => 2

fruits_pca = pca.transform(fruits_2d)
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
# => 0.9933333333333334
# => 0.02146320343017578

차원 축소된 데이터를 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))
# => (array([0, 1, 2], dtype=int32), array([110,  99,  91]))

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

클러스터별 이미지 출력

차원 축소 데이터를 통한 시각화

# 3개 이하의 차원을 줄이면 화면에 출력하기 비교적 쉬워진다
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()

 

이 장에서 사용된 핵심 패키지와 함수

scikit-learn

  • PCA
    • 주성분 분석을 수행하는 클래스
    • n_components : 주성분의 개수를 지정 (기본값-None, 샘플 개수와 특성 개수 중에 작은 것의 값을 사용)
    • random_state : 넘파이 난수 시드 값을 지정
    • components_ : 훈련 세트에서 찾은 주성분이 저장됨
    • explained_variance_ : 설명된 분산이 저장됨
    • explained_variance_ratio_ : 설명된 분산의 비율이 저장됨
    • inverse_transform() : transform() 메서드로 차원을 축소시킨 데이터를 다시 원본 차원으로 복원하는 메서드