[파이썬 머신러닝 완벽 가이드] 07. 군집화

DSOB·2023년 11월 9일
post-thumbnail

07. 군집화

K-Means

군집화 (clustering)에서 가장 일반적으로 사용되는 알고리즘

  • 임의의 지점(군집 중심점)을 선택해 해당 중심에 가장 가까운 포인트들을 선택하는 군집화기법

💡 군집 중심점(centroid)
선택된 포인트의 평균지점으로 이동하고 이동된 중심점에서 다시 가까운 포인트를 선택,
다시 중심점을 평균지점으로 이동하는 프로세스를 반복적으로 수행한다.
모든 데이터포인트에서 더이상 중심점의 이동이 없을 경우, 반복을 멈추고 해당 중심점에 속하는 데이터 포인트들을 군집화한다.

K-평균의 장점

  • 일반적인 군집화에서 가장 많이 활용되는 알고리즘
  • 알고리즘이 쉽고 간결하다.
  • 비지도학습으로 사전 라벨 데이터가 필요없다.

K-평균의 단점

  • 거리 기반 알고리즘으로 속성(피처)의 개수가 매우 많을 경우 군집화 정확도가 떨어진다.
    (이를 위해 PCA 차원 감소를 적용해야 할 수도 있음)
  • 반복을 수행하는데, 반복 횟수가 많을 경우 수행 시간이 매우 느려진다.
  • 몇 개의 군집(cluster)을 선택해야 할지, 즉 K 설정에 대한 가이드하기가 어렵다.

사이킷런 KMeans 클래스 소개
사이킷런 패키지는 K-평균을 구현하기 위해 KMeans 클래스를 제공

# KMeans 초기화 파라미터
class sklearn.cluster.KMeans(n_clusters = 8, init = 'k-means++', n_init = 10, max_iter = 300, tol = 0.0001, precompute_distances ='auto',verbose = 0, random_state = None, copy_x= True, n_jobs = 1, algorithm = 'auto')
  • n_clusters : 군집화할 개수 → 군집 중심점의 개수 (가장 중요한 파라미터)
  • init : 초기에 군집 중심점의 좌표를 설정할 방식 (일반적으로 k-means++방식으로 최초 설정)
  • max_iter : 최대 반복 횟수 (이 횟수 이전에 모든 데이터의 중심점 이동이 없으면 종료)
    KMeans는 fit(데이터 세트) 또는 fit_transform(데이터 세트) 메서드를 이용해 수행
    → 이렇게 수행된 KMeans 객체는 군집화 수행이 완료돼 군집화와 관련된 주요 속성을 알 수 있음
    (사이킷런은 ML 모델 학습을 위해서 fit()을 , 학습된 모델의 예측을 위해 predict() 메서드를 제공)
  • fit(X) : 데이터 x에 대한 군집화 모델 학습
  • fit_predict(X) : 데이터 x에 대한 군집화 모델 학습 및 라벨 반환
    주요 속성
  • labels_ : 각 데이터 포인트가 속힌 군집 중심점 레이블
  • cluster_centers : 각 군집 중심점 좌표 (Shape는 [군집 개수, 피처 개수])
    이를 이용하면 군집 중심점 좌표가 어디인지 시각화할 수 있음

K-평균을 이용한 붓꽃 데이터 세트 군집화

  • 초기 설정
from sklearn.preprocessing import scale
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline

iris = load_iris()
# 보다 편리한 데이터 Handling을 위해 DataFrame으로 변환
irisDF = pd.DataFrame(data=iris.data, columns=['sepal_length','sepal_width','petal_length','petal_width'])
irisDF.head(3)

  • 붓꽃 데이터 세트를 3개 그룹으로 군집화
    → n_cluster = 3 , init(초기 중심 설정 방식) = k-means++ , max_iter(최대 반복 횟수)=300 으로 설정한 KMeans
    객체를 만들고, 여기에 fit()를 수행
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300,random_state=0)
kmeans.fit(irisDF) # fit( )을 수행해 irisDF에 대한 군집화 결과가 kmeans 객체 변수로 반환
  • kmeans의 labels_ 속성값 확인
    → irisDF의 각 데이터가 어떤 중심에 속하는지 알 수 있음
# labels_ 속성값 출력
print(kmeans.labels_)

# labels_의 값 0, 1, 2 => 각 레코드가 첫 번째, 두 번째, 세 번째 군집에 속함 의미

  • 실제 붓꽃 품종 분류 값과 얼마나 차이가 나는지로 군집화가 효과적으로 됐는지 확인
irisDF['target'] = iris.target # 붓꽃 데이터 세트의 target 값을 ‘target’ 칼럼으로 지정
irisDF['cluster']=kmeans.labels_ # labels_ 값을 cluster’ 칼럼으로 지정
iris_result = irisDF.groupby(['target','cluster'])['sepal_length'].count()
# 실제 분류값인 target과 군집화 분류값인 cluster로 적용해 target과 cluster 값 개수 비교
print(iris_result)


→ target이 0인 데이터는 모두 1번 군집으로 잘 분류됨
→ target이 1인 데이터는 2개만 0번 군집으로, 나머지는 2번 군집으로 분류됨
→ target이 2인 데이터는 36개가 0번 군집으로, 14개가 2번 군집으로 분류

  • 붓꽃 데이터 세트의 군집화를 시각화
    2차원 평면상에서 개별 데이터의 군집화을 시각적으로 표현
    → 붓꽃 데이터 세트의 속성이 4개이므로 2차원 평면에 적합X
    ⇒ PCA를 이용해 4개의 속성을 2개로 차원 축소한 뒤 X 좌표, Y 좌표로 개별 데이터를 표현
from sklearn.decomposition import PCA

pca = PCA(n_components=2)
pca_transformed = pca.fit_transform(iris.data)

irisDF['pca_x'] = pca_transformed[:,0]
irisDF['pca_y'] = pca_transformed[:,1]
irisDF.head(3)

# 군집 값이 0, 1, 2인 경우마다 별도의 인덱스로 추출
marker0_ind = irisDF[irisDF['cluster']==0].index
marker1_ind = irisDF[irisDF['cluster']==1].index
marker2_ind = irisDF[irisDF['cluster']==2].index

# 군집 값 0, 1, 2에 해당하는 인덱스로 각 군집 레벨의 pca_x, pca_y 값 추출. o, s, ^ 로 마커 표시
plt.scatter(x=irisDF.loc[marker0_ind, 'pca_x'], y=irisDF.loc[marker0_ind, 'pca_y'], marker='o')
plt.scatter(x=irisDF.loc[marker1_ind, 'pca_x'], y=irisDF.loc[marker1_ind, 'pca_y'], marker='s')
plt.scatter(x=irisDF.loc[marker2_ind, 'pca_x'], y=irisDF.loc[marker2_ind, 'pca_y'], marker='^')

plt.xlabel('PCA 1')
plt.ylabel('PCA 2')
plt.title('3 Clusters Visualization by 2 PCA Components')
plt.show()

→ Cluster 1은 명확하게 잘 분리되어 있음
→ Cluster 0,2는 대부분 잘 분리되어있지만 일부가 완전히 분리되진 않음

군집화 알고리즘 테스트를 위한 데이터 생성

사이킷런은 다양한 유형의 군집화 알고리즘을 테스트해 보기 위한 간단한 데이터 생성기를 제공

대표적인 군집화용 데이터 생성기
→ 비슷하게 여러 개의 클래스에 해당하는 데이터 세트를 만드는데, 하나의 클래스에 여러 개의 군집이 분포될 수 있게 데이터를 생성할 수 있

  • make_blobs() : 개별 군집의 중심점과 표준 편차 제어 기능이 있음
  • make_classification() : 노이즈를 포함한 데이터를 만드는 데 유용하게 사용할 수 있음
  • 이외에도 make_circle() , make_moons()은 중심 기반 군집화로 해결하기 어려운 데이터를 생성

make_blobs()

💡 make_blobs() 사용법
make_blobs()를 호출하면 피처 데이터 세트와 타깃 데이터 세트가 튜플(Tuple)로 반환

make_blobs()의 호출 파라미터

  • n_samples: 생성할 총 데이터의 개수 (디폴트 100개)
  • n_features: 데이터의 피처 개수
    → 시각화를 목표로 할 경우 2개로 설정해 첫 번째 피처는 x 좌표, 두 번째 피처는 y 좌표상에 표현
  • centers: int 값
    ex. 3으로 설정하면 군집의 개수 / ndarray 형태로 표현할 경우 개별 군집 중심점의 좌표를 의미
  • cluster_std: 생성될 군집 데이터의 표준 편차
    → float 값 0.8과 같은 형태로 지정하면 군집 내에서 데이터가 표준편차 0.8을 가진 값으로 생성
    [0.8, 1.2, 0.6]과 같은 형태로 표현되면 3개의 군집에서 첫 번째 군집 내 데이터의 표준편차는 0.8. 두 번째 군집 내 데이터의 표준 편차는 1.2, 세 번째 군집 내 데이터의 표준편차는 0.6으로 생성
    (군집별로 서로 다른 표준 편차를 가진 데이터 세트를 만들 때 사용)
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
%matplotlib inline

# 총200개의 레코드와 2개의 피처가 3개의 군집화 기반 분포도를 가진 피처 데이터 세트 X,
# 동시에 3개의 군집화 값을 가진 타깃 데이터 세트y가 반환
X, y = make_blobs(n_samples=200, n_features=2, centers=3, cluster_std=0.8, random_state=0)
print(X.shape, y.shape)

# y target 값의 분포를 확인
unique, counts = np.unique(y, return_counts=True)
print(unique,counts)

피처 데이터 세트 X는 200개의 레코드와 2개의 피처를 가짐→ shape (200, 2)

군집 타겟 데이터 세트인 y의 shape → (200,)

3개의 cluster의 값 → [0, 1, 2] 이며, 각각 67, 67, 66개로 균일하게 구성

import pandas as pd

# 데이터 세트 DataFrame으로 변경
clusterDF = pd.DataFrame(data=X, columns=['ftr1', 'ftr2']) # 피처 이름 ftr1, ftr2
clusterDF['target'] = y
clusterDF.head(3)

# make_blobs()으로 만든 피처 데이터 세트가 어떠한 군집화 분포를 가지고 만들어졌는지 확인

target_list = np.unique(y)
# 각 타깃별 산점도의 마커 값.
markers=['o', 's', '^', 'P', 'D', 'H', 'x']
# 3개의 군집 영역으로 구분한 데이터 세트를 생성했으므로 target_list는 [0, 1, 2]
# target==0, target==1, target==2 로 scatter plot을 marker별로 생성.
for target in target_list:
    target_cluster = clusterDF[clusterDF['target']==target]
    plt.scatter(x=target_cluster['ftr1'], y=target_cluster['ftr2'], edgecolor='k',
                marker=markers[target] )

plt.show()

# 만들어진 데이터 세트에 KMeans 군집화를 수행한 뒤에 군집별로 시각화

# KMeans 객체를 이용하여 X 데이터를 K-Means 클러스터링 수행 
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=200, random_state=0)
cluster_labels = kmeans.fit_predict(X)
clusterDF['kmeans_label']  = cluster_labels

#cluster_centers_ 는 개별 클러스터의 중심 위치 좌표 시각화를 위해 추출
centers = kmeans.cluster_centers_
unique_labels = np.unique(cluster_labels)
markers=['o', 's', '^', 'P','D','H','x']

# 군집된 label 유형별로 iteration 하면서 marker 별로 scatter plot 수행. 
for label in unique_labels:
    label_cluster = clusterDF[clusterDF['kmeans_label']==label]
    center_x_y = centers[label]
    plt.scatter(x=label_cluster['ftr1'], y=label_cluster['ftr2'], edgecolor='k', 
                marker=markers[label] )
    
    # 군집별 중심 위치 좌표 시각화 
    plt.scatter(x=center_x_y[0], y=center_x_y[1], s=200, color='white',
                alpha=0.9, edgecolor='k', marker=markers[label])
    plt.scatter(x=center_x_y[0], y=center_x_y[1], s=70, color='k', edgecolor='k', 
                marker='$%d$' % label)

plt.show()

make_blobs()의 타깃과 kmeans_label은 군집 번호를 의미하므로 서로 다른 값으로 매핑될 수 있음
(그래서 산점도의 마커가 서로 다를 수 있다)

print(clusterDF.groupby('target')['kmeans_label'].value_counts())

→ Target 0이 cluster label 0으로, target 1이 label 2로, target 2가 label 1로 거의 대부분 잘 매핑

make_blobs()은 cluster_std 파라미터로 데이터의 분포도를 조절

→ cluster_std가 작을수록 군집 중심에 데이터가 모여 있으며, 클수록 데이터가 퍼져 있음

군집 평가 (Cluster Evaluation)

군집화는 분류와 유사해보이지만 성격이 많이 다름

  • 데이터 내에 숨어있는 별도의 그룹을 찾아서 의미를 부여한다.
  • 동일한 분류 값에 속하더라도 그 안에서 더 세분화된 군집화를 추구하기도 한다.
  • 서로 다른 분류 값의 데이터도 더 넓은 군집화 레벨화 등의 영역이 있다.

이러한 군집화가 효율적으로 잘 되었는지 평가 할 수 있는 대표적인 방법엔 실루엣 분석이 있다.

(다만, 비지도학습의 특성상 어떠한 지표라도 정확하게 성능을 평가하기 어렵다.)

실루엣 분석(silhouette analysis)

: 다른 군집과의 거리는 떨어져있고 동일 군집 데이터끼리 서로 가까운지를 나타냄

→ 군집화가 잘 될수록 개별 군집은 비슷한 정도의 여유공간을 가지고 떨어져 있다

실루엣 분석은 실루엣 계수를 기반으로 하며, 실루엣 계수는 개별 데이터가 가지는 군집화 지표이다.

→ 실루엣 계수는 -1 ~ 1 사이 값을 가지며 1에 가까울수록 근처 군집과 멀리 떨어져 있음

0에 가까울수록 근처 군집과 가까워지며, 음수값은 i번째 데이터가 아예 다른 군집에 할당됐다는 뜻

  • a(i) :  i번째 데이터에서 자신이 속한 군집내의 다른 데이터까지의 거리들의 평균
  • b(i) : i번째 데이터에서 가장 가까운 타 군집내의 다른 데이터까지의 거리들의 평균

실루엣 분석을 위한 메서드 (사이킷런 제공)

💡 좋은 군집화의 기준 조건
1. 전체 실루엣 계수의 평균값은 0 ~ 1 사이의 값을 가지며. 1에 가까울수록 좋다.
2. 실루엣 계수 평균값과 더불어 평균값의 편차가 크지 않아야 함.
즉, 개별 군집의 실루엣 계수 평균값이 전체 평균값에 크게 벗어나지 않는 것이 중요
만약, 전체 평균 실루엣 계수는 높지만, 특정 군집 실루엣 계수만 높고 다른 군집들은 낮다면 좋은 군집화 조건이 아님

붓꽃 데이터 세트를 이용한 군집 평가

sklearn.metrics 모듈의 silhouette_samples()와 silhouette_score()를 이용

from sklearn.preprocessing import scale
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
# 실루엣 분석 metric 값을 구하기 위한 API 추가
from sklearn.metrics import silhouette_samples, silhouette_score
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

%matplotlib inline

iris = load_iris()
feature_names = ['sepal_length','sepal_width','petal_length','petal_width']
irisDF = pd.DataFrame(data=iris.data, columns=feature_names)
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300,random_state=0).fit(irisDF)

irisDF['cluster'] = kmeans.labels_

# iris 의 모든 개별 데이터에 실루엣 계수값을 구함. 
score_samples = silhouette_samples(iris.data, irisDF['cluster'])
print('silhouette_samples( ) return 값의 shape' , score_samples.shape)

# irisDF에 실루엣 계수 컬럼 추가
irisDF['silhouette_coeff'] = score_samples

# 모든 데이터의 평균 실루엣 계수값을 구함. 
average_score = silhouette_score(iris.data, irisDF['cluster'])
print('붓꽃 데이터셋 Silhouette Analysis Score:{0:.3f}'.format(average_score))

irisDF.head(3)

  • silhouette_samples()를 사용하면 개별 데이터의 실루엣 계수를 구할 수 있음
  • silhouette_score()는 전체 실루엣 계수의 평균으로 silhouette_samples()의 평균과 같음
  • 두 함수 모두 인자로 개별 데이터(피처)와 군집 정보를 입력

→ 전체 실루엣 계수 평균 = 0.553

# 군집별 평균 실루엣 계수 값으로 확인
# IrisDF DataFrame에서 군집 칼럼별로 group by하여 silhouette_coeff 칼럼의 평균값 구하
irisDF.groupby('cluster')['silhouette_coeff'].mean()

→ Cluster 1의 실루엣 계수 평균이 높은데 비해 Cluster 0과 2는 상대적으로 평균값이 낮다.

군집별 평균 실루엣 계수의 시각화를 통한 군집 개수 최적화 방법

전체 실루엣 계수 평균이 높다고 반드시 최적의 군집 개수로 군집화가 잘 되었다고 볼 수 없다.

→ 특정 군집만 실루엣 계수가 높고 다른 군집은 내부 데이터끼리 거리가 멀어 실루엣 계수가 낮아도
전체 평균은 높을 수 있기 때문

⇒ 군집끼리 적당히 거리를 유지하면서 군집 내 데이터가 잘 뭉쳐있어야 K-평균의 군집 개수가 적절하게 설정되었다고 판할 수 있다.

https://drive.google.com/file/d/1oYTLaid1iMVaoMKFeZtMuSmzyqIotcJv/view?usp=sharing

평균 이동(Mean Shift)

K-Means와 유사하게 중심을 군집의 중심으로 지속적으로 움직이며 군집화

  • 두 방법의 차이점 : 중심을 이동하는 방법
    • K-Means: 중심에 소속된 데이터의 평균 거리 중심으로 이동한다.
    • 평균 이동: 중심을 데이터가 모여 있는 밀도가 가장 높은 곳으로 이동한다.

수행과정

  1. 평균 이동은 KDE(Kernerl Density Estimation)를 이용해서 확률 밀도 함수를 구한다.
  2. 데이터가 집중적으로 모여있어 확률 밀도 함수가 피크인 점을 군집 중심점으로 선정한다.
  3. 이러한 방식을 전체 데이터에 반복적으로 적용하면서 데이터의 군집 중심점을 찾는다.

💡 KDE(Kernel Density Estimation)
: 커널 함수를 통해 어떤 변수의 확률 밀도 함수를 추정하는 대표적인 방법
개별 관측 데이터에 커널 함수를 적용한 값을 모두 더한 후 데이터의 건수로 나눠 확률 밀도 함수를 추정 (대표적인 커널 함수로서 가우시안 분포 함수가 사용)

  • 𝐊 : 커널 함수
  • 𝔁 : 확률 변수값
  • 𝔁𝓲 : 관측값
  • : 대역폭(bandwidth)

대역폭 : KDE 형태를 결정하는 요소로 어떻게 설정하느냐에 따라 확률 밀도 추정 성능을 좌우

대역폭이 작을수록 좁고 뾰족한 KDE를 가짐
→ 변동성이 큰 방식으로 확률 밀도 함수를 추정해 과적합하기 쉽다.

반대로 값이 클수록 넓고 완만한 KDE로 지나치게 단순화 되어 과소적합하기 쉽다.

일반적으로 평균 이동 군집화는 대역폭이 클수록 적은 군집 중심점을 가지고,
작을수록 많은 군집 중심점을 가진다.

또한, K-Means와 달리 군집의 개수를 지정하지 않고 대역폭에 따라 군집화를 수행한다.

장단점

  • 데이터를 특정 형태로 가정하거나, 특정 분포 기반 모델로 가정하지 않으므로 유연한 군집화가 가능하다.
  • 이상치의 영향력이 크지 않으며 군집의 개수를 정할 필요도 없다.
  • 알고리즘 수행 시간이 오래 걸리고 대역폭(bandwidth)의 크기에 따른 군집화 영향이 매우 크다.

MeanShift 클래스

사이킷런은 평균 이동 군집화를 위해 MeanShift 클래스를 제공

가장 중요한 파라미터 : bandwidth (KDE의 대역폭 h와 동일)

대역폭 크기 설정이 군집화의 품질에 큰 영향을 미치기 때문에 최적의 대역폭 계산을 위해 estimate_bandwidth() 함수를 제공

[예제]

make_blobs()의 cluster_std를 0.7로 정한 3개 군집의 데이터에 대해 bandwidth를 0.8로 설정한 평균 이동 군집화 알고리즘을 적용

import numpy as np
from sklearn.datasets import make_blobs
from sklearn.cluster import MeanShift

X, y = make_blobs(n_samples=200, n_features=2, centers=3, 
                  cluster_std=0.7, random_state=0)

meanshift= MeanShift(bandwidth=0.8)
cluster_labels = meanshift.fit_predict(X)
print('cluster labels 유형:', np.unique(cluster_labels))

→ 군집이 3개인 데이터로 평균 이동을 했는데 군집이 6개로 지나치게 세분화되어 군집화

meanshift= MeanShift(bandwidth=1)
cluster_labels = meanshift.fit_predict(X)
print('cluster labels 유형:', np.unique(cluster_labels))

→ 대역폭(bandwith)를 조금 더 증가 시킨 후 평균 이동

→ 3개의 군집으로 잘 군집화 되었다.

→ 대역폭에 따라 군집의 개수는 큰 영향을 받으므로 최적의 대역폭을 찾는 것이 중요함

from sklearn.cluster import estimate_bandwidth

bandwidth = estimate_bandwidth(X)
print('bandwidth 값:', round(bandwidth,3))

estimate_bandwidth()는 최적화된 대역폭 값을 반환해준다.

# 최적 대역폭으로 다시 평균 이동

import pandas as pd

clusterDF = pd.DataFrame(data=X, columns=['ftr1', 'ftr2'])
clusterDF['target'] = y

# estimate_bandwidth()로 최적의 bandwidth 계산
best_bandwidth = estimate_bandwidth(X)

meanshift= MeanShift(bandwidth=best_bandwidth)
cluster_labels = meanshift.fit_predict(X)
print('cluster labels 유형:',np.unique(cluster_labels))

→ 최적의 대역폭으로 평균 이동시 3개의 군집으로 잘 군집화됨

# 시각화

import matplotlib.pyplot as plt
%matplotlib inline

clusterDF['meanshift_label']  = cluster_labels
centers = meanshift.cluster_centers_
unique_labels = np.unique(cluster_labels)
markers=['o', 's', '^', 'x', '*']

for label in unique_labels:
    label_cluster = clusterDF[clusterDF['meanshift_label']==label]
    center_x_y = centers[label]
    # 군집별로 다른 마커로 산점도 적용
    plt.scatter(x=label_cluster['ftr1'], y=label_cluster['ftr2'], edgecolor='k', marker=markers[label] )
    
    # 군집별 중심 표현
    plt.scatter(x=center_x_y[0], y=center_x_y[1], s=200, color='gray', alpha=0.9, marker=markers[label])
    plt.scatter(x=center_x_y[0], y=center_x_y[1], s=70, color='k', edgecolor='k', marker='$%d$' % label)
    
plt.show()

→ 평균 이동도 K-Means처럼 중심을 가지고 있으므로 cluster_centers_ 속성 사용 가능

print(clusterDF.groupby('target')['meanshift_label'].value_counts())

→ target과 평균 이동으로 생성된 군집이 완벽하게 매칭됨을 확인

GMM(Gaussian Mixture Model)

군집화를 적용하고자 하는 데이터가 여러 개의 가우시안 분포(GaussianDistribution)를 가진
데이터 집합들이 섞여서 생성된 것이라는 가정하에 군집화를 수행하는 방식

  • GMM(Gaussian Mixture Model)은 데이터를 여러 개의 가우시안 분포가 섞인 것으로 간주
  • 여러 개의 정규 분포 곡선을 추출하고, 개별 데이터가 어떤 정규 분포에 속하는지 결정

→ 이와 같은 방식을 모수 추정이라고 하는데, 모수 추정은 대표적으로 2가지를 추정

  1. 개별 정규 분포의 평균과 분산
  2. 각 데이터가 어떤 정규 분포에 해당되는지의 확률

모수 추정을 위해 GMM은 EM(Expectation and Maximization) 방법을 적용한다.

GMM을 이용한 붓꽃 데이터 세트 군집화

사이킷런은 GMM의 EM 방식을 통한 모수 추정 군집화를 위해 GaussianMixture 클래스를 지원

  • GMM은 확률 기반 군집화이고, K-평균은 거리 기반 군집화
  • 붓꽃 데이터 세트로 두 가지 방식을 이용해 군집화를 수행한 뒤 양쪽 방식을 비교
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline

iris = load_iris()
feature_names = ['sepal_length','sepal_width','petal_length','petal_width']

# 보다 편리한 데이터 Handling을 위해 DataFrame으로 변환
irisDF = pd.DataFrame(data=iris.data, columns=feature_names)
irisDF['target'] = iris.target

n_components : gaussian mixture의 모델의 총 개수 (가장 중요한 초기화 파라미터)
→ K-평균의 n_clusters와 같이 군집의 개수를 정하는 데 중요한 역할 수행

from sklearn.mixture import GaussianMixture
# GaussianMixture 클래스는 sklearn.mixture 패키지에 위치해 있음

gmm = GaussianMixture(n_components=3, random_state=0).fit(iris.data)
gmm_cluster_labels = gmm.predict(iris.data)

# 클러스터링 결과를 irisDF 의 'gmm_cluster' 컬럼명으로 저장
irisDF['gmm_cluster'] = gmm_cluster_labels
irisDF['target'] = iris.target

# target 값에 따라서 gmm_cluster 값이 어떻게 매핑되었는지 확인. 
iris_result = irisDF.groupby(['target'])['gmm_cluster'].value_counts()
print(iris_result)

→ gmm_cluster는 target이 1인 경우, 5개(10%)만 다르게 매핑되고 나머지는 모두 잘 매핑됨

⇒ 붓꽃 데이터 세트 K-평균 군집화 결과보다 더 효과적인 분류 결과 도출

# 붓꽃 데이터 세트의 K-평균 군집화를 수행한 결과

kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300,random_state=0).fit(iris.data)
kmeans_cluster_labels = kmeans.predict(iris.data)
irisDF['kmeans_cluster'] = kmeans_cluster_labels
iris_result = irisDF.groupby(['target'])['kmeans_cluster'].value_counts()
print(iris_result)

어떤 알고리즘에 더 뛰어나다 X → 붓꽃 데이터 세트가 GMM 군집화에 더 효과적이다.

K-평균은 평균 거리 중심으로 중심을 이동하면서 군집화를 수행하는 방식이므로
개별 군집 내의 데이터가 원형으로 흩어져 있는 경우에 매우 효과적으로 군집화가 수행됨

DBSCAN

대표적인 밀도 기반 군집화 알고리즘

특정 공간 내 데이터 밀도 차이를 기반 알고리즘으로 하고 있어 복잡한 기하학적 분포를 가진 데이터에도 군집화를 잘 수행한다.

DBSCAN 주요 파라미터

  • 입실론 주변 영역(epsilon): 개별 데이터를 중심으로 입실론 반경을 가지는 원형의 영역
  • 최소 데이터 개수(min points): 개별 데이터의 입실론 주변 영역에 포함되는 타 데이터 개수

DBSCAN 데이터 포인트

입실론 주변 영역 내에 포함되는 최소 데이터 개수 충족 여부에 따라 데이터 포인트를 정의

  • 핵심 포인트(core point): 주변 영역 내 최소 데이터 개수 이상의 타 데이터를 가질 경우
  • 이웃 포인트(neighbor point): 주변 영역 내에 위치한 타 데이터를 명칭
  • 경계 포인트(border point): 주변 영역 내에 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지 않지만 (핵심 포인트는 아니지만) 핵심 포인트를 이웃 포인트로 가지는 데이터
  • 잡음 포인트(noise point): 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지도 않으며(핵심 포인트가 아니면서) 동시에 경계 포인트도 아닌 데이터

만약 핵심 포인트끼리 서로가 이웃 포인트라면 서로를 연결하면서 군집화를 구성한다.

경계 포인트의 경우 핵심 포인트의 이웃 포인트로서 군집의 외곽을 형성

즉, 입실론 주변 영역의 최소 데이터 갯수를 포함하는 밀도 기준을 충족시키는 데이터인 핵심 포인트를 연결하면서 군집화를 한다.

DBSCAN 적용하기 - 붓꽃 데이터 세트

붓꽃 데이터 세트를 DataFrame으로 로딩 → GMM을 이용한 예제 코드와 동일

from sklearn.cluster import DBSCAN

dbscan = DBSCAN(eps=0.6, min_samples=8, metric='euclidean')
dbscan_labels = dbscan.fit_predict(iris.data)

irisDF['dbscan_cluster'] = dbscan_labels
irisDF['target'] = iris.target

iris_result = irisDF.groupby(['target'])['dbscan_cluster'].value_counts()
print(iris_result)

  • sklearn.cluster()의 DBSCAN()을 이용해서 DBSCAN을 수행
  • eps는 입실론 주변 영역, min_samples는 최소 데이터 개수
  • 결과를 보면 -1,0,1 값이 있는데 -1은 노이즈에 속하는 군집을 의미
  • 즉, 여기선 0과1, 2개의 군집과 노이즈로 군집화
  • Target 값의 유형이 3가지인데, 군집이 2개가 됐다고 군집화 효율이 떨어진다는 의미 X
    DBSCAN은 군집의 개수를 알고리즘에 따라 자동으로 지정하므로 개수 지정 무의미함
from sklearn.decomposition import PCA
# 2차원으로 시각화하기 위해 PCA n_componets=2로 피처 데이터 세트 변환
pca = PCA(n_components=2, random_state=0)
pca_transformed = pca.fit_transform(iris.data)
# visualize_cluster_2d( ) 함수는 ftr1, ftr2 컬럼을 좌표에 표현하므로 PCA 변환값을 해당 컬럼으로 생성
irisDF['ftr1'] = pca_transformed[:,0]
irisDF['ftr2'] = pca_transformed[:,1]

visualize_cluster_plot(dbscan, irisDF, 'dbscan_cluster', iscenter=False)

  • 별표(★)로 표현된 값 → 노이즈
  • PCA를 통해 2차원으로 표현하면 이상치인 노이즈 데이터가 명확히 드러남
  • DBSCAN을 적용할 때는 특정 군집 개수로 군집을 강제하지 않는 것이 좋다.
  • DBSCAN에 적절한 eps와 min_samples 파라미터를 통해 최적의 군집을 찾는 게 중요
    → eps의 값을 크게 하면 포함하는 데이터가 많아지므로 노이즈 데이터 개수가 작아짐
    → min_samples를 크게 하면 주어진 반경 내에서 더 많은 데이터를 포함시켜야 하므로
    노이즈 데이터 개수가 커짐
from sklearn.cluster import DBSCAN

dbscan = DBSCAN(eps=0.8, min_samples=8, metric='euclidean')
dbscan_labels = dbscan.fit_predict(iris.data)

irisDF['dbscan_cluster'] = dbscan_labels
irisDF['target'] = iris.target

iris_result = irisDF.groupby(['target'])['dbscan_cluster'].value_counts()
print(iris_result)

visualize_cluster_plot(dbscan, irisDF, 'dbscan_cluster', iscenter=False)

eps를 0.6에서 0.8로 증가 → -1값이 줄어들었고 그림으로도 노이즈가 감소한 것이 확인됨

dbscan = DBSCAN(eps=0.6, min_samples=16, metric='euclidean')

min_samples를 8에서 16으로 증가하였더니 -1값이 늘어났고 그림으로도 노이즈가 증가

DBSCAN 적용하기 - make_circles() 데이터 세트

복잡한 기하학적 분포를 가지는 데이터 세트에서 DBSCAN과 타 알고리즘을 비교

# make_circles() 함수를 이용해 내부 원과 외부 원 형태로 돼 있는 2차원 데이터 세트 만들기
# make_circles() 함수는 오직 2개의 피처만을 생성하므로 별도의 피처 개수를 지정할 필요X 
# 파라미터 noise는 노이즈 데이터 세트의 비율이며, factor는 외부 원과 내부 원의 scale 비율

from sklearn.datasets import make_circles

X, y = make_circles(n_samples=1000, shuffle=True, noise=0.05, random_state=0, factor=0.5)
clusterDF = pd.DataFrame(data=X, columns=['ftr1', 'ftr2'])
clusterDF['target'] = y

visualize_cluster_plot(None, clusterDF, 'target', iscenter=False)

K-평균으로 make_circles() 데이터 세트를 군집화

# KMeans로 make_circles( ) 데이터 셋을 클러스터링 수행. 
from sklearn.cluster import KMeans

kmeans = KMeans(n_clusters=2, max_iter=1000, random_state=0)
kmeans_labels = kmeans.fit_predict(X)
clusterDF['kmeans_cluster'] = kmeans_labels

visualize_cluster_plot(kmeans, clusterDF, 'kmeans_cluster', iscenter=True)

→ 위, 아래 군집 중심을 기반으로 위와 아래 절반으로 군집화

→ 거리 기반 군집화로는 데이터가 특정한 형태로 지속해서 이어지는 부분을 찾아내기 어려움

GMM으로 make_circles() 데이터 세트를 군집화

# GMM으로 make_circles( ) 데이터 셋을 클러스터링 수행. 
from sklearn.mixture import GaussianMixture

gmm = GaussianMixture(n_components=2, random_state=0)
gmm_label = gmm.fit(X).predict(X)
clusterDF['gmm_cluster'] = gmm_label

visualize_cluster_plot(gmm, clusterDF, 'gmm_cluster', iscenter=False)

→ 내/외부의 원형으로 구성된 더 복잡한 형태의 데이터 세트에서는 군집화 원하는 방향 X

DBSCAN으로 make_circles() 데이터 세트를 군집화

# DBSCAN으로 make_circles( ) 데이터 셋을 클러스터링 수행. 
from sklearn.cluster import DBSCAN

dbscan = DBSCAN(eps=0.2, min_samples=10, metric='euclidean')
dbscan_labels = dbscan.fit_predict(X)
clusterDF['dbscan_cluster'] = dbscan_labels

visualize_cluster_plot(dbscan, clusterDF, 'dbscan_cluster', iscenter=False)

→ DBSCAN으로 군집화를 적용해 원하는 방향으로 정확히 군집화가 됐음

profile
Data Shows the wOrld Better 서울여자대학교 데이터사이언티스트 직군 트랙 소학회 DSOB 입니다 !

0개의 댓글