AI/혼자공부하는머신러닝딥러닝

[ML] 06-3 주성분 분석

inthyes 2024. 1. 26. 15:03

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

 

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

PCA 클래스

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클래스의 객체를 만들 때 n_components 매개변수에 주성분의 개수를 지정해야한다.

k-평균과 마찬가지로 비지도 학습이기 때문에 fit() 메서드에 타깃값을 제공하지 않는다.

 

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

 

components_ 속성에는 PCA 클래스가 찾은 주성분이 저장된다.

print(pca.components_.shape)

 

n_components = 50으로 지정했기 때문에 첫번째 차원이 50이다. 즉 50개의 주성분을 찾은 것이다.

 

이 주성분을 draw_fruits() 함수를 사용해서 그림으로 그려보자.

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

이 주성분은 원본 데이터에서 가장 분산이 큰 방향을 순서대로 나타낸 것이다.

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

PCA의 transform() 메서드를 사용해 원본 데이터의 차원을 50으로 줄일 수 있다.

 

우선 fruits_2d의 배열을 확인한다.

print(fruits_2d.shape)

 

차원을 50으로 줄이고, 배열을 출력한다.

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

출력 결과를 보면 데이터가 1/200로 줄어들었음을 확인할 수 있다.

원본 데이터 재구성

10,000개의 특성을 50개로 줄였지만 최대한 분산이 큰 방향으로 데이터를 투영했기 때문에 원본 데이터를 상당 부분 재구성할 수 있다.

 

inverse_transform() 메서드를 사용하여 50개의 차원으로 축소한 fruits_pca 데이터를 10,000개의 특성을 복원할 수 있다.

 

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

10,000개의 특성이 복원되었음을 확인할 수 있다. 이제 데이터를 출력해보자.

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

일부 흐리고 번진 부분이 존재하지만 불과 50개의 특성을 10,000개로 늘린 것을 감안한다면 성공적이다. 주성분을 최대로 사용했다면 완벽한 원본 데이터 재구성이 가능했을 것이다.

설명된 분산

설명된 분산은 주성분 분석에서 주성분이 얼마나 원본 데이터의 분산을 잘 나타내는지 기록한 것이다. 사이킷런의 PCA 클래스는 주성분 개수나 설명된 분산의 비율을 지정하여 주성분 분석을 수행할 수 있다.

 

PCA클래스의 explained_variance_ratio_에 각 주성분의 설명된 분산 비율이 기록되어 있다. 이 분산 비율을 모두 더하면 50개의 주성분으로 표현하고 있는 총 분산 비율을 얻을 수 있다.

print(np.sum(pca.explained_variance_ratio_))

 

설명된 분산의 비율을 그래프로 구려보면 적절한 주성분의 개수를 찾는 데 도움이 된다.

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

 

처음 10개의 주성분이 대부분의 분산을 표현하고 그 다음부터는 각 주성분이 설명하고 있는 분산은 비교적 작다는 것을 확인할 수 있다.

 

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

과일 사진 원본 데이터와 PCA로 축소한 데이터를 지도 학습에 적용하여 어떠한 차이가 있는지 확인해볼 수 있다.

 

3개의 과일 사진을 분류해야 하므로 간단히 로지스틱 회귀 모델을 생성하고 타깃값을 생성한다.

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()

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']))

 

PCA로 축소한 fruits_pca를 교차검증해본다.

scores = cross_validate(lr, fruits_pca, target)

print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))

 

 

50개의 특성만 사용했는데도 정확도가 100%이고 훈련 시간도 감소했다.

PCA로 훈련 데이터의 차원을 축소하면 저장 공간뿐만 아니라 머신러닝 모델의 훈련 속도도 높일 수 있다.

 

n_components 매개변수에는 주성분의 개수뿐만 아니라 설명된 분산의 비율을 입력할 수 있다.

설명된 분산의 50%에 달하는 주성분을 찾도록 PCA 모델을 만드는 코드는 아래와 같다.

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

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

 

이 모델로 원본 데이터를 변환하면 주성분이 2개이므로 변환된 데이터의 크기는(300, 2)가 되어야한다.

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

 

2개의 특성만 사용했을 때 교차 검증의 결과를 확인해보자.

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차원으로 표현할 수 있다.

 

km.labels_를 사용하여 클러스터별 산점도를 출력하기 위해서는 아래와 같이 코드를 구현해야한다.

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

각 클러스터의 산점도가 잘 구분되어 있음을 확인할 수 있다. 사과와 파인애플 클러스터의 경계가 가깝다는 것을 데이터 시각화를 통해 확인할 수 있다. 그런 측면에서 차원 축소는 매우 유용한 도구 중 하나이다.

'AI > 혼자공부하는머신러닝딥러닝' 카테고리의 다른 글

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