본문 바로가기

AI/기계학습(Machine Learning)

[기계학습/ML]14. 비지도학습 - K-평균(K-Means)

728x90
반응형

Pupbani는 샘플의 이름 즉 타깃값을 모르는 상태에서의 분류를 하는 방법을 고민하다가 방법을 찾아 냈다.

바로 K-평균 군집 알고리즘이다.

  • 평균값을 자동으로 찾아준다.
  • 평균값은 군집의 중심에 존재한다.
  • 이를 "클러스터 중심(Cluster Center)" 또는 "센트로이드(Centroid)" 라고 부른다.

K-평균을 사용해서 비지도학습 모델을 만들어보자.

 

K-Means

동작 방식은 다음과 같다.

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

출처 : https://www.learnbymarketing.com/methods/k-means-clustering/

이제 K-Means 모델을 직접 만들어보자.

먼저 300개의 과일 데이터를 준비하여 모델에 사용할 수 있게 전처리를 해준다.

import numpy as np

fruits = np.load('fruits_300.npy')
# K-Means 모델은 2차원 배열 형태의 데이터가 필요함.
# (샘플 개수, 너비, 높이) -> (샘플 개수, 너비 x 높이)
fruits_2d = fruits.reshape(-1, 100*100)

K-Means 모델을 생성하고 학습한다.

  • sklearn.cluster 모듈아래 KMeans 클래스에 구현되어 있다.
from sklearn.cluster import KMeans

km = KMeans(n_clusters=3, random_state=42) # 클러스터 3개
km.fit(fruits_2d) # 과일 데이터 군집별 분류
print(km.labels_) # 분류된 결과, labels_ 속성에 저장됨

분류된 결과를 좀더 살펴보자.

numpy의 unique 메서드를 통해 각 레이블 별 샘플 개수를 확인해보자.

result = np.unique(km.labels_, return_counts=True)
print(f"0번 샘플 개수:  {result[1][0]}개")
print(f"1번 샘플 개수:  {result[1][1]}개")
print(f"2번 샘플 개수:  {result[1][2]}개")

이제 각 클러스터별로 어떤 과일들을 모았는지 이미지로 확인해보자.

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

0번 레이블 - 111개

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

1번 레이블 - 98개

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

2번 레이블 - 91

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

클러스터 중심

K-Means로 분류한 클러스터의 중심(센트로이드)은 cluster_centers_ 속성에 저장되어 있다.

중심을 이미지 형태로 출력하려면 2차원에서 3차원으로 변환해줘야 한다.

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

훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환해주는 transform() 메서드를 지원함.

2차원 데이터를 넣어야한다.

반환된 배열은 [[0번 레이블 까지의 거리, 1번 레이블 까지의 거리, 2번 레이블 까지의 거리]] 형태 이다.

print(km.transform(fruits_2d[100:101]))

배열 값에 따르면 이 데이터는 0번 레이블에 속한다. 

진짜 그러한가 알아보자.

print(km.predict(fruits_2d[100:101]))
draw_fruits(fruits[100:101])

0번 레이블에 속하는 것을 알 수 있다.

 

앞에서 말했듯 K-Means 알고리즘이 클러스터 중심을 찾는 과정은 반복적으로 클러스터 중심을 옮기면서 최적의 중심을 찾는다.

이 옮기는 횟수는 n_iter_ 속성에 저장된다.

print(km.n_iter_)

최적의 K 찾기

K-Means의 단점 중 하나는 클러스터 개수를 사전에 지정해야 한다는 것이다.

실전에는 몇개의 클러스터가 있는지 알 수 없다.

어떻게 하면 적절한 클러스터 개수를 찾을 수 있을까?

대표적인 방법은 엘보우(elbow) 방법이다.

 

엘보우 방법은 클러스터 개수를 늘려나가면서 이너셔의 변화를 관찰하여 최적을 클러스터 개수를 찾는 방법이다.

  • 이너셔(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.title("최적의 클러스터 찾기")
plt.xlabel("클러스터 개수")
plt.ylabel("이너셔셔")
plt.show()

그래프에서 보면 꺾이는 지점이 뚜렷하진 않지만 K가 3일 때 기울기가 조금 바뀌었다.

최적의 K개수는 3이라고 볼 수 있다.

 

Pupbani는 K-Means를 통해 과일을 분류하는 비지도 학습모델을 만들었다.

이제 마켓팅 팀에 전달하러 가보자.

728x90
반응형