데이터분석-머신러닝-AI/강의 정리

[혼자 공부하는 머신러닝+딥러닝] 15강. k-평균 알고리즘 작동 방식을 이해하고 비지도 학습 모델 만들기 - 이미지 평균의 자동화

bluebamus 2023. 11. 16.

https://www.youtube.com/watch?v=SBdy0nSctRM&list=PLJN246lAkhQjoU0C4v8FgtbjOIXxSs_4Q&index=15

 

 - 14강에서는 300개의 이미지 중 사과, 파인애플, 바나나가 몇개인지 알고 있었기 때문에 비지도 학습이 아니었다.

 - 아무 정보가 없는 사진 300개를 받았을 때 대표 이미지(평균값)을 자동으로 찾아주는 알고리즘 중 하나가 k-평균 알고리즘이다.

 

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

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

 2. 정의된 클러스터 중심으로 부터 가장 가까운 샘플들을 하나의 클러스터로 묶는다.

   - n_clusters로 생성할 클러스터 수를 정의할 수 있다.

 3. 각각 묶여있는 클러스터 내 샘플들로 다시 클러스터 중심(평균값)을 계산한다.

   - 클러스 중심은 클러스터 내 가장 많이 비슷한 샘플들 위치로 근접한다

 4. 변경된 클러스터 중심에서 가장 인접한 샘들끼리 다시 클러스터링한다.

 5. 클러스터 중심에 변화가 없을 때까지 이 과정을 반복한다.

   - 유사한 샘플들끼리 있다면 클러스터 중심의 위치는 변화가 없게된다.

 

 - 샘플, 너비, 높이 형태의 3차원 배열을 샘플, 너비 * 높이 형태의 2차원 배열로 만든다.

import numpy as np

fruits = np.load('fruits_300.npy')
fruits_2d = fruits.reshape(-1, 100*100) # 3차원 샘플을 2차원 형태로 변경한다

 

 - fit()으로 모델 훈련을 한다. 다만 비지도 학습이라 타겟 데이터 없이 입력 데이터만 정의한다.

from sklearn.cluster import KMeans

km = KMeans(n_clusters=3, random_state=42) # 3개의 클러스터 정의
km.fit(fruits_2d) # 입력 데이터만 정의함

 

 - 군집에 대한 결과는 .labels_ 을 이용해 확인할 수 있다.

   - 0, 1, 2의 넘버링은 자동으로 생성되기 때문에 순서에 대한 의미는 없다.

   - 각 숫자의 의미는 해당 샘플이 어떤 클러스터 중심에 속해있는지를 의미한다.

print(km.labels_)
[0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 2 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 2 2 0 0 0 0 0 0 0 0 2 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 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 1 1 1 1 1 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 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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]

 

 - 레이블 내 클러스터에 몇개의 샘플들이 존재하는지 확인해보자

   - np.unique()를 이용해 유일한 값들을 확인한다.

      - return_counts : 유니크한 값들에 포함되는 샘플 수를 보여준다.

print(np.unique(km.labels_, return_counts=True))
(array([0, 1, 2]), array([ 91,  98, 111], dtype=int64))

 

 - 클러스터 내 이미지들을 직접 그려서 확인하기

import matplotlib.pyplot as plt

def draw_fruits(arr, ratio=1): # 전달값은 arr, ratio는 figsize로 사용
    n = len(arr)    # n은 샘플 개수입니다
    # 한 줄에 10개씩 이미지를 그립니다. 샘플 개수를 10으로 나누어 전체 행 개수를 계산합니다.
    rows = int(np.ceil(n/10))
    # 행이 1개 이면 열 개수는 샘플 개수입니다. 그렇지 않으면 10개입니다.
    cols = n if rows < 2 else 10 # 기본 한줄에 10개를 그리고 2보다 작다면 한줄에 1개만 그린다.
    fig, axs = plt.subplots(rows, cols,
                            figsize=(cols*ratio, rows*ratio), squeeze=False)
    for i in range(rows): # 한 줄마다
        for j in range(cols): # 기본 10개의 샘플을 그린다.
            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])

 

 - 클러스터 중심을 활용하여 새로운 샘플 검증

   - fit() 을 이용한 훈련으로 찾은 "클러스터 중심(샘플 평균값)"은 .cluster_centers_에 저장되어 있다.

   - 3차원 배열로 바꿔 이미지로 확인을 해보면 14강에서 직접 만들어본 평균 이미지 모습과 거의 같다.

draw_fruits(km.cluster_centers_.reshape(-1, 100, 100), ratio=3)

 

 

   - k-평균의 유용한 기능으로 .transform() 메소드이다.

      - 특정 샘플의 정보를 집어 넣으면, 그 샘플과 클러스트 중심들까지의 거리로 반환을 해준다.

      - 100000개(픽셀)의 특성을 3개(거리)의 특성으로 차원을 줄여주는 변환기로 사용이 가능하다.

print(km.transform(fruits_2d[100:101]))
[[5267.70439881 8837.37750892 3393.8136117 ]]

   - 마지막 3번째 값이 제일 낮다. 가깝다는 뜻이다. 배열은 0부터 시작하기 때문에 위치는 2번째가 된다.

 

   - 이 거리를 이용해 예측 클래스를 정의해주는 .predict() 메소드가 있다.

print(km.predict(fruits_2d[100:101]))
[2]

 

 

   - 해당 샘플의 그림을 그려서 확인을 해보자

draw_fruits(fruits[100:101])

   - 클러스터 중심을 옮기면서 찾는 수행을 반복한 횟수는 n_iter_에 있다.

print(km.n_iter_)
4

 

 - 최적의 K 찾기

   - k 평균 알고리즘의 단점

      - 실제 샘플의 데이터를 모르는 경우 클러스터 개수를 몇 개로 정의해야 할지 판단하기 쉽지가 않다.

 

   - 엘보우(elbow) 방법

      1) .transform()으로 구한 거리의 제곱 합을 이너셔(inertia)라 한다.

      2) 이너셔 값이 작다는 것은 클러스터 중심에 샘플들이 잘 모여있다고 할 수 있다.

      3) 일반적으로 클러스터 개수(k)를 늘리면 더 곳곳에 센트로이드가 자리하게 되어 이너셔가 줄어든다.

 

      - 이 원리를 이용해 클러스터 개수를 바꿔가면서 이너셔 값의 변화를 확인하면 감소 속도가 꺽이는 지점이 있다.

         - 해당 위치를 최적의 k 값으로 선택한다.

         - .inertia_ : 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()

   - 그래프를 확인하면 꺽이는 지점이 있다. 이 위치보다 n_clusters를 늘려도 inertia가 더 안낮아짖는 경우도 있다.

   - 여기서는 꺽이는 지점을 최적의 k라 정의했지만, 항상 정확한 방법은 아니다.

   - 실재로 다른 지도학습이나 문제에 적용하면서 후속작업을 진행하고 그 결과를 활용해 feedback 하면서 모델을 개선하는 경우가 일반적이다.

 

 - K-평균 알고리즘을 사용하기 위한 적절한 데이터

 - k-평균 알고리즘은 클러스터 중심에 대한 샘플들의 직선거리를 이용하기에 원형에 가까운 클러스터 형태에 적합하다.

   - 데이터 클러스터 형태가 타원형으로 묶이는 경우가 있다면 잘 찾지 못한다.

 - 특성간 거리가 비슷한 스케일 내의 값들이어야 k-평균이 잘 분석한다.

   - 표준화 등으로 스케일 전처리를 해주고 사용하는 방법들을 고려해야 한다.

  - 이번 강의에서 다룬 사진 데이터의 경우 0~255 사이의 안정적인 데이터 범위로 정의되어 있어서 추가적인 전처리가 요구되지 않았다.

 

 - 배열 슬라이싱의 이해

   - a[100]은 a 배열의 101번째 요소를 반환한다.

   - a[100:101]은 a배열의 101번째 요소를 슬라이싱한 결과를 반환하는데 요소를 리스트로 감싼 결과를 반환한다.

   - 2차원으로 만들어진 fruits_2d 배열에서 슬라이싱을 하면 2차원 배열로 반환한다

   - transform 함수는 2차원 배열을 기대한다.

# 3차원 배열 생성
a = [
    [[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]]
]

# a[0]의 값 출력
print(a[0])
# 결과: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# a[0:1]의 결과 값 출력
print(a[0:1])
# 결과: [[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]

 

 -  reference : 

https://velog.io/@simon919/%ED%98%BC%EA%B3%B5%EB%A8%B8%EC%8B%A0-6-2.-k-%ED%8F%89%EA%B7%A0-8d40083s

 

[혼공머신] 6-2. k-평균

🆖사실 저번엔 제대로 된 비지도학습 아니었음.. 전달받은 이미지 300개가 어떤 과일인지 정말 모르는 상태에서, 대표 이미지(=평균값)을 구할 수 있을까?

velog.io

 

댓글