머신러닝 + 딥러닝

[혼공머신] CHAPTER 06-2 k-평균

2021. 10. 27. 03:05
728x90

이전에는 사과, 파인애플, 바나나 사진임을 이미 알고 있어서 각 과일의 평균을 구할 수 있었지만 실제 비지도 학습에서는 사진에 어떤 과일이 있는지 알지 못한다

 

이런 경우 평균값을 어떻게 구할 수 있을까?

 

 

k-평균 군집 알고리즘(클러스터 중심 or 센트로이드)

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

 

 

!wget https://bit.ly/fruits_300_data -O fruits_300.npy #데이터 다운로드

import numpy as np
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1, 100*100)

 

(샘플 개수, 너비, 높이) 크기의 3차원 배열을 (샘플 개수, 너비x높이) 크기의 2차원 배열로 변경

 

사이킷런의 k-평균 알고리즘은 sklearn.cluster 모듈 아래 KMeans 클래스에 구현되어 있다.

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

print(km.labels_)

km.fit에 타깃이 없다 -> 과대적합, 과소적합 X, 샘플들을 종류별로 나누는 것 O

 

n_clusters : 형성할 클러스터의 개수를 지정(하이퍼파라미터, default = 8)

n_iter : 10번 수행한다는 게 지정되어있다. 10번을 수행하고 그 중 제일 괜찮은 결과를 출력해준다.

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]

n_clusters를 3으로 지정해서 0, 1, 2 3개가 만들어졌다

 

print(np.unique(km.labels_, return_counts=True))

레이블 0,1,2로 모은 샘플의 개수

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

첫 번째 클러스터(레이블 0)가 91개의 샘플을 모으고, 두 번째 클러스터(레이블 1)가 98개의 샘플을 모으고, 세 번째 클러스터(레이블 2)가 111개의 샘플을 모았다.

 

각 클러스터가 어떤 이미지를 나타냈는지 출력하기 위해 draw_fruits()를 만들어본다

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

- (샘플 개수, 너비, 높이)의 3차원 배열을 입력받아 가로로 10개씩 이미지를 출력한다. 샘플 개수에 따라 행과 열의 개수를 계산하고 figsize를 지정한다. figsize는 ratio 매개변수에 비례하여 커진다.(기본값 = 1)

 

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

km.labels_==0으로 쓰면 km.labels_배열에서 값이 0인 위치는 True, 그 외는 False가 된다.

불리언 인덱싱을 적용해 True인 위치의 원소만 모두 추출

>>km.labels_==0이 사과인지, 파인애플인지, 바나나인지 모른다

바나나는 labels_==1, 파인애플은 labels_==2

>> 레이블이 2인 클러스터는 파인애플에 사과 9개와 바나나 2개가 섞여 있다. k-평균 알고리즘이 샘플들을 완벽하게 구별해내지 못했다.

 

KMeans 클래스가 최종적으로 찾은 클러스터 중심은 cluster_centers_ 속성에 저장되어 있다. 

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

이 배열은 fruits_2d 샘플의 클러스터 중심이기 때문에 이미지로 출력하려면 100x100크기의 2차원 배열로 바꿔야 한다.

 

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

인덱스가 100인 샘플에 transform() 메서드를 적용한다. (1, 10000) 크기의 배열을 전달한다

원본 이미지를 다른 형태의 데이터셋으로 변환해준다

 

새로운 샘플이 있을 때, 센트로이드 중심1(c1), 센트로이드 중심2(c2), 센트로이드 중심3(c3)까지의 거리로 특성을 바꿔준다. 샘플의 특성을 c1, c2, c3까지의 거리로 바꿔준다. 샘플의 특성을 센트로이드의 중심 거리까지로 변환해준다.

 

KMeans에 중심값까지의 거리를 마치 특성으로 생각할 수 있어서 tranform() 메서드가 있다.

[[5267.70439881 8837.37750892 3393.8136117 ]]

 

하나의 샘플을 전달했기 때문에 반환된 배열의 크기가 (1, 클러스터 개수)인 2차원 배열이다.

첫 번째 클러스터(레이블 0), 두 번째 클러스터(레이블 1), 세 번째 클러스터(레이블 2) 순으로 세 번째 클러스터까지의 거리가 3393.8로 가장 작은 것을 보아 이 샘플은 레이블 2에 속해있음을 알 수 있다.

 

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

가장 가까운 클러스터 중심을 예측 찾아주는 predict() 메서드를 제공한다

[2]

이는 레이블 2로 예측했음을 알 수 있고,

draw_fruits(fruits[100:101])

레이블 2는

파인애플임을 확인할 수 있다.

print(km.n_iter_)

n_iter_로 3번 반복만에 알고리즘이 종료되었음을 알 수 있다.

3

 

 

실전에서는 클러스터의 개수도 알 수 없는데 우리는 n_clusters를 어떻게 지정해야할까?

> 엘보우 방법

 

엘보우(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.xlabel('k')
plt.ylabel('inertia')
plt.show()

n_clusters 즉, k값을 작은 값에서 큰 값으로(2에서 7로) 바꿔가면서 군집을 여러 개로 나누어본다

클러스터 이너셔 값을 계산한다

k=3에서 그래프의 기울기 변화가 조금 보인 것을 볼 수 있다. 엘보우 지점보다 클러스터 개수가 많아지면 이너셔의 변화가 줄어들면서 군집 효과도 줄어든다.

728x90