군집화란?
이전 내용
GMM(Gaussian Mixture Model)
- GMM 군집화: 군집화를 적용하고자 하는 데이터가 여러 개의 가우시안 분포(GaussianDistribution) 를 가진 데이터 집합들이 섞여서 생성된 것이라는 가정하에 군집화를 수행하는 방식으로, 가우스 혼합 모델은 샘플이 파라미터가 알려지지 않은 여러 개의 혼합된 가우시안 분포에서 생성되었다고 가정하는 확률 모델이다.
하나의 가우시안 분포에서 생성된 모든 샘플은 하나의 클러스터를 형성하며, 일반적으로 이 클러스터는 타원형이다. 각 클러스터는 타원의 모양, 크기, 밀집도, 방향이 다르며, 샘플이 주어지면 가우시안 분포 중 하나에서 생성되었다는 것은 알지만 어떤 분포인지 또 이 분포의 파라미터는 무엇인지 알지 못한다.
※ 가우시안(또는 가우스) 분포: 정규 분포로도 알려진 이 분포는 좌우 대칭형의 종 형태를 가진 통계학에서 가장 잘 알려진 연속 확률 함수이다. ▶ 이 글에서는 가우시안, 가우스를 혼용하여 작성.
[정규 분포 관련 글]
GMM은 데이터를 여러 개의 가우시안 분포가 섞인 것으로 간주하며, 섞인 데이터 분포에서 개별 유형의 가우시안 분포를 추출한다. 전체 데이터 세트는 서로 다른 정규 분포를 가진 여러 가지 확률 분포 곡선으로 구성될 수 있으며, 이러한 서로 다른 정규 분포에 기반해 군집화를 수행하는 것이 GMM 군집화 방식이다. 예를 들어 1000개의 데이터 세트가 있다면 이를 구성하는 여러 개의 장규 분포 곡선을 추출하고, 개별 데이터가 이 중 어떤 정규 분포에 속하는지 결정하는 방식이다.
이와 같은 방식을 GMM에서는 모수 추정이라고 하는데, 모수 추정은 대표적으로 2가지를 추정하는 것이다.
- 개별 정규 분포의 평균과 분산
- 각 데이터가 어떤 정규 분포에 해당되는지의 확률
모수 추정을 위해 GMM은 EM(Expectation and Maximization) 방법을 적용한다.
※ 기댓값 최대화 알고리즘(expectation-maximization algorithm, 약자 EM 알고리즘): 관측되지 않는 잠재변수에 의존하는 확률 모델에서 최대가능도(maximum likelihood)나 최대사후확률(maximum a posteriori, 약자 MAP)을 갖는 모수의 추정값을 찾는 반복적인 알고리즘이다. EM 알고리즘은 모수에 관한 추정값으로 로그가능도(log likelihood)의 기댓값을 계산하는 기댓값 (E) 단계와 이 기댓값을 최대화하는 모수 추정값들을 구하는 최대화 (M) 단계를 번갈아가면서 적용한다. 최대화 단계에서 계산한 변수값은 다음 기댓값 단계의 추정값으로 쓰인다.
[작동 방식]
GMM 알고리즘은 클러스터 파라미터를 랜덤하게 초기화하고 수렴할 때까지 두 단계를 반복한다.
- 기댓값 단계: 샘플을 클러스터에 할당하며 현재 클러스터 파라미터에 기반하여 각 클러스터에 속할 확률을 예측.
- 최대화 단계: 각 클러스터가 데이터셋에 있는 모든 샘플을 사용해 업데이트되며, 클러스터에 속할 추정 확률로 샘플에 가중치 적용. 클러스터 업데이트는 책임이 가장 많음 샘플에 크게 영향 받음 (클러스터의 책임: 클러스터에 속할 추정 확률)
사이킷런에서의 GMM
GMM은 여러 번형이 있으며, 가장 간단한 버전이 sklearn.mixture 패키지의 GaussianMixture 클래스에 구현되어 있다.
from sklearn.mixture import GaussianMixture
gm = GaussianMixture(n_components=3, n_init=10, random_state=42)
gm.fit(X)
이 알고리즘이 추정한 파라미터를 확인해 본다.
# 클러스터 가중치
gm.weights_
# 평균
gm.means_
# 분산 행렬
gm.covariances_
알고리즘이 수렴했는지 여부와 반복 횟수 확인
gm.converged_
gm.n_iter_
하드 군집 / 소프트 군집
- 하드 군집: 새로운 샘플을 가장 비슷한 클러스터에 할당. predict() 메서드 사용
- 소프트 군집: 새로운 샘플이 특정 클러스터에 속할 확률 예측. predict_proba() 메서드 사용
gm.predict(X)
gm.predict_proba(X).round(3)
가우스 혼합은 생성 모델로서, 이 모델에서 새로운 샘플을 만들 수 있다.
반환된 샘플은 클러스터 인덱스 순으로 정렬되어 있다.
X_new, y_new = gm.sample(6)
X_new
y_new
또한 score_sample() 메서드를 사용하여 주어진 위치에서 모델의 밀도를 추정할 수 있다. 샘플이 주어지면 이 메서드는 그 위치의 확률 밀도 함수(PDF)의 로그를 예측하며, 점수가 높을수록 밀도가 높다.
이 점수의 지숫값을 계산하면 샘플의 위치에서 PDF 값을 얻을 수 있는데, 이 값은 확률 밀도로 0에서 1이 아닌 어떤 양숫값도 될 수 있다.
gm.score_samples(X).round(2)
아래는 이 모델의 클러스터 평균, 결정 경계, 밀도 등고선을 보여주는 코드이다.
from matplotlib.colors import LogNorm
def plot_gaussian_mixture(clusterer, X, resolution=1000, show_ylabels=True):
mins = X.min(axis=0) - 0.1
maxs = X.max(axis=0) + 0.1
xx, yy = np.meshgrid(np.linspace(mins[0], maxs[0], resolution),
np.linspace(mins[1], maxs[1], resolution))
Z = -clusterer.score_samples(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(xx, yy, Z,
norm=LogNorm(vmin=1.0, vmax=30.0),
levels=np.logspace(0, 2, 12))
plt.contour(xx, yy, Z,
norm=LogNorm(vmin=1.0, vmax=30.0),
levels=np.logspace(0, 2, 12),
linewidths=1, colors='k')
Z = clusterer.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contour(xx, yy, Z,
linewidths=2, colors='r', linestyles='dashed')
plt.plot(X[:, 0], X[:, 1], 'k.', markersize=2)
plot_centroids(clusterer.means_, clusterer.weights_)
plt.xlabel("$x_1$")
if show_ylabels:
plt.ylabel("$x_2$", rotation=0)
else:
plt.tick_params(labelleft=False)
plt.figure(figsize=(8, 4))
plot_gaussian_mixture(gm, X)
plt.show()
실제 데이터는 이와는 달리 가우스 분포나 저차원이 아닌 경우가 많고, 올바른 클러스터 개수를 모르는 경우가 많다.
특성이나 클러스터가 많거나 샘플이 적을 때는 EM이 최적의 솔루션으로 수렴하기 어려운데, 이런 작업의 어려움을 줄이려면 알고리즘이 학습할 파라미터 개수를 제한해야 한다.
그 중 한 가지는 공분산 행렬에 제약을 추가하여 클러스터의 모양과 방향의 범위를 제한하는 것인데, 사이킷런에서는 covariance_type 매개변수에 아래 중 하나를 추가하면 된다.
(covariance_type 매개변수의 기본값은 'full'로, 각 클러스터는 모양, 크기, 방향에 제약이 없다)
- spherical: 모든 클러스터가 원형. 지름(분산)은 다를 수 있음.
- diag: 클러스터는 크기에 상관없이 어떤 타원형도 가능. 타원의 축은 좌표축과 나란해야 함. (공분산 행렬이 대각 행렬)
- tied: 모든 클러스터가 동일한 타원 모양, 크기, 방향을 가짐(모든 클러스터는 동일한 공분산 행렬을 공유)
covariance_type 별 가우스 혼합 비교 이미지
# Gaussian Mixture 모델 정의
gm_full = GaussianMixture(n_components=3, n_init=10,
covariance_type="full", random_state=42)
gm_tied = GaussianMixture(n_components=3, n_init=10,
covariance_type="tied", random_state=42)
gm_spherical = GaussianMixture(n_components=3, n_init=10,
covariance_type="spherical", random_state=42)
gm_diag = GaussianMixture(n_components=3, n_init=10,
covariance_type="diag", random_state=42)
# 모델 학습
gm_full.fit(X)
gm_tied.fit(X)
gm_spherical.fit(X)
gm_diag.fit(X)
# Gaussian Mixture 모델 비교 함수 정의
def compare_gaussian_mixtures(gm1, gm2, gm3, gm4, X):
plt.figure(figsize=(12, 10))
plt.subplot(221)
plot_gaussian_mixture(gm1, X)
plt.title(f'covariance_type="{gm1.covariance_type}"')
plt.subplot(222)
plot_gaussian_mixture(gm2, X)
plt.title(f'covariance_type="{gm2.covariance_type}"')
plt.subplot(223)
plot_gaussian_mixture(gm3, X)
plt.title(f'covariance_type="{gm3.covariance_type}"')
plt.subplot(224)
plot_gaussian_mixture(gm4, X)
plt.title(f'covariance_type="{gm4.covariance_type}"')
# 함수 호출
compare_gaussian_mixtures(gm_full, gm_tied, gm_spherical, gm_diag, X)
# 그림 출력
plt.show()
◆ 가우스 혼합을 사용한 이상치 탐지
가우스 혼합을 이상치 탐지에 사용 시, 밀도가 낮은 지역에 있는 모든 샘플을 이상치로 볼 수 있다.
이렇게 하려면 사용할 밀도 임곗값을 정해야 하는데, 예를 들어 결함 제품의 비율이 2%라고 가정하고 밀도 임곗값을 이 값으로 설정하면 밀도가 낮은 지역에 있는 샘플의 2%를 얻을 수 있다.
- 완벽하게 정상인 제품이 결합으로 표시되는 거짓 양성이 너무 많다면 임곗값을 더 낮춤
- 결합 제품이 결합으로 표시되지 않는 거짓 음성이 너무 많다면 임곗값을 더 높임.
여기서는 2번째 백분위수(즉, 하위 2%)를 밀도 임곗값으로 사용하여 이상치를 구분하고 이를 시각화해본다.
densities = gm.score_samples(X)
density_threshold = np.percentile(densities, 2)
anomalies = X[densities < density_threshold]
plt.figure(figsize=(8, 4))
plot_gaussian_mixture(gm, X)
plt.scatter(anomalies[:, 0], anomalies[:, 1], color='r', marker='*')
plt.show()
[클러스터 개수 선택 - BIC, AIC]
k-평균처럼 GaussianMixture 알고리즘의 경우에도 클러스터의 개수를 지정해야 하는데, GMM에서는 k-평균에서처럼 이서녀나 실루엣 점수를 사용하지는 못한다. 왜냐하면 이너셔나 실루엣 점수 같은 지표들은 클러스터가 타원형이거나 크기가 다를 때 안정적이지 않기 때문이다.
대신 BIC나 AIC와 같은 이론적 정보 기준을 최소화하는 모델을 찾는다.
BIC나 AIC는 모두 학습할 파라미터가 많은 모델에게 벌칙을 가하고 데이터에 잘 맞는 모델에게 보상을 더하며, 이 둘은 종종 동일한 모델을 선택한다. 만약 둘의 선택이 다를 경우 BIC가 선택한 모델이 AIC가 선택한 모델보다 파라미터가 적은 간단한 경향이 있지만 대규모 데이터셋에서는 데이터에 잘 맞지 않을 수 있다.
bic()와 aic() 메서드를 사용해 BIC와 AIC를 계산한다.
print('BIC:', gm.bic(X))
print('AIC:', gm.aic(X))
그리고 아래와 같이 여러 가지 클러스터 개수 k에 대한 BIC와 AIC를 계산하여 BIC와 AIC가 가장 최소인 k를 선택하면 된다.
베이즈 가우스 혼합 모델
위와 같이 최적의 클러스터 개수를 수동으로 찾지 않고 불필요한 클러스터의 가중치를 0 또는 0에 가깝게 만드는 BayesianGaussianMixture 클래스를 사용할 수 있다. 현재 문제에 대해 최소한의 정보를 가정하고 있다고 가정하여 클러스터 n_components를 최적의 클러스터 개수보다 크다고 믿을 만한 값으로 지정한다. 이 알고리즘은 자동으로 불필요한 클러스터를 제거한다.
예를 들어 클러스터 개수를 10으로 설정하고 결과를 확인해 보면
from sklearn.mixture import BayesianGaussianMixture
bgm = BayesianGaussianMixture(n_components=10, n_init=10, random_state=42)
bgm.fit(X)
bgm.weights_.round(2)
▶ 알고리즘이 자동으로 7개의 클러스터가 필요하다는 것을 감지했다.
가우스 혼합 모델은 타원형 클러스터에서는 잘 작동하나, 다른 모양을 가진 클러스터에서는 잘 작동하지 않는다.
예를 들어 반달 데이터셋(moons) 을 군집하기 위해 베이즈 가우스 혼합 모델을 사용하면 2개가 아닌 8개의 클러스터를 찾는다. 밀도 추정은 나쁘지 않으므로 이상치 탐지를 위해 이 모델을 사용할 수는 있으나, 두 개의 초승달을 식별하는 데는 실패했다.
GMM을 이용한 붓꽃 데이터 세트 군집화
GMM은 확률 기반 군집화이고, K-평균은 거리 기반 군집화이다. 붓꽃 데이터 세트로 이 두 가지 방식을 이용해 군집화를 수행한 뒤 양쪽 방식을 비교해 본다.
GaussianMixture 객체의 가장 중요한 초기화 파라미터는 n_components이며, 이는 gaussian mixture의 모델의 총 개수이다. K-평균의 n_clusters와 같이 군집의 개수를 정하는 데 중요한 역할을 수행한다.
GaussianMixture 객체의 fit(데이터 세트)와 predict(피처 데이터 세트)를 수행해 군집을 결정한 뒤 irisDF DataFrame에 'gmm_cluster' 칼럼명으로 저장하고 나서 타깃별로 군집이 어떻게 매핑됐는지 확인.
from sklearn.datasets import load_iris
from sklearn.cluster import KMeans
from sklearn.mixture import GaussianMixture
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']
# 데이터 핸들링을 위해 DataFrame 변환
irisDF = pd.DataFrame(data=iris.data, columns=feature_names)
irisDF['target'] = iris.target
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 0은 cluster 1로, Target 2는 cluster 2로 매핑되었고, Target1은 cluster 0과 2로 분할되어 매핑되었다.
[붓꽃 데이터 세트의 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)
▶ K-평균은 평균 거리 중심으로 중심을 이동하면서 군집화를 수행하는 방식이므로 개별 군집 내의 데이터가 원형으로 흩어져 있는 경우에 매우 효과적으로 군집화가 수행될 수 있다.
GMM vs K-평균
KMeans는 원형의 범위에서 군집화를 수행하여 데이터 세트가 원형의 범위를 가질수록 군집화 효율이 더욱 높아진다. cluster_std를 작게 설정하면 데이터가 원형 형태로 분산될 수 있다.
그러나 KMeans는 원형의 범위로 퍼져 있지 않는 경우, 예를 들면 데이터가 길쭉한 타원형으로 늘어선 경우에 군집화를 잘 수행하지 못한다.
이를 시각화해보기 위해 visualize_cluster_plot 함수를 만든다. 해당 함수는 인자로 다음과 같은 값을 입력받는다.
visualize_cluster_plot(clusterobj, dataframe, label_name, iscluster=True)
- clusterobj: 사이킷런의 군집 수행 객체. KMeans나 GaussianMixture의 fit()와 predict()로 군집화를 완료한 객체. 만약 군집화 결과 시각화가 아니고 make_blobs()로 생성한 데이터의 시각화일 경우 None 입력
- dataframe: 피처 데이터 세트와 label 값을 가진 DataFrame
- label_name: 군집화 결과 시각화일 경우 dataframe 내의 군집화 label 칼럼명, make_blobs() 결과 시각화일 경우는 dataframe 내의 target 칼럼명
- iscenter: 사이킷런 Cluster 객체가 군집 중심 좌표를 제공하면 True, 그렇지 않으면 False
def visualize_cluster_plot(clusterobj, dataframe, label_name, iscenter=True):
# 군집별 중심 위치: K-Means, Mean Shift 등
if iscenter:
centers = clusterobj.cluster_centers_
# Cluster 값 종류
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()
데이터 세트를 make_blobs() 의 데이터를 변환해 타원형으로 생성해본다.
from sklearn.datasets import make_blobs
# make_blobs()로 300개의 데이터 세트, 3개의 군집 세트, 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 별로 다른 마커로 표시해 시각화
visualize_cluster_plot(None, clusterDF, 'target', iscenter=False)
[KMeans 군집화 확인]
위와 같은 데이터 세트에서는 KMeans의 군집화 정확성이 떨어지는데, KMeans가 위 데이터 세트를 어떻게 군집화하는지 확인
# 3개의 군집 기반 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)
▶ KMeans로 군집화를 수행할 경우, 주로 원형 영역 위치로 개별 군집화가 되면서 원하는 방향으로 구성되지 않는데, KMeans가 평균 거리 기반으로 군집화를 수행하므로 같은 거리상 원형으로 군집을 구성하면서 위와 같이 길쭉한 방향으로 데이터가 밀접해 있을 경우에는 최적의 군집화가 어렵다.
[GMM 군집화 수행]
# 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)
▶ 데이터가 분포된 방향에 따라 정확하게 군집화됐음을 알 수 있다.
GMM은 K-평균과 다르게 군집의 중심 좌표를 구할 수 없기 때문에 군집 중심 표현이 함수에서 시각화되지 않는다. make_blobs()의 target 값과 KMeans, GMM의 군집 label 값을 서로 비교해 위와 같은 데이터 세트에서 얼만큼의 군집화 효율 차이가 발생하는지 확인
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의 경우 군집 1번만 정확히 매핑됐으나, 나머지 군집의 경우 target 값과 어긋나는 경우가 발생하고 있다.
GMM의 경우는 군집이 target 값과 잘 매핑돼 있다. 이를 통해 GMM의 경우는 KMeans 보다 유연하게 다양한 데이터 세트에 잘 적용될 수 있다는 장점이 있음을 알 수 있다. 그러나 GMM은 군집화를 위한 수행 시간이 오래 걸린다는 단점이 있다.
다음 내용
[출처]
파이썬 머신러닝 완벽 가이드
위키백과
dlsalfkd11 코딩코딩 - T Sotry
데이터 사이언스 스쿨
'[파이썬 Projects] > <파이썬 머신러닝>' 카테고리의 다른 글
[머신러닝] 군집화: 실습 - 고객 세그먼테이션 (1) | 2024.10.28 |
---|---|
[머신러닝] 군집화: DBSCAN (0) | 2024.10.27 |
[머신러닝] 군집화: 평균 이동 (0) | 2024.10.25 |
[머신러닝] 군집화: 군집 평가 (3) | 2024.10.25 |
[머신러닝] 군집화: k-평균 (0) | 2024.10.25 |