k-평균
혼공머신 6-2 K-평균
6-1에서 사용한 데이터는 이미 레이블이 되어 있는 데이터이기 때문에 클러스터링하는 것이 매우 쉬웠다
하지만 실제 비지도 학습에서는 각 이미지들이 레이블되어 있지 않을 것이기 때문에 클러스터링할 수 있는 방법이 필요할 것이다.
클러스터 중심
클러스터 중심 = 센트로이드(centroid)
각 군집(클러스터)의 평균 이미지로서 각 클러스터의 기준이 된다.
k-평균 알고리즘
/kmeans.png)
-
k개(하이퍼 파라미터)의 클러스터 중심을 만들어서 군집의 개수를 정해 준다.
-
각 샘플에서 가장 가까운 클러스터 중심을 찾아서 해당 클러스터의 샘플로 지정한다.
-
클러스터에 속한 샘플들의 평균값으로 클러스터 중심을 변경한다.
-
클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다.
KMeans 클래스
300개의 과일 데이터를 사용해서 KMeans로 훈련해 보자
import numpy as np
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1, 100*100) # 이미지 차원 1차원으로 변경
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42) #클러스터 개수 지정
km.fit(fruits_2d)
# train / test를 따로 나누지 않는다.
# 군집에서는 과대적합 괴소적합이라는 개념이 따로 없다.
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]
# n_clusters=3으로 지정했기 때문에 0~2의 값이 지정된것을 볼 수 있다.
print(np.unique(km.labels_, return_counts=True))
#(array([0, 1, 2], dtype=int32), array([111, 98, 91]))
#return_counts를 True로 해주면 unique값 0,1,2를 보여준다.
# unique함수를 통해 개수를 알 수 있다. 0 111개 / 1 98개 / 2 91개
KMeans의 n_iter의 기본값은 10이고 이 중에서 가장 좋은 결과를 출력한다.
결과를 보면 각 레이블 0,1,2에 111, 98, 91로 각 클러스터의 개수를 지정했는데
실제 값들은 각각 100개일 것이므로 클러스터 안에 샘플들이 조금 섞여 있다는 것을 알 수 있다.
각 클러스터 확인
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio=1):
n = len(arr) # n은 샘플 개수입니다
# 한 줄에 10개씩 이미지를 그립니다. 샘플 개수를 10으로 나누어 전체 행 개수를 계산합니다.
rows = int(np.ceil(n/10))
# np.ceil 올림 함수를 사용해서 이미지를 출력할 열 개수를 지정한다.
cols = n if rows < 2 else 10
# 행이 1개 이면 열 개수는 샘플 개수입니다. 그렇지 않으면 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')
# subplot의(i,j)번째 : axs[i,j]
# arrp[i*10 + j] row개수에 n/10해줬으므로 10곱하고 j=cols가 1씩 증가하므로 전부 출력
axs[i, j].axis('off')
# 이미지 선 없애기 위해서 .axis('off')사용
plt.show()
draw_fruits(fruits[km.labels_==0]) # 모든 이미지 중에서 레이블로 선별
draw_fruits(fruits[km.labels_==1])
draw_fruits(fruits[km.labels_==2])
km.labels_==0
/image-20220520221001383.png)
km.labels_==1
/image-20220520221043642.png)
km.labels_==2
/image-20220520221051282.png)
직접 이미지를 확인해 보면 파인애플에 일부 사과와 바나나가 섞겨 있는 것을 볼 수 있다.
클러스터 중심
클러스터 중심값을 통해 어떻게 샘플들을 분류하는지 확인해 보자
kmeans로 찾아낸 클러스터 중심을 이미지로 보기 편하게 다시 3차원으로 바꿔서 출력하면
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)
# cluster_centers_ 에 클러스터 중심 값 (센트로이드값)이 들어있다
# 2차원으로 들어있기 때문에 다시 reshape해준다
/image-20220520232510346.png)
파인애플 바나나 사과 순으로 클러스터 중심(각 클러스터의 평균) 이미지를 보여준다
print(km.transform(fruits_2d[100:101]))
#[[3393.8136117 8837.37750892 5267.70439881]]
transform을 사용해서 각 클러스터 중심 값과의 거리로 그 이미지를 나타낼 수 있다.
원래 한 이미지는 100x100의 벡터로 나타내는데 이 방법으로 3개의 특성이 있는 벡터로 차원 축소가 가능하다.
모든 군집 메소드가 transform을 제공하지는 않는다 kmeans가 특별히 클러스터 중심을 사용하기 때문에 그 중심과의 거리를 계산할 수 있어서 transform이 존재한다.
print(km.predict(fruits_2d[100:101]))
#[0] 예상 : 파인애플
draw_fruits(fruits[100:101])
print(km.n_iter_)
#4 클러스터 중심을 찾기 위해 반복한 횟수
클러스터 중심으로 예측한 레이블을 확인하기 위해 직접 이미지를 출력해 보면 파인애플이 나온 것을 확인할 수 있다.
/image-20220520232552263.png)
최적의 k 찾기
kmeans는 클러스터 개수를 지정해 주어야 해서 다른 군집 알고지즘보다 까다롭다.
그렇기 때문에 k 값을 찾는 알고리즘도 존재한다.
elbow method
k 값을 작은 k 값부터 큰 k 값까지 바꾸어 보면서 클러스터의 intertia 값을 계산한다.
inertia 값 : 클러스터 안에서 클러스터 중심까지의 모든 거리를 평균한 값
이 값이 낮으면 조밀하게 잘 모여 있다는 의미가 된다.
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()
/image-20220521005323198.png)
inertia 값은 k 값이 증가하면 군집이 많아지므로 자연스럽게 낮아질 수밖에 없다.
하지만 k 값과 inertia 사이의 그래프를 보면 특정 k 값에서 기울기가 더 완만해지도록 바뀐 구간이 보이는데
이 k 값보다 더 큰 k 값을 선택해서 inertia 값을 낮추더라도 inertia 값이 이전 구간보다 더 줄어들지 않았기 때문에 더 효과가 좋다고 말할 수 없다.
그러므로 위 그래프에서는 가장 좋은 k 값은 3이라고 할 수 있다.
이 지점이 마치 팔꿈치 모양이라서 엘보우 방법이라고 부른다.
참고
박해선,혼자 공부하는 머신러닝, 한빛미디어, 2021,303~315p