13. k-평균

  • 바로 직전에는 사과, 파인애플, 바나나에 있는 각 픽셀의 평균값을 구해서 가장 가까운 사진을 고르는 작업을 수행했다.
  • 하지만 실제로 비지도 학습에서는 사진에 어떤 과일이 들어 있는지 알지 못한다.
  • 이런 경우 k-평균을 사용한다. 이 평균값이 클러스터 중심에 위치하기 떄문에 클러스터 중심또는 센트로이드라고 부른다.

    1. k-평균 알고리즘

  • k-평균 알고리즘 작동방식
    • 무작위로 k개의 클러슽 중심을 정한다.
    • 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
    • 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다.
    • 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다.
  • k-평균 알고리즘은 처음에는 랜덤하게 클러스터 중심을 선택하고 점차 가장 가까운 샘플의 중심으로 이동하는 알고리즘이다. 이를 사이킷런을 이용해 구현해보자
  • 데이터는 이전에 사용한 데이터 셋을 활용해 구현해 보도록 한다. np.load()함수를 이용해 npy파일을 읽어 넘파이 배열을 준비하고 모델 훈련을 위해 3차원배열을 2차원 크기의 배열로 변경하는 작업을 수행한다.
import pandas as pd
import numpy as np
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

raw = '/Users/janghyeseong/Downloads/fruits_300.npy'
fruits = np.load(raw)
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
       n_clusters=3, n_init=10, n_jobs=None, precompute_distances='auto',
       random_state=42, tol=0.0001, verbose=0)
  • 사이킷런의 k-평균 알고리즘은 sklearn.cluster 모듈 아래 KMeans클래스에 구현되어 있다. 클러스터의 개수를 지정하기 위해 n_cluster 매개변수를 3으로 지정했다.
  • 비지도 학습이기 때문에 타깃데이터를 fit()에 이용하지 않았다.
  • 군집된 결과는 KMeans클래스 객채의 labels_ 속성에 저장된다. 여기서 클러스터의 개수를 3으로 지정했지 떄문에 값은 0,1,2 중 하나로 나타나게 된다.
print(km.labels_)
[0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 2 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 2 2 0 0 0 0 0 0 0 0 2 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 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 2 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 2 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([ 91,  98, 111]))
  • 첫 번째 클러스터는 111개의 샘플을 모았고, 두 번째 클러스터는 98개 그리고 세 번째 클러스터는 91개의 샘플을 모았다. 각 클러스터가 어떤 이미지를 나타냈는지 그림의 출력하기 위해 간단한 유틸리티 함수 draw_fruits()를 만들어보자.
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개씩 이미지를 출력한다.
  • 샘플 개수에 따라 행과 열의 개수를 계산하고 figsize를 지정한다. figsize는 ratio 매개변수에 비례해 커진다.
  • 그리고 이중 for문을 사용해 먼저 첫 번째 행을 따라 이미지를 그린다. 이어서 두 번째를 그리는 식으로 진행한다.
  • 해당 함수를 가지고 레이블이 0인 과일 사진을 모두 그려보자. km.labels_==0과 같이 쓰면 0인 값을 참으로 반환해준다.
draw_fruits(fruits[km.labels_==0])

output_9_0

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

output_10_0

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

output_11_0

  • 레이블 0번의 해당하는 클러스터만 여러 과일이 섞여 있는것을 확인할 수 있다. 하지만 나머지 두 클러스터는 정확하게 분류해 낸 것을 확인할 수 있다.
  • KMeans 클래스가 최종적으로 찾은 클러스터 중심을 cluster_centers_속성에 저장되어 있다. 이 배열은 fruits_2d 샘플의 클러스터 중심이기 때문에 이미지로 출력하려면 100x100의 2차원 배열로 변경해야 한다.
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)

output_13_0

  • KMeans 클래스는 훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환해주는 transform()메서드를 가지고 있다. 이는 표준화를 진행해주는 StandardScaler 클래스처럼 특성값을 반환하는 도구로도 사용할 수 있다는 것을 의미한다.
print(km.transform(fruits_2d[100:101]))
[[5267.70439881 8837.37750892 3393.8136117 ]]
  • 하나의 샘플을 전달해 확인해보면 0번레이블이 가장 거리가 작다. KMeans 클래스는 가장 가까운 클러스터 중심을 예측 클래스로 출력하는 predict() 메서드를 제공한다.
  • 해당 이미지는 레이블 0에 속할 것이고 파인애플의 모양을 가지고 있을 것이다. 이는 아래에서 확인해볼 수 있다.
  • k-평균 알고리즘은 반복적으로 클러스터 중심을 옮기면서 최적의 클러스터를 찾는다. 반복한 횟수는 KMeans 클래스의 n_iter_속성에 저장된다. 클러스터 중심은 특성 공학 처럼 사용해 데이터셋을 저차원으로 변환할 수 있다. 또는 가장 가까운 거리에 있는 클러스터 중심을 샘플의 예측 값으로 사용할 수 있다.
  • 우리는 n_cluster를 3으로 지정한 것을 통해 타깃 정보를 간접적으로 사용했다. 하지만 실제로는 이런 클러스터 개수 조차도 알기 힘들다. 이를 어떻게 사용할 수 있을지는 뒤에서 다루도록 한다.
print(km.predict(fruits_2d[100:101]))
[2]
draw_fruits(fruits[100:101])

output_18_0

print(km.n_iter_)
3

2. 최적의 k 찾기

  • k-평균 알고리즘은 클러스터 개수를 사전에 지정해야 한다. 실전에서는 클러스터의 개수가 몇 개인지 알수 없다. 실제로 군집 알고리즘에서 적절한 k값을 찾기 위한 완벽한 방법은 존재하지 않는다.
  • k-평균 알고리즘은 클러스터 중심과 클러스터에 속한 샘플 사이의 거리를 잴 수 있다. 이 거리의 제곱 합을 이너셔라고 부른다. 이너셔는 클러스터에 속한 샘플이 얼마나 가깝게 모여 있는지를 나타내는 값이다. 일반적으로 클러스터 개수가 늘어나면 클러스터 개개의 크기를 줄어들기 때문에 이너셔도 줄어드는 모습을 보인다.
  • 이너셔의 변화를 관찰해 최적의 클러스터 개수를 찾는 방법이 존재한다. 이를 엘보우 방법이라고 한다. 클러스터 개수를 증가시켜도 어떤 일정 부분에서부터 개수를 늘려도 밀집된 정도가 개선되지 않는다. 이 부분이 마치 팔꿈치 모양과 닮아서 이를 엘보우 방법이라 한다.
  • 해당 데이터를 이용해 이너셔를 계산해보고 시각화를 통해 확인해보자.
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()

output_22_0

3. 마치며

  • 이번 시간에는 k-평균 알고리즘을 이용해 분류를 진행해 보았다. 실제 코드를 진행하는데 있어서 클러스터 값을 3으로 지정해주었고, 위 과정을 이용해 최적의 엘보우 지점을 찾을때는 3으로 나왔다. 하지만 실제에선 클러스터 값을 알지도 못하고 어떤 타깃이 존재하는지도 알지 못하기 때문에 위의 샘플데이터만을 가지고 공부를 완료했다고 볼 수는 없다. 실제로 데이터를 다뤄보는 과정이 필요할 것 같다.

Categories:

Updated:

Leave a comment