오늘의 학습 목표는
📌 k- 평균 알고리즘
입니다~~!
이전 챕터에서는 과일 종류의 개수를 알고 있었기 때문에 각 과일의 평균을 구할 수 있었습니다. 하지만 과일의 종류를 미리 알지 못하는 경우에는 K-평균 군집 알고리즘을 통해 평균값을 구할 수 있습니다.
클러스터 중심(cluster center) or 센트로이드(centroid)
- k-평균 군집 알고리즘의 평균값
과일 데이터를 사용해서 k-평균 모델을 만들어보겠습니다.
!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)
(샘플, 너비, 높이) 크기의 3차원 배열인 fruits 데이터를 (샘플 개수, 너비 x 높이) 크기를 가진 2차원 배열로 변경합니다.
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d)
클러스터 개수를 3으로 지정해서 KMeans 클래스 객체를 학습합니다. 비지도학습이기 때문에 fit( ) 매서드에서 타깃 데이터를 사용하지 않습니다.
print(km.labels_)
[2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 0 0 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]
군집 결과는 KMeans 클래스 객체의 labels_ 속성에 저장됩니다. 배열의 길이는 샘플 개수와 같습니다. 배열은 각 샘플이 어떤 레이블에 해당되는지 나타냅니다.
print(np.unique(km.labels_, return_counts=True))
(array([0, 1, 2], dtype=int32), array([112, 98, 90]))
첫 번째 클러스터(레이블 0)는 91개, 두 번째 클러스터(레이블 1)는 98개, 세 번째 클러스터(레이블 2)는 111개의 샘플을 모았습니다. 각 클러스터가 어떤 과일에 해당하는지 그림으로 출력해보겠습니다.
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1):
n = len(arr) # n은 샘플 개수입니다
# 한 줄에 10개씩 이미지를 그립니다. 샘플 개수를 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: # n 개까지만 그립니다.
axs[i, j].imshow(arr[i*10 + j], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
draw_fruits( ) 함수는 (샘플 개수, 너비, 높이)의 3차원 배열을 입력받아 가로로 10개씩 이미지를 출력합니다.
draw_fruits(fruits[km.labels_==0])
레이블 0으로 클러스터링된 이미지는 대부분 파인애플입니다.
draw_fruits(fruits[km.labels_==1])
레이블이 1인 클러스터는 바나나로만 이루어져있습니다.
draw_fruits(fruits[km.labels_==2])
레이블이 2인 클러스터는 사과로만 이루어져 있습니다.
레이블이 0인 클러스터는 파인애플과 사과, 바나나가 섞여있습니다. k-평균 알고리즘이 이 샘플들을 완벽하게 구별하지는 못했지만, 타깃 레이블을 제공하지 않았음에도 비교적 잘 구별한 것 같습니다.
KMeans 클래스가 최종적으로 찾은 클러스터 중심을 확인해보겠습니다.
각 중심을 이미지로 출력하려면 100 x 100 크기의 2차원 배열로 바꿔야 합니다.
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)
이전 챕터에서 사과, 바나나, 파인애플의 픽셀 평균값을 출력한 것과 비슷해보이네요!
transform( )
- 훈련 데이터 샘플에서 클러스터 중심까지 거리 변환
print(km.transform(fruits_2d[100:101]))
[[3400.24197319 8837.37750892 5279.33763699]]
인덱스가 100인 샘플에 대해서 transform( )매서드를 적용했습니다. fruits_2d[100] 으로 인덱싱을 적용하면 (10000,) 크기의 배열이 되므로 에러가 발생합니다. fruits_2d[100:101] 처럼 슬라이싱 연산자를 사용해서 (1, 10000) 크기의 배열을 transform( ) 매서드에 적용합니다.
반환된 배열은 (1, 클러스터 개수)인 2차원 배열입니다. 첫 번째 클러스터까지의 거리가 약 3393.8로 가장 작은걸 보면 레이블 0에 속하는 샘플인 것 같습니다.
predict( )
- 가장 가까운 클러스터중심을 예측 클래스로 출력
print(km.predict(fruits_2d[100:101]))
[0]
transform( ) 결과에서 예상했듯이 예측값이 레이블 0입니다. 이 샘플이 어떤 과일인지 확인해보겠습니다.
draw_fruits(fruits[100:101])
레이블 0에 해당하는 이 샘플은 파인애플인 걸 알 수 있습니다.
n_iter
- k-평균 알고리즘이 반복적으로 클러스터 중심을 옮기면서 최적의 클러스터를 찾는 과정을 반복한 횟수를 저장하는 속성
print(km.n_iter_)
4
그런데 우리는 n_clusters 값을 지정할 때 과일 종류가 3개라는 점을 미리 알고 있어서 3으로 지정했지만 실제로는 클러스터 개수조차 알 수가 없습니다.
이번에는 최적의 클러스터 개수를 찾는 방법을 알아보겠습니다.
대표적인 방법으로는 엘보우(elbow)가 있습니다.
이너셔(inertia)
- k-평균 알고리즘의 클러스터 중심과 클러스터에 속한 샘플 사이의 거리 제곱의 합
- 클러스터에 속한 샘플이 얼마나 가깝게 모여 있는지를 나타내는 값
- 일반적으로 클러스터 개수가 늘어나면 클러스터 개개의 크기가 줄어들기 때문에 이너셔도 줄어듦
엘보우 방법은 클러스터 개수를 늘려가면서 이너셔의 변화를 관찰하여 최적의 클러스터 개수를 찾는 방법입니다.
클러스터 개수를 증가시키면서 이너셔를 그래프로 그리면 감소하는 속도가 꺾이는 지점이 있는데, 이 지점부터는 클러스터 개수를 늘려도 클러스터에 잘 밀집된 정도가 크게 개선되지 않습니다. 이 지점이 마치 팔꿈치 모양이어서 엘보우라고 부릅니다.
inertia = []
for k in range(2, 7):
km = KMeans(n_clusters=k, n_init='auto', 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()
과일 데이터를 사용해서 이너셔를 계산해보았습니다. 클러스터 개수를 2~6까지 늘려가면서 KMeans 클래스를 5번 훈련합니다. 그리고 inertia의 값의 변화를 그래프로 출력했습니다.
k=3에서 그래프의 기울기가 조금 바뀐 것을 알 수 있습니다.
자료 출처: 한빛 미디어