머신러닝(AI학습 35)

이유진·2024년 7월 8일

--17.비지도 학습.ipynb--

비지도 학습

Unsupervised Learning

target값 (레이블) 없는 머신러닝 알고리즘

과일 사진들을 '종류별'로 모을 수 있을까?

사진에 대한 정답(target, label)은 없다!!

데이터 준비

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os

base_path = r'/content/drive/MyDrive/dataset'

file_path = os.path.join(base_path, 'fruits_300.npy')

fruits = np.load(file_path)

fruits.shape

(샘플 갯수, height, width)

( 300, 100, 100)

사과, 바나나, 파인애플 을 담고 있는 grayscale 이미지

넘파이 저장포맷 npy 형태

사과, 바나나, 파인애플 이 각각 100개씩 들어있다

처음 100개는 사과, 그 다름 100개는 파인애플, 마지막 100개는 바나나

각 이미지 크기는 100 x 100

데이터 출처

https://www.kaggle.com/datasets/moltean/fruits

fruits[0]

첫번째 이미지의 첫번째 행

fruits[0,0,:]

plt.imshow(fruits[0], cmap='gray')
plt.show()

"""
일반적으로 머신러닝에 학습하는 이미지는 반전된 형태.
배경이 검정색(0)에 가깝고 물체가 흰색(255)에 가까운 반전된 형태.

왜?
=> 관심대상이 바탕이 아니라 물체(사과)이기 때문.
'알고리즘' 수많은 덧셈, 곱셈 연산한다. 픽셀값이 0이면 출력도 0이되어 의미 상실됨.
관심대상인 물체에 높은 픽셀값을 부여하여 의미있는 값을 도출하기 위함.
"""
None

시각화시 이를 반전하여 보여줄 수 있다.

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

픽셀값 분석

fruits 를 사과, 파인애플, 바나나로 각각 나누어 보자

apple = fruits[0:100].reshape(-1, 100100)
pineapple = fruits[100:200].reshape(-1, 100
100)
banana = fruits[200:300].reshape(-1, 100*100)

apple.shape

픽셀의 평균값

apple.mean(axis=1) # 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 아래에 집중되어 있습니다.
사과와 파인애플은 90~100 사이에 많이 모여있네요.
이 그림을 보면 바나나는 픽셀 평균값만으로 사과나 파인애플과 확실히 구분됩니다.
바나나는 사진에서 차지하는 영역이 작기 때문에 평균값이 작습니다.

반면, 사과와 파인애플은 많이 겹쳐 있어서 픽셀 값많으로는 구분하기 쉽지 않습니다.
사과나 파인애플은 대체로 형태가 동그랗고 사진에서 차지하는 크기도 비슷하기 때문입니다.
"""
None

좀 더 나은 방법 없을까?

=> 샘플의 평균값이 아니라 픽셀별 평균값을 비교해보면?

np.mean(apple, axis=0)

np.mean(apple, axis=0).shape

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 x 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 배열에 있는 모든 샘플에서 사과의 평균값을 뺀 절대값의 평균 계산

apple_mean

abs_diff = np.abs(fruits - apple_mean)

abs_diff.shape

abs_mean = np.mean(abs_diff, axis=(1,2))
abs_mean.shape # abs_mean 는 각 샘플의 오차평균

abs_mean

여기서! 위 값이 가장 작은 순서대로 100개를 골라보자

apple_mean과 오차가 가장 작은샘플 100개를 고르는 셈.

np.argsort(abs_mean) # 오름차순으로 나열한 abs_mean 배열의 '인덱스'를 리턴한다!!

apple_index = np.argsort(abs_mean)[:100]
apple_index

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') # axis('off') 좌표축 제거. (사진만 보겠다.)

plt.show()

"""
비슷한 샘플끼리 그룹으로 모으는 작업을 '군집 Clustering'이라 합니다.

'군집'은 대표적인 '비지도 학습' 중 하나.
군집 알고리즘에서 만든 그룹을 '클러스터 (cluster)' 라고 한다.
"""
None

"""
하지만! 우리는 이미 사과, 파인애플, 바나나 가 있다는 것을 알고 있었습니다.
즉 타겟 갓을 알고 있었기 때문에 사과, 파인애플, 바나나의 평균값을 계산해서 가장 가까운 과일을
찾을 수 있었습니다.
실제 비지도 학습에서는 타겟 값을 모르기 때문에 이처럼 이처럼 샘플의 평균값을 미리 구할 수 없습니다.

타깃값을 모르면서 어떻게 세 과일의 평균값을 찾을 수 있을까요?
"""
None

K-means (K-평균) 알고리즘

k-평균 (k-means) 군집 알고리즘을 사용하면 '평균값'을 자동으로 찾아줍니다.

'평균값'은 클러스터의 중심에 위치하기 때문에

'클러스터 중심(cluster center)' 또는 '센트로이드(centroid) 라고도 한다

k-평균 알고리즘의 작동방식

1. 무작위로 k개의 클러스터 중심을 정한다.

2. 각 샘플에서 가장 가까운 클러스터 중심을 찾아 해당 클러스터의 샘플로 지정한다

3. 클러스터에 속한 샘플의 평균값으로 클러스터 중심을 변경

4. 클러스터 중심에 변화가 없을 때까지 2번으로 돌아가 반복함.

KMeans

모델 훈련을 위해

(샘플개수, 높이, 너비) -> (샘플개수, 높이 x 너비) 2차원으로 변환

fruits_2d = fruits.reshape(-1, 100 * 100)

fruits_2d.shape

from sklearn.cluster import KMeans

n_clusters : 클러스터의 개수 지정

km = KMeans(n_clusters=3, random_state=42)
km.fit(fruits_2d) # 비지도 학습 이므로 fit()에 target데이터를 사용하지 않는다.

군집된 결과 : labels_ (Array 배열)

이 배열은 각 샘플이 어떤 레이블에 해당되는지 나타냄.

ncluster 으로 지정했기 때문에 labels 배열의 값은 0, 1, 2 값을 가진다.

km.labels_

0, 1, 2 로 모은 샘플의 개수 확인

np.unique(km.labels_, return_counts=True)

각 클러스터가 어떤 이미지들을 모았는지 시각화를 위한 함수

draw_fruits 함수는

arr (샘플개수, 높이, 너비) 의 3차원 배열을 입력받아 10개씩 이미지를 출력합니다

ratio 는 그림그리는 figsize 크기 배율

def draw_fruits(arr, ratio=1) :
n = len(arr) # n: 샘플개수

샘플 개수에 따라 행과 열의 개수 계산, figsize 지정.

rows = int(np.ceil(n/10))

행이 1개이면 열 개수는 샘플개수. 그렇지 않으면 열 개수는 10개.

cols = n if rows < 2 else 10

fig, axs = plt.subplots(rows, cols,
figsize=(colsratio, rowsratio),
squeeze=False # subplot 결과가 2차원으로 나올텐데 이것을 (2, )로 바꿔버린다. False는 차원압축 하지 않겠다는 뜻.
)

이미지 그리기

for i in range(rows) :
for j in range(cols) :
if i 10 + j < n :
axs[i, j].imshow(arr[i
10 + j], cmap='gray_r')
axs[i, j].axis('off')

plt.show()

label이 0인 과일사진 그리기

fruits[km.labels_ == 0]

drawfruits(fruits[km.labels == 0])

drawfruits(fruits[km.labels==1])

drawfruits(fruits[km.labels==2])

클러스터 중심

  • clustercenters

km.clustercenters

km.clustercenters.shape

↑ fruits_2d 샘플의 클러스터 중심이다.

이미지로 출력하려면 100 x 100으로 바꿔야 한다.

drawfruits(km.cluster_centers.reshape(-1, 100, 100),ratio=3)

  • transform(), predict()

transform() : 훈련데이터 샘플에서 클러스터 중심까지 거리로 변환해 준다.

인덱스 100인 샘플을 transform 적용해보자

fruits_2d[100].shape

km.transform(fruits_2d[100]) <- 에러

km.transform(fruits_2d[100:101])

"""
↑ 하나의 샘플을 전달했기 때문에 리턴된 배열은 크기가 (1, 클러스터 개수)인 2차원 배열이다.

첫번째가 레이블0, 두번째가 레이블1 .....

첫번째 클러스터까지의 거리가 3393.8로 가장 작다. 이 샘플(100번째)는 레이블 0에 속한 것 같다.
"""
None

predict() : 가장 가까운 '클러스터 중심'을 예측 클래스로 출력.

km.predict(fruits_2d[100:101])

draw_fruits(fruits[100:101])

  • niter 속성

k-평균 알고리즘은 반복적으로 클러스터 중심을 옮기면서 최적의 클러스터를 찾는다.

알고리즘이 반복한 횟수는 niter에 저장됨.

km.niter

"""
n_cluster=3 으로 지정했다? <- 이미 타겟에 대한 정보를 활용한 셈이다.

실전의 데이터는 이러한 정보도 없을것이다.
과연 최적의 클러스터 개수는 어떻게 지정할 것인가?
"""
None

최적의 k 찾기

"""
사실 군집 알고리즘에서 적절한 k 값을 찾기위한 완벽한 방법은 없다.
다양한 방법은 있지만, 장단점들이 있다.

대표적인 방법인 엘보우(elbow) 방법을 소개합니다.
"""
None

inertia(이너셔)

k-평균 알고리즘은 '클러스터 중심'과 클러스터에 속한 샘플 사이의 거리를 잴 수 있다.

이 거리의 제곱 합을 이너셔(inertia)라 한다

이너셔는 클러스터에 속한 샘플이 얼마나 가깝게 모여 있는지 나타내는 값으로 활용할 수 있다.

일반적으로 클러스터 개수가 늘어나면 클러스터 개개의 크기는 줄어들기 때문에 이너셔도 줄어든다.

엘보우 방법 : 클러스터의 개수를 늘려가면서

이니셔의 변화를 관찰하여 최적의 클러스터 개수를 찾는방법

클러스터 개수를 증가시키면서 이너셔를 그래프로 그리면 감소하는 속도가 꺾이는 지점이 있다.

이 지점 부터는 클러스터 개수를 늘려도 클러스터에 잘 밀집된 정도가 크게 개선되지 않는다.

KMeans 클래스의 inertia_ 속성 : 자동으로 이너셔 계산

inertia = []
for k in range(2, 7) : # 클러스터 개수를 2~6까지 바꿔가며 5번 훈련
km = KMeans(nclusters=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()

profile
독해지자

0개의 댓글