4 분 소요

❗ 문제점 ❗

앞 장에서는 앞에서부터 100개는 사과, 100개는 파인애플, 100개는 바나나로 순서가 결정되어 있었다. 하지만 진짜 비지도 학습에서는 어떤 과일이 어디에 있는지 모름!

💡 해결책 💡

k-평균 군집 알고리즘을 사용

🔮 k-평균 군집 알고리즘

k-평균 군집 알고리즘은 어떤 과일이 들어있는지 알지 못하는 상태에서 평균값을 자동으로 찾아준다. 이때, 평균값 = 클러스터 중심 = 센트로이드

📍 진행 방식

  1. 무작위로 k개의 클러스터 중심을 정함
  2. 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정
  3. 2번 과정에서 만들어진 클러스터에 속한 샘플의 평균값으로 클러스터 중심 변경
  4. 클러스터 중심에 변화가 없을 때까지 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])


download1

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


download2

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


download3

0 레이블을 가져다 쓰려면 km.labels_==0이라고 하고, 그러면 0은 true가 되고, 나머지 값은 false가 됨 -> 불리언 인덱싱

📍 클러스터 중심

clustercenters 속성에 저장되어 있다. 그 중심값을 이용해서 이미지를 100 * 100 크기로 출력

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

download1

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 : 클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱 합

download1

꺾이는 지점 이후 클러스터 개수를 늘려도 밀집 정도가 크게 개선되지 않음. 즉, 꺾이는 지점이 최적의 K 개수

🌗 정리

비지도 학습을 위해서 KMeans 알고리즘을 사용했고, fit()을 통해서 나온 결과는 무슨 레이블에 속해있는지가 나온다. 이때 클러스터의 개수를 n_cluster를 통해서 정의하는데 이때 엘보우가 사용이 되었다. -> fit() 의 결과가 무슨 의미인지 몰라서 이미지를 그리는 과정이 필요했다.

댓글남기기