K-평균(K-Means)에 대한 이론이 필요하신 분들은 아래 링크를 참조해주시기 바랍니다.
[인공지능][개념] K-평균(K-means) 알고리즘과 군집화(Clustering) + 이너셔(Inertia) 이해하기 : https://itstory1592.tistory.com/17
이번 글에서는 K-평균(K-means) 알고리즘을 사용하여 과일 사진을 비지도 학습으로 분류해보고
엘보우(Elbow) 방법을 통해 최적의 k값을 찾아보자.
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
우선, 과일 사진을 다운로드하기 위해서 위의 코드를 입력해준다.
파이썬에서는 !(느낌표)를 사용하고 리눅스 명령어를 입력하면 해당 명령어를 사용할 수 있다.
여기서는 웹 서버로부터 콘텐츠를 가져오는 wget 명령어를 통해 과일 사진 300장을 가져오도록 하겠다.
그럼 위와 같은 문장들이 출력되면서 과일 사진이 다운로드되는 것을 확인해볼 수 있다.
이 파일은 흑백 과일 사진으로, 사과🍎 바나나🍌 파인애플🍍 사진이 각각 100개씩 총 300장 저장된 fruits_300.npy라는 파일이다.
import numpy as np
fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1, 100*100)
가져온 fruits_300.npy 파일을 넘파이를 통해 변수로 저장한다.
저장을 하였다면, reshape() 메소드를 통해 모든 사진을 1차원으로 펼쳐 주어야 한다.
그 이유는, 다운로드 받은 사진들은 모두 100px * 100px로 이루어져 있는데,
k-means 알고리즘은 각 픽셀 하나하나를 기준으로 분류할 것이기 때문에, 이미지를 1px씩 10000개로 펼쳐주는 작업을 해야 하는 것이다.
위 코드를 그림으로 표현하면 이런식으로 나타낼 수 있다.
100px * 100px의 그림 300장이 1px * 10,000px로 펼쳐졌음을 확인할 수 있다.
from sklearn.cluster import KMeans
km = KMeans(init='random', n_clusters=3, random_state=42)
km.fit(fruits_2d)
이제 본격적으로 k-means 알고리즘을 사용하기 위해, 사이킷런의 cluster 패키지에서 KMeans 클래스를 임포트한다.
KMeans 클래스를 생성할 때, init 이라는 매개변수를 설정할 수 있는데,
기본값인 random으로 설정하면 이론에서 설명한 기본적인 K-Means 알고리즘을 사용하게 되고,
random 대신, 'k-means++'로 설정하면 K-Means++ 알고리즘을 사용할 수 있다.
(K-Means++은 다음 글에서 설명하도록 하겠다.)
실제 K-Means를 사용할 때는 몇 가지로 분류해야 하는지 알 수 없기 때문에 정확한 클러스터 개수를 알 수 없지만,
해당 예제에서는 이미지가 사과, 바나나, 파인애플 총 3가지로 이루어져 있다는 것을 알기 때문에
n_clusters를 3으로 설정하여 클러스터의 개수를 3개로 지정하였다.
그리고 훈련을 마치면, 우리가 훈련시킨 모델에 대한 정보가 출력될 것이다.
init은 설정한 대로 기본 k-means 알고리즘인 'random'으로, 클러스터의 개수는 3개로 제대로 설정되었다.
최대 반복 횟수 변수인 'max_iter'는 따로 설정하지 않아 300으로 지정되었다.
print(km.labels_)
훈련된 모델에서 데이터가 어떻게 분류되었는지 확인하고 싶다면 labels_ 변수에 접근하면 된다.
labels_에 저장된 숫자들은 총 300개의 데이터들을 결과에 따라 분류해놓은 것이다.
결과를 확인해보면, 어느 정도 잘 분류된듯하나 각 숫자 사이에 다른 숫자들이 껴있는 것을 확인할 수 있다.
각 레이블에 데이터를 몇 개씩 포함하고 있는지 확인해보자.
print(np.unique(km.labels_, return_counts=True))
넘파이의 unique() 메소드에 return_counts를 True로 설정하면,
입력한 배열에 동일한 값의 개수가 몇 개 있는지를 알 수 있다.
labels_에는 '0'이 91개, '1'이 111개, '2'가 98개 포함되어 있다.
그럼 0, 1, 2라는 숫자는 각각 무슨 과일을 의미하는 것일까?
각 레이블로 분류된 결과를 확인해보도록 하자!
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()
각 레이블에 해당하는 결과를 보기 위해서는 draw_fruits()라는 사용자 정의 함수를 만들어야 한다.
[코드 설명]
배열에 저장되어 있는 값을 10개씩 쪼개어 행(row), 열(column)을 만들고,
맷플롭립(matplotlib) 라이브러리의 pyplot에 있는 subplots() 메소드로 행열에 맞추어 이미지를 출력시킬 공간을 만든다.
그리고 subplots()을 출력함으로써 반환되는 값인 axs의 imshow()를 호출하여, 2중 for문 내에서 배열의 값을 이미지로 출력한다.
(결과적으로 위 코드는 배열을 입력받고, 배열에 있는 값을 한 줄에 10개씩 그림으로 출력해주는 함수이다.)
이제 우리가 정의한 draw_fruits() 함수를 사용하여 각 label에 들어간 과일의 이미지를 출력해보자! 🍎🍌🍍
draw_fruits(fruits[km.labels_==0])
불리언 인덱싱을 통해 label이 0에 해당하는 인덱스들만을 골라 출력해보았다.
예상대로 10개씩 잘 출력되었으며, label 0에 해당하는 이미지는 모두 91개의 사과로 분류되었었다는 것이 확인되었다.
draw_fruits(fruits[km.labels_==1])
label 1도 총 111개의 이미지로 구성된 과일들이 정상적으로 출력되었다.
label 1에 해당하는 과일 이미지에는 대부분이 파인애플이지만, 일부 사과와 바나나가 섞여 분류되었다.
모델의 정확도가 100%가 아니라는 것을 알 수 있다.
draw_fruits(fruits[km.labels_==2])
label 2에 해당하는 이미지들을 출력한 결과, 바나나 이미지로 구성된 98개의 과일 이미지가 모두 출력되었다.
제대로 분류되지 않은 바나나 2개는 label 1로 분류되었기 때문에 2개가 비는 것이다.
매우 놀랍게도 정확도가 100%는 아니지만 모델에게 정답(target)을 알려주지 않아도,
각 픽셀의 특징을 통해 어느 정도 과일들이 분류되었다.
이런 방식의 학습이 바로 비지도 학습(Unsupervised Learning)이다.
그렇다면 클러스터의 중심!
센트로이드(Centroid)는 과연 각각의 과일 이미지를 어떤 식으로 평균 짓고 있을까?
draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)
학습된 모델에서 변수 cluster_centers_ 에 접근하면 센트로이드 값을 얻을 수 있다.
이 값에 접근하여 3개의 클러스터의 평균에 위치한 센트로이드를 다시 100px * 100px의 형태로 변형(reshape)하여 출력해보자!
🍎 첫 번째 사진은 단숨에 사과임을 알아볼 수 있지만,
🍍🍌 나머지 두 과일들은 우리가 상식적으로 알고 있는 파인애플과 바나나의 형태가 아니다.
하지만 신비롭게도 인공지능은 저 세 가지 이미지를 과일들의 평균적인 형태라고 인식하고 분류해낸 것이다.
우리는 이번 예제에서 데이터가 사과, 바나나, 파인애플로만 이루어져 있다는 사실을 이미 알고 있었다.
그러나 실제 K-평균 알고리즘을 사용할 때는 무작위 데이터 중에서 분류를 수행해야 하는 일이 비일비재하다.
그렇다면, 총 몇 개의 클러스터로 모델을 구성해야 할까?
다시 말해 몇 가지의 종류로 데이터를 분류해야 하는 것일까?!
우리는 이전 이론에서 엘보우(Elbow)라는 최적의 k값을 찾는 방법에 대해 공부하였다.
특정 k값에서 이너셔(inertia)가 급격하게 줄어들어 그래프로 표현했을 때, 사람의 팔꿈치처럼 꺾여 보이는 경우가 있다고 했는데
이때의 k값이 최적의 클러스터 개수가 되는 것이다.
직접 이 과일 데이터를 바탕으로 최적의 클러스터 개수를 찾아보도록 하자.
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값에서 이너셔가 몇인지 알아낼 수 있도록 클러스터 값을 2부터 6까지 바꿔가면서 훈련을 시킨 후,
훈련을 통해 얻은 이너셔를 이전에 생성한 inertia 배열에 담는다.
그래프를 보면 확연하게는 아니지만 k가 3.0에서 꺾인다는 것을 알 수 있다.
데이터가 사과, 바나나, 파인애플로 총 3가지였으므로, 엘보우 방법을 통해 적절한 클러스터 개수를 얻었다고 할 수 있다.
전체 소스 코드 :
(이해가 다소 힘들거나, 틀린 부분이 있다면 댓글 부탁드리겠습니다! 😊)
💖댓글과 공감은 큰 힘이 됩니다!💖