머신러닝(AI학습 36)

이유진·2024년 7월 8일

--18.비지도학습-PCA.ipynb--

사진들이 계속 생산되어 쏟아져 들어온다..

저장공간도 문제가 된다..

나중에 '군집' 이나 '분류' 성능에 영향을 주지 않으면서 사진의 용량을 줄일 수 없을까?

=> 차원축소!!

(3차원을 2차원으로 줄이기 ... <= 여기서 논하는 '차원'은 이런 차원이 아니다!!!)

차원과 차원축소

과일사진은 몇개의 '특성'이 있나? -> 10000개의 픽셀 -> 10000개의 특성이 있는셈이다.

머신러닝에서는 이러한 특성을 차원(dimension) 이라고도 부릅니다.

차원을 줄일 수 있다면 저장공간을 크게 절약 할 수 있다.

잠깐!!

다차원배열 에서 다뤄왔던 '차원'은 축(axis) 을 말하는 것 이였다.

차원축소 (dimensionality reduction) 알고리즘

비지도 학습 중 하나

"""
특성이 많으면 선형모델의 성능이 높아지고 훈련데이터에 쉽게 과대적합된다는 것을 배웠습니다.
차원축소는 데이터를 가장 잘 나타내는 일부 특성을 선택하여
데이터 크기를 줄이고 지도학습모델의 성능을 향상시킬수 있는 방법입니다

또한, 줄어든 차원에서 다시 원본 차원 (ex: 과일 사진의 경우 10000개의 차원)으로
손실을 최대한 줄이면서 복원할 수도 있습니다.

이번 장에서는 대표적인 차원축소 알고리즘인
'주성분 분석' (PCA: Principal Component Analysis) 를 배워보겠습니다

"""
None

주성분 분석(PCA)

"""
주성분 분석은 데이터에 있는 '분산이 큰 방향' 을 찾는것.
'분산'은 '데이터가 널리 퍼져있는 정도'를 말함.
'분산이 큰 방향' 이라는 데이터를 잘 표현하는 '어떤 벡터'(라고 생각 할 수 있다.)

위 벡터를 주성분 (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

PCA

from sklearn.decomposition import PCA

pca = PCA(n_components=50) # n_components : 주성분의 개수
pca.fit(fruits_2d)

PCA가 찾은 주성분은 components_ 속성에 저장되어있다.(Array 배열)

print(pca.components_.shape)

(50, 10000) n_components 는 50으로 지정. 50개의 주성분 찾음.

원본 데이터 특성의 개수 10000

주성분

각 클러스터가 어떤 이미지들을 모았는지 시각화를 위한 함수

draw_fruits 함수는

arr (샘플개수, 높이, 너비) 의 3차원 배열을 입력받아 10개씩 이미지를 출력합니다

ratio 는 그림그리는 figsize 크기 배율

def draw_fruits(arr, ratio=1) :
n = len(arr) # n: 샘플개수

샘플 개수에 따라 행과 열의 개수 계산, figsize 지정.

rows = int(np.ceil(n/10))

행이 1개이면 열 개수는 샘플개수. 그렇지 않으면 열 개수는 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[i
10 + 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_2d 는 (300, 10000) 크기의 배열이었다. 10000개의 픽셀(특성)을 가진 300개의 이미지입니다

50개의 주성분을 찾은 PCA 모델을 사용해 이를 (300, 50) 크기의 배열로 변환했습니다.

이제 fruits_pca 배열을 50개의 특성을 가진 데이터 입니다 (차원축소!)

원본 데이터 재구성

inverse_transform() : 원본 데이터 복원

fruits_inverse = pca.inverse_transform(fruits_pca)

fruits_inverse.shape

↑ 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개의 특성은 얼마나 분산을 보존하고 있는것일까?
"""
None

설명된 분산

explained variance

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

explainedvariance_ratio 에 각 주성분의 '설명된 분산' 비율이 기록되어 있다.

당연히! 첫번째 주성분의 설명된 분산이 가장 클 것이다.

이 분산 비율을 모두 더하면 50개의 주성분으로 표현하고 있는 총 분산 비율을 얻을 수 있다.

pca.explainedvariance_ratio

np.sum(pca.explainedvariance_ratio) # 총 분산 비율

↑ 92%가 넘는 분산을 유지하고있다.

우너본 데이터로 복원했을때 이미지 품질이 높았던 이유는 이거다.

'설명된 분산'의 비율을 시각화 해보면

적절한 주성분 개수를 찾는데 도움이 된다.

plt.plot(pca.explainedvariance_ratio)
plt.show()

"""
↑ 첫 10개의 주서분이 대부분의 분산을 표현하고 있다.
그 다음부터의 주성분이 설명하고 있는 분산은 비교적 적다.
"""
None

차원 축소된 데이터로 '지도학습 모델을 훈련' 해보자.

원본 데이터를 사용했을때와 어떠한 차이가 있을것인가.

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()

타겟 값

0 : 사과, 1 : 파인애플, 2 : 바나나

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

from sklearn.model_selection import cross_validate

원본데이터 사용 : fruits_2d

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

"""
교차검증 점수가 무려 0.997!! 특성이 10000개나 되기에 금방 과대적합된 모델이 나온다.
"""
None

차원 축소한 데이터 사용 : fruits_pca

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

"""
50개의 특성만 사용. 정확도 100%! 훈련시간이 매우 감소했다.
차원축소하면 저장공간 뿐 아니라, 모델 훈련속도도 향상 시킬 수 있다.
"""
None

n_components 에 주성분의 개수 대신 '원하는 설명된 분산의 비율'을 입력할 수 있다.

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

pca = PCA(n_components=0.5) # 설명된 분산의 50%에 달하는 주성분을 찾도록 PCA 생성
pca.fit(fruits_2d)

몇개의 주성분을 찾았나?

pca.ncomponents

위 모델로 원본데이터 변환해보면!

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

2개의 특성으로 차원축소 했는데, 교차검증의 결과는 어떨까?

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

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

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

차원축소의 장점 또하나! => 시각화

fruits_pca는 2개의 특성이 있기 때문에 2차원으로 표현 할 수 있다.

km.labels_ 를 사용해 클러스터 별로 나누어 scatter plot을 그릴 수 있다.

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

profile
독해지자

0개의 댓글