본문 바로가기
Study/혼자 공부하는 머신러닝

혼자 공부하는 머신러닝 + 딥러닝 - K-Means 알고리즘

by Wanooky 2022. 3. 29.

비지도 학습은 사진에 어떤 과일이 들어 있는지 모른다. 이럴 때 k-means 군집 알고리즘으로 평균값을 자동으로 찾는다.

평균값이 클러스터의 중심에 위치하기 때문에 클러스터 중심 또는 센트로이드 라고 부른다.

 

작동 순서는 다음과 같다.

1. 무작위로 k개의 클러스터 중심 정하기

2. 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정하기

3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경

4. 클러스터 중심에 변화가 없을 때까지 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)

 

k-means 알고리즘을 사용하는 방법은 sklearn.cluster 모듈 아래 KMeans 클래스에 구현되어 있다.

 

다만 비지도 학습이므로 fit() 매서드에서 타깃 데이터를 사용하지 않는다.

from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d)

군집된 결과는 KMeans 클래스 객체의 labels_ 속성에 저장된다. labels_ 배열의 길이는 샘플 개수와 같다.

n_clusters=3으로 지정했기 때문에 labels_ 배열의 값은 0,1,2 중 하나이다.

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 2 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]

그 다음에는 레이블 0, 1, 2로 모은 샘플의 개수를 확인해보자.

 

print(np.unique(km.labels_, return_counts=True))

(array([0, 1, 2], dtype=int32), array([111,  98,  91]))

 

각 클러스터가 어떤 이미지를 나타냈는지 그림으로 출력하는 draw_fruits() 함수를 만들어보자

 

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개씩 이미지를 출력.

그러면 레이블이 0, 1, 2로 된 이미지를 각각 출력해보자.

 

draw_fruits(fruits[km.labels_==0])

 

draw_fruits(fruits[km.labels_==1])

draw_fruits(fruits[km.labels_==2])

KMeans 클래스가 최종적으로 찾은 클러스터 중심은 cluster_centers_ 속성에 저장되어 있다.

그 중심을 draw_fruits 함수의 매개변수로 지정해 출력해보자.

 

draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)

KMeans 클래스에 transform() 메서드는 훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환해준다.

 

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 알고리즘은 클러스터 중심을 옮기면서 최적의 클러스터를 찾는데, 반복한 횟수는 n_iter_속성에 저장된다.

print(km.n_iter_)

4

 

최적의 k를 찾아보자.

여기서는 엘보우 방법을 쓸 것이다.

클러스터 중심과 클러스터에 속한 샘플사이의 거리 제곱 합을 이너셔(inertia)라고 한다.

이너셔는 클러스터에 속한 샘플이 얼마나 가깝게 모여있는지 나타내는 값으로 생각할 수 있다.

 

일반적으로 클러스터 개수가 늘어나면 클러스터 개개의 크기는 줄어드므로 이너셔도 줄어든다.

 

클러스터 개수를 증가시키면서 이너셔를 그래프로 그리면 감소하는 속도가 꺾이는 지점 (기울기가 줄어드는) 이 있는데, 이 지점부터 클러스터 개수를 늘려도 크게 개선되지가 않는다.

즉 이너셔가 줄어들지 않고, 이 지점이 팔꿈치 모양과 비슷해서 엘보우 방법이라 부른다.

 

그러면 엘보우 방법을 구현해보자.

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=3일때 기울기가 줄어드는 것을 알 수 잇다.