● Chpater 06의 학습목표
1. 타깃이 없는 데이터를 사용하는 비지도 학습과 대표적인 알고리즘을 소개합니다.
2. 대표적인 군집 알고리즘인 k-평균을 배웁니다.
3. 대표적인 차원 축소 알고리즘인 주성분 분석(PCA)을 배웁니다.
● 학습목표: 차원 축소에 대해 이해하고 대표적인 차원 축소 알고리즘 중 하나인 PCA(주성분 분석) 모델을 만들어봅니다.
● 키워드: 차원 축소, 주성분 분석, 설명된 분산
● 지난 시간 - K-Means 알고리즘 문제 풀이
1. 3차원 (샘플수 x 너비 x 높이)의 과일 사진 데이터를 2차원 (샘플수 x pixel)으로 축소합니다.
2. KMeans model을 만들어 학습 시킵니다.
3. 각 class들이 얼마나 분포하고 있는지 확인합니다. (KMeans.labels_ 속성 활용)
4. Boolean indexing을 활용해 실제로 model에 의해 분류된 결과 값이 실제 사진으로 출력 했을 때, 맞게 분류 됐는지 확인합니다.
5. KMeans.cluster.center_를 이용해서 평균 사진을 볼 수도 있습니다.
6. KMeans.inertia_의 값을 활용해 최적의 K 값을 찾고 다시 학습 시킬 수 있습니다.
● 주성분 분석 알고리즘 문제 풀이
1. 데이터의 주성분 찾기 (n_components = 2 이상의 정수 or 0 ~ 1 사이의 실수)
2. 데이터를 주성분에 투영하여 차원 축소하기 (transform)
3. 차원이 축소된 데이터를 이용해서 다른 Algorithm과 연계하여 시각화 하거나 성능을 높히기
3-1. Unsupervised learning algorithm (ex. KMeans를 통한 시각화)
3-2. Supervised learning algorithm (cross validation의 score 확인을 통한 성능 확인과 fit time 감소율 확인)
[문제상황]
지속적으로 분류할 사진들이 등록 되어 저장 공간이 부족합니다. 군집이나 분류에 영향을 끼치지 않으면서 업로드된 사진의 용량을 줄일수 있는 방법을 찾아봅니다.
차원과 축소 (Dimension & Dimensionality reduction)
차원의 차이 (1차원 배열 vs 다차원 배열)
- 1차원 배열(벡터) 에서 차원: 원소의 개수 ex) 원소의 개수가 5개면 5차원 벡터라고 함.
- 다차원 배열에서 차원: 축의 개수 ex) 2차원 배열에서 행과 열이 차원이 됨.
- 차원 (Dimension): 데이터가 가진 특성 ex) 샘플 100개와 10,000개의 특징이 있다면, 10,000차원의 데이터라고 할 수 있음.
차원 축소 (Dimensionality reduction): 데이터를 가장 잘 나타내는 일부 특성을 선택하여 데이터 크기를 줄이고 지도 학습 모델의 성능을 향상시킬 수 있는 방법
주성분 분석 소개
주성분 분석 (Principal component analysis, PCA): 데이터에 있는 분산이 큰 방향을 찾는 것
# 내 해석: 주성분을 찾는다는것 = 데이터를 대표하는 규칙을 찾는다는 것.
- 분산: 데이터가 널리 퍼져있는 정도
- 분산이 큰 방향: 해당 벡터에 의해서 data들이 굉장히 크게 분산 되었으니, 해당 벡터는 데이터들을 잘 분류 한다. = 데이터를 잘 표현한다.
- 주성분: 데이터에서 가장 분산이 큰 방향
- 주성분 벡터와 데이터의 관계 & 특성 (중요!)
- 주성분 벡터는 원본 데이터에 있는 어떤 방향
- 주성분 벡터의 원소 개수는 원본 데이터셋에 있는 특성 개수와 같다. (중요! 원본 차원과 같다.)
- 주성분으로 바꾼 (투영한) 데이터는 차원 (특성) 이 줄어든다.
- 주성분은 가장 분산이 큰 방향이기 때문에, 주성분으로 투영한 데이터는 원본이 가지고 있는 특성을 가장 잘 나타내며, 분산을 잘 보존하고 있음.
- 해당 주성분에 수직이고 분산이 가장 큰 다음 방향을 찾음. (2번째 주성분)
- 일반적으로 주성분은 원본 특성의 개수만큼 찾을수 있다고 생각합니다. (기술적인 이유로 주성분은 원본 특성의 개수 or 샘플 개수 중 작은 값만큼 찾을 수 있습니다. 일반적으로 비지도 학습은 대량 데이터를 이용하기 때문에, 원본 특성 개수만큼 찾는다고 할 수 있습니다.)
(300, 10000) 과일 데이터를 PCA model 에 학습 시키고, 50개의 주성분을 찾아보겠습니다.
# 1. 데이터 불러오고 2차원 배열로 바꾸기
!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)
#2. PCA model 학습 시키고 주성분 50개 찾기
from sklearn.decomposition import PCA
pca = PCA(n_components = 50) # 찾을 주성분을 50개로 하겠다.
pca.fit(fruits_2d)
print(pca.components_.shape) # (50개의 주성분 x 샘플 개수) = (50, 10000) # 두번째 차원은 항상 원본 데이터의 특성 개수와 같다.
주성분을 찾고 나면, PCA.components_에 주성분이 저장되어 있으며, (50,10000)으로 차원이 나타납니다.
50은 주성분의 개수, 10000은 원본 데이터 특성의 개수입니다.
그 이후에, 주성분을 다시 2차원 (100,100)으로 변형해 그림으로 그려보겠습니다.
#3. figure를 나타낼 함수 지정
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio = 1):
n = len(arr)
rows = int(np.ceil(n/10))
cols = n if rows < 2 else 10
fig, axs = plt.subplots(rows, cols, figsize = (cols * ratio, rows * ratio), squeeze = False)
for i in range(rows):
for j in range(cols):
if 10 * i + j < n:
axs[i,j].imshow(arr[10*i + j], cmap = 'gray_r')
axs[i,j].axis('off')
plt.show()
#4. 데이터를 잘 표현할 수 있는 주성분 50가지 선택하여 차원을 늘려 그리기
draw_fruits(pca.components_.reshape(-1,100,100))
이 주성분은 원본 데이터에서 가장 분산이 큰 방향을 순서대로 나타낸것입니다. 한편으로, 데이터셋에 있는 어떤 특징을 잡아낸것입니다.
transform(): 데이터 주성분에 투영하기
다음으로 10,000개의 특성을 가진 300개의 샘플 데이터를 50개의 주성분으로 투영해 차원을 줄여보겠습니다.
#5. 원본 데이터를 50개의 주성분에 투영하기
print(fruits_2d.shape) # (300, 10000)
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape) # (300, 50)
10,000개의 특성을 가진 데이터가 50개의 특성을 가진 데이터로 차원이 줄었습니다.
inverse_transform(): 데이터 복구
데이터의 차원이 줄었으면, 손실이 있을수 밖에 없습니다. 하지만, 주성분으로 데이터를 투영했기 때문에, 원본 데이터를 상당 부분 재구성 할 수 있을것입니다. 이를 위해 PCA.inverse_transform() method가 있습니다. 이 예제에서는 50개의 주성분으로 투영 되었던 데이터들을 다시 10,000개의 특성으로 복원합니다.
#6. 원본 데이터 복원하기
fruits_inverse = pca.inverse_transform(fruits_pca)
print(fruits_inverse.shape) # (300, 10000)
이후, 복원된 데이터를 가지고 다시 그림을 그려봅니다.
#7. 복원된 데이터를 다시 그림으로 그려보기
fruits_reconstruct = fruits_inverse.reshape(-1, 100, 100)
for n in [0, 100, 200]:
draw_fruits(fruits_reconstruct[n : n+100], ratio = 0.7)
복원한 데이터도 기존의 사진들과 매우 유사합니다. 주성분이 기존의 데이터들을 잘 설명하고 있다는 의미가 되기도 합니다.
설명된 분산 (explained variance)
주성분이 원본 데이터의 분산을 얼마나 잘 나타내는지 기록한 값
PCA.explained_variance_ratio_
- 각 주성분의 설명된 분산 비율이 기록되어 있습니다.
- 첫번째 주성분의 설명된 분산이 가장 큽니다.
- 이를 모두 더하면, 50개의 주성분이 표현하고 있는 총 분산비를 얻을 수 있습니다.
#8. 50개의 주성분이 분산을 얼마나 설명하고 있는지 확인하기
print(np.sum(pca.explained_variance_ratio_)) # 0.9215433500498339
#9. 적은 개수의 주성분으로 분산을 최대한 설명하기 위해서 몇개의 주성분이 필요한지 확인
plt.plot(pca.explained_variance_ratio_, linewidth = 2, c = 'k')
plt.xlabel('Number of Principal Component', fontsize = 14)
plt.ylabel('Explained variance', fontsize = 14)
plt.xticks(fontsize = 12, color = 'gray', fontweight = 'bold')
plt.yticks(fontsize = 12, color = 'gray', fontweight = 'bold')
plt.grid(True, which = 'both', linestyle = '--', linewidth = 0.7, color = 'gray')
plt.show()
50개의 주성분이 92% 이상의 분산을 설명하고 있으며, 10개 정도의 주성분이 대부분의 분산을 설명하는 것을 확인했습니다.
다음은, 차원을 축소한 데이터를 통해 지도학습 모델을 훈련해서 축소하기 전 데이터로 훈련한 model과 비교해보겠습니다.
차원 축소 전 vs 후 데이터 학습 Model 비교
#10. 로지스틱 회귀 모델을 이용해서 차원 축소 전후의 model 정확도와 훈련 시간 확인해보기
target = np.array([0]*100 + [1]*100 + [2]*100)
#10-1. 차원 축소 전
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(fruits_2d, target)
from sklearn.model_selection import cross_validate
scores = cross_validate(lr, fruits_2d, target)
print(np.mean(scores['test_score'])) # 0.9966666666666667
print(np.mean(scores['fit_time'])) # 2.0536828994750977
#10-2. 차원 축소 후
lr = LogisticRegression()
lr.fit(fruits_pca, target)
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score'])) # 1.0
print(np.mean(scores['fit_time'])) # 0.027897024154663087
차원 축소를 진행하면, 정확도도 증가하고 훈련 시간도 감소하는 것을 볼 수 있습니다. 따라서, 차원을 축소하여 지도 학습 알고리즘이나 다른 비지도 학습 알고리즘에 재사용하여 성능을 높이거나 훈련 속도를 빠르게 할 수 있습니다. KMeans와 같은 비지도 학습 알고리즘에도 차원 축소 한 뒤에 사용 가능합니다.
PCA plot 시각화
PCA 객체를 생성시, n_components 매개 변수를 0 ~ 1 사이 실수로 지정하면, 해당 분산까지 설명할 수 있는 주성분을 찾습니다.
#11. 원하는 설명된 분산 비율을 얻을때 까지 주성분을 찾기
pca = PCA(n_components = 0.5)
pca.fit(fruits_2d)
#12. 몇개의 주성분으로 50% 분산을 설명하는지 보기
## 주의 n_components: 설명된 분산
## 주의 n_components_ : 주성분 개수
print(pca.n_components_) # 2
여기서는 50% 분산을 설명 할 수 있는 주성분이 2개면 충분하다고 판단 됩니다.
이제, 주성분에 데이터를 투영하겠습니다. 이후, 교차 검증을 통해 정확도와 훈련 시간을 확인해보겠습니다.
#13. 2개의 주성분에 데이터 투영하기
fruits_pca = pca.transform(fruits_2d)
fruits_pca.shape # (300, 2)
#14. 2개의 특성만 가지고 교차 검증의 효과가 좋은지 확인
lr = LogisticRegression(max_iter=1000) # max_iter: 수렴하기 위한 반복 훈련 값.
lr.fit(fruits_pca, target)
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score'])) # 0.9933333333333334
print(np.mean(scores['fit_time'])) # 0.057269716262817384
2개의 특성으로도 정확도가 굉장히 높습니다.
이제, 차원이 축소된 데이터를 활용해 KMeans algorithm으로 clustering을 진행하겠습니다.
#15. 차원 축소된 데이터로 KMeans algorithm 적용해보기
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]))
#16. Kmeans로 분류된 데이터를 그려보기
for k in range(0,3):
draw_fruits(fruits[km.labels_ == k], ratio = 0.5)
print("\n")
차원 축소 전 데이터는 [0, 1, 2] 가 111, 98, 91 였는데 거의 비슷합니다. 또한, 그림을 그려보았을 때도 파인애플에 사과 몇개와 바나나 한개가 들어와 있는것을 제외하고 분류 기능이 정말 높습니다.
마지막으로 시각화를 진행하겠습니다. 주성분이 2개이므로 주성분을 축으로 시각화가 가능합니다.
#17. 축소된 차원 데이터로 Kmeans cluster 그리기
for k in range(0,3):
data = fruits_pca[km.labels_ == k]
plt.scatter(data[:,0], data[:,1])
plt.legend(['Apple', 'Banana', 'Pineapple'])
plt.show()
# 복습 코드
# !wget https://bit.ly/fruits_300_data -O fruits_300.npy
# 3차원 데이터 2차원으로 축소
import numpy as np
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1,10000)
# PCA model을 주성분 50개로 설정하여 model 학습
from sklearn.decomposition import PCA
pca = PCA(n_components = 50)
pca.fit(fruits_2d)
# 실제 데이터의 차원과 주성분의 차원을 비교
print(fruits_2d.shape, pca.components_.shape)
## 함수만들고 주성분을 3차원으로 압축하여 사진 출력해보기
fruits_pca = pca.components_.reshape(50,100,100)
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio = 1):
n = len(arr)
if n < 10: cols = n
else: cols = 10
rows = int(np.ceil(n/10))
fig, axs = plt.subplots(rows, cols, figsize = (cols * ratio, rows * ratio), squeeze = False)
for i in range(rows):
for j in range(cols):
if 10 *i + j < n :
axs[i,j].imshow(arr[10 * i + j], cmap = 'gray_r')
axs[i,j].axis('off')
plt.show()
# 주성분 출력 해보기
draw_fruits(fruits_pca)
# 주성분에서 다시 차원 복구해서 사진 출력해보기
fruits_transform = pca.transform(fruits_2d)
print(fruits_transform.shape)
pca_inverse = pca.inverse_transform(fruits_transform)
pca_reconstruct = pca_inverse.reshape(-1,100,100)
draw_fruits(pca_reconstruct)
# 주성분의 개수를 늘려가면서, 몇개의 주성분이 데이터 전부를 표현 하는지 확인
plt.plot(pca.explained_variance_ratio_)
# 회귀모델을 차원 축소전으로 학습해보고 cross validation 수행해보기 (score와 fit time 학습)
target = [0]* 100 + [1] * 100 + [2] * 100
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_validate
lr = LogisticRegression()
lr.fit(fruits_2d, target)
scores = cross_validate(lr, fruits_2d, target)
print(np.mean(scores['test_score']), np.mean(scores['fit_time']))
# 회귀모델을 차원 축소후로 학습해보고 cross validation 수행해보기 (score와 fit time 학습)
pca_transform = pca.transform(fruits_2d)
lr = LogisticRegression()
lr.fit(pca_transform, target)
scores = cross_validate(lr, pca_transform, target)
print(np.mean(scores['test_score']), np.mean(scores['fit_time']))
# 데이터의 50%를 설명 할 수 있는 주성분의 개수 확인 해보기
pca = PCA(n_components = 0.5)
pca.fit(fruits_2d)
print(pca.n_components_)
# 해당 주성분에 데이터를 투영하고 kmeans model에 학습해보기
from sklearn.cluster import KMeans
km = KMeans(n_clusters = 3)
pca_transform = pca.transform(fruits_2d)
km.fit(pca_transform)
# clustering 진행
for k in range(3):
data = pca_transform[km.labels_ == k]
plt.scatter(data[:,0], data[:,1])
plt.legend(['Apple', 'Banana', 'Pineapple'])
plt.show()