Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

cb

[혼공머신] Chapter 06 - 비지도 학습 본문

ai - study

[혼공머신] Chapter 06 - 비지도 학습

10011001101 2024. 2. 26. 18:00

본 게시물은 <혼자서 공부하는 머신러닝+딥러닝>의 Chapter 05 - 트리 알고리즘을 보고 정리한 글입니다. 원본 코드는 책의 저자인 박해선님의 깃허브 코드를 참고하시길 바랍니다.

 

GitHub - rickiepark/hg-mldl: <혼자 공부하는 머신러닝+딥러닝>의 코드 저장소입니다.

<혼자 공부하는 머신러닝+딥러닝>의 코드 저장소입니다. Contribute to rickiepark/hg-mldl development by creating an account on GitHub.

github.com


비지도 학습

타깃이 없을 때 사용하는 머신러닝 알고리즘을 비지도 학습(unsupervised learning)이라고 한다. 예를 들어, 타깃을 모르는 사진을 종류별로 분리하는 상황을 비지도 학습이라고 할 수 있다.

 

과일 데이터를 통해 비지도 학습을 알아보자. 아래의 코드를 실행하여 데이터를 준비할 수 있다.

# 과일 사진 데이터 준비하기
!wget https://bit.ly/fruits_300_data -O fruits_300.npy

# 필요한 패키지 임포트
import numpy as np
import matplotlib.pyplot as plt

# npy 파일 로드
fruits = np.load('fruits_300.npy')

 

fruits의 크기를 출력해보면 (300,100,100)이라는 출력을 얻을 수 있다. 각각의 숫자는 샘플의 개수, 이미지 높이, 이미지 너비를 의미한다.

 

# 픽셀값 출력
print(fruits[0, 0, :])

 

픽셀값을 출력해 보면 이미지의 픽셀은 0에서 255까지의 정숫값을 가지는 것을 볼 수 있다.

저 픽셀에서의 높은 값은 흰 바탕으로 나타난다. 그렇기 때문에 이미지를 그릴 때에는 gray를 반전시킨 gray_r을 사용하여 아래와 같이 이미지를 그릴 수 있다.

# 이미지 그리기
plt.imshow(fruits[0], cmap='gray_r')
plt.show()

 

 

사과 뿐만 아니라 다른 클래스의 이미지도 다음과 같이 그려줄 수 있다.

# 이미지 그리기
fig, axs = plt.subplots(1, 2)
axs[0].imshow(fruits[100], cmap='gray_r')
axs[1].imshow(fruits[200], cmap='gray_r')
plt.show()


픽셀값 분석하기

 

먼저, 각 데이터를 1차원 배열로 바꾼 후에 샘플의 픽셀 평균값을 비교해 보자.

아래의 코드를 사용하여 히스토그램을 나타내면 쉽게 분포를 확인할 수 있다.

# 1차원 배열로 변경
apple = fruits[0:100].reshape(-1, 100*100)
pineapple = fruits[100:200].reshape(-1, 100*100)
banana = fruits[200:300].reshape(-1, 100*100)

# 히스토그램 그리기
plt.hist(np.mean(apple, axis=1), alpha=0.8)
plt.hist(np.mean(pineapple, axis=1), alpha=0.8)
plt.hist(np.mean(banana, axis=1), alpha=0.8)
plt.legend(['apple', 'pineapple', 'banana'])
plt.show()

 

이를 통해 바나나는 샘플의 픽셀 평균값이 40 근처에 많이 분포되어 있음을 알 수 있다. 하지만 사과나 파인애플은 꽤 많이 겹쳐져 보이는 것을 알 수 있다. 두 과일 모두 대체로 형태가 동그랗고 사진에서 차지하는 비율도 비슷하기 때문이다.

 

이는 픽셀의 평균값으로 구분한 결과이지만, 픽셀별 평균값을 비교해보면 다른 결과를 얻을 수 있다. 샘플의 평균값이 아니라, 전체 샘플에 대해 각 픽셀의 평균을 계산하는 것이다.

# 막대 그래프 그리기
fig, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].bar(range(10000), np.mean(apple, axis=0))
axs[1].bar(range(10000), np.mean(pineapple, axis=0))
axs[2].bar(range(10000), np.mean(banana, axis=0))
plt.show()

 

순서대로 사과, 파인애플, 바나나이다. 세 그래프는 값이 높은 구간이 다르다. 

이를 100*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, axs = plt.subplots(1, 3, figsize=(20, 5))
axs[0].imshow(apple_mean, cmap='gray_r')
axs[1].imshow(pineapple_mean, cmap='gray_r')
axs[2].imshow(banana_mean, cmap='gray_r')
plt.show()

 

세 과일은 픽셀의 위치에 따라 값의 크기가 차이 난다는 사실을 알 수 있다. 대표 이미지와 가까운 사진을 골라낸다면 세 과일을 구분할 수 있을 것이다.


평균값과 가까운 사진 고르기

fruits 배열에 있는 모든 샘플에서 사과의 평균을 뺀 절댓값의 평균을 계산해 보자. 그런 다음, 오차가 가장 작은 샘플 100개를 골라 그래프를 그려볼 것이다. 사과의 평균과 가장 차이가 적게 나는 사진을 출력하면 대부분 사과가 출력될 것이다.

# 각 샘플의 오차 평균
abs_diff = np.abs(fruits - apple_mean)
abs_mean = np.mean(abs_diff, axis=(1,2))
print(abs_mean.shape)

# 오차가 작은 100개 이미지 그리기
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(fruits[apple_index[i*10 + j]], cmap='gray_r')
        axs[i, j].axis('off')
plt.show()

 

 

이렇게 비슷한 샘플끼리 그룹으로 모으는 작업을 군집(clustering)이라고 한다. 이는 대표적인 비지도 작업 중 하나이고, 군집 알고리즘에서 만든 그룹을 클러스터(cluster)라고 부른다.


k-평균

실제 비지도 학습에서는 k-평균(k-means) 군집 알고리즘을 통해 평균값을 자동으로 찾아 준다. 평균값은 클러스터의 중심에 위치하기 떄문에 클러스터 중심(cluster center) 또는 센트로이드(centroid)라고 부른다.

 

k-평균 알고리즘의 작동 방식은 다음과 같다.

1.  무작위로 k개의 클러스터 중심을 정한다.
2. 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다.
3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경한다.
4. 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복한다.

 

k-평균 알고리즘은 처음에는 랜덤하게 클러스터 중심을 선택하고, 점차 가까운 샘플의 중심으로 이동하는 간단한 알고리즘이다.

 

k-평균 모델을 훈련하기 위해 (샘플 개수, 너비, 높이) 크기의 3차원 배열을 (샘플 개수, 너비*높이)의 2차원 배열로 변경한다.

이후 사이킷런의 KMeans 클래스를 불러와 클러스터 개수를 3으로 지정해 준 후, 모델을 훈련해 준다.

# 데이터 2차원 변경
fruits_2d = fruits.reshape(-1, 100*100)

# KMeans 라이브러리 임포트
from sklearn.cluster import KMeans

# 클래스 객체 생성, 훈련
km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d)

 

이는 비지도 학습이므로 타깃 데이터가 필요하지 않다.

 

아래의 코드를 사용하면 머신러닝 모델이 각 데이터를 어떤 클래스로 분류했는지 확인할 수 있다.

print(km.labels_)

 

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()
    
    
# 0 클래스에 분류된 과일 그리기
draw_fruits(fruits[km.labels_==0])
# 1 클래스에 분류된 과일 그리기
draw_fruits(fruits[km.labels_==1])
# 2 클래스에 분류된 과일 그리기
draw_fruits(fruits[km.labels_==2])

label==0
label==1
label==2

 

레이블이 0인 클러스터에 파인애플, 사과, 바나나가 섞여 있는 것을 확인할 수 있다. 이는 K-평균 알고리즘이 샘플들을 완벽하게 구별해내지 못했다는 것을 알려준다.


최적의 k 찾기

적절한 클러스터 개수를 찾기 위한 방법 중, 엘보우(elbow)라는 방법에 대해 알아보자. 앞서 말한 것처럼 k-평균 알고리즘은 클러스터 중심과 클러스터에 속한 샘플 사이의 거리를 잴 수 있다. 이 거리의 제곱 합을 이너셔(inertia)라고 부른다. 이너셔는 클러스터에 속한 샘플이 얼마나 가깝게 모여 있는지를 나타내는 값으로 생각할 수 있다.

 

 

엘보우 방법은 클러스터 개수를 늘려 가면서 이너셔의 변화를 관찰하고, 최적의 클러스터 개수를 찾는 방법이다. 이때 그래프를 살펴보면 이너셔 값이 감소하는 속도가 꺾이는 지점이 있다. 이 지점부터는 클러스터의 개수를 늘려도 클러스터에 잘 밀집된 정도가 크게 개선되지 않는다.

 

위에서 사용했던 과일 데이터셋을 사용해 이너셔를 계산해 보자.

inertia = []
for k in range(2, 7):
    km = KMeans(n_clusters=k, n_init='auto', 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()

 

이 그래프는 k=3에서 그래프의 기울기가 조금 바뀐 것을 볼 수 있고, 최적의 k는 3이라고 결론을 내릴 수 있다.


주성분 분석

과일 사진의 경우 10000개의 픽셀이 있기 때문에 10000개의 특성을 가진다고도 볼 수 있다. 이 특성을 차원(dimension)이라고 하는데, 이 차원을 줄인다면 저장 공간을 크게 절약할 수 있다.

 

이를 위해 비지도 학습 작업에는 차원 축소(dimmensionality reduction)가 있다. 

 

주성분 분석(Principal Component Analysis)은 데이터에 있는 분산이 큰 방향을 찾는 것이다. 아래 사진에서 해당 직선이 데이터의 분포를 가장 잘 나타내면서도 데이터의 분산을 크게 한다면, 이 벡터를 주성분(principal component)이라고 한다.