본문 바로가기
혼공학습단 머신러닝+딥러닝

[5주차] 비지도학습, 주성분 분석(PCA)

by 알래스카코코넛 2024. 2. 1.
반응형

Ch.6 비지도 학습

6-1. 군집 알고리즘 

자, 뜬금없지만 (1, 2, 3, 50, 51, 52, 100, 101)을 세 그룹으로 나누어 봅시다. (1,2,3), (50,51,52), (100,101)로 보통 나누죠? 세 그룹이라고 했는데 짝수와 홀수.... 이런 분은 없겠죠? 허허. 왜 저 세 그룹으로 나누어질까요? 어째 물음표 살인마가 되고 있습니다. 아마 한 자리수/ 두 자리수/ 세 자리수 처럼 숫자가 가진 크기에 따라 세 그룹으로 나누셨을 것 같습니다. 만약 그러셨다면, 이미 당신은 비지도학습의 개념을 익혔습니다. 비지도 학습이란 이전(지도 학습을 배우던 시기)에 쓰던 타깃값이 없는 데이터가 있을 때 엔지니어들이 어떻게 하면 좋을까?라는 의문에서 시작된 머신러닝 알고리즘입니다. 따라서 타깃값이 없을 때 사용하는 방법으로, 특성을 위에 했던 것처럼 수치로 변경한 후 예측이나 분류를 시행합니다. 

그 중 특성을 수치로 변경한 후 제일 처음에 했던 것처럼 비슷한 수치끼리 모아서 군집을 만드는 알고리즘이 군집 알고리즘입니다. 

군집 알고리즘이란 군집을 만드는 알고리즘!

오늘의 이론은 이걸로 끝입니다. 설명이 간단해서 좋네요. 그럼 이번에는 군집 알고리즘을 구현하는 코드를 정리해봅시다.

군집 알고리즘이 작동하려면, 크게 가지 3순서가 있습니다.

  1. 데이터를 준비하기
  2. 특성을 수치화하기
  3. 비슷한 수치들을 가진 데이터끼리 모으기(군집, 즉 클러스터 만들기)

이 순서를 따라 코드를 알아봅시다. 이번에 우리가 할 목표는 과일 이미지들이 섞여 있을 때, 같은 과일끼리 묶는 작업입니다. 쉽게 말해서, (사과1,사과2,배1,사과3,수박1,배2,수박2)가 있을 때, 이들을 (사과1, 사과2, 사과3), (배1, 배2), (수박1, 수박2)로 클러스터링하는 것이 목표입니다. 

1. 데이터를 준비해봅시다. 이번에는 kaggle에 사용된 과일 이미지 데이터셋을 사용하겠습니다. 

https://www.kaggle.com/datasets/moltean/fruits 에 해당 데이터가 어떻게 생겼는지를 볼 수 있습니다. 귀찮으신 여러분을 위해 이미지를 슬쩍 들고왔습니다.

 

Fruits-360

A dataset with 90380 images of 131 fruits and vegetables

www.kaggle.com

대충 요런 데이터입니다.

#데이터 다운로드 
!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)

print(fruits[0, 0, :])
''' 참고로 이 점 3개로 주석 표시입니다. 여러 줄을 주석 처리할 때 용이하죠.
[  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]'''
   
plt.imshow(fruits[0], cmap='gray')  #fruits 배열의 [0]번 요소를 보고 흑백으로(cmap='gray') 싶다
plt.show()  #보여달라

아 fruits[0]이 이렇게 생겼구나!

데이터를 plt.show()로 확인하니 잘 들고 온 것 같습니다. 이제 다음 단계로 넘어갑니다.

2. 특성을 수치화하기

#사과, 파인애플, 바나나 데이터를 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)

print(apple.mean(axis=1))  #사과 데이터의 픽셀 평균값 계산 (특성을 수치화)
''' 다시 등장한 점3개 주석 
[ 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]'''
 
#과일의 종류 별로 히스토그램을 그려서 시각화해보자! 
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()

바나나는 다른 과일과 겹치는 범위가 없이 자기주장이 강한데, 사과와 파인애플은 겹치는 범위가 많아서 뚜렷히 구별하기가 어렵네요. 파인애플과 애플이 비슷하기는 합니다.

그러면 이 단점을 해결하기 위해 특성을 각 샘플이 아니라, 픽셀이 차지하는 값의 평균으로 바꿔보겠습니다. 파인애플은 세로로 길고, 사과는 둥근 점을 이용해서 구분해본다는 뜻입니다. 

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()

각각 자기주장들이 뚜렷해서 구분하기가 더 쉬워졌습니다. 이제 각각의 평균값과 가장 가까운 사진들을 모아서 군집을 만들면 군집 알고리즘의 끝입니다. 

3. 비슷한 수치들을 가진 데이터끼리 모으기(군집, 즉 클러스터 만들기)

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) #바나나의 픽셀 평균 

#전체 배열(fruits)에서 사과이 픽셀 평균(apple_mean)을 뺀 것의 절댓값을 abs_diff로 선언하겠다.(=편차의 절댓값)
abs_diff = np.abs(fruits - apple_mean) 
abs_mean = np.mean(abs_diff, axis=(1,2))  #abs_diff의 평균을 배열로 만들기 

#군집 생성(apple_mean과 오차가 가장 작은 샘플 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()

후일담: 이 이미지 로드하는데 티스토리가 3번 다운되었습니다... 왜지?

사과 100개를 잘 찾았네요! 역시 머신러닝은 똑똑합니다. 이제 군집 알고리즘이 어느 정도 마스터되었습니다.

6-2. k-평균 

 이번에는 비지도학습의 대표적인 예시 k-평균(= k-means)에 대해 알아봅시다. k-평균이 무슨 뜻일까요? 대한민국 평균이 아닙니다. 1주차에 했던 k-최근접 알고리즘을 기억하나요? 주변 k개의 요소들을 특징으로 예측 혹은 분류를 수행하는 알고리즘이었습니다. k-평균도 비슷합니다. k-평균 알고리즘이란, 주어진 데이터를 k개의 클러스터로 묶는 알고리즘입니다. 머신러닝에서 k는 무언가의 개수라고 생각하면 맞을 가능성이 높습니다. (1, 2, 3, 50, 51, 52, 100, 101)을 세 그룹으로 나누어 보는 것은 k-평균 알고리즘 중 k=3인 경우라고 생각하면 되겠네요.

 

 k-평균의 작동 방법은 어떻게 되는 걸까요? 이게 사실 기본미션입니다. 기본미션 침투력이 좋네요. 

 

오늘도 찌그러진 손글씨입니다. 하지만 알아 볼 수 있다면 되는거 아닐까요? 아무튼 작동방식은 이렇습니다. 그런데 여기서 최적의 k가 뭘까요? 당연히 샘플이 어떤 종류로 이루어져 있는지 아는 숫자요!라고 하셨다면, 비지도 학습에서는 통하지 않습니다. 비지도 학습이란, 그 '종류'에 해당하는 숫자를 모를 때 사용하는 방법이기 때문입니다. 그렇다고 냅다 찍기에는 윗부분처럼 파인애플과 사과를 구별 못해서 PPAP 아저씨가 또 아른거릴 것입니다. 이 문제를 해결하려고 우리의 똑똑한 머신러닝 엔지니어들은 새로운 방법을 고안했습니다. 

 

(클러스터 중심과 클러스터에 속한 샘플 사이의 거리)² 을 이너셔(inertia)라고 합니다. 판타지 소설 나라 이름같네요. 이런 이너셔는 보통 클러스터 개수에 따라 달라지는데, 특히 이너셔가 급격히 달라지는 지점이 존재합니다. 

이 그래프를 보면 k=3일 때 이너셔가 급격히 달라집니다. 그럼 저 부분의 k가 최적의 k입니다. 쉽죠? 이렇게 급격히 꺾이는 모습이 팔꿈치 같다고 해서, 이렇게 이너셔를 이용한 최적의 k를 찾는 방법을 엘보우(elbow, 팔꿈치) 방법이라고 합니다. 

6-3. 주성분 분석 

머신러닝을 진행하다 보면, 분류나 예측에 별로 필요 없는 특성이 있기도 합니다. 예를 들어, '20년 뒤 평균 인구 수'를 예측하는데 '현재 연필의 개수' 같은 특성은 없어도 상관이 없겠죠. 반대로, '최근 5년 동안 발생한 이민자 수'는 필요할 것 같습니다. 이런 식으로 데이터를 가장 잘 나타내는 일부 특성을 선택하여 데이터 크기를 줄이고 지도학습 모델의 성능을 향상시키는 방법을 '차원 축소'라고 합니다. 

 

예시를 하나 더 볼까요?

이건 제가 했던 머신러닝 프로젝트의 일부를 발췌한 것입니다. 전문이 궁금하시다면 여기로... 이 표를 바탕으로 봤을 때, 가장 중요한 특성은 뭘까요? covid_infection입니다. 그리고 bed, nurs, nurs_assist, ambulance는 확실히 없어도 상관없는 특성 같습니다. 따라서 가장 중요한 특성은 강조하고, 없어도 되는 특성은 컴퓨터 자원을 위해 버리면 됩니다. 이제 차원 축소가 무엇인지 감이 좀 잡힙니다. 

 

이 차원 축소 중 대표적인 알고리즘이 주성분 분석(PCA)입니다. 주성분 분석은 데이터에서 가장 분산이 큰 방향을 찾는 방법입니다. 사실 잘 모르겠으면 그냥 차원 축소에서 많이 사용하는 방법이구나~만 아시면 됩니다. 더 전문적인 설명은 교수님이라는 박사 출신 설명자가 있으니 여쭤봅시다. 이론적으로 모든 특성은 주성분이 될 가능성이 있습니다. 코드...를 보려 했으나 이 부분은 저자님의 화려하고 상세한 설명을 같이 보는게 좋을 것 같습니다. 모두 책을 사서 직접 얼마나 뛰어난지 눈으로 봅시다. 근데 티스토리도 눈으로 보는거긴 한데 아무튼. 이번 파트는 이론만 정리했습니다!

 

주성분 분석의 장점은 원본 데이터로 복구가 쉽습니다. 또, 데이터셋의 크기가 줄어들고 시각화하기도 좋습니다. 마지막으로 차원 축소의 특성 상 알고리즘의 성능을 높이거나, 훈련 속도를 빠르게 만드는 데도 용이합니다. 실제로 머신러닝 프로그램을 돌리다가 CPU나 GPU가 터지면 주성분 분석을 도입하는 것도 좋습니다. 

 

마지막으로 선택 미션 하고 마칩니다!

반응형