본문 바로가기
국비 교육/머신러닝, 딥러닝

[머신러닝] 비지도 학습 알고리즘 - 군집분석 : K- 평균 군집

by 육츠 2023. 11. 23.
Contents 접기

K - 평균 군집 K -means Clustering

군집 Clustering
- 데이터를 여러개의 군집으로 묶는 분석 방법을 말하며 유사한 개체들을 군집으로 그룹화하여 각 집단의 성격을 파악한다.
- 같은 군집에 속하는 데이터는 다른 군집에 속하는 데이터 보다 유사성이 높다.

K 평균 군집화
- 군집 중심점이라는 특정한 임의의 지점을 선택해 해당 중심에서 가장 가까운 포인트들을 선택하는 군집화 기법이다.
- 거리기반 알고리즘이기 때문에 속성의 개수가 너무 많으면 군집화 정확도가 떨어진다.

 

KMeans 클래스 생성자 주요 속성 

n_ cluster 군집화 개수 (군집 중심점의 개수)
init 초기에 군집 중심점의 좌표를 설정할 방식
max_iter 최대 반복 횟수, 이 횟수 이전에 모든 데이터의 중심점 이동이 없으면 종료한다.
random_state 중심 초기화 위한 난수 생성 seed값

 

KMeans 군집화 수행 완료 후 주요 속성

labels_ 각 데이터가 속한 군집 중심점 레이블(클러스터 번호)
cluster_centers_ 각 군집 중심점 좌표

 

최적의 k 란?

k 값을 늘려가면서 최적의 군집을 찾는 방법을 말한다. 
KMean 의 단점인  어떠한 k의 값이 적절한지에 대해 명확한 답이 없으므로 응집도가 완만하게 줄어드는 곳을 기점으로 군집의 수를 정해야한다.

4-5 가 최적의 k 이고 이 두 개 중에 무엇이 나은지는 분석자의 선택이다.

해당 그림은 Elbow Function (팔꿈치 함수) 라고 불린다. 

 

[실습] 최적의 K 찾기 - iris

분석하기 위한 x 값의 범위는 임의로 잡았다.

from sklearn.datasets import load_iris

x,y = load_iris(return_X_y=True)
x = x[:,2:] # 꽃잎의 길이와 넓이 데이터만 추출

해당 코드를 진행시키면 꽤나 긴 경고 메세지 창을 만나게 된다.
warning 패키지를 로드하여 경고메세지를 무시하도록 해놓는다.

import warnings
warnings.filterwarnings(action='ignore')

 

KMeans 클래스의 inertia 속성 사용

각 데이터에 할당된 클러스터의 중심까지의 제곱 거리 합계

from sklearn.cluster import KMeans
import matplotlib.pyplot as plt

inertia_arr = []

# 군집의 개수를 늘려가며 
k_range = range(1,10)

for k in k_range:
    kmeans = KMeans(n_clusters=k, random_state= 10)
    kmeans.fit(x) # 학습
    inertia_arr.append(kmeans.inertia_)

# Elbow function 그리기
plt.plot(k_range, inertia_arr, marker = 'o')
plt.xlabel('Number of Clusters')
plt.ylabel('Inertia')
plt.show()

# 품종이 3개 이므로 군집에서 적절한 k 의 값 = 3

iris 데이터는 앞에서 분석함으로서 알고 있지만 종속 변수가 3가지로 이루어져 있다.
그러므로 이 함수에서 최적의 k는 3인 것을 알 수 있다.

 

K-Means Clustering 하기

군집화된 값을 체크

import pandas as pd 

kmeans = KMeans(n_clusters = 3, random_state= 10)
kmeans.fit(x)

# 어느 군집에 있는지 군집 번호를 가지고 있다.
cluster_num = kmeans.labels_
df = pd.DataFrame(x, columns = ['꽃잎의 길이','꽃잎의 넓이'])
df['class'] = y # 정답
df['cluster'] = cluster_num
df.head()

 

시각화

plt.subplots 옵션을 사용하여 그래프를 두 가지로 분할하여 보게 해주었다.

fig, axes = plt.subplots(1,2,figsize = (16,8)) # 1행 2열의 그림

# 실제 정딥 그래프
axes[0].scatter(x[:,0],x[:,1], c = y, s = 100)
axes[0].set_title('Actual', fontsize = 18)

# 군집 분석 통해 예측한 결과 그래프
axes[1].scatter(x[:,0],x[:,1], c = cluster_num, s = 100)
axes[1].set_title('Predicted', fontsize = 18)

plt.show()

 

군집 평가

군집은 비지도학습 특성상 어떠한 지표라도 확실하게 성능을 측정하기 어렵다. 
이러한 군집화의 성능을 평가하는 대표적인 방법으로 실루엣 분석이 있다.

실루엣 분석 silhouette Analysis

- 실루엣 계수 기반
두 군집간의 거리가 얼마나 효율적으로 분리되어 있는지를 나타낸다.
효율적인 분리란 다른 군집과의 거리는 떨어져있고, 동일 군집끼리의 데이터는 서로 가깝게 잘 뭉쳐있다는 것을 의미한다.

s(o) = b(o) - a(o) / max{a(o),b(o)}
s(o) = 데이터 포인트 o의 실루엣 계수 

a(o): o 와 o 가 속한 클러스터의 다른 모든 데이터 포인터 사이의 평균 거리  (사진에서 파란색 군집)
b(o): o 에서부터 o가 속하지 않은 모든 군집 중 가장 가까운 군집과의 평균 거리 (파란색 군집과 빨간색 군집간의 거리)
max{a(o),b(o)} : 두 군집 간의 거리값을 정규화하기 위함이다. 

 

x 축: 실루엣 계수 - y축: 해당 군집에 속해있는 데이터의 개수
점선 : 전체 평균 실루엣 계수 값 (적절하게 데이터들과 겹쳐야한다.)

 

silhouette_samples() 를 이용하여 모든 개별 데이터 별 실루엣 계수 산출하였다
kmeans (: KMeans() 를 통해 평균 군집을 학습한 모델).labels_ : 군집된 번호

from sklearn.metrics import silhouette_samples, silhouette_score
import pandas as pd


iris = load_iris()
df = pd.DataFrame(iris.data, columns= iris.feature_names)
df['cluster'] = kmeans.labels_ # 학습이 된 각 데이터의 군집 번호

# 실루엣 계수
# iris 모든 개별 데이터에 대한 실루엣 계수 산출
score_samples = silhouette_samples(iris.data, kmeans.labels_)
df['silhouette coef'] = score_samples

# 모든 데이터의 평균 실루엣 계수 산출
avg_score = silhouette_score(iris.data, kmeans.labels_)

print(f'실루엣 계수: {avg_score:.2f}')
display(df.head())
실루엣 계수: 0.52

 

groupby() 를  사용하여 클러스터 번호 별로 평균을 구한다.

# 클러스터 별 평균 실루엣 계수
group_by_cluster = df.groupby('cluster')['silhouette coef'].mean()
print(group_by_cluster)
cluster
0    0.416037
1    0.790033
2    0.358131
Name: silhouette coef, dtype: float64

 

클러스터 별 중심 좌표 보기

kmeans.cluster_centers_ 
# 클러스터 별 중심 좌표
array([[4.26923077, 1.34230769],
       [1.462     , 0.246     ],
       [5.59583333, 2.0375    ]])

 

실루엣 계수를 이용한 군집 계수 최적화

> 복붙한 함수 => 모듈처럼 쓰면 됨

def visualize_silhouette(cluster_lists, X_features): 

    from sklearn.datasets import make_blobs
    from sklearn.cluster import KMeans
    from sklearn.metrics import silhouette_samples, silhouette_score

    import matplotlib.pyplot as plt
    import matplotlib.cm as cm
    import math

    # 입력값으로 클러스터링 갯수들을 리스트로 받아서, 각 갯수별로 클러스터링을 적용하고 실루엣 개수를 구함
    n_cols = len(cluster_lists)

    # plt.subplots()으로 리스트에 기재된 클러스터링 수만큼의 sub figures를 가지는 axs 생성 
    fig, axs = plt.subplots(figsize=(4*n_cols, 4), nrows=1, ncols=n_cols)

    # 리스트에 기재된 클러스터링 갯수들을 차례로 iteration 수행하면서 실루엣 개수 시각화
    for ind, n_cluster in enumerate(cluster_lists):

        # KMeans 클러스터링 수행하고, 실루엣 스코어와 개별 데이터의 실루엣 값 계산. 
        clusterer = KMeans(n_clusters = n_cluster, max_iter=500, random_state=0)
        cluster_labels = clusterer.fit_predict(X_features)

        sil_avg = silhouette_score(X_features, cluster_labels)
        sil_values = silhouette_samples(X_features, cluster_labels)

        y_lower = 10
        axs[ind].set_title('Number of Cluster : '+ str(n_cluster)+'\n' \
                          'Silhouette Score :' + str(round(sil_avg,3)) )
        axs[ind].set_xlabel("The silhouette coefficient values")
        axs[ind].set_ylabel("Cluster label")
        axs[ind].set_xlim([-0.1, 1])
        axs[ind].set_ylim([0, len(X_features) + (n_cluster + 1) * 10])
        axs[ind].set_yticks([])  # Clear the yaxis labels / ticks
        axs[ind].set_xticks([0, 0.2, 0.4, 0.6, 0.8, 1])

        # 클러스터링 갯수별로 fill_betweenx( )형태의 막대 그래프 표현. 
        for i in range(n_cluster):
            ith_cluster_sil_values = sil_values[cluster_labels==i]
            ith_cluster_sil_values.sort()

            size_cluster_i = ith_cluster_sil_values.shape[0]
            y_upper = y_lower + size_cluster_i

            color = cm.nipy_spectral(float(i) / n_cluster)
            axs[ind].fill_betweenx(np.arange(y_lower, y_upper), 0, ith_cluster_sil_values, \
                                facecolor=color, edgecolor=color, alpha=0.7)
            axs[ind].text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
            y_lower = y_upper + 10

        axs[ind].axvline(x=sil_avg, color="red", linestyle="--")

 

from sklearn.datasets import make_blobs  # 클러스터링을 위한 샘플데이터 제공해주는 데이터
import numpy as np

x,y = make_blobs(n_samples= 500, n_features= 2, centers= 4, cluster_std= 1, 
                  shuffle= True, random_state= 1) # 500개의 랜덤 샘플 데이터, 독립변수 2개, 클러스터 개수 4개

# visualize_silhouette([2,3,4,5], x) # 클러스터 수 변화
visualize_silhouette([2,3,4,5], iris.data)

make_blobs(n_samples= 500, n_features= 2, centers= 4, cluster_std= 1, shuffle= True, random_state= 1)
# 500개의 랜덤 샘플 데이터, 독립변수 2개, 클러스터 개수 4개, 표준편차는 1

최적의 군집 계수는 애매하지만 4 이다.