2 minute read

차원과 차원 축소

6-2 kmeans를 공부할 때 사용한 이미지는 300장이었지만 실제 서비스를 진행한다면 이미지들을 계속해서 등록해야 할 것이므로 저장공간이 부족할 수 있다.

나중에 군집이나 분류에 영향을 끼치지 않으면서 업로드된 사진의 용량을 줄이는 방법

데이터가 가진 속성을 특성이라고 부를 수 있다. 과일 사진 한 장은 100x100 인 10000개의 픽셀을 가지고 있으므로 10000개의 특성을 가지고 있다고 할 수 있다.

머신러닝에서는 이런 특성을 차원이라고 부르는데 그렇다면 이 10000개의 차원을 줄일 수 있다면 저장공간을 절약할 수 있을 것이다.

차원 축소 알고리즘

3장에서 특성이 많으면 선형 모델의 성능이 높아지고 훈련 데이터에 쉽게 과대적합된다는 것을 배웠다.

차원 축소는 데이터를 가장 잘 나타내는 일부 특성을 선택하여 데이터 크기를 줄이고 지도학습 모델의 성능을 향상시킬 수 있는 방법이다.

주성분 분석 PCA

줄어든 차원에서 다시 원본 차원으로 손실을 최대한 줄이면서 복원할 수도 있다.

주성분 분석은 데이터에 있는 분산이 큰 방향을 찾는 것으로 이해할 수 있다. 분산 : 데이터가 퍼져있는 정도

주성분 분석은 분산이 큰 방향을 표현하는 벡터를 찾는 것

주성분 : 분산이 큰 방향을 표현하는 벡터

방법은 기존 특성을 모은 벡터를 Transpose해서 곱해주어서 공분산 행렬을 만들어주고

이 공분산 행렬의 고유벡터에 모든 특성을 정사형시켰을 때 분산이 최대가 된다.

이 부분은 따로 포스팅 하려고 한다.

PCA 클래스

300개의 과일 데이터를 사용해서

from sklearn.decomposition import PCA

pca = PCA(n_components=50) #주성분을 50개만 찾도록 지정
pca.fit(fruits_2d)

print(pca.components_.shape) #components_ 에 주성분 데이터가 들어가 있다.
#'_'는 하이퍼파라미터가 아닌 학습된 데이터에 붙는다.(구분하기 위해)
#(50, 10000) 50개의 행과 10000개의 열로 이루어져 있다.

draw_fruits(pca.components_.reshape(-1, 100, 100))
#다시 100x100으로 reshape해서 주성분의 이미지를 그려볼 수 있다.

print(fruits_2d.shape)
#(300, 10000) 원래의 특성

fruits_pca = pca.transform(fruits_2d) 
print(fruits_pca.shape)
#(300, 50) transform은 10000개의 특성을 50개로 변환해준다.
#300개의 샘플이 각각 50개의 특성으로 줄어든 것 1/200만큼의 데이터 size가 줄어들었다.

image-20220522130210728

원본 데이터 재구성

fruits_inverse = pca.inverse_transform(fruits_pca)
print(fruits_inverse.shape)
#(300, 10000) 다시 특성이 300,10000으로 바뀜

fruits_reconstruct = fruits_inverse.reshape(-1, 100, 100) #이미지를 보기위해 reshape
for start in [0, 100, 200]:
    draw_fruits(fruits_reconstruct[start:start+100])
    print("\n")

image-20220522133205518

image-20220522133220027

image-20220522133231419

1/200만큼 줄어들었지만 구분할 수 있을 정도로 재구성이 된 것을 볼 수 있다.

설명된 분산

print(np.sum(pca.explained_variance_ratio_))
#0.9215808173798626 원래 훈련 데이터의 92%만큼의 분산을 보존하고 있다.
#각 주성분 50개가 얼마만큼 분산을 잘 유지했는지 알려주는 값

plt.plot(pca.explained_variance_ratio_)

image-20220522133902664

10개의 주성분이 대부분의 분산을 표현하고 있다.

이후 50개까지는 거의 필요없는것으로 보인다.

다른 알고리즘과 같이 사용하기

transform()을 통해서 다른 모델과 연결해서 사용

분류기와 함께 사용

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()

target = np.array([0] * 100 + [1] * 100 + [2] * 100) #LogisticRegression사용하기 위해 타겟 설정

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.9966666666666667 99%로 맞춤
#1.1039844512939454 훈련 시간

scores = cross_validate(lr, fruits_pca, target) #주성분50개 데이터로 훈련
print(np.mean(scores['test_score'])) 
print(np.mean(scores['fit_time']))
#1.0
#0.021912097930908203 훈련 시간 1/50

주성분으로 변환한 데이터로 훈련했을 때 정확도와 시간 둘 다 상승했다.

pca = PCA(n_components=0.5) #설명된 분산의 비율
# 1이상: 주성분의 개수 1미만 : 설명된 분산의 비율
pca.fit(fruits_2d)

print(pca.n_components_)
#2 설명된 분산 50% -> 주성분 개수 2개

fruits_pca = pca.transform(fruits_2d) # 특성 변환
print(fruits_pca.shape)
#(300, 2) 특성도 2개만 존재

scores = cross_validate(lr, fruits_pca, target) #교차 검증 진행
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
#0.9933333333333334 특성을 2개만 사용했음에도 불구하고 정확도 0.99
#0.034157419204711915

설명된 분산의 비율을 하이퍼파라미터로 지정해 주어서 따로 그래프를 그려보지 않고 바로 훈련 진행

위에서는 기존샘플의 분산의 50%만큼 지정한 모습니다.

군집과 함께 사용

kmeans 와 같이 사용해보면

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))
#(array([0, 1, 2], dtype=int32), array([110,  99,  91]))

#시각화
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()

image-20220522142700146

n_components값을 0.5로 했을 때 특성을 2개만 갖기 때문에 그래프로 그릴 수 있다.

x축 : pca1 y축 pca2 로 2개의 특성을 축으로 그래프를 그린 모습니다.

차원을 2,3개정도로 줄인다면 시각화에 장점을 가질 수 있다.

참고

박해선,혼자 공부하는 머신러닝, 한빛미디어, 2021,318~335p