k-평균 알고리즘 소개
k-평균 알고리즘은 처음에 랜덤하게 클러스터 중심을 정하고 클러스터를 만든다. 그다음 클러스터의 중심을 이동하고 다시 클러스터를 만드는 식으로 반복해서 최적의 클러스터를 구성하는 알고리즘이다.
클러스터 중심은 k-평균 알고리즘이 만든 클러스터에 속한 샘플의 특성 평균값이다. 센트로이드라고 부르며 가장 가까운 클러스터 중심을 샘플의 또 다른 특성으로 사용하거나 새로운 샘플에 대한 예측으로 활용할 수 있다.
알고리즘의 작동 방식은 아래와 같다.
- 무작위로 k개의 클러스터 중심을 정한다.
- 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
- 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다.
- 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다.
KMeans 클래스
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
k-평균 모델을 훈련하기 위해 크기의3차원 배열을 2차원 배열로 변경한다.
import numpy as np
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1, 100 * 100)
n_clusters 매개변수를 이용해 클러스터 개수를 지정할 수 있다. 비지도학습이기 때문에 fit()메서드를 타깃 데이터를 사용하지 않고 수행한다.
from sklearn.cluster import KMeans
km = KMeans(n_clusters = 3, random_state = 42)
km.fit(fruits_2d)
labels_속성에는 군집된 결과가 저장된다. 배열의 길이는 샘플 개수와 같고 이 배열은 각 샘플이 어떤 레이블에 해당되는지를 나타낸다.
print(km.labels_)
unique()를 통해 각 클러스터에 해당하는 샘플 수를 출력할 수 있다.
print(np.unique(km.labels_, return_counts = True))
유틸리티 함수 draw_fruits()를 만들어 그림으로 출력해보자.
draw_fruits() 함수는 3차원 배열(샘플 개수, 너비, 높이)를 입력받아 가로로 10개씩 이미지를 출력한다.
2중 for 반복문을 사용하여 첫 번째 행을 따라 이미지를 그리고 두 번째 행의 이미지를 그리는 식으로 진행한다.
import matplotlib.pyplot as plt
def draw_fruits(arr, ratio = 1):
n = len(arr) # 샘플 개수
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:
axs[i, j].imshow(arr[i * 10 + j], cmap = 'gray_r')
axs[i, j].axis('off')
plt.show()
km.labels_ == 0과 같이 쓰면 km.labels_ 배열에서 0인 위치는 True, 그 외는 모두 False가 되고, True인 위치의 원소만 모두 추출하도록 사용 가능하다.
draw_fruits(fruits[km.labels_== 0])
레이블 0에 해당하는 원소를 출력해본 결과 다수의 파인애플과 9개의 사과, 2개의 바나나가 혼합되어 있음을 확인할 수 있다.
k-평균 알고리즘이 이 샘플들을 완벽하게 구별해내지 못했지만 훈련 데이터에 타깃 레이블을 제공하지 않았음에도 스스로 비슷한 샘플들을 잘 모았다고 판단할 수 있다.
클러스터 중심
KMeans 클래스가 최종적으로 찾은 클러스터 중심은 cluster_centers_ 속성에 조정된다.
이 배열은 fruits_2d 샘플의 클러스터 중심이기 때문에 이미지로 출력하려면 100 * 100크기의 2차원 배열로 바꿔야 한다.
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio = 3)
KMeans 의 transform() 메서드는 훈련 데이터 샘플에서 클러스터 중심까지 거리로 변환해주는 역할을 한다.
transform() 메서드는 fit() 과 마찬가지로 2차원 배열을 기대하기 때문에 슬라이싱 연산자를 사용해서 (1, 10000) 크기의 배열을 전달한다.
print(km.transform(fruits_2d[100:101]))
결과값을 통해 첫 번째 클러스터(레이블 0)의 거리가 가장 작다는 것을 확인할 수 있다.
predict() 메서드를 활용하여 가장 가까운 클러스터 중심을 예측 클래스로 출력해보자.
print(km.predict(fruits_2d[100:101]))
레이블 0을 예측하였다. 클러스터 중심을 그려보았을 때 레이블 0은 파인애플이었으므로 이 샘플은 파인애플일 것이라는 예측이 가능하다.
draw_fruits(fruits[100:101])
k-평균 알고리즘은 반복적으로 클러스터 중심을 옮기면서 최적의 클러스터를 찾는다.
n_iter_ 속성에는 알고리즘이 반복(이동)한 횟수가 저장된다.
print(km.n_iter_)
최적의 k 찾기
엘보우 방법은 최적의 클러스터 개수를 정하는 방법 중 하나이다. 이너셔는 클러스터 중심과 샘플 사이 거리의 제곱 합을 말한다. 클러스터 개수에 따라 이너셔 감소가 꺾이는 지점이 적절한 클러스터 개수 k가 될 수 있다.
클러스터 개수 k를 2~6까지 바꿔가며 KMeans 클래스를 5번 훈련하고 fit()메서드로 모델을 훈련한 뒤 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()
위 그래프에서는 꺾이는 지점이 뚜렷하지는 않지만, k = 3에서 그래프의 기울기가 조금 변경된다. 엘보우 지점보다 클러스터 개수가 많아지면 이너셔의 변화가 줄어들면서 군집 효과도 줄어든다. 이 그래프에서는 이런 지점이 명확하지는 않지만 존재하는 것을 확인할 수 있다.
'AI > 혼자공부하는머신러닝딥러닝' 카테고리의 다른 글
[DL] 07-1 인공 신경망 (0) | 2024.02.21 |
---|---|
[ML] 06-3 주성분 분석 (1) | 2024.01.26 |
[ML] 06-1 군집 알고리즘 (1) | 2024.01.23 |
[ML] 05-3 트리의 앙상블 (1) | 2024.01.23 |
[ML] 05-2 교차 검증과 그리드 서치 (1) | 2024.01.22 |