[혼공머신] 5주차 06-1 군집 알고리즘
본 게시글은 한빛미디어의 혼자 공부하는 머신러닝+딥러닝을 바탕으로 작성되었습니다.
비지도 학습
지금까지 우리는 Supervised learning에 대해서 살펴보았었다. 다시 한번 설명하자면, supervised learning은 우리가 친절하게 컴퓨터에게 어떤 문제가 있을 때 친절하게 답을 알려주고 학습을 시키는 것이다. 그와 반면 지금부터 우리가 살펴볼 unsupervised learning은 그냥 데이터만 주고 컴퓨터에게 너가 어디 한번 이거 가지고 해봐라 하면서 답도 없이 그냥 주는 것이다. 그런데 이렇게 설명을 하면 unsupervised learning이 이해가 잘 가지 않을 수도 있다. 그렇기에 우리는 unsupervised learning의 간단한 예시를 살펴볼 것이다.
Unsupervised learning은 답을 알려주지 않는 것이라고 했었다. 그러면 어떤 것을 할 수 있을까? 바로 군집화(Clustering)가 가능하다. Clustering은 또 무엇일까? 예를 들어 과일이 있다. 그런데 과일은 한 가지만 존재하지 않는다. 사과도 있고, 바나나도 있고, 파인애플도 있다. 그럴 때 이런 여러가지 과일이 섞여 있을 경우, 비슷한 특성을 가진 과일끼리 묶게 된다면 사과, 파인애플, 바나나끼리 묶여서 나뉘게 될 것이다. 이렇게 비슷한 특성을 지닌 data끼리 묶는 것을 clustering이라고 한다.
이미지 데이터
이 clustering을 실제로 코드로 살펴보도록 하자. 일단, data를 불러오고 어떤 것이 있는지 살펴보자. 첫번째 데이터를 보니 사과가 있다. 사과처럼 안 보일 수도 있겠지만 사과다. 그리고 계산의 용이성을 위해 흑백 이미지를 준비해준 것 같다. 왜 계산의 용이성을 위해 흑백 이미지를 사용한 것인지 궁금해할 수 있다. 컬러라면 다를까? 사실 이 이미지는 하나의 그래프라고 생각해도 된다. 이미지에 대해서 알고 있다면 들어본 적 있겠지만, 이미지에는 비트맵 이미지와 벡터 이미지가 있고, 비트맵 이미지의 경우, 최소 단위인 픽셀 단위로 쪼갤 수 있어 확대할 경우 계단화 현상이 일어난다. 벡터에 대한 설명은 넘어가는 것으로 하고, 지금 우리가 아래에서 본 사과 이미지는 비트맵 이미지라고 생각하면 된다.
1
2
3
4
5
import numpy as np
import matplotlib.pyplot as plt
data = np.load('fruits_300.npy')
plt.imshow(data[0], cmap='gray')
비트맵 이미지의 경우, 최소 단위인 픽셀이 모여서 만들어지는데, 아주 작은 네모들이 모여서 하나의 이미지를 만든다고 생각하면 된다. 위의 사과 이미지도 자세히 보면 작은 네모들이 있는 것을 볼 수 있을 것이다. 이러한 네모들이 모여서 사과가 되는데, 이 네모에는 값이 부여되어 있다. 만약 네모에 할당된 값이 0이면 검은색이고, 255이면 하얀색인 형태로 픽셀의 색이 정해진다. 이것에 대해서도 간단히 설명하자면, 우리는 이전에 빛의 삼원색에 대해서 들어본 적이 있을 것이다. 일반적인 물감의 색은 더하면 더할수록 어두운 검은색이 되게 된다. 그러나 빛의 경우, 색을 더할수록 하얀색이 된다. 몰랐다면 지금 들어보면 되는 것이다. 이것처럼 여기서도 아무것도 없는 상태, 즉 빛이 없기에 0은 검은색이 되고, 값이 클수록 빛이 점점 많아지기에 밝은 색이 되는 것이다. 아래와 같이 사과 이미지의 제일 윗부분의 픽셀 첫번째 줄의 값을 출력해보면 배경이 다 낮은 값으로 검은색이 출력되지만, 사과 꼭지 부분에 해당하는 부분은 값이 높게 나와 밝은색이 출력되게 됨을 알 수 있다.
1
data[0, 0, :]
array([ 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], dtype=uint8)
하지만, 우리는 이러한 빛이나 값에 집중하는 것이 아니라, 사과라는 개체를 시각적으로 보고 싶다면 아래와 같이 색 반전을 하는 방식으로 출력을 할 수 있다. 다만, 이렇게 하면 분석을 할 때는 사과라는 개체에 해당하는 부분의 값이 낮게 배정되기에 안 좋을 수도 있다. 따라서 분석을 할 때는 원래 데이터를 사용하고, 시각적을 볼 때만 반전을 해서 보는 것이 좋은 방향일 수 있다.
1
2
plt.imshow(data[0], cmap='gray_r')
plt.show()
이제 사과가 있다는 건 확인을 했다. 다른 건 어떤 것이 있을까? 아까 말했던 파인애플과 바나나가 있다. 파인애플과 바나나를 살펴보자. 파인애플이 파인애플이 아니라 람부탄이나 솔방울처럼 보이지만 어쩔 수 없다. 우린 이걸 파인애플이라고 할 것이다.
1
2
3
4
fig, axs = plt.subplots(1, 2)
axs[0].imshow(data[100], cmap='gray_r')
axs[1].imshow(data[200], cmap='gray_r')
plt.show()
그런데 우리는 한 가지 궁금한 것이 있다. 이렇게 사과, 파인애플, 바나나가 있는데 이미지로 있다. 이걸 어떻게 clustering을 할 수 있을까? 일반적으로 가장 쉽게 생각할 수 있는 방법은 대표값을 만들어서 비교하는 것이다. 우리는 여기서 mean을 사용해볼 것이다. 그런데 여기서 또 다른 의문이 생길 수 있다. 이미지의 mean을 구할 수 있을까? 가능하다. 아까 우리는 이 이미지가 하나의 plot이라고 했었다. 각 픽셀들이 값을 가지고 있기에 그냥 단순히 모든 픽셀들의 값의 mean을 구하는 것이다.
이미지 데이터 평균 분석
사과, 파인애플, 바나나의 이미지가 총 $n=100$개씩 있고, 각 이미지의 mean을 구해서 histogram을 그려 비교해보면 아래와 같이 표현할 수 있다. 아까 우리가 이미지를 통해서 살펴봤듯이 어두운 부분은 값이 작고, 밝은 부분은 값이 크다 그런데 우리가 아까 파인애플, 바나나 이미지를 시각화해서 봤을 때는 배경이 밝은 색이었으나, 이는 반전을 시킨 값으로, 원래 data에서는 배경에 검은색이라고 해야 할 것이다. 따라서 실제로 값이 높은 부분은 실제 과일의 부분이라고 할 수 있다. 사실 이러한 배경 지식 없이 단순히 과일이 그려진 부분이 높은 값을 가진다고 생각하면 된다. 아래의 plot을 보면 간단하게 한 가지를 알 수 있다. 바나나의 값은 일반적으로 낮은 값을 가진다는 것이다. 이는 그림을 통해서도 쉽게 알 수 있다. 바나나가 사과나 파인애플에 비해서 현저하게 차지하는 면적이 작기 때문에 값의 mean이 낮을수밖에 없다. 하지만, 사과와 파인애플이 문제이다. 크기도 비슷해 보이기에 mean에 큰 차이가 없게 된다. 따라서 이러한 경우, 단순히 mean을 통해 둘을 나누기에는 어려움이 있을 것이다.
1
2
3
4
5
6
fruits = ["apple", "pineapple", "banana"]
for fruit in fruits:
plt.hist(np.mean(globals()[fruit], axis=1), alpha=0.8, label=fruit)
plt.legend()
plt.show()
그러면 우리는 평균을 사용하는 또 다른 방법을 생각해볼 수 있다. 지금 사용하고 있는 이미지는 100*100 크기의 비트맵 이미지이다. 따라서 100,000개의 픽셀이 존재하게 된다. 그러면 이 100,000개의 각 픽셀별로 평균을 구해서 경향성을 찾아보는 것은 어떨까? 이를 histogram을 그려보면 아래와 같다. 확실히 아까와는 차이가 나는 것을 확인할 수 있다. 사과와 파인애플을 이정도면 충분히 구분할 수 있을 것으로 보인다.
1
2
3
4
5
6
7
fig, axs = plt.subplots(1, 3, figsize=(20, 5))
for i, fruit in enumerate(fruits):
axs[i].bar(range(10000), np.mean(globals()[fruit], axis=0))
axs[i].set_title(f"{fruit.capitalize()} Mean")
plt.show()
그런데 위와 같은 histogram을 봤을 때 차이가 난다는 것은 알겠는데, 저게 무엇을 의미하는 것인지는 잘 모를 것이다. 이것은 단순하게 말하면, 모든 이미지를 겹쳤을 때 픽셀의 값이 저렇게 나오게 된다는 것이다. 이를 시각화하면 아래와 같다.
1
2
3
4
5
6
7
8
9
fruit_means = {fruit: np.mean(globals()[fruit], axis=0).reshape(100, 100) for fruit in fruits}
fig, axs = plt.subplots(1, 3, figsize=(20, 5))
for i, fruit in enumerate(fruits):
axs[i].imshow(fruit_means[fruit], cmap='gray_r')
axs[i].set_title(f"{fruit.capitalize()} Mean")
plt.show()
이미지 클러스터링
이제 각 과일 이미지들의 특성을 살펴보았다. 그러면 이제 실제로 clustering을 진행해보자. 바나나를 가지고 할 것인데, clustering을 하는 방법을 간단히 설명하자면, 바나나의 평균과 검사 대상 이미지의 차이를 구하고 그 오차의 평균을 구한 다음, 오차의 평균을 기준으로 작은 순서대로 나열해 오차가 작은 이미지들을 고르는 것이다. 이러한 방식으로 바나나와 유사하다고 판단한 100개의 이미지를 확인해보자. 아래의 이미지를 보면 바나나를 대체로 잘 구분한 것을 확인할 수 있다. 마지막에 사과가 두 개 보이기는 하지만 100개 중에 98개를 올바르게 구분한 것이기에 생각보다 잘 구분했다는 생각이 든다. 이런 식으로 사과, 파인애플, 바나나로 나누는 것을 clustering이라고 하고, 사과, 파인애플, 바나나와 같은 하나의 그룹, 군집을 클러스터(cluster)라고 한다.
1
2
3
4
5
6
7
8
9
10
abs_diff = np.abs(data - fruit_means["banana"])
abs_mean = np.mean(abs_diff, axis=(1, 2))
apple_index = np.argsort(abs_mean)[:100]
fig, axs = plt.subplots(10, 10, figsize=(10, 10))
for i in range(10):
for j in range(10):
axs[i, j].imshow(data[apple_index[i*10+j]], cmap='gray_r')
axs[i, j].axis('off')
plt.show()
지금까지 clustering을 살펴보았는데, 처음 우리가 설명한 것이랑 약간 다른 것을 확인할 수 있다. 분명 우리는 unsupervised learning을 살펴보기로 했는데, 사과나 파인애플, 바나나의 mean을 기준으로 판명을 했으므로 이것도 일종의 정답이 있는 것이라고 할 수 있다. 따라서 정확히 말하자면 supervised learning을 해버린 것이다. 그러면 unsupervised learning은 도대체 어떻게 하는 것일까? 이것은 다음 게시글에서 K-Means 알고리즘을 통해 살펴볼 것이다.