학습 목표
- 흑백 사진을 분류하기 위해 여러 가지 아이디어를 내면서 비지도 학습솨 군집 알고리즘에 대해 이해한다.
fruits = np.load('fruits_300.npy')
print(fruits.shape)
▶
(300, 100, 100)
# imshow로 numpy 배열로 저장된 이미지 출력(밝을수록 255 짙을수록 0)
plt.imshow(fruits[0], cmap='gray')
plt.show()
# cmap = 'gray_r'로 흑백을 반전(밝을수록 0 짙을수록 255)
plt.imshow(fruits[0], cmap='gray_r')
plt.show()
# 파인애플, 바나나 이미지 출력
fig,axs = plt.subplots(1,2)
axs[0].imshow(fruits[100], cmap='gray_r')
axs[1].imshow(fruits[200], cmap='gray_r')
plt.show()
결과 이미지
사과
사과(흑백반전)
파인애플, 바나나
apple = fruits[:100].reshape(-1,100*100)
pineapple = fruits[100:200].reshape(-1,100*100)
banana = fruits[200:300].reshape(-1,100*100)
apple.shape, pineapple.shape, banana.shape
▶
((100, 10000), (100, 10000), (100, 10000))
# 과일별 평균 픽셀값의 히스토그램
plt.hist(np.mean(apple, axis=1), alpha=0.8, label = 'apple')
plt.hist(np.mean(pineapple, axis=1), alpha=0.8, label = 'pineapple')
plt.hist(np.mean(banana, axis=1), alpha=0.8, label = 'banana')
plt.legend()
plt.show()
바나나 사진의 평균값은 40 아래에 집중되어 있고, 사과와 파인애플은 90~100 사이에 많이 모여 있다.
바나나는 이미지 내에서 바나나가 차지하는 영역이 작기때문에 평균값이 작다.
픽셀의 평균값으로 바나나는 구분이 가능하지만 사과나 파인애플은 구분이 힘들다.
fig, axs = plt.subplots(1,3,figsize=(20,5))
axs[0].bar(range(10000), np.mean(apple, axis=0))
axs[1].bar(range(10000), np.mean(pineapple, axis=0))
axs[2].bar(range(10000), np.mean(banana, axis=0))
plt.show()
apple_mean = np.mean(apple, axis=0).reshape(100,100)
pineapple_mean = np.mean(pineapple, axis=0).reshape(100,100)
banana_mean = np.mean(banana, axis=0).reshape(100,100)
fig,axs = plt.subplots(1,3,figsize=(20,5))
axs[0].imshow(apple_mean, cmap='gray_r')
axs[1].imshow(pineapple_mean, cmap='gray_r')
axs[2].imshow(banana_mean, cmap='gray_r')
plt.show()
abs_diff = np.abs(fruits - apple_mean)
abs_mean = np.mean(abs_diff, axis=(1,2))
print(abs_mean.shape)
▶
(300,)
apple_index = np.argsort(abs_mean)[:100]
fig, axs = plt.subplots(10,10,figsize=(10,10))
for i in range(10):
for j in range(10):
axs[i,j].imshow(fruits[apple_index[i*10+j]], cmap='gray_r')
axs[i,j].axis('off') # 축을 안보이게 함
plt.show()
apple_mean과 가장 가까운 사진 100개가 전부 사과이다.
❗ discussion
이렇게 비슷한 샘플끼리 그룹으로 모으는 작업을 군집(clustering)이라고 한다. 군집은 대표적인 비지도 학습 작업 중 하나이며, 이 군집 알고리즘에서 만든 그룹을 클러스터라고 한다.
비지도 학습
머신러닝의 한 종류로 훈련 데이터에 타깃이 없고, 그로 인해 외부의 도움 없이 스스로 유용한 무언가를 학습해야함
히스토그램
구간별로 값이 발생한 빈도를 그래프로 표시한 것
군집
비슷한 샘플끼리 하나의 그룹으로 모으는 대표적인 비지도 학습 작업
학습 목표
- k-평균 알고리즘의 작동 방식을 이해하고 과일 사진을 자동으로 모으는 비지도 학습 모델을 만들어 봅니다.
import numpy as pn
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1,100*100)
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d)
print(np.unique(km.labels_, return_counts=True))
▶
(array([0, 1, 2], dtype=int32), array([111, 98, 91]))
첫번째 클러스터(레이블 0)가 111개의 샘플을 모았고, 두번째 클러스터(레이블 1)가 98개의 샘플을 모았고 세번째 클러스터(레이블 2)는 91개의 샘플을 모았다.
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1) :
n = len(arr) # n은 샘플 개수
# 한 행에 10개씩 이미지 생성
rows = int(np.ceil(n/10))
# 행이 1개면 열의 개수는 샘플 개수, 그렇지 않으면 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 i*10 + j < n :
axs[i,j].imshow(arr[i*10+j], cmap='gray_r')
axs[i,j].axis('off')
plt.show()
draw_fruits(fruits[km.labels_==0])
draw_fruits(fruits[km.labels_==1])
draw_fruits(fruits[km.labels_==2])
결과이미지
label==0
label==1
label==2
레이블이 1인 클러스터는 바나나로만 이루어져 있으나,
레이블이 2인 클러스터는 사과 9개와 바나나 2개가 섞여있다.
draw_fruits(km.cluster_centers_.reshape(-1,100,100), ratio=3)
print(km.transform(fruits_2d[100:101]))
▶
[[3393.8136117 8837.37750892 5267.70439881]]
print(km.predict(fruits_2d[100:101]))
▶
[0]
draw_fruits(fruits[100:101])
결과 이미지
k-means 알고리즘이 반복한 횟수 확인
print(km.n_iter)
▶
4
inertia= []
for k in range(2,7) :
km = KMeans(n_clusters=k, random_state=42)
km.fit(fruits_2d)
inertia.append(km.inertia_)
plt.plot(range(2,7), inertia)
plt.xlabel('k')
plt.ylabel('inertia')
plt.show()
k-means
랜덤한 초기값을 가진 클러스터의 중심을 정하고, 이동&재설정을 반복하여 최적의 클러스터를 구성하는 알고리즘
클러스터 중심(centroid)
k-means 알고리즘이 만든 클러스터에 속한 샘플의 특성 평균값
엘보우 방법
최적의 클러스터 개수를 정하는 방법 중 하나이다. 클러스터 중심과 샘플 사이 거리의 제곱 합을 이너셔라고 하는데, 클러스터 개수에 따라 이너셔 감소가 꺾이는 지점이 적절한 클러스터 개수 k가 될 수 있다.
학습 목표
- 차원 축소에 대해 이해하고, 대표적인 차원 축소 알고리즘 중 하나인 PCA(주성분 분석) 모델을 만들어 본다.
from sklearn.decomposition import PCA
pca = PCA(n_components=50)
pca.fit(fruits_2d)
print(pca.components_.shape)
▶
(50, 10000)
draw_fruits(pca.components_.reshape(-1,100,100))
print(fruits_2d.shape)
▶
(300, 10000)
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)
▶
(300, 50)
fruits_inverse = pca.inverse_transform(fruits_pca)
print(fruits_inverse.shape)
▶
(300, 10000)
fruits_reconstruct = fruits_inverse.reshape(-1,100,100)
for start in [0,100,200]:
draw_fruits(fruits_reconstruct[start:start+100])
print('\n')
print(np.sum(pca.explained_variance_ratio_))
▶
0.9215956549690777
plt.plot(pca.explained_variance_ratio_)
plt.show()
처음 10개의 주성분이 상위의 분산비율을 가지고, 그 이후로는 각 주성분이 설명하고 있는 분산은 비교적 작다.
과일 사진 원본 데이터와 PCA로 축소한 데이터를 지도 학습에 적용해보고 어떤 차이가 있는지 알아보자.
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
scores = cross_validate(lr, fruits_2d, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
▶
0.9966666666666667
1.7810904026031493
from sklearn.model_selection import cross_validate
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
▶
1.0
0.020934200286865233
50개의 특성만 사용했는데도 정확도가 100%이고, 훈련 시간은 0.03초로 20배 이상 감소하였다.
pca = PCA(n_components=0.5)
pca.fit(fruits_2d)
print(pca.n_components_)
▶
2
단 2개의 특성만으로 원본 데이터에 있는 분산의 50%를 표현할 수 있다.
fruits_pca = pca.transform(fruits_2d)
print(fruits_pca.shape)
▶
(300, 2)
scores = cross_validate(lr, fruits_pca, target)
print(np.mean(scores['test_score']))
print(np.mean(scores['fit_time']))
▶
0.9933333333333334
0.039040756225585935
2개의 특성만 사용해도 99%의 정확도를 달성하였다.
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):
draw_fruits(fruits[km.labels_==label])
print('\n')
결과이미지
label==0
label==1
label==2
원본 데이터로 찾은 클러스터와 비슷하게 파인애플과 사과가 조금 혼돈되는 면이 있다.
for label in range(0,3) :
data = fruits_pca[km.labels_ == label]
plt.scatter(data[:,0], data[:,1])
plt.legend(['pineapple','banana','apple'])
plt.show()
차원 축소
원본 데이터의 특성을 적은 수의 새로운 특성으로 변환하는 비지도 학습의 한 종류
주성분 분석
차원 축소 알고리즘의 하나로 데이터에서 가장 분산이 큰 방향을 찾는 방법이다. 이런 방향을 주성분이라고 하며, 원본 데이터를 주성분에 투영하여 새로운 특성을 만들 수 있다. 일반적으로 주성분은 원본 데이터에 있는 특성 개수보다 작다.
설명된 분산
주성분 분석에서 주성분이 얼마나 원본 데이터의 분산을 잘 나타내는지 기록한 것