# 22.02.03 머신러닝 스터디 7장. 군집화
<파이썬 머신러닝 완벽 가이드>¶
군집화 ( p409 ~ p465 )¶
01. K - 평균 알고리즘¶
K-평균이란? : 군집화에서 가장 일반적으로 사용되는 알고리즘으로 군집 중심 점(centroid)이라는 특정한 임의의 지점을 선택해 해당 중심에 가장 가까운 포인트들을 선택하는 군집화 기법이다. 군집 중심점은 선택된 포인트의 평균 지점으로 이동하고 이동된 중심점에서 다시 가까운 포인트를 선택, 다시 중심점을 평균 지점으로 이동하는 프로세스를 반복적으로 수행한다. 모든 데이터 포인트에서 더이상 중심점의 이동이 없을 경우에 반복을 멈추고 해당 중심점에 속하는 데이터 포인트들을 군집화하는 기법이다.
- 군집화의 기준이 되는 중심을 구성하려는 군집화 개수만큼 임의의 위치에 가져다 놓는다.
- 각 데이터는 가장 가까운 곳에 위치한 중심점에 소속된다. ( 각각의 중심까지의 유클리드 거리를 계산해 최소 거리인 중심을 선택 )
- 이렇게 소속이 결정되면 군집 중심점을 소속된 데이터의 평균 중심으로 이동한다.
- 중심점이 이동했기 때문에 각 데이터는 기존에 속한 중심점보다 더 가까운 중심점이 있다면 해당 중심점으로 다시 소속을 변경한다.
- 다시 중심을 소속된 데이터의 평균 중심으로 이동한다.
- 중심점을 이동했는데 디이터의 중심점 소속 변경이 없다면 군집화를 종료한다.
KMeans 클래스의 파라미터
- n_clusters : 군집화할 개수, 군집 중심점의 개수
- init : 초기에 군집 중심점의 좌표를 설정할 방식
- max_iter : 최대 반복 횟수, 이 횟수 이전에 모든 데이터의 중심점 이동이 없다면 종료
fit또는 fit_transform 메소드를 이용해 학습을 수행하고 수행후 군집화와 관련된 주요 속성을 알 수 있다.
- labels_ : 각 데이터 포인트가 속한 군집 중심점 레이블
- clustercenters : 각 군집 중심점 좌표, 이를 이용하면 군집 중심점 좌표가 어디인지 시각화할 수 있다.
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)
sepal_length | sepal_width | petal_length | petal_width | |
---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 |
1 | 4.9 | 3.0 | 1.4 | 0.2 |
2 | 4.7 | 3.2 | 1.3 | 0.2 |
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=300,random_state=0) # 초기 중심 설정 방식인 init는 default 값으로
kmeans.fit(irisDF)
KMeans(n_clusters=3, random_state=0)
labels_ 속성값을 확인하여 irisDF의 각 데이터가 어떤 중심에 속하는지를 확인
print(kmeans.labels_)
[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 1 1 1 1 1 1 1 1 1 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 0 2 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 2 2 2 0 2 2 2 2 2 2 0 0 2 2 2 2 0 2 0 2 0 2 2 0 0 2 2 2 2 2 0 2 2 2 2 0 2 2 2 0 2 2 2 0 2 2 0]
실제 붓꽃 품종 분류 값과 얼마나 차이가 나는지로 군집화가 효과적으로 됐는지 확인
irisDF['target'] = iris.target
irisDF['cluster']=kmeans.labels_
iris_result = irisDF.groupby(['target','cluster'])['sepal_length'].count()
print(iris_result)
target cluster 0 1 50 1 0 48 2 2 2 0 14 2 36 Name: sepal_length, dtype: int64
분류 타깃이 0값인 데이터는 1번 군집으로 잘 그루핑되었다. 타깃이 1인 데이터는 2개만빼고 0번 군집으로 잘 그루핑 되었지만 타깃이 2인 데이터는 잘 그루핑되지 않았다.
# PCA를 통해 2개로 속성을 차원 축소한 뒤에 군집화를 시각화
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)
sepal_length | sepal_width | petal_length | petal_width | target | cluster | pca_x | pca_y | |
---|---|---|---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | 0 | 1 | -2.684126 | 0.319397 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | 0 | 1 | -2.714142 | -0.177001 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | 0 | 1 | -2.888991 | -0.144949 |
# cluster 값이 0, 1, 2 인 경우마다 별도의 Index로 추출
marker0_ind = irisDF[irisDF['cluster']==0].index
marker1_ind = irisDF[irisDF['cluster']==1].index
marker2_ind = irisDF[irisDF['cluster']==2].index
# cluster값 0, 1, 2에 해당하는 Index로 각 cluster 레벨의 pca_x, pca_y 값 추출. o, s, ^ 로 marker 표시
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()
군집화 알고리즘 테스트를 위한 데이터 생성¶
사이킷런에서는 다양한 유형의 군집화 알고리즘을 테스트해 보기 위해 간단한 데이터 생성기를 제공한다. 대표적인 군집화용 데이터 생성기로는 make_blobs()와 make_classification() API가 있다. 두 API는 비슷하게 여러 개의 클래스에 해당하는 데이터 세트를 만드는데, 하나의 클래스에 여러 개의 군집이 분포될 수 있게 데이터를 생성할 수 있다. make_blobs()는 개별 군집의 중심점과 표준 편차 제어 기능이 추가돼 있으며 make_classification()은 노이즈를 포함한 데이터를 만드는 데 유용하게 사용할 수 있다. 이 외에 make_circle(), make_moon() API는 중심 기반의 군집화로 해결하기 어려운 데이터 세트를 만드는 데 사용된다.
make_blobs()의 파라미터
- n_samples : 생성할 총 데이터의 개수, default = 100
- n_features : 데이터의 피처 개수, 시각화를 목표로 할 경우 보통 2개로 설정해 첫 번째는 x좌표, 두 번째는 y 좌표상에 표현한다.
- centers : int 값으로 설정하면 군집의 개수를 나타내고 ndarray 형태로 표현하면 개별 군집 중심점의 좌표를 의미한다.
- cluster_std : 생성될 군집 데이터의 표준 편차 float 값 형태면 군집 내에서 데이터가 해당 float 값으로 만들어 지고, 리스트 형태로 주어지면 각각의 군집에 해당하는 표준편차를 가지고 데이터 세트가 만들어진다.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
%matplotlib inline
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)
(200, 2) (200,) [0 1 2] [67 67 66]
import pandas as pd
clusterDF = pd.DataFrame(data=X, columns=['ftr1', 'ftr2'])
clusterDF['target'] = y
clusterDF.head(3)
ftr1 | ftr2 | target | |
---|---|---|---|
0 | -1.692427 | 3.622025 | 2 |
1 | 0.697940 | 4.428867 | 0 |
2 | 1.100228 | 4.606317 | 0 |
target_list = np.unique(y)
# 각 target별 scatter plot 의 marker 값들.
markers=['o', 's', '^', 'P','D','H','x']
# 3개의 cluster 영역으로 구분한 데이터 셋을 생성했으므로 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 객체를 이용하여 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()
print(clusterDF.groupby('target')['kmeans_label'].value_counts())
target kmeans_label 0 0 66 1 1 1 2 67 2 1 65 2 1 Name: kmeans_label, dtype: int64
make_blobs()는 cluster_std 파라미터로 데이터의 분포도를 조절한다. cluster_std가 작을수록 군집 중심에 데이터가 모여있으며, 클수록 데이터가 퍼져 있음을 알 수 있다.
02. 군집 평가 ( Cluster Evaluation )¶
붓꽃 데이터 세트의 경우 결괏값에 품종을 뜻하는 타깃 레이블이 있었지만 대부분의 군집화 데이터 세트는 이렇게 비교할 만한 타깃 레이블을 가지고 있지 않다. 또한 군집화는 분류와 유사해 보일 수 있으나 성격이 매우 다르다. 데이터 내에 숨어 있는 별도의 그룹을 찾아서 의미를 부여하거나 동일한 분류 값에 속하더라도 그 안에서 더 세분화된 군집화를 추구하거나 서로 다른 분류 값의 데이터도 더 넓은 군집화 레벨화 등의 영역을 가지고 있다. 그렇다면 군집화가 효율적으로 잘 됐는지 평가할 수 있는 지표에는 어떤 것들이 있을까?
실루엣 분석이란? : 실루엣 분석은 각 군집 간의 거리가 얼마나 효율적으로 분리돼 있는지를 나타낸다. 효율적으로 잘 분리되었다는 의미는 다른 군집과의 거리는 떨어져 있고 동일 군집끼리의 데이터는 서로 가깝게 잘 뭉쳐 있다는 의미이다. 군집화가 잘 될수록 개별 군집은 비슷한 정도의 여유공간을 가지고 떨어져 있을 것이다.
실루엣 분석은 실루엣 계수(silhouette coefficient)를 기반으로 한다. 실루엣 계수는 개별 데이터가 가지는 군집화 지표로 개별 데이터가 가지는 실루엣 계수는 해당 데이터가 같은 군집 내의 데이터와 얼마나 가깝게 군집화돼 있고, 다른 군집에 있는 데이터와는 얼마나 멀리 분리돼 있는지를 나타내는 지표이다.
$$ s(i) = \frac{(b(i)-a(i))}{(max(a(i),b(i))}$$- a(i) : 해당 데이터 포인트와 같은 군집 내에 있는 다른 데이터 포인트와의 거리를 평균한 값
- b(i) : 해당 데이터 포인트가 속하지 않은 군집 중 가장 가까운 군집과의 평균 거리
두 군집 간의 거리가 얼마나 떨어져 있는지는 b(i) - a(i)로 알 수 있고, 이 값을 정규화하기 위해 Max(a(i), b(i)) 값으로 나누어주면 i 번째 데이터 포인트의 실루엣 계수인 s(i)를 구할 수 있다. 실루엣 계수는 -1 ~ 1 사이의 값을 가지며 1로 가까워질 수록 근처의 군집과 더 멀리 떨어져 있다는 것이고 0에 가까울수록 근처의 군집과 가까워진다는 것이다. -값은 아예 다른 군집에 데이터 포인트가 할당된것을 의미한다.
좋은 군집화가 되려면 다음과 같은 조건을 만족해야 한다.
- 전체 실루엣 계수의 평균값 ( sihouette_score() ) 은 0 ~ 1 사이로 1에 가까울 수록 좋다.
- 하지만 전체 실루엣 계수의 평균값과 더불어 개별 군집의 평균값의 편차가 크지 않아야 한다. 즉, 개별 군집의 실루엣 계수 평균값이 전체 실루엣 계수의 평균값에서 크게 벗어나지 않는 것이 중요하다. 만약 전체 실루엣 계수의 평균값은 높지만, 특정 군집의 실루엣 계수 평균값만 유난히 높고 다른 군집들의 실루엣 평균값이 낮다면 좋은 군집화 조건이 아니다.
사이킷런의 실루엣 분석 메소드
- sklearn.metrics.silhoutte_samples(X, labels, metric='euclidean', **kwds) : 인자로 X feature 데이터 세트와 각 피처 데이터 세트가 속한 군집 레이블 값인 labels 데이터를 입력해주면 각 데이터 포인트의 실루엣 계수를 계산해 반환합니다.
- sklearn.metrics.silhouette_score(X, labels, metric='euclidean', sample_size=None, **kwds) : 인자로 X feature 데이터 세트와 각 피처 데이터 세트가 속한 군집 레이블 값인 labels 데이터를 입력해주면 전체 데이터의 실루엣 계수 값을 평균해 반환합니다. 즉 np.mean(silhouette_samples())이다. 일반적으로 이 값이 높을수록 군집화가 어느정도 잘 됐다고 판단할 수 있다. 하지만 무조건 이 값이 높다고 군집화가 잘 됐다고 판단할 수는 없다.
붓꽃 데이터 세트를 이용한 군집 평가¶
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( ) return 값의 shape (150,) 붓꽃 데이터셋 Silhouette Analysis Score:0.553
sepal_length | sepal_width | petal_length | petal_width | cluster | silhouette_coeff | |
---|---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | 1 | 0.852955 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | 1 | 0.815495 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | 1 | 0.829315 |
# 군집별 평균 실루엣 계수 값
irisDF.groupby('cluster')['silhouette_coeff'].mean()
cluster 0 0.417320 1 0.798140 2 0.451105 Name: silhouette_coeff, dtype: float64
Elbow 지점 찾아보기
irisDF = pd.DataFrame(data=iris.data, columns=['sepal_length','sepal_width','petal_length','petal_width'])
iris_real = irisDF.copy()
# PCA를 통해 2개로 속성을 차원 축소한 뒤에 군집화를 시각화
from sklearn.decomposition import PCA
pca = PCA(n_components=2)
pca_transformed = pca.fit_transform(iris_real)
iris_real['pca_x'] = pca_transformed[:,0]
iris_real['pca_y'] = pca_transformed[:,1]
iris_real = iris_real[['pca_x', 'pca_y']]
iris_real.head(5)
pca_x | pca_y | |
---|---|---|
0 | -2.684126 | 0.319397 |
1 | -2.714142 | -0.177001 |
2 | -2.888991 | -0.144949 |
3 | -2.745343 | -0.318299 |
4 | -2.728717 | 0.326755 |
clustering_inertia = []
for n in range(1, 11):
model = KMeans(n_clusters = n, init = 'k-means++', n_init = 10,
max_iter=300, random_state = 2020)
model.fit(iris_real.values)
clustering_inertia.append(model.inertia_)
plt.plot(np.arange(1,11), clustering_inertia, 'o')
plt.plot(np.arange(1, 11), clustering_inertia, '-')
plt.xlabel('Number of Clusters')
plt.ylabel('inertia')
plt.show()
C:\ProgramData\Anaconda3\lib\site-packages\sklearn\cluster\_kmeans.py:881: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=1. warnings.warn(
from yellowbrick.cluster import KElbowVisualizer
model = KMeans(init='k-means++', n_init = 10, max_iter=300,random_state=2020)
visualizer = KElbowVisualizer(model, k = (1,11))
plt.xlabel('Number of Clusters')
plt.ylabel('inertia')
iris_visual = visualizer.fit(iris_real.values)
C:\ProgramData\Anaconda3\lib\site-packages\sklearn\cluster\_kmeans.py:881: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=1. warnings.warn(
# 시각화 결과
model = KMeans(n_clusters = 3, init='k-means++', n_init = 10, max_iter=300,random_state=2020)
model.fit(iris_real.values)
label = model.labels_
centroid = model.cluster_centers_
print(label)
print(centroid)
[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 1 1 1 1 1 1 1 1 1 2 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 0 2 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 2 2 2 0 2 2 2 2 2 2 0 0 2 2 2 2 0 2 0 2 0 2 2 0 0 2 2 2 2 2 0 2 2 2 2 0 2 2 2 0 2 2 2 0 2 2 0] [[ 0.66567601 -0.3316042 ] [-2.64241546 0.19088505] [ 2.34652659 0.27393856]]
h = 0.02
x_min, x_max = iris_real['pca_x'].min() - 1, iris_real['pca_x'].max() + 1
y_min, y_max = iris_real['pca_y'].min() - 1, iris_real['pca_y'].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
plt.figure(1, figsize = (15, 7))
plt.clf() # 플롯의 모든 것을 지움
Z = Z.reshape(xx.shape)
# 이미지 출력하기
plt.imshow(Z, interpolation = 'nearest', extent = (xx.min(), xx.max(), yy.min(), yy.max()),
cmap = 'Pastel1', aspect = 'auto', origin = 'lower' )
plt.scatter ( x = 'pca_x', y = 'pca_y', data = iris_real, c = label, s = 200)
plt.scatter( x = centroid[:, 0], y = centroid[: , 1], s = 300, c = 'red', alpha = 0.5)
plt.show()
# 시각화 결과
model2 = KMeans(n_clusters = 4, init='k-means++', n_init = 10, max_iter=300,random_state=2020)
model2.fit(iris_real.values)
label2 = model2.labels_
centroid2 = model2.cluster_centers_
print(label)
print(centroid)
[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 1 1 1 1 1 1 1 1 1 2 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 0 2 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 2 2 2 0 2 2 2 2 2 2 0 0 2 2 2 2 0 2 0 2 0 2 2 0 0 2 2 2 2 2 0 2 2 2 2 0 2 2 2 0 2 2 2 0 2 2 0] [[ 0.66567601 -0.3316042 ] [-2.64241546 0.19088505] [ 2.34652659 0.27393856]]
h = 0.02
x_min, x_max = iris_real['pca_x'].min() - 1, iris_real['pca_x'].max() + 1
y_min, y_max = iris_real['pca_y'].min() - 1, iris_real['pca_y'].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
Z2 = model2.predict(np.c_[xx.ravel(), yy.ravel()])
plt.figure(1, figsize = (15, 7))
plt.clf() # 플롯의 모든 것을 지움
Z2 = Z2.reshape(xx.shape)
# 이미지 출력하기
plt.imshow(Z2, interpolation = 'nearest', extent = (xx.min(), xx.max(), yy.min(), yy.max()),
cmap = 'Paired', aspect = 'auto', origin = 'lower' )
plt.scatter ( x = 'pca_x', y = 'pca_y', data = iris_real, c = label2, s = 200)
plt.scatter( x = centroid2[:, 0], y = centroid2[: , 1], s = 300, c = 'red', alpha = 0.5)
plt.show()
군집별로 데이터의 개수가 비슷하게 들어갔는지, 실루엣 계수가 전부 평균 언저리에 위치하는지 두 조건을 모두 만족한다면 전체적인 실루엣 계수가 더 높은 군집화를 선택하는 것이 좋다!!
군집별 평균 실루엣 계수의 시각화를 통한 군집 개수 최적화 방법¶
전체 데이터의 평균 실루엣 계수 값이 높다고 해서 반드시 최적의 군집 개수로 군집화가 잘 됐다고 볼 수는 없다. 특정 군집 내의 실루엣 계수 값만 너무 높고, 다른 군집은 내부 데이터끼리의 거리가 너무 떨어져 있어 실루엣 계수 값이 낮아져도 평균적으로 높은 값을 가질 수 있다. 개별 군집별로 적당히 분리된 거리를 유지하면서도 군집 내의 데이터가 서로 뭉쳐 있는 경우에 K-평균의 적절한 군집 개수가 설정됐다고 판단할 수 있다.
< 참고 자료 >
https://scikit-learn.org/stable/auto_examples/cluster/plot_kmeans_silhouette_analysis.html
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 numpy as np
# Generating the sample data from make_blobs
# This particular setting has one distinct cluster and 3 clusters placed close
# together.
X, y = make_blobs(
n_samples=500,
n_features=2,
centers=4,
cluster_std=1,
center_box=(-10.0, 10.0),
shuffle=True,
random_state=1,
) # For reproducibility
range_n_clusters = [2, 3, 4, 5, 6]
for n_clusters in range_n_clusters:
# Create a subplot with 1 row and 2 columns
fig, (ax1, ax2) = plt.subplots(1, 2)
fig.set_size_inches(18, 7)
# The 1st subplot is the silhouette plot
# The silhouette coefficient can range from -1, 1 but in this example all
# lie within [-0.1, 1]
ax1.set_xlim([-0.1, 1])
# The (n_clusters+1)*10 is for inserting blank space between silhouette
# plots of individual clusters, to demarcate them clearly.
ax1.set_ylim([0, len(X) + (n_clusters + 1) * 10])
# Initialize the clusterer with n_clusters value and a random generator
# seed of 10 for reproducibility.
clusterer = KMeans(n_clusters=n_clusters, random_state=10)
cluster_labels = clusterer.fit_predict(X)
# The silhouette_score gives the average value for all the samples.
# This gives a perspective into the density and separation of the formed
# clusters
silhouette_avg = silhouette_score(X, cluster_labels)
print(
"For n_clusters =",
n_clusters,
"The average silhouette_score is :",
silhouette_avg,
)
# Compute the silhouette scores for each sample
sample_silhouette_values = silhouette_samples(X, cluster_labels)
y_lower = 10
for i in range(n_clusters):
# Aggregate the silhouette scores for samples belonging to
# cluster i, and sort them
ith_cluster_silhouette_values = sample_silhouette_values[cluster_labels == i]
ith_cluster_silhouette_values.sort()
size_cluster_i = ith_cluster_silhouette_values.shape[0]
y_upper = y_lower + size_cluster_i
color = cm.nipy_spectral(float(i) / n_clusters)
ax1.fill_betweenx(
np.arange(y_lower, y_upper),
0,
ith_cluster_silhouette_values,
facecolor=color,
edgecolor=color,
alpha=0.7,
)
# Label the silhouette plots with their cluster numbers at the middle
ax1.text(-0.05, y_lower + 0.5 * size_cluster_i, str(i))
# Compute the new y_lower for next plot
y_lower = y_upper + 10 # 10 for the 0 samples
ax1.set_title("The silhouette plot for the various clusters.")
ax1.set_xlabel("The silhouette coefficient values")
ax1.set_ylabel("Cluster label")
# The vertical line for average silhouette score of all the values
ax1.axvline(x=silhouette_avg, color="red", linestyle="--")
ax1.set_yticks([]) # Clear the yaxis labels / ticks
ax1.set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
# 2nd Plot showing the actual clusters formed
colors = cm.nipy_spectral(cluster_labels.astype(float) / n_clusters)
ax2.scatter(
X[:, 0], X[:, 1], marker=".", s=30, lw=0, alpha=0.7, c=colors, edgecolor="k"
)
# Labeling the clusters
centers = clusterer.cluster_centers_
# Draw white circles at cluster centers
ax2.scatter(
centers[:, 0],
centers[:, 1],
marker="o",
c="white",
alpha=1,
s=200,
edgecolor="k",
)
for i, c in enumerate(centers):
ax2.scatter(c[0], c[1], marker="$%d$" % i, alpha=1, s=50, edgecolor="k")
ax2.set_title("The visualization of the clustered data.")
ax2.set_xlabel("Feature space for the 1st feature")
ax2.set_ylabel("Feature space for the 2nd feature")
plt.suptitle(
"Silhouette analysis for KMeans clustering on sample data with n_clusters = %d"
% n_clusters,
fontsize=14,
fontweight="bold",
)
plt.show()
For n_clusters = 2 The average silhouette_score is : 0.7049787496083262 For n_clusters = 3 The average silhouette_score is : 0.5882004012129721 For n_clusters = 4 The average silhouette_score is : 0.6505186632729437 For n_clusters = 5 The average silhouette_score is : 0.56376469026194 For n_clusters = 6 The average silhouette_score is : 0.4504666294372765
x축은 실루엣 계수 값, y축은 개별 군집과 이에 속하는 데이터, 점선은 전체 평균 실루엣 계수 값
- 군집의 개수 = 2, 전체 데이터의 평균 실루엣 계수인 silhouette_score가 높게 나타났다. 하지만 1번 군집의 모든 데이터는 모두 평균 실루엣 계수 값 이상이지만, 2번 군집의 경우에는 평균보다 적은 데이터 값이 매우 많다. 또한 scatter plot을 봤을 때에도 1번 군집의 경우에는 0번 군집과 멀리 떨어져 있고, 내부 데이터끼리도 잘 뭉쳐있지만 0번 군집의 경우에는 내부 데이터끼리 많이 떨어져 있는 모습이다.
- 군집의 개수 = 3, 0번 군집이 전체 데이터의 평균 실루엣 계수보다 낮은 실루엣 계수 값을 가진다. 또한 scatter plot을 보면 0번 군집은 내부 데이터 간의 거리도 멀고, 2번 군집과도 가깝게 위치하고 있는 모습을 보인다.
- 군집의 개수 = 4, 개별 군집의 평균 실루엣 계수 값이 비교적 균일하게 위치하고 있다. 0, 2, 3번 군집에서 조금씩 평균보다 낮은 계수를 가지고 있고, 군집이 2개인 경우보다는 평균 실루엣 계수 값이 작지만 이상적인 군집화 개수로 판단할 수 있다.
### 여러개의 클러스터링 갯수를 List로 입력 받아 각각의 실루엣 계수를 면적으로 시각화한 함수 작성
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="--")
# make_blobs 을 통해 clustering 을 위한 4개의 클러스터 중심의 500개 2차원 데이터 셋 생성
from sklearn.datasets import make_blobs
X, y = make_blobs(n_samples=500, n_features=2, centers=4, cluster_std=1, \
center_box=(-10.0, 10.0), shuffle=True, random_state=1)
# cluster 개수를 2개, 3개, 4개, 5개 일때의 클러스터별 실루엣 계수 평균값을 시각화
visualize_silhouette([ 2, 3, 4, 5 ], X)
# 붓꽃 데이터
from sklearn.datasets import load_iris
iris=load_iris()
visualize_silhouette([ 2, 3, 4, 5 ], iris.data)
실루엣 계수를 통한 K-means 군집 평가 방법은 데이터 양이 늘어날 경우 수행시간이 크게 늘어난다. 개인용 PC에서 수행할 경우 메모리 부족 등의 에러가 발생하기 쉬운데, 이 경우 군집별로 임의의 데이터를 샘플링해 실루엣 계수를 평가하는 방안을 고민해야 한다.
03. 평균 이동¶
평균 이동이란? : K-평균과 유사하게 중심을 군집의 중심으로 지속적으로 움직이면서 군집화를 수행하지만 k-평균이 중심에 소속된 데이터의 평균 거리 중심으로 이동하는 것과 달리 데이터가 모여 있는 밀도가 가장 높은 곳으로 중심을 이동시키는 것을 의미한다. 평균 이동 군집화는 데이터의 분포도를 이용해 군집 중심점을 찾고 이를 위해 확률 밀도 함수를 이용한다. 가장 집중적으로 데이터가 모여있어 확률 밀도 함수가 피크인 점을 군집 중심점으로 선정하며 일반적으로 주어진 모델의 확률 밀도 함수를 찾기 위해서 KDE를 이용한다. 특정 데이터를 반경 내의 데이터 분포 확률 밀도가 가장 높은 곳으로 이동하기 위해 주변 데이터와의 거리 값을 KDE 함수 값으로 입력한 뒤 그 반환 값을 현재 위치에서 업데이트 하면서 이동하는 방식을 취하고, 이러한 방식을 전체 데이터에 반복적으로 적용하면서 데이터의 군집 중심점을 찾아낸다.
- 개별 데이터의 특정 반경 내에 주변 데이터를 포함한 데이터 분포도를 KDE 기반의 Mean Shift 알고리즘으로 계산
- KDE로 계산된 데이터 분포도가 높은 방향으로 데이터 이동
- 모든 데이터를 1 ~ 2까지 수행하면서 데이터를 이동하고 개별 데이터들이 군집 중심점으로 모임
- 지정된 반복 횟수만큼 전체 데이터에 대해서 KDE 기반으로 데이터를 이동시키면서 군집화 수행
- 개별 데이터들이 모인 중심점을 군집 중심점으로 설정
< 참고 자료 > https://bab2min.tistory.com/637
KDE(Kernel Density Estimation)는 커널 함수를 통해 어떤 변수의 확률 밀도 함수를 추정하는 대표적인 방법이다. 관측된 데이터 각각에 커널 함수를 적용한 값을 모두 더한 뒤 데이터 건수로 나눠 확률 밀도 함수를 추정한다. 확률 밀도 함수의 PDF(Probability Density Function)는 확률 변수의 분포를 나타내는 함수로, 널리 알려진 정규분포 함수, 감마 분포, t-분포 등이 있다. 확률 밀도 함수를 알면 특정 변수가 어떤 값을 갖게 될지에 대한 확률을 알게 되므로 이를 통해 변수의 특성, 확률 분포 등 변수의 많은 요소를 알 수가 있다.
KDE는 개별 관측 데이터에 커널 함수를 적용한 뒤, 이 적용 값을 모두 더한 후 개별 관측 데이터의 건수로 나눠 확률 밀도 함수를 추정하며, 대표적인 커널 함수로서 가우시안 분포 함수를 사용한다.
$$가우시안 커널 : K(u) = \frac{1}{\sqrt{2 \pi }} e^{-u^2/2}$$$$KDE = \frac{1}{nh} \sum_{i=1}^n K\big( \frac{x-x_i}{h} \big) $$K는 커널 함수, x는 확률 변수 값, xi는 관측값, h는 대역폭이다. 대역폭 h는 KDE 형태를 부드러운 형태로 평활화 하는데 적용되며, 이 h를 어떻게 설정하느냐에 따라 확률 밀도 추정 성능을 크게 좌우할 수 있다. h가 작을 경우 좁고 뾰족한 KDE를 가지게 되며, 이는 변동성이 큰 방식으로 확률 밀도 함수를 추정하므로 과적합되기 쉽다. 반대로 h가 매우 클 경우에는 과도하게 평활화되어 KDE로 인해 지나치게 단순화된 방식으로 확률 밀도 함수를 추정하며 과소적합 될 가능성이 높다. 따라서 적절한 KDE의 대역폭 h를 계산하는 것이 KDE 기반의 평균 이동 군집화에서 매우 중요하다.
이산 데이터에서는 값이 모두 다르기 때문에 최빈값을 구하는 것이 어려워 구간을 설정해서 해당 구간에 위치한 값들을 세는 방식의 히스토그램으로 전체 데이터 분포를 파악한다. 하지만 이렇게 되면 비는 구간에 값이 0이 되기 때문에 이를 좀더 유하게 봐주어 전체적인 분포를 Smooth하게 나타내는 방식이 KDE이다. 따라서 얼마나 유하게 봐줄지를 h로 설정해주고 이에 따른 분포를 파악한다.
일반적으로 평균 이동 군집화는 대역폭이 클수록 KDE가 평활화되어 적은 수의 군집 중심점을 가지며 대역폭이 적을수록 많은 수의 군집 중심점을 가진다. 또한 평균 이동 군집화는 군집의 개수를 지정하지 않고 오직 대역폭의 크기에 따라 군집화를 수행한다. 대역폭 크기 설정이 군집화의 품질에 큰 영향을 미치기 때문에 사이킷런은 최적의 대역폭 계산을 위해 estimate_bandwidth() 함수를 제공한다.
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) # 3개의 군집 데이터
meanshift= MeanShift(bandwidth=0.8)
cluster_labels = meanshift.fit_predict(X)
print('cluster labels 유형:', np.unique(cluster_labels))
cluster labels 유형: [0 1 2 3 4 5]
군집이 6개로 지나치게 세분화돼 군집화되었다
meanshift= MeanShift(bandwidth=1) # bandwidth를 살짝 높였다.
cluster_labels = meanshift.fit_predict(X)
print('cluster labels 유형:', np.unique(cluster_labels))
cluster labels 유형: [0 1 2]
# bandwidth를 최적화 값으로 설정하기 위해 값을 찾음
from sklearn.cluster import estimate_bandwidth
bandwidth = estimate_bandwidth(X)
print('bandwidth 값:', round(bandwidth,3))
bandwidth 값: 1.816
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))
cluster labels 유형: [0 1 2]
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()
# target값과 군집 label 값을 비교
print(clusterDF.groupby('target')['meanshift_label'].value_counts())
target meanshift_label 0 0 67 1 1 67 2 2 66 Name: meanshift_label, dtype: int64
평균 이동의 장점은 데이터 세트의 형태를 특정 형태로 가정하거나, 특정 분포도 기반의 모델로 가정하지 않기 때문에 좀 더 유연한 군집화가 가능하다는 것이다. 또한 이상치의 영향력도 크지 않으며 미리 군집의 개수를 정할 필요도 없다. 하지만 최적 군집의 개수를 찾기 위한 알고리즘의 수행 시간이 오래 걸리고 bandwidth의 크기에 따른 군집화 영향도가 매우 크다. 이러한 특징들 때문에 일반적으로 분석 업무 기반의 데이터 세트보다는 컴퓨터 비전 영역에서 더 많이 사용된다. 이미지나 영상 데이터에서 특정 개체를 구분하거나 움직임을 추적하는 데 뛰어난 역할을 수행하는 알고리즘이다.
04. GMM(Gaussian Mixture Model)¶
GMM 이란? : 군집화를 적용하고자 하는 데이터가 여러 개의 가우시안 분포를 가진 데이터 집합들이 섞여서 생성된 것이라는 가정하에 군집화를 수행하는 방식이다. ( 가우시안 분포 = 정규 분포 )
GMM은 데이터를 여러 개의 가우시안 분포가 섞인 것으로 간주해 섞인 데이터 분포에서 개별 유형의 가우시안 분포를 추출한다. 여러 개의 데이터 세트가 있을 때 이를 구성하는 여러 개의 정규 분포 곡선을 추출하고, 개별 데이터가 이 중 어떤 정규 분포에 속하는지를 결정한다. 이와 같은 방식을 모수 추정이라고 하는데 모수 추정은 대표적으로 다음과 같은 2가지를 추정한다.
- 개별 정규 분포의 평균과 분산
- 각 데이터가 어떤 정규 분포에 해당되는지의 확률
이러한 모수 추정을 위해 GMM은 EM(Expectation and Maximization) 방법을 적용한다. 사이킷런에서는 GaussianMixture클래스로 GMM의 EM 방식을 통한 모수 추정 군집화를 지원한다.
< GMM과 EM에 대한 참고자료 >
https://angeloyeo.github.io/2021/02/08/GMM_and_EM.html
GMM을 이용한 붓꽃 데이터 세트 군집화¶
GMM = 확률 기반 군집화
K-means = 거리 기반 군집화
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-means의 n_clusters와 같이 군집의 개수를 정하는 데 중요한 역할을 수행한다.
from sklearn.mixture import GaussianMixture
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)
target gmm_cluster 0 0 50 1 2 45 1 5 2 1 50 Name: gmm_cluster, dtype: int64
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)
target kmeans_cluster 0 1 50 1 0 48 2 2 2 2 36 0 14 Name: kmeans_cluster, dtype: int64
K-means보다 군집화가 잘 된 모습, 붓꽃 데이터 세트가 GMM 군집화에 더 효과적이라는 의미이다. K-means는 개별 군집 내의 데이터가 원형으로 흩어져 있는 경우에 효과적!
GMM과 K-means 비교¶
K-means는 원형의 범위에서 군집화를 수행하기 때문에 데이터 세트가 원형의 범위를 가질수록 군집화 효율이 높아진다. ( make_blobs()에서 cluster_std를 작게 설정하면 데이터가 원형 형태로 분산될 수 있다 ) K-means는 데이터가 길쭉한 타원형으로 늘어선 경우에 군집화를 잘 수행하지 못한다.
visualize_cluster_plot 인자 설명
- clusterobj : 사이킷런의 군집 수행 객체, KMeans나 GaussianMixture의 fit과 predict로 군집화를 완료한 객체
- dataframe : 피처데이터와 label 값을 가진 DataFrame
- label_name : 군집화의 결과를 시각화 할 경우 dataframe 내의 군집화 label 칼럼 명, make_blobs()의 결과를 시각화 하는 경우는 target 칼럼 명
- iscenter : 사이킷런 Cluster객체가 군집 중심 좌표를 제공하면 True
### 클러스터 결과를 담은 DataFrame과 사이킷런의 Cluster 객체등을 인자로 받아 클러스터링 결과를 시각화하는 함수
def visualize_cluster_plot(clusterobj, dataframe, label_name, iscenter=True):
if iscenter :
centers = clusterobj.cluster_centers_
unique_labels = np.unique(dataframe[label_name].values)
markers=['o', 's', '^', 'x', '*']
isNoise=False
for label in unique_labels:
label_cluster = dataframe[dataframe[label_name]==label]
if label == -1:
cluster_legend = 'Noise'
isNoise=True
else :
cluster_legend = 'Cluster '+str(label)
plt.scatter(x=label_cluster['ftr1'], y=label_cluster['ftr2'], s=70,\
edgecolor='k', marker=markers[label], label=cluster_legend)
if iscenter:
center_x_y = centers[label]
plt.scatter(x=center_x_y[0], y=center_x_y[1], s=250, 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)
if isNoise:
legend_loc='upper center'
else: legend_loc='upper right'
plt.legend(loc=legend_loc)
plt.show()
from sklearn.datasets import make_blobs
# make_blobs() 로 300개의 데이터 셋, 3개의 cluster 셋, cluster_std=0.5 을 만듬.
X, y = make_blobs(n_samples=300, n_features=2, centers=3, cluster_std=0.5, random_state=0)
# 길게 늘어난 타원형의 데이터 셋을 생성하기 위해 변환함.
transformation = [[0.60834549, -0.63667341], [-0.40887718, 0.85253229]]
X_aniso = np.dot(X, transformation)
# feature 데이터 셋과 make_blobs( ) 의 y 결과 값을 DataFrame으로 저장
clusterDF = pd.DataFrame(data=X_aniso, columns=['ftr1', 'ftr2'])
clusterDF['target'] = y
# 생성된 데이터 셋을 target 별로 다른 marker 로 표시하여 시각화 함.
visualize_cluster_plot(None, clusterDF, 'target', iscenter=False)
# 3개의 Cluster 기반 Kmeans 를 X_aniso 데이터 셋에 적용
kmeans = KMeans(3, random_state=0)
kmeans_label = kmeans.fit_predict(X_aniso)
clusterDF['kmeans_label'] = kmeans_label
visualize_cluster_plot(kmeans, clusterDF, 'kmeans_label',iscenter=True)
# 3개의 n_components기반 GMM을 X_aniso 데이터 셋에 적용
gmm = GaussianMixture(n_components=3, random_state=0)
gmm_label = gmm.fit(X_aniso).predict(X_aniso)
clusterDF['gmm_label'] = gmm_label
# GaussianMixture는 cluster_centers_ 속성이 없으므로 iscenter를 False로 설정.
visualize_cluster_plot(gmm, clusterDF, 'gmm_label',iscenter=False)
print('### KMeans Clustering ###')
print(clusterDF.groupby('target')['kmeans_label'].value_counts())
print('\n### Gaussian Mixture Clustering ###')
print(clusterDF.groupby('target')['gmm_label'].value_counts())
### KMeans Clustering ### target kmeans_label 0 2 73 0 27 1 1 100 2 0 86 2 14 Name: kmeans_label, dtype: int64 ### Gaussian Mixture Clustering ### target gmm_label 0 2 100 1 1 100 2 0 100 Name: gmm_label, dtype: int64
GMM이 군집화가 더 잘 진행된 것을 확인할 수 있다.
05. DBSCAN ( Density Based Spatial Clustering of Applications with Noise )¶
DBSCAN이란? : 밀도 기반 군집화의 대표적인 알고리즘으로 간단하고 직관적으로 되어 있음에도 데이터의 분포가 기하학적으로 복잡한 데이터세트일 경우에도 효과적으로 군집화가 가능하다. ( ex. 내부의 원, 외부의 원 모양 형태의 분포를 가진 데이터 세트를 군집화할 때 ) 특정 공간 내에 데이터 밀도 차이를 기반 알고리즘으로 하고 있어서 복잡한 기하학적 분포도를 가진 데이터 세트에 대해서도 군집화를 잘 수행한다.
< 참고자료 >
https://bcho.tistory.com/1205
DBSCAN을 구성하는 중요한 두 가지 파라미터
- 입실론 주변 영역(epsilon) : 개별 데이터를 중심으로 입실론 반경을 가지는 원형의 영역
- 최소 데이터 개수(min points) : 개별 데이터의 입실론 주변 영역에 포함되는 타 데이터의 개수
입실론 주변 영역 내에 포함되는 최소 데이터 개수를 충족시키는지, 아닌지에 따라 데이터 포인트를 다음과 같이 정의한다.
- 핵심 포인트(Core Point) : 주변 영역 내에 최소 데이터 개수 이상의 타 데이터를 가지고 있을 경우 해당 데이터를 핵심 포인트라고 한다.
- 이웃 포인트(Neighbor Point) : 주변 영역 내에 위치한 타 데이터를 이웃 포인트라고 한다.
- 경계 포인트(Border Point) : 주변 영역 내에 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지 않지만 핵심 포인트를 이웃 포인트로 가지고 있는 데이터를 경계 포인트라고 한다.
- 잡음 포인트(Noise Point) : 최소 데이터 개수 이상의 이웃 포인트를 가지고 있지 않으며, 핵심 포인트도 이웃 포인트로 가지고 있지 않는 데이터를 잡음 포인트라고 한다.
핵심 포인트의 이웃 포인트가 역시 핵심 포인트일 경우 직접 접근이 가능하다. 직접 접근이 가능한 핵심 포인트들을 연결하면서 군집화를 구성하고 군집 영역을 확장해 나가는 것이 DBSCAN 군집화 방식이다. 경계 포인트는 군집의 외곽을 형성한다. 이와 같이 DBSCAN은 입실론 주변 영역의 최소 데이터 개수를 포함하는 밀도 기준을 충족시키는 데이터인 핵심 포인트를 연결하면서 구집화를 구성한다. 사이킷런은 DBSCAN 클래스로 DBSCAN 알고리즘을 지원하고 이때 2가지 초기화 파라미터를 가진다.
- eps : 입실론 주변 영역의 반경
- min_samples : 핵심 포인트가 되기 위해 입실론 주변 영역 내에 포함되어야 할 데이터의 최소 개수 (자신의 데이터 포함)
DBSCAN 적용하기 - 붓꽃 데이터 세트¶
from sklearn.datasets import load_iris
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
일반적으로 eps 값으로는 1 이하의 값을 설정한다
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)
target dbscan_cluster 0 0 49 -1 1 1 1 46 -1 4 2 1 42 -1 8 Name: dbscan_cluster, dtype: int64
0과 1외에 특이하게 -1이 군집 레이블로 있는 것을 확인할 수 있다. 군집 레이블이 01인 것은 노이즈에 속하는 군집을 의미한다. 위의 경우에는 0과 1 두 개의 군집으로 군집화 되었는데 DBSCAN은 군집의 개수를 알고리즘에 따라 자동으로 지정하므로 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)
피처 하나의 분포에 군집화를 적용할 경우 Noise 값을 이상치로 치부하고 삭제할 수 있을까??
# eps를 증가시켜 노이즈 데이터의 개수를 줄인 모습
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)
target dbscan_cluster 0 0 50 1 1 50 2 1 47 -1 3 Name: dbscan_cluster, dtype: int64
# min_samples를 늘림 -> 노이즈 데이터가 기존보다 많이 증가함
dbscan = DBSCAN(eps=0.6, min_samples=16, 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)
target dbscan_cluster 0 0 48 -1 2 1 1 44 -1 6 2 1 36 -1 14 Name: dbscan_cluster, dtype: int64
DBSCAN 적용하기 - make_circles() 데이터 세트¶
from sklearn.datasets import make_circles # 내부 원과 외부 원으로 구분되는 데이터 세트를 생성한다.
# noise는 노이즈 데이터 세트의 비율, factor는 외부 원과 내부 원의 scale 비율
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)
# 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( ) 데이터 셋을 클러스터링 수행.
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)
# 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)
'머신러닝(Machine Learning)' 카테고리의 다른 글
[파이썬 머신러닝 완벽 가이드] 8장. 텍스트 분석 - 1 (0) | 2022.02.07 |
---|---|
[파이썬 머신러닝 완벽 가이드] 6장. 차원 축소 (0) | 2022.01.30 |
[파이썬 머신러닝 완벽 가이드] 5장. 회귀 - 2 (0) | 2022.01.26 |
댓글