Post

[머신러닝] 6-2. k-평균

[머신러닝] 6-2. k-평균

k-평균 알고리즘의 작동 방식을 이해하고 과일 사진을 자동으로 모으는 비지도 학습 모델을 만들어보자

타깃값을 모르는 비지도 학습에서 어떻게 평균값을 구할까? k-평균 군집 알고리즘이 평균값을 찾아준다. 이 평균 값이 클러스터의 중심에 위치하기 때문에 클러스터 중심, 센트로이드라고 부른다.

클러스터의 사전적 정의는 집합, 군집을 말한다. 비슷한 공통점이 있는 친구들끼리 뭉치고 친해지는 것과 같다.

k-평균 알고리즘 소개

어떻게 작동하는 것일까?

  1. 무작위로 k개의 클러스터 중심을 정한다.
  2. 각 샘플에서 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
  3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다
  4. 클러스터 중심에 변화가 없을때까지 2번으로 돌아가 반복한다.

alt

  1. 3개의 클러스터 중심(빨간 점)을 랜덤하게 지정한다. 클러스터 중심에서 가장 가까운 샘플을 집합으로 묶는다.
    클러스터의 중심을 다시 계산하여 이동시킨다. 맨 아래 클러스터는 사과쪽으로 중심이 조금 이동하고 왼쪽 위 클러스터는 바나나 쪽으로 중심이 더 이동한다.
  2. 다시 계산해서 가장 가까운 샘플을 다시 클러스터로 묶는다. 또 클러스터 중심을 계산한다. 빨간 점을 클러스터 가운데 부분으로 이동시킨다.
  3. 이동 클러스터 중심에서 또 묶는다. 2번과 변화가 없으니 k-평균 알고리즘을 종료한다.

정리하면 처음에는 랜덤으로 클러스터 중심을 선택하고 점점 가까운 샘플의 중심으로 이동하는 알고리즘이다.

KMeans 클래스

1
2
# 데이터 다운로드
!wget https://bit.ly/fruits_300 -0 fruits_300.npy

훈련을 위해 (샘플 개수, 너비, 높이) -> (샘플 개수, 너비*높이) 2차원으로

1
2
3
4
5
6
import numpy as np

#넘파이 배열 준비
fruits=np.load('fruits_300.npy')

fruits_2d=fruits.reshape(-1,100*100)

n_clusters로 클러스터 개수를 지정한다
비지도 학습이니 fit에서 타깃 데이터는 사용하지 않는다

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

결과는 labels_에 저장된다. 길이는 샘플 개수로 각 샘플이 어떤 레이블에 해당되는지 나타낸다. n_clusters=3이니 0,1,2중 하나다

1
2
3
print(km.labels_)

#[ 0 0 0 0 0 0 2 0 0 0 ...]
1
2
3
print(np.unique(km.labels_, return_counts=True))

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

레이블 0이 91개 1이 98개 2가 111개

draw_fruits로 그림으로 출력해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1):
    n_len(arr) #n은 샘플 개수
    #한줄에 10개씩 이미지
    rows=int(np.ceil(n/10))
    #행이 1개면 열의 개수는 샘플 개수
    cols=n if rows<2 else 10
    fig,axs=plt.subplots(row, cols, figsize=(cols*ratio, rows*ratio), sqeeze=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()

km.labels_==0을 하면 km.labels_배열에서 값이 0인 위치는 True, 나머지는 False가 된다. 이런 불리언 배열을 통해 원소를 선택할 수 있다 이를 불리언 인덱싱이라고 한다.

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

alt

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

alt

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

alt

어랏? 섞여있잖아?

클러스터 중심

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

alt

transform에서 훈련 데이터 샘플에서 클러스터 중심까지 거리를 반환해준다.

1
2
print(km.transform(fruits_2d[100:101]))
#[[5267.70 8837.37 3393.81]]

세번째 클러스터(레이블 2)의 거리가 3393.8로 가장 작다. 그렇다면 2에 속한거겠지? 확인해보자

1
2
3
#가장 가까운 클러스터 중심을 예측 클래스로 출력
print(km.predict(fruits_2d[100:101]))
#[2]
1
draw_fruits(fruits[100:101])

alt

처음에 말했듯 중심을 옮기면서 최적 클러스터를 찾는다. 몇번 했는지는 n_iter_에 있다

1
2
print(km.n_iter_)
#3

최적의 k찾기

k-평균 알고리즘은 클러스터 개수를 사전에 지정해야 한다. 어떻게 찾을 수 있을까?

완벽한 방법은 없다. 대표적인 방법은 엘보우이다. k-평균 알고리즘은 클러스터 중심과 클러스터에 속한 샘플 사이의 거리를 잴 수 있다

클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱 합을 이너셔라고 한다. 얼마나 가깝게 있는지 알 수 있다. 클러스터 개수가 늘어나면 클러스터 개개의 크기는 줄어들어 이너셔도 줄어든다.

개수를 증가하다보면 그래프가 꺾이는 지점이 있다. 이 때는 개수를 늘려도 클러스터가 밀집된 정도가 개선되지 않는다

이 꺾이는 모양이 팔꿈치 모양이라서 엘보우방법이라고 한다.

alt

  1. KMeans 클래스에서 자동으로 이너셔를 계산하여 inertia_ 속성에 들어간다
  2. fit메서드로 훈련한다
  3. inertia_에 저장된 이너셔값을 inertia에 추가한다.
  4. 그래프로 출력
1
2
3
4
5
6
7
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(rnage(2,7), inertia)
plt.show()

alt

k=3에서 꺾이는 것을 볼 수 있다. 클러스터 수가 많아지만 이너셔 변화가 줄면서 군집 효과도 줄어든다


마무리

키워드

k-평균 클러스터 중심 엘보우 방법

핵심 패키지와 함수

scikit-learn

KMeans


참고

  1. 혼자 공부하는 머신러닝+딥러닝
  2. 클러스터(CLUSTER)를 알아보자
This post is licensed under CC BY 4.0 by the author.