--18.비지도학습-PCA.ipynb--
"""
특성이 많으면 선형모델의 성능이 높아지고 훈련데이터에 쉽게 과대적합된다는 것을 배웠습니다.
차원축소는 데이터를 가장 잘 나타내는 일부 특성을 선택하여
데이터 크기를 줄이고 지도학습모델의 성능을 향상시킬수 있는 방법입니다
또한, 줄어든 차원에서 다시 원본 차원 (ex: 과일 사진의 경우 10000개의 차원)으로
손실을 최대한 줄이면서 복원할 수도 있습니다.
이번 장에서는 대표적인 차원축소 알고리즘인
'주성분 분석' (PCA: Principal Component Analysis) 를 배워보겠습니다
"""
None
![]()
"""
주성분 분석은 데이터에 있는 '분산이 큰 방향' 을 찾는것.
'분산'은 '데이터가 널리 퍼져있는 정도'를 말함.
'분산이 큰 방향' 이라는 데이터를 잘 표현하는 '어떤 벡터'(라고 생각 할 수 있다.)
위 벡터를 주성분 (Proncipal Component)라고 부릅니다.
이 주성분 벡터는 '원본데이터'에 있는 '어떤방향'입니다.
주성분 벡터의 원소 개수는 원본 데이터 셋에 있는 특성의 개수와 같다.
원본데이터는 주성분을 사용해서 차원을 줄일 수 있다.
일반적으로 주성분은 원본 특성의 개수만큼 찾을 수 있다.
"""
None
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
base_path = r'/content/drive/MyDrive/dataset'
file_path = os.path.join(base_path, 'fruits_300.npy')
fruits = np.load(file_path)
fruits.shape
fruits_2d = fruits.reshape(-1, 100* 100)
fruits_2d.shape
from sklearn.decomposition import PCA
pca = PCA(n_components=50) # n_components : 주성분의 개수
pca.fit(fruits_2d)
print(pca.components_.shape)
def draw_fruits(arr, ratio=1) :
n = len(arr) # n: 샘플개수
rows = int(np.ceil(n/10))
cols = n if rows < 2 else 10
fig, axs = plt.subplots(rows, cols,
figsize=(colsratio, rowsratio),
squeeze=False # subplot 결과가 2차원으로 나올텐데 이것을 (2, )로 바꿔버린다. False는 차원압축 하지 않겠다는 뜻.
)
for i in range(rows) :
for j in range(cols) :
if i 10 + j < n :
axs[i, j].imshow(arr[i10 + j], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
drawfruits(pca.components.reshape(-1, 100, 100))
"""
위 주성분은 원본 데이터에서 '가장 분산이 큰 방향'을 '순서대로' 나타냄.
한편으로는 데이터 셋에 있는 어떤 특징을 잡아낸 것처럼 생각할 수도 있다.
주성분을 찾았으므로 원본 데이터를 주성분에 투영하여 특성의 개수를 10000개에서 50개로 줄일 수 있다.
이는 마치 원본 데이터를 각 주성분으로 분해하는 것으로 생각 할 수 있다.
transform() 메소드를 사용하여 원본데이터의 차원을 50으로 축소해보자.
"""
None
print(fruits_2d.shape)
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)
fruits_inverse = pca.inverse_transform(fruits_pca)
fruits_inverse.shape
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개의 특성은 얼마나 분산을 보존하고 있는것일까?
"""
None
explained variance
원본데이터의 분산을 얼마나 잘 나타내는지 기록한 값
explainedvariance_ratio 에 각 주성분의 '설명된 분산' 비율이 기록되어 있다.
pca.explainedvariance_ratio
np.sum(pca.explainedvariance_ratio) # 총 분산 비율
plt.plot(pca.explainedvariance_ratio)
plt.show()
"""
↑ 첫 10개의 주서분이 대부분의 분산을 표현하고 있다.
그 다음부터의 주성분이 설명하고 있는 분산은 비교적 적다.
"""
None
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']), np.mean(scores['fit_time']))
"""
교차검증 점수가 무려 0.997!! 특성이 10000개나 되기에 금방 과대적합된 모델이 나온다.
"""
None
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']), np.mean(scores['fit_time']))
"""
50개의 특성만 사용. 정확도 100%! 훈련시간이 매우 감소했다.
차원축소하면 저장공간 뿐 아니라, 모델 훈련속도도 향상 시킬 수 있다.
"""
None
pca = PCA(n_components=0.5) # 설명된 분산의 50%에 달하는 주성분을 찾도록 PCA 생성
pca.fit(fruits_2d)
pca.ncomponents
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']), np.mean(scores['fit_time']))
"""
0.9933333333333334 0.024299240112304686 <- 2개의 특성을 사용했을 뿐인데도 99%의 정확도!
"""
None
from sklearn.cluster import KMeans
km = KMeans(nclusters=3, random_state=42)
km.fit(fruits_pca)
np.unique(km.labels, return_counts=True)
for label in range(0,3) :
drawfruits(fruits[km.labels == label])
print('\n')
for label in range(0,3) :
data = fruitspca[km.labels == label]
plt.scatter(data[:, 0], data[:, 1])
plt.legend(['pineapple,', 'banana', 'apple'])
plt.show()
"""
각 클러스터의 산점도가 매우 잘 구분된다! 2개의 특성만 사용했음에도!
(이래서 로지스틱회귀 모델의 교차검증 점수가 거의 99% 였었다!)
"""
None