[chap 6-2] k-평균
❗ 문제점 ❗
앞 장에서는 앞에서부터 100개는 사과, 100개는 파인애플, 100개는 바나나로 순서가 결정되어 있었다. 하지만 진짜 비지도 학습에서는 어떤 과일이 어디에 있는지 모름!
💡 해결책 💡
k-평균 군집 알고리즘을 사용
🔮 k-평균 군집 알고리즘
k-평균 군집 알고리즘은 어떤 과일이 들어있는지 알지 못하는 상태에서 평균값을 자동으로 찾아준다. 이때, 평균값 = 클러스터 중심 = 센트로이드
📍 진행 방식
- 무작위로 k개의 클러스터 중심을 정함
- 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정
- 2번 과정에서 만들어진 클러스터에 속한 샘플의 평균값으로 클러스터 중심 변경
- 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가기
1️⃣ 데이터 준비하기
import numpy as np
fruits = np.load("fruits_300.npy")
fruits_2d = fruits.reshape(-1, 100*100)
- 넘파이 배열(fruits) : (샘플 개수, 너비, 높이)
- fruits_2d : (샘플 개수, 너비 * 높이)
📍 KMeans 클래스
from sklearn.cluster import KMeans
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d)
print(km.labels_)
>>>
[2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 0 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 0 0 2 2 2 2 2 2 2 2 0 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 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 0 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 0 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 : 클러스터 개수 지정
- fit() : 학습 / 비지도 학습이므로 타깃 데이터를 사용하지 X
- labes_ 속성 : 각 샘플이 어떤 레이블에 해당되는지 나타낸다.
fruits_300.npy 자체는 사과 -> 파인애플 -> 바나나 순으로 정렬이 되어있지만 결국 임이의 클러스터 중심을 만들어서 거기서 가까운 샘플들을 묶어 클러스터를 형성하기 때문에 딱히 섞을 필요는 없는 듯?
random_state를 맞췄는데,,, 왜 다르게 나오는지는 모르겠음,,, 😢 여튼 그냥 이 상태로 진행(책 결과와는 차이가 있을 수 O)
그리고 레이블 숫자만 보고 어떤 그림인지 알 수 없으므로 그림을 그려봐야 알 수 있다.
2️⃣ 그림 그리기! ㅎ
print(np.unique(km.labels_, return_counts=True)) # 왜 거꾸로 나오지?
>>> (array([0, 1, 2], dtype=int32), array([111, 98, 91]))
위에서 말했던 것 처럼,,, 레이블 값 자체가 0번 레이블과 2번 레이블 위치가 바뀌었으므로 책 결과와 0번, 2번 레이블 개수 차이가 존재.
- 출력값 해석
- 레이블 0 : 샘플 111개 모음
- 레이블 1 : 샘플 98개 모음
- 레이블 2 : 샘플 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:
axs[i, j].imshow(arr[i*10 + j], cmap="gray_r")
axs[i, j].axis("off")
plt.show()
- n : 샘플의 개수
사진을 10행을 그릴 것이기 때문에
rows = int(np.ceil(n/10))
근데 사진 개수를 모르니까 정확히 몇 행이 나올지 몰라cols = n if rows < 2 else 10
아 맞다……..파이썬은 데이터형이 없었지…….? 맞,,,?맞지! 어휴 ㅉ 행이 1개면 열의 개수는 샘플 개수이므로 n을 리턴, 아니라면 10개씩 그릴 것이므로 10을 리턴.fig, axs = plt.subplots(rows, cols, figsize=(cols*ratio, rows*ratio), squeeze=False)
- 위에서 구한 (rows, cols) 만큼 이미지를 출력
- figsize 에서 ratio를 곱한 이유는 그냥 출력 비율과 관련. ratio가 커지면 figsize도 커짐!
- squeeze 의 역할은 subplots() 에서 반환되는 Axis 객체가 Array 형태로 반환되는데 이 배열을 2차원 형태로 반환할건지 1차원 형태로 반환할건지를 squeeze가 결정, 디폴트가 True이고, 2차원 배열 리턴이므로 false를 통해서 1차원 배열로 리턴함.
- 2중 for문 : 각 위치에 맞게 이미지 출력
- axis(“off”) : 축 안그림
draw_fruits(fruits[km.labels_==0])
⏬
draw_fruits(fruits[km.labels_==1])
⏬
draw_fruits(fruits[km.labels_==2])
⏬
0 레이블을 가져다 쓰려면 km.labels_==0이라고 하고, 그러면 0은 true가 되고, 나머지 값은 false가 됨 -> 불리언 인덱싱
📍 클러스터 중심
clustercenters 속성에 저장되어 있다. 그 중심값을 이용해서 이미지를 100 * 100 크기로 출력
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)
print(km.transform(fruits_2d[100:101]))
print(km.predict(fruits_2d[100:101]))
>>>
[[3393.8136117 8837.37750892 5267.70439881]]
[0]
- transform() 메소드 : StandardScaler 클래스처럼 특성 변환 도구임 -> 얘도 2차원 배열로 전달해야되기 때문에 100행을 가져온다는 것을 이렇게 표현 = ([100:101])
결과값이 가장 작은값이 가까이 있다는 뜻이므로 이 샘플은 레이블 0번째에 있는 값임을 유추할 수 있다.
- niter : 최적의 클러스터를 찾기 위해 반복한 횟수
📍 최적의 클러스터 개수 찾기
대표적인 방법 엘보우
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()
- inertia : 클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱 합
꺾이는 지점 이후 클러스터 개수를 늘려도 밀집 정도가 크게 개선되지 않음. 즉, 꺾이는 지점이 최적의 K 개수
🌗 정리
비지도 학습을 위해서 KMeans 알고리즘을 사용했고, fit()을 통해서 나온 결과는 무슨 레이블에 속해있는지가 나온다. 이때 클러스터의 개수를 n_cluster를 통해서 정의하는데 이때 엘보우가 사용이 되었다. -> fit() 의 결과가 무슨 의미인지 몰라서 이미지를 그리는 과정이 필요했다.
댓글남기기