본문 바로가기
AI/혼공머신

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

by 뒹굴거리는프로도 2024. 11. 9.
반응형

 


이번 단원에서는 k-평균 알고리즘의 작동 방식을 이해하고, 과일 사진을 자동으로 모으는 비지도 학습 모델을 만들어본다.

https://youtu.be/SBdy0nSctRM?si=LJ1qOffRbHGHUKmf

 

 

키워드

k-평균

  • k-평균 알고리즘은 처음에 랜덤하게 클러스터 중심을 정하고 클러스터를 생성합니다. 그다음 클러스터의 중심을 이동하고 다시 클러스터를 생성하는 것을 반복하여 최적의 클러스터를 만드는 알고리즘입니다.

클러스터 중심

  • k-평균 알고리즘이 만든 클러스터에 속한 샘플의 특성 평균값입니다. 센트로이드centriod라고도 불립니다. 가장 가까운 클러스터 중심을 샘플의 또 다른 특성으로 사용하거나, 새로운 샘플에 대한 예측으로 활용할 수 있습니다.

엘보우 방법

  • 최적의 클러스터 개수를 정하는 방법 중 하나입니다. 이너셔는 클러스터 중심과 샘플 사이 거리의 제곱 합입니다. 클러스터 개수에 따라 이너셔 감소가 꺾이는 지점이 적절한 클러스터 개수 k가 될 수 있습니다. 이 그래프의 모양을 따서 엘보우 방법이라고 부릅니다.


핵심 패키지와 함수

scikit-learn

  • KMeans
    • k-평균 알고리즘 클래스이다.
    • n_clusters에는 클러스터 개수를 지정한다. 기본값은 8이다.
    • 처음에 랜덤하게 센트로이드를 초기화하기 때문에, 여러 번 반복하여 이너셔를 기준으로 가장 좋은 결과를 선택한다. n_init은 이 반복 횟수를 지정한다. 기본값은 10이었으나, 사이킷런 1.4 버전에서는 'auto'로 변경될 예정이다.
    • max_iter는 k-평균 알고리즘의 한 번 실행에서 최적의 센트로이드를 찾기 위해 반복할 수 있는 최대 횟수이다. 기본값은 200이다.


 

06-2 단원 내용

시작하기 전에

  • 06-1 단원의 경우와 다르게, 데이터에 어떤 과일이 들어있는지 전혀 알지 못할 때 어떻게 평균값을 구할 수 있을까? 바로 k-평균(k-means) 군집 알고리즘이 평균값을 자동으로 찾아준다. 이 평균값이 클러스터의 중심에 위치하기 때문에, 클러스터 중심(cluster center) 또는 센트로이드(centroid)라고 부른다.
  • 이번 단원에서 k-평균 알고리즘의 작동 방식을 이해하고 사과, 파인애플, 바나나를 구분하는 비지도 학습 모델을 만들어본다.

k-평균 알고리즘 소개

  • k-평균 알고리즘의 작동 방식은 다음과 같다.
    • 1. 무작위로 k개의 클러스터 중심을 정한다.
    • 2. 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
    • 3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다.
    • 4. 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다.
  • k-평균 알고리즘은 처음에는 랜덤하게 클러스터 중심을 선택하고, 점차 가장 가까운 샘플의 중심으로 이동하는 비교적 간단한 알고리즘이다.

KMeans 클래스

  • wget 명령으로 데이터를 다운로드
!wget https://bit.ly/fruits_300_data -0 fruits_300.npy

 

  • 3차원 크기의 넘파이 배열을 2차원 크기의 배열로 변경
import numpy as np 
fruits = np.load('fruits_300.npy') 
fruits_2d = fruits.reshape(-1, 100*100)
 

 

  • 사이킷런 KMeans 클래스 사용하여 군집
from sklearn.cluster import KMeans 

#n_clusters는 클러스터 개수를 지정하는 매개변수임. 
#클러스터 개수를 3으로 지정
km = KMeans(n_clusters=3, random_state=42) 

#비지도 학습임으로 fit() 메서드에 타깃 데이터를 사용하지 않음
km.fit(fruits_2d)

 

  • 결과 배열 확인하기
#군집된 결과는 KMeans 클래스 객체의 labels_속성에 저장됨. 
#labels_ 배열의 길이는 샘플 개수와 같음.
print(km.labels_)
 

 

  • 각 레이블의 샘플 개수 확인하기
#레이블 0,1,2로 모은 샘플 개수 확인하기
print( np.unique(km.labels_, return_counts=True) ) 

"""
결과 : (array([0,1,2], dtype=int32), array([111, 98, 91])) 
"""
 

  • 각 클러스터가 어떤 이미지를 나타내는지 간단한 함수 만들어보기
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()

 

  • 위 함수를 사용해 km.lables_ == 0 이미지 출력하기
draw_fruits(fruits[km.lables_ ==0])
 

 

클러스터 중심

  • kMeans 클래스가 최종적으로 찾은 클러스터 중심은 cluster_centers_ 속성에 저장됨
  • KMeans 클래스는 훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환해 주는 transform() 메소드 존재
#인덱스가 100인 샘플에 transform() 적용해보기

print( km.transform(fruits_2d[100:101]) ) 
"""
[[3393 8837 5267]]이 출력된다. 
하나의 샘플을 전달했기 때문에 반환된 배열은 2차원 배열이다. 
배열의 각 원소값은 각 클러스터(각 레이블)이다.
"""
 

  • KMeans 클래스는 가장 가까운 클러스터 중심을 예측 클래스로 출력하는 predict() 메서드를 제공한다.
print( km.predict(fruits_2d[100:101]) ) 
#[0]이 출력된다. 이것은 레이블 0을 뜻한다. 
#레이블 0은 파인애플이었으므로 이 샘플은 파인애플일 것이다.
draw_fruits(fruits[100:101])
 
  • 알고리즘이 반복한 횟수는 KMeans 클래스의 n_iter_ 속성에 저장된다.
print( km.n_iter_ )
 

 

최적의 k 찾기

  • k-평균 알고리즘의 단점 중 하나는 클러스터 개수를 사전에 지정해야 한다는 것이다. 사실 군집 알고리즘에서 적절한 k 값을 찾기 위한 완벽한 방법은 없다. 여기서는 적절한 클러스터 개수를 찾기 위한 대표적인 방법인 엘보우elbow방법에 대해 알아본다.
  • 이너셔inertia는 클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱합을 말한다. 클러스터에 속한 샘플이 얼마나 가깝게 모여있는지를 나타내는 값으로 생각할 수 있다. 일반적으로 클러스터 개수가 늘어나면 클러스터 개개의 크기는 줄어들기 때문에 이너셔도 줄어든다. 엘보우 방법은 클러스터 개수를 늘려가면서 이너셔의 변화를 관찰하여 최적의 클러스터 개수를 찾는 방법이다.
  • 클러스터 개수를 증가시키며 이너셔를 그래프로 그리면 감소하는 속도가 꺾이는 지점이 있다. 이 지점 이후에 클러스터 개수를 늘려도 클러스터에 잘 밀집된 정도가 크게 개선되지 않는다. 이 지점이 마치 팔꿈치 모양이어서 엘보우 방법이라고 부른다.

  • 과일 데이터셋을 사용해 이너셔를 계산해 보자.
inertia = [] 

for k in range(2, 7) : 
   km = KMeans(n_clusters=k, n_init='auto', 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()

 

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

과일을 자동으로 분류하기


 

[출처. 구글 이미지]

반응형