머신러닝 + 딥러닝

[혼공머신] CHAPTER 06-3 주성분 분석

2021. 10. 27. 08:58
728x90

차원(dimension) : 머신러닝에서 10000개의 특성이 있다면 10000개의 차원이 있는 것

 

차원 축소(dimensionality reduction)

데이터를 가장 잘 나타내는 일부 특성을 선택해 데이터 크기를 줄이고 지도 학습 모델의 성능을 향상시키는 방법 또는 줄어든 차원에서 원본 차원으로 손실을 최대한 줄이면서 복원->특성의 개수를 줄인다

 

주성분 분석(principal component analysis, 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 클래스로 주성분 분석 알고리즘 제공

from sklearn.decomposition import PCA
pca = PCA(n_components=50)
pca.fit(fruits_2d)

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

 

print(pca.components_.shape)

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

(50, 10000)

주성분 개수를 50으로 지정해서 배열의 첫 번째 차원이 50개로, 50개의 주성분을 찾았고, 원본 데이터의 특성 개수와 같은 10000개이다

 

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

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

 

>> 주성분을 찾았으므로 원본 데이터를 주성분에 투영하여 특성의 개수를 10000개에서 50개로 줄일 수 있다.

PCA의 transform() 메서드로 원본 데이터의 차원을 줄여보자!

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

원래 (300, 10000) 크기의 배열인 fruits_pca를 (300, 50) 크기로 변환한다

(300, 50)

 

즉, 50개의 주성분을 이용해 10000개의 특성을 50개로 줄였다. 이렇게 하면 데이터에 손실이 발생할 수 있다. 하지만 분산이 큰 방향으로 데이터를 투영했기 때문에 원본 데이터를 재구성 할 수 있다.

 

PCA 클래스의 inverse_transform() 메서드로 50개의 차원으로 축소한 fruits_pca 데이터를 10000개의 특성으로 복원해보자

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

10000개의 특성이 복원되었다.

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

일부 흐리고 번진 부분이 있지만 과일이 잘 복원되었음을 확인할 수 있다

50개의 특성이 분산을 가장 잘 보존하도록 변환된 것이기 때문에 특성이 50개에서 10000개가 되었어도 잘 복원되었다.

 

 

설명된 분산(explained variance)

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

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

 

print(np.sum(pca.explained_variance_ratio_))

첫 번째 주성분의 설명된 분산이 가장 크고 이 분산 비율을 모두 더하면 50개의 주성분으로 표현하고 있는 총 분산 비율을 얻을 수 있다.

0.921452867428021

92% 정도의 분산을 유지하고 있다.

 

plt.plot(pca.explained_variance_ratio_)
plt.show()

처음 10개 정도의 성분이 주로 분산을 표현하고 있다.

 


  • 분류기와 함께 사용

로지스틱 회귀를 이용해 과일 원본 데이터와 PCA로 축소한 데이터를 지도학습에 적용해본다

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression() #사이킷런의 LogisticRegression 모델

target = np.array([0]*100 + [1]*100 + [2]*100)

지도학습 모델을 사용하려면 타깃값이 필요한데, 사과를 0, 파인애플을 1, 바나나를 2로 지정해 0, 1, 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']))

원본 데이터인 fruits_2d의 교차검증을 수행해보니

0.9966666666666667
1.5239572048187255

교차 검증의 점수가 매우 높은 것을 볼 수 있다

훈련 시간은 1.52초 정도 걸렸다

 

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

주성분을 변환해 50개의 특성만 있는 fruits_pca는

1.0
0.037810993194580075

정확도가 100%이며, 훈련시간이 더 낮음을 볼 수 있다.

-> PCA로 훈련 데이터의 차원을 축소하면 저장 공간과 머신러닝 모델의 훈련 속도도 높일 수 있다

 

PCA 클래스를 사용할 때 n_components 매개변수 대신 원하는 분산의 비율을 입력할 수 있다

PCA 클래스는 지정된 비율에 도달할 때까지 자동으로 주성분을 찾는다.

pca = PCA(n_components=0.5) #원본데이터의 50%만 설명할 수 있는 주성분 개수 찾기
pca.fit(fruits_2d)

print(pca.n_components_)

주성분 개수 대신 0~1사이의 비율을 입력하면 된다(설명된 분산의 비율)

2

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

 

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

이 모델로 원본 데이터를 변환하면 300개의 샘플이 주성분이 2개가 된다

(300, 2)

 

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

2개의 특성만을 사용해서 교차 검증을 해보면

0.9933333333333334
0.047495698928833006

99%의 정확도를 보여준다

 


  • 군집과 함께 사용
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))
#return_counts로 0,1,2에 대한 개수를 셀 수 있도록 지정

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

(array([0, 1, 2], dtype=int32), array([ 91,  99, 110]))

2절에서 원본 데이터를 사용했을 때와 비슷한 결과를 나타낸다

 

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

 

 

파인애플에 사과와 바나나가 섞여있다 -> 그치만 특성 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()

fruits_pca 데이터는 2개의 특성이 있어 2차원으로 표현할 수 있다.

앞에서 찾은 km.labels_로 클러스터별로 산점도를 그려보면

각 클러스터의 산점도가 잘 구분되었음을 볼 수 있다.

사과와 파인애플의 클러스터 경계가 가깝게 붙어있어 위의 결과가 나타난 것 같다고 볼 수 있다!

 

 

728x90