from sklearn.mixture import GaussianMixture
n_components = 4 # 분포 개수
random_state = 10 # 모델 고정
model = GaussianMixture(n_components=n_components, random_state=random_state)
# GMM 모델 학습
model.fit(df)
df['gmm_label'] = model.predict(df)
# 시각화
sns.scatterplot(x=df[0], y=df[1], hue=df['gmm_label'], palette='rainbow', alpha=0.7, s=100)
계층 기반 클러스터링
: 유사한 데이터를 묶은 클러스터들을 층으로 쌓아가며 클러스터링 하는 방식
데이터간 관계를 쉽게 파악할 수 있고, 원하는 수의 클러스터로 간단하게 구분이 가능하다.
계층적 클러스터링(Hierachical Clustering)은 순차적으로 유사한 데이터끼리 같은 클러스터로 묶어 나가는 모델이다.
데이터를 아래에서부터 묶어 나간다고 하여 Bottom-up 클러스터링이라고도 한다.
계층적 클러스터링 과정
1. 각 데이터 사이의 거리를 모두 계산하여 가장 가까운 데이터 쌍을 차례대로 묶어 나간다.
2. 묶인 데이터 쌍 끼리도 거리를 계산하여 가까운 쌍은 하나로 묶는다.
3. 모든 데이터가 하나의 클러스터로 묶일 때 까지 위 과정을 반복한다.
4. 모든 클러스터의 계층이 구분되어 연결된 상태의 그래프를 '덴드로그램(Dendrogram)이라고 한다.
계층적 클러스터링은 덴드로그램을 이용해 원하는 개수로 클러스터를 나눌 수 있다.
만약, 클러스터의 개수를 3개로 하고 싶다면 아래 좌측 이미지와 같이 계층에 따라 A, B, C, D / E / F, G, H, I 의 클러스터로 묶이게 된다.
ㅂ반면 우측 이미지는 4개의 클러스터로 나뉘게 되며 위에서 I만 다른 클러스터로 분류된다.
Ward 거리
계층을 묶을 때 특정 데이터가 서로 유사하다는 것을 정의하기 위해 사용하는 방법이다.
각 클러스터의 Center와 속한 데이터들 사이 거리를 제곱하여 더한 값의 증가분을 뜻한다.
예를 들어, 위의 F, G, H와 I가 서로 다른 클러스터로 분류되었을 때 F, G, H 세 데이터의 중심점과 각 데이터 사이 거리의 제곱합이 있을것이다. 이때 I까지 하나의 클러스터로 묶이게 되면 이 값이 조금 더 커지게 된다. 이 두 값 사이의 차이(증가분)가 Ward 거리이다.
계층적 클러스터링은 이 값이 최대한 작게 증가되는 순서로 계층을 묶어 나간다.
예제
# 계층적 클러스터링을 하기 위해 scipy 라이브러리를 불러옴
# 이후 덴드로그램 시각화는 matplolib도 해야하므로 함께 불러옴
from scipy.cluster.hierarchy import dendrogram, linkage, cut_tree
import matplotlib.pyplot as plt
# csv 데이터 불러오기
scaled_df = pd.read_csv('/data/scaled_data.csv')
# 모델 학습 -> 계층적 클러스터링에는 linkage() 함수가 사용됨
# 파라미터로 데이터와 어떠한 거리 메소드를 사용할 지정해줘야 함 -> Ward 거리 사용
# 거리 : ward method
model = linkage(scaled_df, 'ward')
# 학습된 결과를 덴드로그램으로 시각화하기
labelList = scaled_df.index # 인덱스 값들을 labelList에 저장
# 덴드로그램 사이즈와 스타일 조정
plt.figure(figsize=(16, 9))
plt.style.use("default") # Matplotlib의 스타일을 기본값으로 설정함
dendrogram(model, labels=labelList)
plt.show()
# 클러스터를 5개로 나눠보고, 각 클러스터별로 고객이 몇 명씩 속해 있는지 확인하기
# 5개의 클러스터로 나누기
cluster_num = 5
# 고객별 클러스터 라벨 구하기
scaled_df['label'] = cut_tree(model, cluster_num) # cut_tree: model을 cluster_num개의 클러스터로 자름
# 클러스터별 고객 수 확인
pd.DataFrame(scaled_df['label'].value_counts())
import seaborn as sns
# 시각화 해보기
sns.set(style="darkgrid",
rc={'figure.figsize':(16,9)})
# 계층적 클러스터링
sns.scatterplot(data=scaled_df, x='total_price', y='total_buy_cnt', hue='label', s=200, palette='bright')
계층적 클러스터링 장단점
- 장점: 모델을 학습시킬 때, 클러스터의 개수를 미리 가정하지 않아도 된다. (*k-means는 사전에 정한 k값에 따라 결과가 달라짐)
- 단점: 모든 데이터끼리의 거리를 반복해서 계산해야 하기 때문에 많은 연산이 필요하다. 예를 들어 천 개의 데이터를 가지고 있다면 그 제곱인 백 만번의 연산이 필요하다.
밀도 기반 클러스터링
: 밀도가 높은 부분을 같은 클러스터로 묶어 나가는 방식
어떤 데이터가 클러스터에 속할 경우 클러스터 내에 다른 많은 데이터와 가까운 위치에 있을 것이라는 아이디어에서 출발하며, 기하학적인 형태의 데이터를 클러스터링 할 때 효과적이다.
DBSCAN
: Density-Based Spatial Clustering of Applications with Noise의 줄임말로, '밀도 기반 클러스터링 방법'을 의미한다.
전제 조건은 '어떤 데이터가 특정 클러스터에 속할 경우, 클러스터 내의 다른 데이터들과 가까운 위치에 있어야 한다'이다.
이때, 얼마나 많은 데이터와 얼마나 가까운 위치에 있어야 하나의 클러스터로 분류되는지는 모델 학습 시 임의로 지정해주어야 한다.
- 얼마나 가까운 위치에 데이터가 있어야 하는지: 반경(Radius)
- 반경 내에 얼마나 많은 데이터가 있어야 하는지: 최소 데이터 개수(Minimum Points)
DBSCAN 클러스터링 과정
1. 특정 데이터에서 지정한 반경 내 몇 개의 데이터가 포함되는지 탐색한다.
2. 정해진 반경 내 최소 데이터 개수가 포함되면 하나의 클러스터로 묶는다. 만약, 최소 데이터 개수가 4개라고 하면 아래 이미지에서는 두 개의 클러스터가 생기게 된다.
3. 만들어진 두 개의 클러스터의 경계에 있는 데이터들에서 그린 반경이 서로 겹치는 경우가 생긴다면 두 클러스터를 하나로 묶는다.
4. 조건에 만족하지 못하고 어떠한 클러스터에도 포함되지 못한 데이터는 이상치(Outlier)가 된다.
k-means는 이상치가 Centroid 위치에 관여하며 결과에 영향을 끼치지만, DBSCAN은 기준에 포함되지 못하는 데이터를 제외하기 때문에 이상치에 강건(Robbust)한 방법이다.
또, 데이터의 밀도에 따라 클러스터를 만들기 때문에 복잡하거나 기하학적인 형태를 가진 데이터 세트에도 효과적이다.
반면, 고차원 데이터일수록 데이터 간 밀도를 계산하기 어렵고, 연산이 많아져 학습 속도가 느려질 수 있다.
예제
# sklearn의 데이터 세트인 make_moons를 활용함 -> 초승달 모양의 데이터 분포 생성 가능
from sklearn.datasets import make_moons
import numpy as np
n_samples = 1000 # 1,000개의 샘플 생성
np.random.seed(3) # 랜덤 함수에서 동일한 순서의 난수를 생성하도록 시드 값을 3으로 설정
X, y = make_moons(n_samples=n_samples, noise=.05) # noise: 잡음의 크기, 0이면 정확한 반원을 이룸 -> 0.05는 정확한 반달 모양이 아닌, 약간 퍼지게 만듦
df = pd.DataFrame(X)
# 시각화
plt.figure(figsize=(16, 9))
sns.scatterplot(x=df[0], y=df[1], marker='o', s=200)
이 데이터를 k-means와 DBSCAN 각각으로 클러스터링 해보고 결과를 비교해 보자.
# k-means 모델 학습
model = KMeans(n_clusters=2, random_state = 123)
model.fit(df)
# 클러스터 라벨링
df['kmeans_label'] = model.predict(df)
# 각 군집의 중심점
centers = model.cluster_centers_
# 클러스터링 결과 시각화
plt.figure(figsize=(16, 9))
sns.scatterplot(x=df[0], y=df[1], hue=df['kmeans_label'] , s=200)
sns.scatterplot(x=centers[:,0], y=centers[:,1], color='black', s=200)
Centroid를 중심으로 가까운 거리의 데이터가 같은 클러스터로 묶다. 그러나 반달 모양으로 구분된 데이터의 특성이 잘 반영되지 못 한 것을 볼 수 있다.
이제 DBSCAN으로 클러스터링을 해보자.
# 'kmeans_label' 제거
df = df.drop(columns=['kmeans_label'], axis=1)
임의로 최소 데이터 개수는 5, 반경은 0.1로 한다.
(*DBSCAN에서 클러스터링 된 결과를 확인할 때에는 predict() 대신 labels_ 속성을 사용함)
from sklearn.cluster import DBSCAN
eps = 0.1 # 직경
min_samples = 5 # 최소 데이터 갯수
# DBSCAN 모델 학습
model = DBSCAN(eps=eps, min_samples=min_samples)
model.fit(df)
df['dbscan_label'] = model.labels_ # 라벨 붙이기(*DBSCAN에서 클러스터링 된 결과를 확인할 때에는 predict() 대신 labels_ 속성을 사용함)
# 시각화
plt.figure(figsize=(16, 9))
sns.scatterplot(x=df[0], y=df[1], hue=df['dbscan_label'], s=200)
클러스터를 두 개의 반달 모양으로 잘 구분한 것을 알 수 있다. 위 그림처럼 DBSCAN은 기하학적인 분포의 데이터를 클러스터링하는데 유용하다.
예제2
from sklearn.datasets import make_circles
from sklearn.cluster import DBSCAN
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
%matplotlib inline
X, y = make_circles(n_samples=1000, shuffle=True, noise=0.05, random_state=0, factor=0.5)
# n_samples=1000: 총 1000개의 샘플을 생성합니다.
# shuffle=True: 데이터를 섞습니다.
# noise=0.05: 데이터에 약간의 노이즈를 추가합니다.
# random_state=0: 랜덤 상태를 고정하여 재현성을 확보합니다.
# factor=0.5: 내부 원의 크기를 외부 원의 절반으로 설정합니다.
df = pd.DataFrame(X)
# DBSCAN 모델 학습
eps = 0.1 # 직경
min_samples = 5 # 최소 데이터 갯수
# 여기에 코드를 작성하세요
model = DBSCAN(eps=eps, min_samples=min_samples)
model.fit(df)
df['label'] = model.labels_
# 시각화
sns.set(style="darkgrid")
sns.scatterplot(x=df[0], y=df[1], hue=df['label'], marker='o', s=100, palette="bright")
분포 기반 클러스터링
: 각 클러스터에 포함된 데이터들이 정규분포를 따른다고 가정했을 때, 특정 데이터가 포함될 확률이 가장 높은 분포의 클러스터가 무엇인지를 찾아 클러스터를 나누는 방법이다.
GMM(Gaussian Mixture Model)
: 데이터가 서로 다른 k개의 정규분포에서 생성되었다고 가정한다.
정규분포란, 평균을 중심으로 대칭이며 표준편차에 따라 흩어진 정도가 정해지는 분포를 의미한다.
데이터가 정규분포를 따를 때 가장 큰 이점은 값이 특정 구간에 속할 확률을 계산할 수 있다는 점이다. GMM은 이 확률을 통해서 클러스터를 구분한다.
예를 들어, 아래 3개의 정규분포에서 6이라는 데이터가 관측되었을 때, 해당 데이터는 F3(X)에 속할 확률이 가장 높다. 따라서 6이라는 데이터는 F3(X)에 속하는 클러스터로 분류할 수 있다.
- F1(X) : 평균 = -2, 표준 편차 = 1
- F2(X) : 평균 = 1.5, 표준 편차 = 1.5
- F3(X) : 평균 = 5, 표준 편차 = 2.
위와 같이 GMM은 특정 데이터의 값이 어떤 분포에 포함될 확률이 더 큰지를 구분하여 각 클러스터로 구분한다.
- 장점: 클러스터별로 중심(평균)을 표현하면서 분산의 구조도 함께 띄고 있는 데이터 세트에 효과적
예를 들어, 데이터가 원형으로 흩어져 있으면 평균은 있지만 분산이 없는 데이터이기 때문에 k-means를 사용하는게 더 적합하지만, 데이터가 타원형으로 흩어져 있다면 중심(평균)과 분산의 값을 갖는 형태이기 때문에 GMM모델을 적용하는게 적합하다.
- 단점:
- k-means와 비슷하게 사전에 클러스터 개수를 임의로 설정해야 하며, 그 값에 따라 결과가 달라질 수 있다.
- 특정 분포에 할당되는 데이터 수가 적으면 모수 추정이 잘 이뤄지지 않기 때문에, 충분히 많은 수의 데이터가 있지 않으면 적용이 어렵다.
- 정규분포를 따른다고 가정하는 데이터를 구분해주는 방법이므로 정규분포가 나올 수 없는 범주형 데이터는 다룰 수 없다.
예제
타원형 학습 데이터를 가지고 k-means와 GMM 모델 각각으로 클러스터링 해보자.
from sklearn.datasets import make_blobs # make_blobs()로 생성한 데이터에 특수 행렬을 곱해주면 타원형으로 길게 늘어진 데이터를 생성할 수 있음
n_samples = 500 # 샘플 데이터 개수
centers = 4 # 클러스터 개수
cluster_std = 0.75 # 클러스터 내 표준편차
random_state = 13 # 샘플 데이터 고정
data, clusters = make_blobs(n_samples=n_samples, centers=centers, cluster_std=cluster_std, random_state=random_state)
# 데이터 타원형으로 만들기
tf = [[0.6, -0.6], [-0.4, 0.2]]
data_tf = data @ tf # @ : 행렬의 곱을 나타냄
df = pd.DataFrame(data_tf)
# 시각화
sns.scatterplot(x=df[0], y=df[1], alpha = 0.7, edgecolor="k", s=100)
k-means로 클러스터링 하기
# k-means 학습
model = KMeans(n_clusters=4, random_state=123)
model.fit(df)
df['kmeans_label'] = model.predict(df)
centers = model.cluster_centers_ # 각 클러스터의 Centorid
# 시각화
sns.scatterplot(x=df[0], y=df[1], hue=df['kmeans_label'], palette='rainbow', alpha=0.7, s=200)
sns.scatterplot(x=centers[:,0], y=centers[:,1], color='black', alpha=0.8, s=100)
타원형의 데이터는 k-means로 클러스터링이 잘 되지 않는 것을 알 수 있다.
df['kmeans_label']를 제거한 후에 GMM 모델을 학습시켜 보자.
df = df.drop(columns=['kmeans_label'], axis=1)
from sklearn.mixture import GaussianMixture
n_components = 4 # 분포 개수
random_state = 10 # 모델 고정
model = GaussianMixture(n_components=n_components, random_state=random_state)
# GMM 모델 학습
model.fit(df)
df['gmm_label'] = model.predict(df)
# 시각화
sns.scatterplot(x=df[0], y=df[1], hue=df['gmm_label'], palette='rainbow', alpha=0.7, s=100)
4개의 타원형 분포에 맞게 클러스터링이 잘 된 것을 알 수 있다.
예제2
from sklearn.mixture import GaussianMixture
from sklearn.datasets import make_classification # sklearn의 make_classification()을 활용해 가상의 분류용 데이터를 생성
import seaborn as sns
import pandas as pd
%matplotlib inline
# 데이터 생성
X, y = make_classification(n_samples=500, n_features=2, n_informative=2, n_redundant=0, n_clusters_per_class=1, random_state=6)
df = pd.DataFrame(X)
n_components = 2 # 분포 개수
random_state = 123 # 모델 고정
# GMM 모델을 학습하기 위해 GaussianMixture()를 활용
# 이후 model.fit()으로 모델을 학습하고,
# model.predict()로 클러스터링된 결과를 df['gmm_label']에 저장
model = GaussianMixture(n_components=n_components, random_state=random_state)
model.fit(df)
df['gmm_label'] = model.predict(df)
# 시각화(주어진 데이터를 scatterplot에 나타낼 때 df['gmm_label']을 기준으로 분류해 시각화)
sns.set(style="darkgrid")
sns.scatterplot(x=df[0], y=df[1], hue=df['gmm_label'], alpha=0.7, s=100)