[ Python ]비지도 군집, 병합군집, 덴드로그램, DBSCAN

2018. 1. 9. 00:58ML(머신러닝)/BASIC

728x90

도움이 되셨다면, 광고 한번만 눌러주세요.  블로그 관리에 큰 힘이 됩니다 ^^

# 군집 

데이터셋 -> 클러스터란느 그룹으로 나누는 것 ,  한 클러스터 안의 데이터 포인트끼리는 매우 비슷,
다른 클러스터라는 구분 되도록 하는 것이 목표
분류 알고리즘과 비슷하게 군집 알고리즘은 각 데이터 포인트가 어느 클러스터에 속하는지 할당 (또는 예측) 

K-평균 군집

K-평균 군집은 가장 간단, 널리 사용하는 알고리즘
어떤 영역을 대표하는 클러스터 중심을 찾는다.
두단계를 반복 

1. 데이터 포인트를 가장 가까운 클러스터 중심에 할당
2. 클러스터에 할당된 데이터 포인트의 평균으로 클러스터 중심을 다시 지정

 

-> 포인트 변화가 없을 때 종료

 

 

삼각형 : 클러스터 중심 / 원 : 데이터 포인트 

 

클러스터 중심으로 삼을 데이터 포인트 3개를 무작위로 초기화 -> 반복

 

포인트 평균값으로 클러스터 중심으로 갱신 -> 반복

 

코드 :

from sklearn.datasets import make_blobs from sklearn.cluster import KMeans # 인위적으로 2차원 데이터를 생성합니다 X, y = make_blobs(random_state=1) # 군집 모델을 만듭니다 kmeans = KMeans(n_clusters=3) kmeans.fit(X)

 

print(kmeans.labels_)

print(kmeans.predict(X)

 

이런식의 알고리즘에서 숫자 0은 아무런 의마 없습니다 -> 알고리즘이 주는 정보 -> 0에 있는 무리들이 비슷하다는 것만 안다.

 

k-평균 알고리즘이 실패하는 경우 

 

데이터셋의 클러스터를 정확하게 알고 있더라도 -> 항상 구별하는 것은 아니다. -> 각 클러스트를 정의하는 것이 중심 하나뿐

 

-> 클러스터는 둥근 형태 -> 이런 이유로 비교적 간단한 형태로 구분 -> 또한 K - 평균 모든 클러스터의 반경이 똑같다는 가정

 

-> 중심 사이의 정확히 중간게 경계를 그린다. 

 

X_varied, y_varied = make_blobs(n_samples=200,
                                cluster_std=[1.0, 2.5, 0.5],
                                random_state=170)
y_pred = KMeans(n_clusters=3, random_state=0).fit_predict(X_varied)

mglearn.discrete_scatter(X_varied[:, 0], X_varied[:, 1], y_pred)
plt.legend(["클러스터 0", "클러스터 1", "클러스터 2"], loc='best')
plt.xlabel("특성 0")
plt.ylabel("특성 1")

 

잘 보면 클러스터 0과 클러스터 1은 클러스터 중심에서 멀리 떨어진 포인트도 포함

 

K-평균은 또 클러스터에서 모든 방향이 똑같다고 중요하다고 가정

 

-> 대각선으로 늘어진 데이터 -> 가까운 것만 고려하기 때문에 잘 처리 못한다.

# 무작위로 클러스터 데이터 생성합니다
X, y = make_blobs(random_state=170, n_samples=600)
rng = np.random.RandomState(74)

# 데이터가 길게 늘어지도록 변경합니다
transformation = rng.normal(size=(2, 2))
X = np.dot(X, transformation)

# 세 개의 클러스터로 데이터에 KMeans 알고리즘을 적용합니다
kmeans = KMeans(n_clusters=3)
kmeans.fit(X)
y_pred = kmeans.predict(X)

# 클러스터 할당과 클러스터 중심을 나타냅니다
mglearn.discrete_scatter(X[:, 0], X[:, 1], kmeans.labels_, markers='o')
mglearn.discrete_scatter(
    kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], [0, 1, 2],
    markers='^', markeredgewidth=2)
plt.xlabel("특성 0")
plt.ylabel("특성 1")

 

 

더 어려운 데이터 k-means -> 성능 더 떨어짐

# two_moons 데이터를 생성합니다(이번에는 노이즈를 조금만 넣습니다)
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=200, noise=0.05, random_state=0)

# 두 개의 클러스터로 데이터에 KMeans 알고리즘을 적용합니다
kmeans = KMeans(n_clusters=2)
kmeans.fit(X)
y_pred = kmeans.predict(X)

# 클러스터 할당과 클러스터 중심을 표시합니다
plt.scatter(X[:, 0], X[:, 1], c=y_pred, cmap=mglearn.cm2, s=60, edgecolors='k')
plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1],
            marker='^', c=[mglearn.cm2(0), mglearn.cm2(1)], s=100, linewidth=2, edgecolors='k')
plt.xlabel("특성 0")
plt.ylabel("특성 1")

 

벡터 양자화 또는 분해 메서드로서의 k-평균

 

K-MEANS 군집 알고리즘 / PCA NMF 같은 분해 알고리즘 사이에는 유사점 존재

 

PCA - 데이터에서 큰 분산ㄴ이 가장 큰 방향 찾기

 

NMF - 데이터의 극단 또는 일부분에 상응되는 중첩할수 있는 성분 찾기

 

두 방법 모두 데이터 포인트를 어떤 성분의 합으로 표현

 

반면에 K-MEANS 클러스터 중심으로 각 데이터 포인트 표현

 

이를 각 데이터 포인트가 클러스터 중심 즉, 하나의 성분으로 표현된다고 볼 수 있음 

 

K-평균을 이렇게 각 포인트가 하나의 성분으로 분해되는 관점 : 벡터 양자화 

 

PCA, NMF K-평균에서 추출한 성분과 100개 성분으로 테스트 세트의 얼굴 재구성 한것 비교하기

 

K-평균의 경우 재구성은 훈련 세트에서 찾은 가장 가까운 클러스터의 중심이다.

 

X_train, X_test, y_train, y_test = train_test_split(
    X_people, y_people, stratify=y_people, random_state=0)
nmf = NMF(n_components=100, random_state=0)
nmf.fit(X_train)
pca = PCA(n_components=100, random_state=0)
pca.fit(X_train)
kmeans = KMeans(n_clusters=100, random_state=0)
kmeans.fit(X_train)

X_reconstructed_pca = pca.inverse_transform(pca.transform(X_test))
X_reconstructed_kmeans = kmeans.cluster_centers_[kmeans.predict(X_test)]
X_reconstructed_nmf = np.dot(nmf.transform(X_test), nmf.components_)
fig, axes = plt.subplots(3, 5, figsize=(8, 8), subplot_kw={'xticks': (), 'yticks': ()})
fig.suptitle("추출한 성분")
for ax, comp_kmeans, comp_pca, comp_nmf in zip(
        axes.T, kmeans.cluster_centers_, pca.components_, nmf.components_):
    ax[0].imshow(comp_kmeans.reshape(image_shape))
    ax[1].imshow(comp_pca.reshape(image_shape), cmap='viridis')
    ax[2].imshow(comp_nmf.reshape(image_shape))

axes[0, 0].set_ylabel("kmeans")
axes[1, 0].set_ylabel("pca")
axes[2, 0].set_ylabel("nmf")

fig, axes = plt.subplots(4, 5, subplot_kw={'xticks': (), 'yticks': ()},
                         figsize=(8, 8))
fig.suptitle("재구성")
for ax, orig, rec_kmeans, rec_pca, rec_nmf in zip(
        axes.T, X_test, X_reconstructed_kmeans, X_reconstructed_pca,
        X_reconstructed_nmf):
    
    ax[0].imshow(orig.reshape(image_shape))
    ax[1].imshow(rec_kmeans.reshape(image_shape))
    ax[2].imshow(rec_pca.reshape(image_shape))
    ax[3].imshow(rec_nmf.reshape(image_shape))

axes[0, 0].set_ylabel("원본")
axes[1, 0].set_ylabel("kmeans")
axes[2, 0].set_ylabel("pca")

 

 

K-평균을 사용한 벡터 양자화의 흥미로운 면은 입력 데이터의 차원보다 더 많은 클러스터를 사용해 인코딩 가능

 

X, y = make_moons(n_samples=200, noise=0.05, random_state=0)

kmeans = KMeans(n_clusters=10, random_state=0)
kmeans.fit(X)
y_pred = kmeans.predict(X)

plt.scatter(X[:, 0], X[:, 1], c=y_pred, s=60, cmap='Paired', edgecolors='black')
plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 1], s=60,
            marker='^', c=range(kmeans.n_clusters), linewidth=2, cmap='Paired', edgecolors='black')
plt.xlabel("특성 0")
plt.ylabel("특성 1")
print("클러스터 레이블:\n{}".format(y_pred))

 

 

 

10차원 -> 포인터가 속한 클러스터에 해당하는 특성 제외한 다른 특성 0이라고 생각

 

2개만으로는 불가능 했지만 10차원에서는 선형 모델을 사용해 두개의 반달 모델로 구분 할 수 있다.

 

또한 클러스터 중심까지의 거리를 특성으로 사용하면 데이터를 더욱 잘 나타낼수 있다.

 

distance_features = kmeans.transform(X)
print("클러스터 거리 데이터의 형태: {}".format(distance_features.shape))
print("클러스터 거리:\n{}".format(distance_features))
 
클러스터 거리 데이터의 형태: (200, 10)
클러스터 거리:
[[ 0.922  1.466  1.14  ...,  1.166  1.039  0.233]
 [ 1.142  2.517  0.12  ...,  0.707  2.204  0.983]
 [ 0.788  0.774  1.749 ...,  1.971  0.716  0.944]
 ..., 
 [ 0.446  1.106  1.49  ...,  1.791  1.032  0.812]
 [ 1.39   0.798  1.981 ...,  1.978  0.239  1.058]

[ 1.149 2.454 0.045 ..., 0.572 2.113 0.882]]

 

K-평균 비교적 이해하기 쉽고 구현 쉬움 빠름 -> 대용량에도 잘 작동 -> SCIKIT-LEARN (Minibatchkmeans 제공)

 

단점 : 무작위 초괴하를 사용하여 알고리즘 출력이 난수 초깃값에 따라 달라진다.

 

scikit-learn 은 서로 다른 난수 초기값 10번 반복하여 최선의 결과를 만든다.

 

더 큰 단점 : 클러스터 모양 가정하고 있어서 -> 활용범위 제한적 -> 개수를 지정해야한다.

 

==> 이러한 단점 극복한 군집 알고리즘 있다.

 

 


 

 

# 병합 군집

 

시작 할 때 각 포인트를 하나의 클러스터로 지정 -> 어떤 종료 조건 만족할 때 까지 가장 비슷한 두 클러스터 합쳐나간다.

 

종료 조건은 클러스터 개수 -> 지정된 갯수의 클러스터가 남을 때 까지 비슷한 클러스터 합친다.

 

linkage  옵션 -> 가장 비슷한 클러스터를 측정하는 방법 지정 -> 이 측정은 항상 두 클러스터 사이에서 발생

 

옵션

1. ward

 

모든 클러스터 내의 분산을 가장 작게 증가시키는 두 클러스터를 합친다. 그래서 크기가 비교적 비슷한 클러스터 생산

 

2. average

 

average 연결은 클러스터 포인트 사이의 평균 거리를 가장 짧은 두 클러스터 합친다.

 

3. complete

 

complete 연결 (최대 연결) 은 클러스터 포인트 사이의 최대 거리가 가장 짧은 두 클러스터를 합칩니다.

 

ward 가 대부분 옵션에 잘 맞음

 

클러스터에 속한 포인트 수가 많이 다를 땐 (한클러스터가 다른것보다 매우 클때) -average, complete가 더 낫다.

 

 

특성상 -> 새로운 데이터에 대한 예측 x 

 

->predict 메서드가 없음 -> 대신 클러스터 정보 소속을 얻기 위해 fit_predict

from sklearn.cluster import AgglomerativeClustering
X, y = make_blobs(random_state=1)

agg = AgglomerativeClustering(n_clusters=3)
assignment = agg.fit_predict(X)

mglearn.discrete_scatter(X[:, 0], X[:, 1], assignment)
plt.legend(["클러스터 0", "클러스터 1", "클러스터 2"], loc="best")
plt.xlabel("특성 0")
plt.ylabel("특성 1")

 

# 계층적 군집과 덴드로그램

 

병합군집은 계측정 군집을 만든다. 

 

군집 반복 -> 모든 포인트는 하나의 포인트를 가진 클러스터에서 시작 -> 마지막 클러스터로 이동

 

 

자세히 나타내지만 2차원 데이터일 뿐이며 -> 셋 이상인 데이터셋에서는 사용 불가

 

하지만 계층 군집 시각화하는 덴드로그램 -다차원셋에서 처리 가능

 

scikit-learn 에는 없고 scipy에 존재 

 

# SciPy에서 ward 군집 함수와 덴드로그램 함수를 임포트합니다
from scipy.cluster.hierarchy import dendrogram, ward

X, y = make_blobs(random_state=0, n_samples=12)
# 데이터 배열 X 에 ward 함수를 적용합니다
# SciPy의 ward 함수는 병합 군집을 수행할 때 생성된
# 거리 정보가 담긴 배열을 리턴합니다
linkage_array = ward(X)
# 클러스터 간의 거리 정보가 담긴 linkage_array를 사용해 덴드로그램을 그립니다
dendrogram(linkage_array)

# 두 개와 세 개의 클러스터를 구분하는 커트라인을 표시합니다
ax = plt.gca()
bounds = ax.get_xbound()
ax.plot(bounds, [7.25, 7.25], '--', c='k')
ax.plot(bounds, [4, 4], '--', c='k')

ax.text(bounds[1], 7.25, ' 두 개 클러스터', va='center', fontdict={'size': 15})
ax.text(bounds[1], 4, ' 세 개 클러스터', va='center', fontdict={'size': 15})
plt.xlabel("샘플 번호")
plt.ylabel("클러스터 거리")

 

덴드로그램에서 -> y 축이 단순히 병합 알고리즘에서 두 클러스터가 합쳐질 떄를 나타낸 것만은 아님

 

-> 가지의 길이는 합쳐진 클러스터가 얼마나 멀리 떨어져 있는지를 보여준다.

 

-> 가장 긴 가지는 " 세개의 클러스터" 세개의 수직선

 

-> 이 가지가 가장 길다는 것은 클러스터가 3개에서 2개가 될 때 꽤 먼거리의 포인터를 모은다는 뜻

 

# 단점 : two_moons 같은 복잡한 형상 구분은 못한다 => 해결 : DBSCAN으로 가능하다.

 


 

# DBSCAN

 

주요 장점 : 클러스터 개수를 미리 지정할 필요가 없다.

 

복잡한 형상도 찾을 수 있으며, 어떤 클래스에도 속하지 않는 포인트를 구분할 수 있습니다.

 

속도 : DBSCAN < K-평군,병합군집 = 속도는 다소 느린편 / 그래도 비교적 큰 데이터셋에서 적용 가능

 

특성 공간에서 가까이 있는 데이터가 많아 붐비는 지역의 포인트를 찾는다. = 특성공간의 밀집지역

 

IDEA : 데이터 밀집 지역이 한 클러스터를 구성하며 비교적 비어있는 지역을 경계로 다른 클러스터와 구분된다.

 

밀집지역에 있는 포인트를 핵심 샘플(핵심포인트) 

 

두개의 매개변수 : min_samples / eps 

 

metric 매개 변수에서 거리 재는 방식 바꿀수 있음 (default : euclidean)

 

한 데이터 포인트에서 eps 거리 안에 데이터가 min_samples 개수 만큼 들어 있으면 이 데이터를 핵심 샘플로 분류

 

eps 보다 가까운 핵심 샘플은 DBSCAN에 의해 동일한 클러스터로 합쳐진다.

 

시작 : 무작위로 포인트 선택 -> 그 포인트에서 EPS 거리 안의 모든 포인트를 찾는다. 

 

만약 EPS 거리 안에 있는 포인트 수가 min_samples 보다 적다면 그 포인트는 어떤 클래스에도 속하지 않는 잡읍으로 레이블

 

eps 거리 안에 min_samples 보다 많은 포인트가 있다면 그 포인트는 핵심 샘플로 레이블하고 새로은 클러스터 레이블을 할당

 

그런 다음 그 포인트의 (eps 거리안의) 모든 이웃을 check

 

만약 어떤 클러스터에도 아직 할당되지 않았다면 -> 바로 전에 만든 클러스터 레이블을 할당

 

만약 핵심 샘플이면 -> 그 포인트 이웃을 차례로 방문 -> 계속 진행 -> eps 거리 안에 더이상 핵심샘플이 없을 때까지 자라난다.

 

그런 다음 방문하지 못한 포인트 선택하여 같은 과정 반복

 

포인트의 종류는 3가지

 

핵심포인트 , 경계포인트(핵심포인트에서 eps 거리 안에 있는 포인트) , 잡음 포인트

 

dbscan 을 한 데이터셋에서 여러 번 실행하면 핵심 포인트의 군집은 항상 같고 매번 같은 포인트를 잡음으로 레이블 가능

 

그러나 경계포인트는 한 개 이상의 크러스터 핵심 샘플의 이웃이 될 수 있다. -> 그렇기 때문에

 

-> 경계포인트가 어떤 클러스터에 속할지는 포인트를 방문하는 순서에 따라 달라진다.

 

-> 보통 경계포인트는 많지 않으며 -> 포인트 순서 때문에 받는 영향도 적어 중요한 이슈는 아니다.

 

 

 

from sklearn.cluster import DBSCAN
X, y = make_blobs(random_state=0, n_samples=12)

dbscan = DBSCAN()
clusters = dbscan.fit_predict(X)
print("클러스터 레이블:\n{}".format(clusters))
 
클러스터 레이블:
[-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1]

 

모든 포인트에 잡읍 포인트를 의미하는 [-1] 할당 => 적합하지 않은 eps와 min_samples 기본 값

 

 

 

잡음 포인트 : 하얀색 / 핵심 샘플은 크게 / 경계 포인트는 작게

 

eps 증가 -> 하나의 클러스터에 더 많은 포인트 포함 -> 클러스터가 커지게 하지만 여러 클러스터 하나로 합친다.

 

min_samples 증가 -> 핵심 포인트 수가 줄어 들며 -> 잡음 포인트 증가

 

eps 매개변수는 가까운 포인트의 범위를 결정하기 때문에 더 중요

 

eps 줄이면 -> 어떤 포인트든 핵심 포인트 x -> 모든 포인트 잡음 됨

 

eps 크게하면 -> 모든 포인트가 단 하나의 클러스터에 속하게 됨

 

min_samples - 덜 조밀한 지역에 있는 포인트들이 잡음 포인트가 될 것인지, 아니면 하나의 클러스터가 될 것인지 결정하는 역할

 

min_samples 증가 -> min_samples의 수보다 작은 클러스터들은 잡음 포인트가 된다.

 

min_samples : 클러스터의 최소 크기 정한다.

 

dbscan : 클러스터 개수 지정 x ->  적절한 eps 로 정한다.

 

적절한 eps 정하는 방법 : 1. StandardScaler 2. MinMaxScaler

 

X, y = make_moons(n_samples=200, noise=0.05, random_state=0)

# 평균이 0, 분산이 1이 되도록 데이터의 스케일을 조정합니다
scaler = StandardScaler()
scaler.fit(X)
X_scaled = scaler.transform(X)

dbscan = DBSCAN()
clusters = dbscan.fit_predict(X_scaled)
# 클러스터 할당을 표시합니다
plt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=clusters, cmap=mglearn.cm2, s=60, edgecolors='black')
plt.xlabel("특성 0")
plt.ylabel("특성 1")

 

잘 분류가 된다. 기본 eps =0.5

 

내리면 -> 여러개의 클러스터 / 올리면 -> 하나의 클러스터


 

군집 알고리즘 비교와 평가

 

어려움 점 : 알고리즘이 잘 작동하는지 평가하거나 여러 알고리즘 출력 비교하기가 어려움

 

타킷값으로 군집 평가하기

 

결과를 실제 정답 클러스터와 비교하여 평가 할 수있는 지표들이 있다

 

1(최적) 0(무작위 분류) 사이의 값을 제공하느니 ARI(adjusted rand index) NMI(normalized mutual information)

 

from sklearn.metrics.cluster import adjusted_rand_score
X, y = make_moons(n_samples=200, noise=0.05, random_state=0)

# 평균이 0, 분산이 1이 되도록 데이터의 스케일을 조정합니다
scaler = StandardScaler()
scaler.fit(X)
X_scaled = scaler.transform(X)

fig, axes = plt.subplots(1, 4, figsize=(15, 3),
                         subplot_kw={'xticks': (), 'yticks': ()})

# 사용할 알고리즘 모델을 리스트로 만듭니다
algorithms = [KMeans(n_clusters=2), AgglomerativeClustering(n_clusters=2),
              DBSCAN()]

# 비교를 위해 무작위로 클러스터 할당을 합니다
random_state = np.random.RandomState(seed=0)
random_clusters = random_state.randint(low=0, high=2, size=len(X))

# 무작위 할당한 클러스터를 그립니다
axes[0].scatter(X_scaled[:, 0], X_scaled[:, 1], c=random_clusters,
                cmap=mglearn.cm3, s=60, edgecolors='black')
axes[0].set_title("무작위 할당 - ARI: {:.2f}".format(
        adjusted_rand_score(y, random_clusters)))

for ax, algorithm in zip(axes[1:], algorithms):
    # 클러스터 할당과 클러스터 중심을 그립니다
    clusters = algorithm.fit_predict(X_scaled)
    ax.scatter(X_scaled[:, 0], X_scaled[:, 1], c=clusters,
               cmap=mglearn.cm3, s=60, edgecolors='black')
    ax.set_title("{} - ARI: {:.2f}".format(algorithm.__class__.__name__,

 

무작위에 가까울 수록 ARI -> 0에 가까움 / 무작위보다 나쁘게 하면 음수 값 (ARI 범위 -1~ 1)

ARI 공식 분석해 보면 -0.5 가 최저값

 

normalized_mutual_info_score(nmi) / adjusted_rand_score(ari)

 

흔히 하는 실수 

 

::  adjusted_rand_score 나 normalized_mutual_info_score 같은 방법 사용하지 않고  accuracy_score 사용하는 것

 

정확도 사용하면 -> 클러스터의 레이블 이름이 실제 레이블과 맞는지 확인

 

-> 클러스터 레이블은 -> 그 자체적으로 의미가 있는 것이 아님 -> 포인트들이 같은 클러스터에 속해 있는가만이 중요

 

from sklearn.metrics import accuracy_score

# 포인트가 클러스터로 나뉜 두 가지 경우
clusters1 = [0, 0, 1, 1, 0]
clusters2 = [1, 1, 0, 0, 1]
# 모든 레이블이 달라졌으므로 정확도는 0입니다
print("정확도: {:.2f}".format(accuracy_score(clusters1, clusters2)))
# 같은 포인트가 클러스터에 모였으므로 ARI는 1입니다
print("ARI: {:.2f}".format(adjusted_rand_score(clusters1, clusters2)))
 
정확도: 0.00
ARI: 1.00

 

타깃값 없이 군집 평가하기

 

ARI 도 큰 문제 존재 -> 군집알고리즘을 할 때 보통 타깃값이 X 

 

ARI . NMI 같은 지표는 에플리케이션의 성능평가가 아니라 알고리즘을 개발할 때나 도움이 된다.

 

다른 군집용 지표로 타깃값 필요없는 것은 실루엣 계수(SHIHOUETTE COEFFICIENT) 

 

그러나 실제로 잘 작동하지 않는다.

 

실루엣 점수는 클러스터의 밀집 정도를 계산 -> 높을 수록 좋으며 최대는 1

 

밀집된 것은 잘하지만 모양이 복잡(TWO MOONS)같은 것에는 적절하지가 않다.

 

 

(from sklearn.metrics.cluster import silhouette_score

X, y = make_moons(n_samples=200, noise=0.05, random_state=0)

# 평균이 0, 분산이 1이 되도록 데이터의 스케일을 조정합니다
scaler = StandardScaler()
scaler.fit(X)
X_scaled = scaler.transform(X)

fig, axes = plt.subplots(1, 4, figsize=(15, 3),
                         subplot_kw={'xticks': (), 'yticks': ()})

# 비교를 위해 무작위로 클러스터 할당을 합니다
random_state = np.random.RandomState(seed=0)
random_clusters = random_state.randint(low=0, high=2, size=len(X))

# 무작위 할당한 클러스터를 그립니다
axes[0].scatter(X_scaled[:, 0], X_scaled[:, 1], c=random_clusters,
                cmap=mglearn.cm3, s=60, edgecolors='black')
axes[0].set_title("무작위 할당: {:.2f}".format(
        silhouette_score(X_scaled, random_clusters)))

algorithms = [KMeans(n_clusters=2), AgglomerativeClustering(n_clusters=2),
              DBSCAN()]

for ax, algorithm in zip(axes[1:], algorithms):
    clusters = algorithm.fit_predict(X_scaled)
    # 클러스터 할당과 클러스터 중심을 그립니다
    ax.scatter(X_scaled[:, 0], X_scaled[:, 1], c=clusters, cmap=mglearn.cm3,
               s=60, edgecolors='black')
    ax.set_title("{} : {:.2f}".format(algorithm.__class__.__name__,
                                      silhouette_score(X_scaled, clusters)))

 

DBSCAN에 결과과 더 낫지만 K-평균 실루엣 점수가  더 높다.

 

클러스터에 평가에 더 적합한 전략은 견고성 기방(ROBUST-BASED)의 지표입니다.

 

데이터에 잡은 포인트를 추가하거나 여러 매개 변수설정으로 알고리즘 실행 -> 결과 비교하기

 

매개변수와 데이터 변화를 주며 -> 반복 -> 결과 일정 -> 신뢰할만 하다 

 

군집 모델이 매우 안정적 . 실루엣 점수 높다 할지다로 -> 군집에 어떤 유의미한 것이 있는지 또는 군집이 데이터의 흥미로운 면을 반영하는지

 

알수가 없습다 -> 클러스터는 우리가 원하는 방식에 맞는 것인지 잘 알지 못함 

 


 

얼굴 데이터 셋으로 군집 알고리즘 비교

 

# LFW 데이터에서 고유얼굴을 찾은 다음 데이터를 변환합니다
from sklearn.decomposition import PCA
pca = PCA(n_components=100, whiten=True, random_state=0)
pca.fit_transform(X_people)
X_pca = pca.transform(X_people)

 

DBSCAN으로 얼굴 데이터셋 분석
# 기본 매개변수로 DBSCAN을 적용합니다
dbscan = DBSCAN()
labels = dbscan.fit_predict(X_pca)
print("고유한 레이블: {}".format(np.unique(labels)))
 
고유한 레이블: [-1]

레이블 -1 뿐 -> 잡음 포인트로 레이블 분류 -> 두가지 바꿀 수 있음

 

-> EPS 크게 하여 이웃 늘리기 /MIN_SAMPLES 낮추어 클러스터 모을 포인트 줄이기

dbscan = DBSCAN(min_samples=3)
labels = dbscan.fit_predict(X_pca)
print("고유한 레이블: {}".format(np.unique(labels)))
 
고유한 레이블: [-1]
dbscan = DBSCAN(min_samples=3, eps=15)
labels = dbscan.fit_predict(X_pca)
print("고유한 레이블: {}".format(np.unique(labels)))
 
고유한 레이블: [-1  0]
# 잡음 포인트와 클러스터에 속한 포인트 수를 셉니다.
# bincount는 음수를 받을 수 없어서 labels에 1을 더했습니다.
# 반환값의 첫 번째 원소는 잡음 포인트의 수입니다.
print("클러스터별 포인트 수: {}".format(np.bincount(labels + 1)))
 
클러스터별 포인트 수: [  32 2031]

잡읍 32개
noise = X_people[labels==-1]

fig, axes = plt.subplots(3, 9, subplot_kw={'xticks': (), 'yticks': ()},
                         figsize=(12, 4))
for image, ax in zip(noise, axes.ravel()):
    ax.imshow(image.reshape(image_shape), vmin=0, vmax=1)

 

이 레이블이 잡음으로 레이블이 되었는지 -

> 두번째 줄의 세번째 이미지 (손이 얼굴을 가린고) / 다섯번째 이미지 잔에 든것을 마시는 이미지 / 모자를 쓴 이미지도 존재 

다른 이미지 들은 얼굴 각도 이상, 얼굴 너무 가까이 혹은 멀리서 자른 경우

 

특이한 것을 찾아내는 이러한 분석을 이상치 검출

 

실제 어플리케이션 -> 이미지 여백을 잘라 -> 일정한 데이터셋 만드는 것이 좋음

 

-> 모자 쓰거나 무언가를 마시거나 얼굴 앞에 뭔가를 들고있는 것은 할수 있는 것이 없지만 -> 우리가 적용할 알고리즘에는 이슈

 

 

for eps in [1, 3, 5, 7, 9, 11, 13]:
    print("\neps={}".format(eps))
    dbscan = DBSCAN(eps=eps, min_samples=3)
    labels = dbscan.fit_predict(X_pca)
    print("클러스터 수: {}".format(len(np.unique(labels))))
    print("클러스터 크기: {}".format(np.bincount(labels + 1)))

 

 
eps=1
클러스터 수: 1
클러스터 크기: [2063]

eps=3
클러스터 수: 1
클러스터 크기: [2063]

eps=5
클러스터 수: 1
클러스터 크기: [2063]

eps=7
클러스터 수: 14
클러스터 크기: [2004    3   14    7    4    3    3    4    4    3    3    5    3    3]

eps=9
클러스터 수: 4
클러스터 크기: [1307  750    3    3]

eps=11
클러스터 수: 2
클러스터 크기: [ 413 1650]

eps=13
클러스터 수: 2
클러스터 크기: [ 120 1943]

EPS 작으면 -> 모두 잡음

 

재미있는 사실 -> 큰 클러스터는 하나빡에 없다 -> 큰 클러스터 하나가 대부분 포인트를 포함

-> 작은 클러스터 몇개 -> 이는 데이터에 확연히 구분되는 얼굴이 아니고 거의 동일하게 나온다는 것 의미

 

EPS =7 흥미로움

dbscan = DBSCAN(min_samples=3, eps=7)
labels = dbscan.fit_predict(X_pca)

for cluster in range(max(labels) + 1):
    mask = labels == cluster
    n_images =  np.sum(mask)
    fig, axes = plt.subplots(1, 14, figsize=(14*1.5, 4),
                             subplot_kw={'xticks': (), 'yticks': ()})
    i = 0
    for image, label, ax in zip(X_people[mask], y_people[mask], axes):
        ax.imshow(image.reshape(image_shape), vmin=0, vmax=1)
        ax.set_title(people.target_names[label].split()[-1])
        i += 1
    for j in range(len(axes) - i):
        axes[j+i].imshow(np.array([[1]*65]*87), vmin=0, vmax=1)
        axes[j+i].axis('off')

 

몇개의 클러스터는 슈뢰더나 고이즈미 처럼 매우 뚜렷한 얼굴 -> 같은 클러스터 이미지 -> 표정 각도 거의 동일 

 


 

K-평균 / 병합 군집

 

DBSCAN - 하나의 클러스터 외에는 만들 수 없었다 -> K-평균,병합군집 비슷한 크기의 클러스터 만들 수 잇지만 개수 지정해야함

실제 사람 수를 알고 있으므로 개수 지정할수 있지만 -> 10개 정도로 줄여서 해본다.

 

n_clusters = 10
# k-평균으로 클러스터를 추출합니다
km = KMeans(n_clusters=n_clusters, random_state=0)
labels_km = km.fit_predict(X_pca)
print("k-평균의 클러스터 크기: {}".format(np.bincount(labels_km)))

k-평균의 클러스터 크기: [155 175 238  75 358 257  91 219 323 172]

128~ 353까지의 클러스터로 나눔 -> DBSCAN 과 매우 다르다.


fig, axes = plt.subplots(2, 5, subplot_kw={'xticks': (), 'yticks': ()},
                         figsize=(12, 4))
for center, ax in zip(km.cluster_centers_, axes.ravel()):
    ax.imshow(pca.inverse_transform(center).reshape(image_shape),
              vmin=0, vmax=1)

X_PCA 는 주성분으로 변환된 (2063 X 100) km.cluster_centers_ 도 100개 특성 가진다.

시각화 하려면 5655 개의 원본 특성 공간으로 되돌려야 한다.

pca.inverse_transform 을 통해 원래 공간으로 되돌린 후 시각화 

 

k-평균이 찾은 클러스터 중심은 - 매우 부드러운 얼굴 이미지 -> 사실 각 중심 이미지는 128개에서 353 까지의 얼굴 이미지 평균

 

-> 놀라운 일은 아니고 -> 차원 감소된 pca 성분이 이미지를 더 부드럽게 바꾼다. 

 

-> 군집 알고리즘은 각기 다른 얼굴의 각도와 다른 표정 셔츠 깃의 여부로 선택한 것 같다 .

mglearn.plots.plot_kmeans_faces(km, pca, X_pca, X_people,
                                y_people, people.target_names)

 

3번째 클러스터 잘 보면 클러스터가 웃는 얼굴 

나머지 클러스터 - 얼굴 각도 중시 하는 것 확인

중심에서 멀수록 클러스터 중심과 많이 달라보이고 특별한 규칙이 없어 보인다 

k-평균 이 DBSCAN 처럼 잡음 포인트 개념이 없어 -> 모든 포인트를 구분하기 때문 

클러스터 늘릴수록 -> 직접 조사 어려워짐

 

병합군집

# 병합 군집으로 클러스터를 추출합니다
agglomerative = AgglomerativeClustering(n_clusters=10)
labels_agg = agglomerative.fit_predict(X_pca)
print("병합 군집의 클러스터 크기: {}".format(
       np.bincount(labels_agg)))
 
병합 군집의 클러스터 크기: [169 660 144 329 217  85  18 261  31 149]
print("ARI: {:.2f}".format(adjusted_rand_score(labels_agg, labels_km)))
 
ARI: 0.09

ARI = 0.09 -> 두 군집 LABELS_AGG ,LABELS_KM 공통인 것이 거의 없다

linkage_array = ward(X_pca)
# 클러스터 사이의 거리가 담겨있는 linkage_array로 덴드로그램을 그립니다
plt.figure(figsize=(20, 5))
dendrogram(linkage_array, p=7, truncate_mode='level', no_labels=True)

# 최대 7까지 깊이 제한해서 수직선의 개수가 7이하가 된다.

plt.xlabel("샘플 번호") plt.ylabel("클러스터 거리") ax = plt.gca() bounds = ax.get_xbound() ax.plot(bounds, [36, 36], '--', c='k')

 

수직선이 10개가 되는 비교적 높은 위치 잘라내어서 10개의 클러스터 만들었다

덴드그램에서는 가지의 길이를 보고 데이터를 나눈다 -> 얼굴 데이터셋에서는 자연 스러운 위치 못 찾음

-> K-MEAN 처럼 10개 클러스터 그림 나타내보기  병합군집 -> 클러스터중심개념 X

 

 

n_clusters = 10

for cluster in range(n_clusters):
    mask = labels_agg == cluster
    fig, axes = plt.subplots(1, 10, subplot_kw={'xticks': (), 'yticks': ()},
                             figsize=(15, 8))
    axes[0].set_ylabel(np.sum(mask))
    for image, label, asdf, ax in zip(X_people[mask], y_people[mask],
                                      labels_agg[mask], axes):
        ax.imshow(image.reshape(image_shape), vmin=0, vmax=1)
        ax.set_title(people.target_names[label].split()[-1],
                     fontdict={'fontsize': 9})

병합 군집의 클러스터 크기: [ 43 120 100 194  56  58 127  22   6  37  65  49  84  18 168  44  47  31
  78  30 166  20  57  14  11  29  23   5   8  84  67  30  57  16  22  12
  29   2  26   8]

더 일관된 클러스터 얻기 위해 -> 40개로 늘려서 해보기

 

# 병합 군집으로 클러스터를 추출합니다
agglomerative = AgglomerativeClustering(n_clusters=40)
labels_agg = agglomerative.fit_predict(X_pca)
print("병합 군집의 클러스터 크기: {}".format(np.bincount(labels_agg)))

n_clusters = 40
for cluster in [13, 16, 23, 38, 39]: # 흥미로운 클러스터 몇개를 골랐습니다
    mask = labels_agg == cluster
    fig, axes = plt.subplots(1, 15, subplot_kw={'xticks': (), 'yticks': ()},
                             figsize=(15, 8))
    cluster_size = np.sum(mask)
    axes[0].set_ylabel("#{}: {}".format(cluster, cluster_size))
    for image, label, asdf, ax in zip(X_people[mask], y_people[mask],
                                      labels_agg[mask], axes):
        ax.imshow(image.reshape(image_shape), vmin=0, vmax=1)
        ax.set_title(people.target_names[label].split()[-1],
                     fontdict={'fontsize': 9})
    for i in range(cluster_size, 15):
        axes[i].set_visible(False)
 
병합 군집의 클러스터 크기: [ 43 120 100 194  56  58 127  22   6  37  65  49  84  18 168  44  47  31
  78  30 166  20  57  14  11  29  23   5   8  84  67  30  57  16  22  12

29 2 26 8]

군집 알고리즘은 -> 대머리 옆모습 검은 피부색과 웃는 얼굴 / 웃는 여성 / 후세인

매우 비슷한 것을 뽑아냄


 

군집 알고리즘 요약 

군집 알고리즘 평가 -> 매우 정성적인 분석 과정

-> 탐색적 분석에서 크게 도움 -> K-평균, DBSCAN., 병합군집

K-평균 , 병합군집 :

원하는 클러스터 개수 지정 

DBSCAN :

EPS 매개변수 사용하여 클러스터 크기 간접적으로 조절 

각기 다른 장점 보유

K-평균 : 클러스터 중심을 사용해 클러스터 구분 . 각 데이터포인트를 클러스터 중심으로 대표 => 분해 방법으로 간주

DBSCAN : 클러스터 할당되지 않은 잡음 포인트를 인식 -> 클러스터 개수 자동 결정 (TWO MOONS 도 가능)

크기가  많이 다른 클러스터를 만드는데, 장점이자 단점 

병합군집 : 전체 데이터의 분할 계층도를 만들어주며 덴드로그램을 사용해 손쉽게 확인

728x90