Python/ML & DL 공부

[ML] 06-3 주성분 분석

dori_0 2022. 3. 12. 16:40

혼자 공부하는 머신러닝+딥러닝 책을 바탕으로 공부한 내용입니다.

 

CH6 비지도 학습 ③

주성분 분석으로 차원 축소

 


 

차원 축소에 대해 이해하고 대표적인 차원 축소 알고리즘 중 하나인 PCA(주성분 분석) 모델을 만들어보자

 

▶ 차원과 주성분 분석

차원 축소

  • 원본 데이터의 특성을 적은 수의 새로운 특성으로 변환하는 비지도 학습의 한 종류이다.
  • 차원 축소는 저장 공간을 줄이고 시각화하기 쉽다.
  • 다른 알고리즘의 성능을 높일 수도 있다.
  • 대표적인 차원 축소 알고리즘은 주성분 분석 (PCA)

 

주성분 분석

  • 차원 축소 알고리즘의 하나로 데이터에서 가장 분산이 큰 방향을 찾는 방법 (주성분)
  • 원본 데이터를 주성분에 투영하여 새로운 특성을 만들 수 있다.
  • 일반적으로 주성분은 원본 데이터에 있는 특성 개수보다 작다.

 

 

▶ PCA 클래스

먼저, 데이터를 불러와 2차원 배열로 변경해주자

# 데이터 불러오기
import wget
url = "https://bit.ly/fruits_300_data" 
wget.download(url)

# 2차원 배열로 변경
import numpy as np
fruits = np.load('fruits_300_data')
fruits_2d = fruits.reshape(-1, 100*100)

 

 

사이킷런은 PCA 클래스로 주성분 분석 알고리즘을 제공한다.

PCA 클래스의 객체를 만들 때 n_components 매개변수에 주성분의 개수를 지정해야 한다.

# 주성분 분석 알고리즘
from sklearn.decomposition import PCA
pca = PCA(n_components=50)
pca.fit(fruits_2d)

# PCA가 찾은 주성분 배열 크기
print(pca.components_.shape)

  • 주성분의 개수를 50개로 지정하였다.
  • PCA 클래스가 찾은 주성분은 components_ 속성에 저장되어 있다.
  • 첫번째 차원은 50개의 주성분, 두번째 차원은 원본 데이터의 특성 개수

 

 

이제 앞장에서 만들었던 draw_fruits() 함수를 사용해 주성분을 그림으로 그려보자

# draw_fruits 함수
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1):
    n = len(arr)  # 샘플 개수
    rows = int(np.ceil(n/10))  # 한 줄에 10개씩
    # 행이 1개면 열 개수는 샘플 개수, 아니면 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: # 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))

  • 원본 데이터에서 가장 분산이 큰 방향을 순서대로 나타낸 것

 

 

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

# 원본 데이터의 차원 50으로 줄이기
print(fruits_2d.shape)
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)

  • 데이터를 성공적으로 줄인 것을 확인할 수 있다.
  • fruits_2d 대신 fruits_pca를 저장한다면 공간을 훨씬 줄일 수 있을 것으로 보인다.

 

 

▶ 원본 데이터 재구성

이번에는 inverse_transform() 메서드를 이용해 10,000개의 특성을 복원해보자

# 10,000개 특성 복원
fruits_inverse = pca.inverse_transform(fruits_pca)
print(fruits_inverse.shape)

 

이제 이 데이터를 100 X 100 크기로 바꾸고 100개씩 나누어 출력해보자

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

  • 일부 흐리고 번진 부분이 있지만 거의 모든 과일이 잘 복원된 것을 확인할 수 있다.

 

 

▶ 설명된 분산

주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값을 설명된 분산이라고 한다.

PCA 클래스의 explained_variance_ratio_에는 각 주성분의 설명된 분산 비율이 기록되어 있다.

 

50개의 주성분으로 표현하고 있는 총 분산 비율을 구해보자

# 설명된 분산
print(np.sum(pca.explained_variance_ratio_))

  • 92%가 넘는 분산을 유지하고 있다.
  • 이 덕분에 앞서 50개의 특성에서 원본 데이터를 복원했을 때 원본 이미지의 품질이 높았던 것으로 보인다.

 

 

이번에는 설명된 분산을 그래프로 출력해 적절한 주성분의 개수를 확인해보자

# 적절한 주성분의 개수 찾기
plt.plot(pca.explained_variance_ratio_)
plt.show()

  • 처음 10개의 주성분이 대부분의 분산을 표현하고 있는 것으로 보인다.

 

 

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

과일 사진 원본 데이터와 PCA로 축소한 데이터를 지도 학습에 적용해보고 어떤 차이가 있는지 확인해보자

3개의 사진을 분류해야 하므로 로지스틱 회귀 모델을 사용해보자

# 로지스틱 회귀 모델
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()

# 타깃값 지정 (사과 0, 파인애플 1, 바나나 2)
target = np.array([0]*100 + [1]*100 + [2]*100)

 

 

먼저 원본 데이터인 fruits_2d를 사용해보자

# 원본 데이터 사용
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로 매우 높은 것을 확인할 수 있다.
  • 교차 검증 폴드의 훈련 시간은 약 0.608초로 확인된다.

 

 

이번에는 fruits_pca를 사용해보자

# PCA로 축소한 데이터 사용
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))

  • 50개의 특성만 사용했는데도 정확도가 100%임을 확인할 수 있다.
  • 훈련 시간은 0.04초로 훈련 속도가 많이 감소한 것이 확인된다.
  • PCA로 훈련 데이터의 차원을 축소하면 저장 공간뿐만 아니라 머신러닝 모델의 훈련 속도도 높일 수 있다.

 

 

처음에 n_components=50으로 주성분의 개수를 지정했었는데, 이 대신 분산의 비율을 입력할 수도 있다.설명된 분산의 50%에 달하는 주성분을 찾도록 PCA 모델을 만들어보고 몇 개의 주성분을 찾았는지 확인해보자

# 비율로 주성분 결정
pca = PCA(n_components=0.5)
pca.fit(fruits_2d)
print(pca.n_components_)  #2
  • 2개의 특성만으로 원본 데이터에 있는 분산의 50%를 표현할 수 있다.

 

2개의 특성만 사용하더라도 교차 검증의 결과가 좋을지 확인해보자

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

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

  • 2개의 특성을 사용했음에도 정확도가 0.99로 매우 높은 것을 확인할 수 있다.

 

 

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

# 차원 축소 데이터로 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))

  • fruits_pca로 찾은 클러스트는 각각 110, 99, 91개의 샘플을 포함하고 있다.

 

 

KMeans가 찾은 레이블을 사용해 과일 이미지를 출력해보자

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

 

 

 

훈련 데이터의 차원을 줄였을 때 또 하나의 장점은 시각화이다.

3개 이하로 차원을 줄이면 화면에 출력하기가 비교적 쉽다.

km.labels_를 사용해 클러스터별로 나누어 산점도를 그려보자

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

  • 2개의 특성만을 사용했음에도 각 클러스터의 산점도가 잘 구분되는 것이 확인된다.
  • 이 그림을 보면 사과와 파인애플의 클러스터 경계가 가깝게 붙어 있는 것을 확인할 수 있다.
  • 데이터를 시각화하면 예상치 못한 통찰을 얻을 수 있으므로 차원 축소는 매우 유용한 도구 중 하나이다.

 

 

▶ 정리

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

 

 

'Python > ML & DL 공부' 카테고리의 다른 글

[DL] 07-2 심층 신경망  (0) 2022.03.23
[DL] 07-1 인공 신경망  (0) 2022.03.20
[ML] 06-2 k-평균 알고리즘  (0) 2022.03.12
[ML] 06-1 군집 알고리즘  (0) 2022.03.12
[ML] 05-3 트리의 앙상블  (0) 2022.03.11