[혼자 공부하는 머신러닝 + 딥러닝] Chapter 06-2 k-평균

2023. 8. 29. 10:002023-2 KHUDA/ML 기초세션

이 게시물은 한빛미디어의 <혼자 공부하는 머신러닝 + 딥러닝>를 정리한 글입니다.

https://hongong.hanbit.co.kr/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-%EB%94%A5%EB%9F%AC%EB%8B%9D/

 

혼자 공부하는 머신러닝+딥러닝

혼자 공부하는 머신러닝 딥러닝, 무료 동영상 강의, 머신러닝+딥러닝 용어집을 다운로드 하세요. 포기하지 마세요! 독학으로 충분히 하실 수 있습니다. ‘때론 혼자, 때론 같이’ 하며 힘이 되겠

hongong.hanbit.co.kr

 

k-평균 알고리즘 소개

k-평균 알고리즘은 가장 간단하면서 성능이 좋아 많이 사용되는 군집 방법 중 하나이다. 

 

1. 무작위로 k개의 클러스터 중심을 구한다.

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

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

4. 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다.

 

KMeans 클래스

!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-평균 모델을 훈련하기 위해 (샘플 개수, 너비, 높이) 크기의 3차원 배열을 (샘플 개수, 너비*높이) 크기를 가진 2차원 배열로 변경한다. 

 

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로 나타난다.

 

레이블 값과 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]

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(fruits[km.labels_==0])

 

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

레이블이 0인 클러스터는 파인애플에 사과 9개와 바나나 2개가 섞여있다. k-평균 알고리즘이 이 샘플들을 완벽하게 구별하지는 못했다. 하지만 훈련 데이터에 타깃 레이블을 전혀 제공하지 않았음에도 스스로 비슷한 샘플들을 아주 잘 모았다.

 

클러스터 중심

최종적으로 찾은 클러스터 중심은 cluster_centers_ 속성에 저장되어 있다. 이 배열은 fruits_2d의 클러스터 중심이기 때문에 이미지로 나타내려면 100*100 크기의 2차원 배열로 바꿔야 한다.

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

 

 

인덱스가 100인 샘플은 어떤 레이블을 가지고 있을까?

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

첫 번째 클러스터까지의 거리가 약 3393으로 레이블 0에 속한 것 같다. 예측 메서드를 사용해 출력해보면 역시 0 레이블에 속함을 알 수 있다. 

 

print(km.n_iter_)
# -> 3

여기서는 타깃에 대한 정보를 미리 알고 활용해 n_cluster=3으로 지정하였다. 실전에서는 클러스터의 개수를 알 수 없다. 그렇다면 n_clusters를 어떻게 지정해야 할까?

 

최적의 k 찾기

엘보우 방법은 최적의 k를 찾는 방법이다. k를 증가시켜 가면서 inertial(클러스터의 중심과 소속 샘플들 간 거리의 제곱 합)을 계산한 다음, inertial가 감소하는 속도가 꺾이는 지점을 최적의 k로 선택한다.

 

from sklearn.cluster import KMeans

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에서 그래프의 기울기가 조금 바뀜을 볼 수 있다. 엘보우 지점보다 클러스터 개수가 많아지면 이너셔의 변화가 줄어들면서 군집 효과가 줄어든다.