고객이 마트에서 사고싶은 과일 사진을 보내면 그 중 많이 요청하는 과일을 판매 품목으로 선정하려고 한다. 또 1위로 선정된 과일 사진을 보낸 고객 중 몇명을 뽑아 이벤트 당첨자로 선정할 것이다.
그런데 고객이 보낸 사진을 사람이 하나 하나 모두 분류하기는 어려워 보인다. 그렇다고 미리 과일 분류기를 학습하기에는 고객들이 어떤 과일 사진을 보낼지 알 수 없으니 곤란하다. 사진에 대한 정답(타깃)을 알지 못할 때는 어떻게 해야 할까?
비지도학습이란?
타깃(예측하려는 클래스)가 없을 때 사용하는 머신러닝 알고리즘
바로 사람이 가르쳐 주지 않아도 데이터에 있는 무언가를 학습하는 것이다.
#리눅스 쉘 명령어를 이용하여 데이터 다운받기.
!wget https://bit.ly/fruits_300_data -O fruits_300.npy
import numpy as np
import matplotlib.pyplot as plt
fruits = np.load('fruits_300.npy')
print(fruits.shape)
#출력값: (300, 100, 100)
이 배열의 첫 번째 차원(300)은 샘플의 개수를 의미하고, 두 번째 차원(100)은 이미지 높이, 세 번째 차원(100)은 이미지 너비이다. 즉 하나의 이미지 크기가 100 x 100인 샘플이 총 300개 있는 데이터셋인 것이다.
첫 번째 이미지의 첫 번째 행을 출력해보자.
print(fruits[0, 0, :])
#출력값: [ 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1
2 2 2 2 2 2 1 1 1 1 1 1 1 1 2 3 2 1
2 1 1 1 1 2 1 3 2 1 3 1 4 1 2 5 5 5
19 148 192 117 28 1 1 2 1 4 1 1 3 1 1 1 1 1
2 2 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]
첫 번째 행에 있는 픽셀 100개를 출력한 것이다. 픽셀 하나는 0~255의 정숫값을 가진다.
matplotlib의 imshow()함수를 이용하여 첫 번째 픽셀의 이미지를 그려보자.
plt.imshow(fruits[0], cmap='gray')
plt.show()
첫 번째 이미지는 사과 같다. 0에 가까울 수록 검게 나타나고, 255에 가까워질 수록 밝게 나타나는 것같다.
하지만 보통 흑백 샘플 이미지는 바탕이 밝고 물체가 짙다. 그런데 왜 여기에서는 반대일까? 사실 이 흑백 이미지는 사진으로 찍은 이미지를 넘파이 배열로 변환할 때 반전시킨 것이다.
왜 이렇게 반전시켰을까? 우리의 관심은 바탕이 아니라 사과이다. 컴퓨터는 255에 가까운 값을 집중한다. 그러므로 우리의 관심인 사과를 255에 가까운 값으로, 바탕을 0에 가까운 값으로 반전시킨다.
컴퓨터는 왜 255에 가까운 값에 집중을 할까?
알고리즘이 어떤 출력을 만들기 위해 곱셈, 덧셈을 한다. 하지만 픽셀값이 0이면 출력도 0이 되어 의미가 없다. 픽셀값이 높으면 출력값도 커지기 때문에 의미를 부여하기 좋다.
바나나와 파인애플 이미지도 출력해보자.
fig, axes = plt.subplots(1, 2)
axes[0].imshow(fruits[100], cmap='gray_r')
axes[1].imshow(fruits[200], cmap='gray_r')
plt.show()
사용하기 쉽게 fruits 데이터를 사과, 파인애플, 바나나로 각각 나누어보자. 넘파이 배열을 나눌 때 100 X 100 이미지를 펼쳐서 길이가 10,000인 1차원 배열로 만들어보자. 이렇게 펼치면 이미지로 출력하기는 어렵겠지만, 배열을 계산할 때 편리하다.
apple = fruits[:100].reshape(-1, 100*100)
pineapple = fruits[100:200].reshape(-1, 100*100)
banana = fruits[200:300].reshape(-1, 100*100)
print(apple.shape)
#출력값: (100, 100000)
이제 샘플의 픽셀 평균값을 계산해보자. 우리가 100 X 100 이미지를 가로로 나열했다. 그러므로 axis=1로 설정하여 평균을 계산해보자.
print(apple.mean(axis=1))
#출력값: [ 88.3346 97.9249 87.3709 98.3703 92.8705 82.6439 94.4244 95.5999
90.681 81.6226 87.0578 95.0745 93.8416 87.017 97.5078 87.2019
88.9827 100.9158 92.7823 100.9184 104.9854 88.674 99.5643 97.2495
94.1179 92.1935 95.1671 93.3322 102.8967 94.6695 90.5285 89.0744
97.7641 97.2938 100.7564 90.5236 100.2542 85.8452 96.4615 97.1492
90.711 102.3193 87.1629 89.8751 86.7327 86.3991 95.2865 89.1709
96.8163 91.6604 96.1065 99.6829 94.9718 87.4812 89.2596 89.5268
93.799 97.3983 87.151 97.825 103.22 94.4239 83.6657 83.5159
102.8453 87.0379 91.2742 100.4848 93.8388 90.8568 97.4616 97.5022
82.446 87.1789 96.9206 90.3135 90.565 97.6538 98.0919 93.6252
87.3867 84.7073 89.1135 86.7646 88.7301 86.643 96.7323 97.2604
81.9424 87.1687 97.2066 83.4712 95.9781 91.8096 98.4086 100.7823
101.556 100.7027 91.6098 88.8976]
사과 샘플 100개에 대한 픽셀 평균값을 계산했다.
이제 히스토그램(histogram)을 그려보면 평균값이 어떻게 분포되어 있는지 한눈에 확인할 수 있다.
plt.hist(np.mean(apple, axis=1), alpha=0.8, label='apple')
plt.hist(np.mean(pineapple, axis=1), alpha=0.8, label='pineapple')
plt.hist(np.mean(banana, axis=1), alpha=0.8, label='banana')
plt.legend()
plt.show()
히스토그램을 보면 바나나 이미지의 픽셀 평균값은 40 아래에 집중되어 있다. 사과와 파인애플은 90~100 사이에 많이 모여있다. 이 그림을 보면 바나나는 픽셀 평균값만으로 사과나 파인애플과 확실하게 구분이 된다. 바나나는 사진에서 차지하는 영역이 작기 때문에 평균값이 작은 것이다.
반면 사과와 파인애플은 많이 겹쳐있어서 픽셀의 평균값만으로는 구분하기 쉽지 않다. 다른 방법이 없을까?
샘플의 평균값이 아닌, 픽셀별 평균값을 비교하면 어떨까? 전체 샘플에 대해 각 픽셀 하나의 평균을 계산하는 것이다. 3개의 과일은 모양이 다르므로 픽셀값이 높은 위치가 조금 다를 것 같다.
픽셀별 평균을 계산하는 방법은 axis=1이 아닌, axis=0으로 지정하면 된다.
fig, axes = plt.subplots(1, 3, figsize=(20,5))
axes[0].bar(range(10000), np.mean(apple, axis=0))
axes[1].bar(range(10000), np.mean(pineapple, axis=0))
axes[2].bar(range(10000), np.mean(banana, axis=0))
plt.show()
이 픽셀의 평균값을 다시 100 X 100 크기로 바꾸어 이미지처럼 출력해보자. 이것은 평균낸 이미지를 모두 합쳐놓은 대표 이미지라 생각할 수 있다.
apple_mean = np.mean(apple, axis=0).reshape(100, 100)
pineapple_mean = np.mean(pineapple, axis=0).reshape(100, 100)
banana_mean = np.mean(banana, axis=0).reshape(100, 100)
fig, axes = plt.subplots(1, 3, figsize=(20,5))
axes[0].imshow(apple_mean, cmap='gray_r')
axes[1].imshow(pineapple_mean, cmap='gray_r')
axes[2].imshow(banana_mean, cmap='gray_r')
plt.show()
3개의 과일은 픽셀 위치에 따라 값의 크기가 차이가 난다. 이 대표 이미지와 가까운 사진을 골라낸다면 사과, 파인애플, 바나나를 구분할 수 있지 않을까?
사과 사진의 평균값인 apple_mean과 가장 가까운 사진을 골라보자. 절대값 오차(MAE)를 사용해보자.
abs_diff = np.abs(fruits - apple_mean)
#각 샘플에 대한 평균을 구하기 위하여, 축을 2, 3 모두 지정함.
abs_mean = np.mean(abs_diff, axis=(1, 2))
print(abs_mean.shape)
그 다음 이 값이 가장 작은 순서대로 100개를 골라보자. np.argsort() 함수는 작은 것에서 큰 순서대로 나열한 abs_mean 배열의 인덱스를 반환한다. 이 인덱스 중에서 처음 100개를 선택하여 10 X 10 격자로 이루어진 그래프를 그려보자.
apple_index = np.argsort(abs_mean)[:100] #절대값 오차가 작은 순대로 100개 추출
fig, axes = plt.subplots(10, 10, figsize=(10, 10))
for i in range(10):
for j in range(10):
#apple_index를 0~100까지
axes[i, j].imshow(fruits[apple_index[i*10 + j]], cmap='gray_r')
axes[i, j].axis('off')
plt.show()
100개 모두 사과인 것을 볼 수 있다.
하지만 이번에 우리는 이미 사과, 파인애플, 바나나가 있다는 것을 알고 있었다. 즉 타깃을 알고 있었단 뜻이다. 때문에 사과, 파인애플, 바나나의 사진 평균값을 계산하여 가장 가까운 과일을 찾을 수 있었다.
하지만 비지도 학습은 우리가 타깃을 모를 때 사용하는 알고리즘이라 하였다. 이 경우는 다음 장에서 설명하겠다.
정리
- 이렇게 비슷한 샘플끼리 그룹으로 모으는 작업을 군집(Clustering)이라 한다.
- 군집은 대표적인 비지도 학습 중 하나이다.
- 군집 알고리즘에서 만든 그룹을 클러스터(Cluster)이라 한다.