[머신러닝] 6-2. k-평균
k-평균 알고리즘의 작동 방식을 이해하고 과일 사진을 자동으로 모으는 비지도 학습 모델을 만들어보자
타깃값을 모르는 비지도 학습에서 어떻게 평균값을 구할까? k-평균 군집 알고리즘이 평균값을 찾아준다. 이 평균 값이 클러스터의 중심에 위치하기 때문에 클러스터 중심, 센트로이드라고 부른다.
클러스터의 사전적 정의는 집합, 군집을 말한다. 비슷한 공통점이 있는 친구들끼리 뭉치고 친해지는 것과 같다.
k-평균 알고리즘 소개
어떻게 작동하는 것일까?
- 무작위로 k개의 클러스터 중심을 정한다.
- 각 샘플에서 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
- 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다
- 클러스터 중심에 변화가 없을때까지 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])
1
draw_fruits(fruits[km.labels_==1])
1
draw_fruits(fruits[km.labels_==2])
어랏? 섞여있잖아?
클러스터 중심
1
draw_fruits(km.clusters__.reshape(-1,100,100),ratio=3)
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])
처음에 말했듯 중심을 옮기면서 최적 클러스터를 찾는다. 몇번 했는지는 n_iter_에 있다
1
2
print(km.n_iter_)
#3
최적의 k찾기
k-평균 알고리즘은 클러스터 개수를 사전에 지정해야 한다. 어떻게 찾을 수 있을까?
완벽한 방법은 없다. 대표적인 방법은 엘보우이다. k-평균 알고리즘은 클러스터 중심과 클러스터에 속한 샘플 사이의 거리를 잴 수 있다
클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱 합을 이너셔라고 한다. 얼마나 가깝게 있는지 알 수 있다. 클러스터 개수가 늘어나면 클러스터 개개의 크기는 줄어들어 이너셔도 줄어든다.
개수를 증가하다보면 그래프가 꺾이는 지점이 있다. 이 때는 개수를 늘려도 클러스터가 밀집된 정도가 개선되지 않는다
이 꺾이는 모양이 팔꿈치 모양이라서 엘보우방법이라고 한다.
- KMeans 클래스에서 자동으로 이너셔를 계산하여 inertia_ 속성에 들어간다
- fit메서드로 훈련한다
- inertia_에 저장된 이너셔값을 inertia에 추가한다.
- 그래프로 출력
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()
k=3에서 꺾이는 것을 볼 수 있다. 클러스터 수가 많아지만 이너셔 변화가 줄면서 군집 효과도 줄어든다
마무리
키워드
k-평균 클러스터 중심 엘보우 방법
핵심 패키지와 함수
scikit-learn
KMeans
참고
- 혼자 공부하는 머신러닝+딥러닝
- 클러스터(CLUSTER)를 알아보자







