Developer's Development

3.2.13 [머신러닝] 비지도 학습 본문

데이터 분석과 머신러닝, 딥러닝/머신러닝

3.2.13 [머신러닝] 비지도 학습

mylee 2025. 8. 3. 18:40
군집 (Clustering)

 

비지도 학습 알고리즘의 한 종류로, 레이블이 없는 데이터를 유사성에 따라 그룹(클러스터)로 나눈 데 사용된다. 군집은 데이터의 내재된 구조를 파악하거나, 탐색적 데이터 분석(EDA)에 유용하다.

각 클러스터는 내부적으로는 데이터 간의 유사성이 높고, 다른 클러스터와는 유사성이 낮도록 구성된다. 군집은 데이터의 숨겨진 패턴이나 구조를 발견하는 데 중점을 둔다.

 

https://scikit-learn.org/stable/modules/clustering.html

 

2.3. Clustering

Clustering of unlabeled data can be performed with the module sklearn.cluster. Each clustering algorithm comes in two variants: a class, that implements the fit method to learn the clusters on trai...

scikit-learn.org

 

  • 군집의 목적

데이터의 그룹화, 데이터의 분포 이해, 노이즈 제거, 새로운 데이터의 레이블 생성

 

  • 군집과 분류의 차이

군집 : 레이블이 없는 데이터를 그룹화한다. (비지도 학습)

분류 : 이미 정의된 레이블에 데이터를 매핑한다. (지도 학습)

 

  • 군집 알고리즘

1. K-평균 군집 (K-Means Clustering)

👉🏻 데이터를 k개의 클러스터로 나누는 가장 널리 사용되는 군집 알고리즘이다.

👉🏻 알고리즘 작동 방식

  1) 클러스터 중심(centroid)을 초기화한다.

  2) 각 데이터 포인트를 가장 가까운 클러스터 중심에 할당한다.

  3) 클러스터 중심을 재계산한다.

  4) 중심이 수렴할 때까지 반복한다.

👉🏻 장점 : 간단하고 빠르므로 대규모 데이터셋에 적합하다.

👉🏻 단점 : K값을 미리 설정해야 하고, 데이터 분포가 구형(Gaussian)에 가까워야 잘 작동한다.

 

2. 계층적 군집 (Hierarchical Clustrering)

👉🏻 데이터를 계층 구조로 그룹화하는 알고리즘이다.

👉🏻 계층적 군집의 두 가지 유형

  - 병합형(Agnes) : 각 데이터를 개별 클러스터로 시작하여 점차 병합

  - 분할형(Diana) : 하나의 클러스터로 시작하여 점차 분할

👉🏻 결과는 덴드로그램(Dendrogram)으로 시각화된다.

 

3. DBSCAN (Destiny-Based Spatial Clustrering of Applications with Noise)
👉🏻 밀도 기반 군집 알고리즘으로, 밀도가 높은 지역을 클러스터로 식별한다.

👉🏻 장점 : 클러스터의 모양이 비구형이어도 잘 작동하고, 노이즈를 효과적으로 처리한다.

👉🏻 단점 : 하이퍼파라미터 ϵ(epsilon)과 최소 데이터 포인트 설정에 민감하다.

 

 

가우시안 혼합 (Gaussian Mixture Model, GMM)

 

데이터가 여러 개의 가우시안 분포로 구성된다고 가정하고, 이를 기반으로 군집을 수행하는 비지도 학습 알고리즘이다. 각 데이터 포인트는 특정 가우시안 분포에 속할 확률로 표현된다.

 

  • 혼합 분포

GMM은 각 데이터 포인트가 K개의 가우시안 분포 중 하나에서 생성된 것으로 본다.

 

  • GMM과 K-평균의 차이

K-평균 : 각 데이터가 특정 클러스터에 완전히 속한다고 가정한다.

GMM : 각 데이터가 클러스터에 속할 확률을 기반으로 군집화한다.

 

 

실루엣 분석 (Silhouette Analysis)

 

  • 실루엣 계수 (Silhouette Coefficient)

데이터 포인트가 얼마나 잘 군집화되었는지 평가하는 지표다.

 

 

DBSCAN 군집화

 

밀도 기반 군집화 알고리즘이다. 군집은 데이터 포인트 주변의 밀집도를 기준으로 형성된다.

핵심 파라미터는 ϵ(거리 임계값)과 minPts(최소 이웃 데이터 수)이다.

👉🏻 핵심 포인트(Core Point) : ϵ 거리 내에 최소 minPts 이상의 이웃이 있는 데이터

👉🏻 경계 포인트(Border Point) : 핵심 포인트 주변에 있지만, minPts에는 미치지 못하는 데이터

👉🏻 노이즈 포인트(Noise Point) : 어느 군집에도 속하지 않는 데이터

 

 

실습 (KMeans)

 

  • KMeans

K개의 그룹(중심점을 기준)으로 데이터 포인트를 나눔

 

👉🏻 작동 단계

1. K개의 중심점 임의 선택

2. 각 데이터 포인트를 가장 가까운 중심점에 할당 > 군집 형성

3. 각 군집의 데이터 포인트 기반으로 새로운 중심점 계산

4. 2~3단계를 중심점의 변화가 거의 없을 때까지 반복 실행 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
iris = load_iris()

iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_df[iris.target_names[0]] = iris.target


# KMeans
from sklearn.cluster import KMeans
kmeans = KMeans(
    n_clusters=3,       # 클러스터 개수 (= 중심정 개수)
    init='k-means++',   # 초기 중심점 설정 방식
    max_iter=300,       # 최대 반복 횟수
    random_state=0
)

kmeans.fit(iris.data)           # 중심점 찾기
kmeans.transform(iris.data)     # 중심점과의 거리 계산 및 반환 /(150, 3) 배열 반환
kmeans.labels_					# 군집화 결과

# 군집화 결과 확인
iris_df['cluster'] = kmeans.labels_
iris_df.groupby('species')['cluster'].value_counts()


# 시각화를 위한 PCA (2개의 주성분으로 변환)
from sklearn.decomposition import PCA

pca = PCA(n_components=2)
pca_transformed = pca.fit_transform(iris.data)
iris_df['pca1'] = pca_transformed[:, 0]
iris_df['pca2'] = pca_transformed[:, 1]


print(kmeans.cluster_centers_)  # 클러스터의 중심점

# 시각화를 위해 중심점도 차원 축소
centers = pca.transform(kmeans.cluster_centers_)


# species(라벨 데이터) 기준 시각화
plt.scatter(x=iris_df['pca1'], y=iris_df['pca2'], c=iris_df['species'])
plt.title('Species')
plt.show()

# cluster(kmeans 군집 데이터) 기준 시각화
plt.scatter(x=iris_df['pca1'], y=iris_df['pca2'], c=iris_df['cluster'])
plt.title('Cluster')
plt.show()

 

  • 과일 데이터 군집

흑백 이미지 데이터 픽셀값 0(흑) ~ 255(백)

# 이미지 시각화 함수
def draw_fruits(arr, ratio=1):
    N = len(arr)
    rows = int(np.ceil(N / 10))
    cols = N if rows < 2 else 10
    fig, ax = plt.subplots(rows, cols, figsize=(cols * ratio, rows * ratio), squeeze=False)

    for i in range(rows):
        for j in range(cols):
            if i * 10 + j < N:
                ax[i, j].imshow(arr[i * 10 + j], cmap='gray_r')
            ax[i, j].axis('off')

    plt.show()
    
fruits = np.load('./data/fruits_300.npy')
fruits.shape  # (300, 100, 100)


# KMeans 군집 적용을 위한 reshape
fruits_1d = fruits.reshape(-1, 100 * 100)

# KMeans 적용
kmeans = KMeans(n_clusters=3, random_state=42)
kmeans.fit(fruits_1d)
kmeans.transform(fruits_1d)

np.unique(kmeans.labels_, return_counts=True)  # (array([0, 1, 2], dtype=int32), array([112,  98,  90]))

# 각 클러스터별 이미지 시각화
draw_fruits(fruits[kmeans.labels_ == 0])
draw_fruits(fruits[kmeans.labels_ == 1])
draw_fruits(fruits[kmeans.labels_ == 2])

 

# PCA 적용 후 clustering
pca = PCA(n_components=2)
fruits_pca = pca.fit_transform(fruits_1d)
fruits_pca.shape

kmeans = KMeans(n_clusters=3, random_state=42)
kmeans.fit(fruits_pca)

np.unique(fruits[kmeans.labels_ == 0])
np.unique(fruits[kmeans.labels_ == 1])
np.unique(fruits[kmeans.labels_ == 2])

draw_fruits(fruits[kmeans.labels_ == 0])
draw_fruits(fruits[kmeans.labels_ == 1])
draw_fruits(fruits[kmeans.labels_ == 2])

 

# 중심점 시각화
draw_fruits(kmeans.cluster_centers_.reshape(-1, 100, 100))

pred = kmeans.predict(fruits_pca[100:101])
print(pred)  # [0]
draw_fruits(fruits[100:101])  # 시각화 결과 파인애플 (파인애플 == 0 클러스터)

 

  • 최적의 k값 찾기

inertia : 중심점으로 각 데이터포인트의 분산값 ▶️ 이너셔 값이 작을수록 군집이 잘 되어 있다고 볼 수 있음

Elbow 기법 : inertia 값이 급격히 감소하는 k값을 최적의 k값으로 판단

inertias = []

for k in range(2, 12):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(fruits_pca)
    inertias.append(kmeans.inertia_)  # inertia 속성 확인

inertias


plt.plot(range(2, 12), inertias)
plt.xlabel('K')
plt.ylabel('inertia')
plt.show()

 

 

 

실습 (Silhouette Score: 군집 평가)

 

  • Silhouette Score : 군집 평가

- 실루엣 계수를 통해 군집화의 품질 평가

- 실루엣 계수는 -1에서 1 사이의 값을 가짐

  - 1에 가까울수록 군집화 잘됨 (다른 군집과 잘 분리되어 있음)

  - 0은 군집의 경계에 위치함

  - -1은다른 군집과 겹치거나 잘못 분류된 경우

 

👉🏻 주요 함수

- silhouette_samples : 개별 데이터 포인트의 점수

- silhouette_score : 전체 데이터 포인트의 평균값 

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

iris_data = load_iris()
iris_df = pd.DataFrame(iris_data.data, columns=iris_data.feature_names)
iris_df['species'] = iris_data.target

# KMeans 군집화
kmeans = KMeans(n_clusters=3, init='k-means++', max_iter=100, random_state=0)
kmeans.fit(iris_data.data)
iris_df['cluster'] = kmeans.labels_

# 실루엣 계수 측정
sil_samples = silhouette_samples(iris_data.data, kmeans.labels_)
sil_samples.shape  # (150,)

# 전체 클러스터의 실루엣 계수 == 개별 데이터 포인트의 실루엣 계수 평균
sil_score = silhouette_score(iris_data.data, kmeans.labels_)
sil_score  # (0.551191604619592, np.float64(0.551191604619592))

 

 

실습 (GMM: Gaussian Mixture Model)

 

# 데이터 생성 및 시각화
from sklearn.datasets import make_classification
import matplotlib.pyplot as plt

X, _ = make_classification(
    n_samples=300,              # 데이터 개수
    n_features=2,               # 특성 개수
    n_informative=2,            # 유의미한 특성 개수
    n_redundant=0,              # 중복 특성 개수
    n_clusters_per_class=1,     # 클래스 당 클러스터 개수
    n_classes=2,                # 클래스(레이블) 개수
    random_state=42
)

plt.scatter(X[:, 0], X[:, 1], s=50, c='gray', marker='o', edgecolors='k')
plt.title('scikit-learn making data')
plt.show()

# GNM 적용 및 군집화 결과 시각화
from sklearn.mixture import GaussianMixture

gnm = GaussianMixture(n_components=2, random_state=42)
gnm.fit(X)

labels = gnm.predict(X)

plt.scatter(X[:, 0], X[:, 1], c=labels, s=50, edgecolors='k')
plt.title('GNM Cluster result')
plt.show()

# 군집 포함 확률 시각화
probs = gnm.predict_proba(X)

plt.scatter(X[:, 0], X[:, 1], c=probs.max(axis=1), s=50, edgecolors='k')
plt.title('GNM Cluster rate')
plt.colorbar(label='rate')
plt.show()

 

 

실습 (DBSCAN, Destiny-Based Special Clustering of Application with Noise)

 

밀도(데이터포인트의 간격) 기반 군집 알고리즘

 

👉🏻 장점

비구형 클러스터 방지

노이즈 데이터 처리

비지도 학습 (클러스터 개수를 사전에 알 필요가 없음)

 

👉🏻 단점

데이터 밀도가 자주 변하거나 아예 변하지 않으면 군집화 성능 저하

특성 개수가 많으면 군집화 성능 저하 (고차원 데이터에서의 밀도 불균형)

매개변수 민감성

from sklearn.datasets import make_moons
from sklearn.cluster import DBSCAN
import matplotlib.pyplot as plt

X, _ = make_moons(n_samples=300, noise=0.1, random_state=42)

dbscan = DBSCAN(eps=0.2, min_samples=6)  # eps(이웃 점의 거리 반지름, 0.5), min_samples(minPts, 5)
dbscan.fit(X)  # 클러스터링 계산

plt.scatter(X[:, 0], X[:, 1], c=dbscan.labels_)
plt.show()

 

  • iris 데이터셋에 DBSCAN 적용
from sklearn.datasets import load_iris
import pandas as pd
from sklearn.decomposition import PCA

iris = load_iris()

iris_df = pd.DataFrame(iris.data, columns=iris.feature_names)
iris_df['species'] = iris.target


# DBSCAN 적용
dbscan = DBSCAN(eps=0.6, min_samples=4)
dbscan.fit_predict(iris.data)  # dbscan은 transform, predict가 없음

iris_df['cluster'] = dbscan.labels_
iris_df.groupby('species')['cluster'].value_counts()


# 시각화를 위한 PCA
pca = PCA(n_components=2)
pca_transformed = pca.fit_transform(iris.data)
iris_df['pca1'] = pca_transformed[:, 0]
iris_df['pca2'] = pca_transformed[:, 1]

# species(라벨 데이터) 기준 시각화 + cluster(dbscan 군집 데이터) 기준 시각화
fit, ax = plt.subplots(1, 2, figsize=(12, 6))
ax[0].scatter(x=iris_df['pca1'], y=iris_df['pca2'], c=iris_df['species'])
ax[1].scatter(x=iris_df['pca1'], y=iris_df['pca2'], c=iris_df['cluster'])
plt.show()