k-평균
k-평균 알고리즘은 클러스터링 알고리즘 중 하나로, 주어진 데이터를 k개의 클러스터로 묶는 방법이며, 이를 위해 먼저 k개의 클러스터 중심(센트로이드)을 무작위로 선정하고, 각 데이터를 그 중 가장 가까운 클러스터 중심에 속하도록 할당한다. 그리고 다시 클러스터 중심을 해당 클러스터에 속하는 데이터들의 평균값으로 재계산하고, 이를 반복하여 더 이상 클러스터 중심이 움직이지 않을 때까지 계속해서 갱신한다.
하지만 k-평균 알고리즘에서는 클러스터의 개수 k를 미리 지정해야 하는데, 만약 k가 너무 작으면 클러스터들이 서로 겹치게 되고, 반대로 k가 너무 크면 클러스터들이 서로 분리되어 의미 있는 그룹화가 이루어지지 않게 된다. 그러므로 적절한 k 값을 찾는 것이 중요한데, 이를 위해서는 다양한 지표들을 고려해야 한다.
그 중에서도 이너셔( inertia )는 클러스터 중심과 클러스터에 속한 샘플 사이의 거리의 제곱합을 말한다. 즉, 클러스터에 속한 샘플이 얼마나 가깝게 모여있는지를 나타내는 값으로, 클러스터 개수가 늘어날수록 클러스터 개개의 크기가 줄어들면서 이너셔도 함께 감소하는 경향을 보인다.
엘보우 방법은 클러스터 개수를 늘려가면서 이너셔의 변화를 관찰하여 최적의 클러스터 개수를 찾는 방법이다. 클러스터 개수를 증가시키며 이너셔를 그래프로 그리면 감소하는 속도가 꺾이는 지점이 있는데, 이 지점 이후에 클러스터 개수를 늘려도 클러스터에 잘 밀집된 정도가 크게 개선되지 않는다. 이 지점이 마치 팔꿈치 모양 같아서 엘보우 방법이라고 부른다.
그러나 실제로는 여러 가지 요인에 의해 최적의 k 값을 찾는 것이 어려울 수 있으며, 특히 대규모 데이터셋이나 복잡한 구조의 데이터에서는 더욱 어렵다. 그래서 최근에는 k-평균 알고리즘 외에도 다양한 클러스터링 알고리즘이 개발되고 있으며, 데이터의 특성에 따라 적합한 알고리즘을 선택하는 것이 중요하다.
k-평균 알고리즘 훈련하기
레이블 없는 데이터셋에 k-평균 알고리즘을 훈련해본다.
먼저 훈련을 위한 데이터셋을 로드한 후 만들어본다.
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs
blob_centers = np.array([[ 0.2, 2.3], [-1.5 , 2.3], [-2.8, 1.8],
[-2.8, 2.8], [-2.8, 1.3]])
blob_std = np.array([0.4, 0.3, 0.1, 0.1, 0.1])
X, y = make_blobs(n_samples=2000, centers=blob_centers, cluster_std=blob_std,
random_state=7)
k = 5
kmeans = KMeans(n_clusters=k, n_init=10, random_state=42)
y_pred = kmeans.fit_predict(X)
이 데이터셋을 시각화해본다
def plot_clusters(X, y=None):
plt.scatter(X[:, 0], X[:, 1], c=y, s=1)
plt.xlabel("$x_1$")
plt.ylabel("$x_2$", rotation=0)
plt.figure(figsize=(8, 4))
plot_clusters(X)
plt.gca().set_axisbelow(True)
plt.grid()
plt.show()
▶ 샘플 덩어리 다섯 개로 이루어진 것을 확인할 수 있다.
각 샘플은 다섯 개의 클러스터 중 하나에 할당되는데, 군집에서 각 샘플의 레이블은 알고리즘이 샘플에 할당한 클러스터의 인덱스이다. KMeans 클래스의 인스턴스는 labels_ 인스턴스 변수에 훈련된 샘플의 예측 레이블을 가지고 있다.
※ k-평균 알고리즘은 알고리즘이 최적의 클러스터 개수를 지정해야 하는데 이 예에서는 데이터를 보고 k를 5로 지정. (일반적으로는 먼저 클러스터 중심을 무작위로 선정 후, 각 데이터를 그 중 가장 가까운 클러스터 중심에 속하도록 할당한 뒤, 클러스터 중심을 해당 클러스터에 속하는 데이터들의 평균값으로 재계산하는 과정을 반복하여 더 이상 클러스터 중심이 움직이지 않을 때까지 계속해서 갱신하는 방식으로 최적의 클러스터 개수를 찾는다.)
y_pred
y_pred is kmeans.labels_
이 알고리즘이 찾은 센트로이드 다섯 개도 확인해본다
kmeans.cluster_centers_
새로운 샘플에 가장 가까운 센트로이드의 클러스터를 할당할 수 있다.
import numpy as np
X_new = np.array([[0, 2], [3, 2], [-3, 3], [-3, 2.5]])
kmeans.predict(X_new)
클러스터의 결정 경계를 그벼로면 보로노이 다이어그램(Voronoi tessellation)을 얻을 수 있으며, 센트로이드가 x로 표시되어 있다.
※ 보로노이 다이어그램: 평면을 특정 점까지의 거리가 가장 가까운 점의 집합으로 분할한 그림.
def plot_data(X):
plt.plot(X[:, 0], X[:, 1], 'k.', markersize=2)
def plot_centroids(centroids, weights=None, circle_color='w', cross_color='k'):
if weights is not None:
centroids = centroids[weights > weights.max() / 10]
plt.scatter(centroids[:, 0], centroids[:, 1],
marker='o', s=35, linewidths=8,
color=circle_color, zorder=10, alpha=0.9)
plt.scatter(centroids[:, 0], centroids[:, 1],
marker='x', s=2, linewidths=12,
color=cross_color, zorder=11, alpha=1)
def plot_decision_boundaries(clusterer, X, resolution=1000, show_centroids=True,
show_xlabels=True, 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.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)
plt.contourf(Z, extent=(mins[0], maxs[0], mins[1], maxs[1]),
cmap="Pastel2")
plt.contour(Z, extent=(mins[0], maxs[0], mins[1], maxs[1]),
linewidths=1, colors='k')
plot_data(X)
if show_centroids:
plot_centroids(clusterer.cluster_centers_)
if show_xlabels:
plt.xlabel("$x_1$")
else:
plt.tick_params(labelbottom=False)
if show_ylabels:
plt.ylabel("$x_2$", rotation=0)
else:
plt.tick_params(labelleft=False)
plt.figure(figsize=(8, 4))
plot_decision_boundaries(kmeans, X)
plt.show()
샘플 대부분은 적절한 클러스터에 잘 할당되었으나, 몇 개는 레이블이 잘못 부여되었다.
k-평균 알고리즘은 샘플을 클러스터에 할당할 때 센트로이드까지 거리를 고려하는 것이 전부이기 때문에 클러스터의 크기가 많이 다르면 잘 작동하지 않는다.
[샘플을 할당하는 방식: 하드 군집 vs 소프트 군집]
- 하드 군집: 샘플을 하나의 클러스터에 할당
- 소프트 군집: 클러스터마다 샘플에 점수를 부여하는 방식. 점수는 샘플과 센트로이드 사이의 거리나 가우스 방사 기저 함수와 같은 유사도 점수(similarity score)가 될 수 있다.
KMeans 클래스의 transform() 메서드는 샘플과 각 센트로이드 사이의 거리를 반환한다.
kmeans.transform(X_new).round(2)
▶ 이것을 해석하자면,
첫 번째 샘플이 첫 번째 센트로이드에서 약 2.84,
두 번째 샘플이 두 번째 센트로이드에서 약 0.59,
세 번째 센트로이드에서 약 1.5,
네 번째 센트로이드에서 약 2.9,
다섯 번째 센트로이드에서 약 0.31 거리만큼 떨어져있다는 뜻이다.
고차원 데이터셋을 이런 방식으로 변환하면 k차원 데이터셋이 만들어지며, 이 변환은 매우 효율적인 비선형 차원 축소 기법이 될 수 있다. 또는 이러한 거리를 추가 특성으로 사용하여 다른 모델을 훈련할 수 있다.
[k-평균 알고리즘]
아래의 그림은 k-평균 알고리즘이 작동하는 것을 보여준다.
<알고리즘 진행 과정>
- 센트로이드를 랜덤하게 초기화한다
- 샘플에 레이블을 할당한다
- 센트로이드를 업데이트 한다
- 샘플에 다시 레이블을 할당한다
- 센트로이드를 다시 업데이트 한다
- (세 번만에) 최적으로 보이는 클러스터에 도달
▶ 이 알고리즘의 수렴은 보장되지만 적절한 솔루션으로 수렴하지 못할 수 있는데, 이 여부는 센트로이드 초기화에 달려있다.
k-평균 알고리즘에서 센트로이드 초기화를 개선하는 이유는 센트로이드의 위치가 잘못 선정되면 알고리즘의 성능이 저하될 수 있기 때문이다. 센트로이드는 각 클러스터의 중심점을 의미하며, 이는 각 데이터의 거리를 계산하는 데 사용된다. 따라서 센트로이드의 위치가 잘못 선정되면 데이터의 거리가 정확하게 계산되지 않아 잘못된 클러스터링 결과가 나올 수 있다.
센트로이드 초기화를 개선하는 방법으로는 다양한 방법이 있는데, 이러한 방법을 사용하면 센트로이드의 위치가 더 정확하게 선정되어 알고리즘의 성능이 향상될 수 있다.
[센트로이드 초기화 방법]
1) init 매개변수에 n_init=1 로 설정하여 센트로이드 초기화를 한 번만 진행
센트로이드 위치를 근사할 수 있다면 init 매개변수에 센트로이드 리스트를 담은 넘파이 배열을 지정하고, n_init을 1로 설정할 수 있다. 이렇게 되면 센트로이드 초기화를 한 번만 진행하게 되므로 알고리즘의 실행 시간을 단축할 수 있다.
일반적으로 k-평균 알고리즘에서는 초기 센트로이드를 무작위로 선택하는데, 이 경우에는 매번 다른 결과가 나와서 수렴 여부를 판단하기 어렵다. 하지만 미리 지정한 센트로이드 리스트를 사용하면 동일한 초기 조건에서 알고리즘을 실행할 수 있으므로 수렴 여부를 쉽게 판단할 수 있다.
단, 이 경우에는 초기 센트로이드 리스트가 올바른 값이어야 하며, 그렇지 않으면 알고리즘이 제대로 동작하지 않을 수 있다. 따라서 초기 센트로이드 리스트를 선택할 때는 신중하게 고려해야 한다.
good_init = np.array([[-3, 3], [-3, 2], [-3, 1], [-1, 2], [0, 2]])
kmeans = KMeans(n_clusters=5, init=good_init, n_init=1, random_state=42)
kmeans.fit(X)
2) 랜덤 초기화를 다르게 하여 여러 번 알고리즘 실행 후 가장 좋은 솔루션 선택
랜덤 초기화 횟수는 n_init 매개변수로 조절하며, 기본값은 10이다. 이는 fit() 메서드를 호출할 때 앞서 설명한 전체 알고리즘이 10번 실행된다는 뜻이다.
사이킷런은 이 중에 최선의 솔루션을 반환하는데, 이때 최선의 솔루션을 확인할 때 사용하는 이너셔(inertia)라는 성능 지표가 있다.
이너셔라는 값은 각 샘플과 가장 가까운 센트로이드 사이의 제곱 거리 합을 말하며 KMeans 클래스는 알고리즘을 n_init 번 실행하여 이너셔가 가장 낮은 모델을 반환한다. 또한, 이너셔 값이 궁금하면 inertia_ 인스턴스 변수로 모델의 이너셔를 확인할 수 있다.
kmeans.inertia_
score() 메서드는 이너셔의 음숫값을 반환하는 데, 음수를 반환하는 이유는 예측기의 score() 메서드는 사이킷런의 '큰 값이 좋은 것이다'라는 규칙을 따라야 하기 때문이며, 한 예측기가 다른 것보다 좋다면 score() 메서드가 더 높은 값을 반환해야 한다.
kmeans.score(X)
k-평균++ 알고리즘
k-평균++ 알고리즘은 k-평균 알고리즘의 문제점인 초기 센트로이드를 무작위로 선택하는 문제를 해결하기 위해 데이비드 아서와 세르게이 바실비츠키가 2006년 논문에서 제안한 알고리즘이다. k-평균++ 알고리즘은 초기 센트로이드를 무작위로 선택하는 대신, 이미 선택된 센트로이드와 가장 멀리 떨어진 데이터를 선택하므로 보다 정확한 클러스터링 결과를 얻을 수 있다.
[k-평균++ 알고리즘의 원리]
1. 첫 번째 센트로이드를 무작위로 선택
: k-평균++ 알고리즘에서는 첫 번째 센트로이드를 무작위로 선택.
2. 나머지 센트로이드를 선택
:첫 번째 센트로이드를 선택한 후에는 나머지 센트로이드를 선택. 이때, 이미 선택된 센트로이드와 가장 멀리 떨어진 데이터를 선택.
3.선택된 센트로이드를 중심으로 데이터를 분류
4. 분류된 데이터의 평균을 새로운 센트로이드로 설정
5. 위 과정을 반복
: 위 과정을 반복하여 최종적인 클러스터링 결과를 도출.
초기화를 K-Means++로 설정하려면 init="k-means++"를 설정하기만 하면 된다.
k-평균 속도 개선과 미니배치 k-평균
[k-평균 속도 개선]
2013년 찰스 엘칸은 클러스터가 논문에서 클러스터가 많은 일부 대규모 데이터셋에서 불필요한 거리 계산을 피함으로써 알고리즘의 속도를 상당히 높일 수 있다는 중요한 개선을 제안했다. 엘칸은 이를 위해 두 점 사이의 직선은 항상 가장 짧은 거리가 된다는 삼각 부등식을 사용했다. 그리고 샘플과 센트로이드 사이의 거리를 위한 하한선과 상한선을 유지한다.그러나 엘칸의 알고리즘이 항상 훈련 속도를 높일 수 있는 것은 아니며 데이터셋에 따라 훈련 속도가 상당히 느려질 수도 있다.
엘칸 방식의 K-평균을 사용하려면 algorithm="elkan"을 사용한다. (일반 K-평균의 경우 algorithm="full"을 사용)
[미니배치 k-평균]
2010년 데이비드 스컬리는 논문에서 k-평균 알고리즘의 또 다른 변형을 제시하였는데, 이 알고리즘은 전체 데이터셋을 사용해 반복하지 않고 각 반복마다 미니배치를 사용해 센트로이드를 조금씩 이동한다. 이는 일반적으로 알고리즘의 속도를 3배에서 4배 정도 높이고, 메모리에 들어가지 않는 대량의 데이터셋에 군집 알고리즘을 적용할 수 있다.
사이킷런은 MinibatchKMeans 클래스에 이 알고리즘을 구현했다.
from sklearn.cluster import MiniBatchKMeans
minibatch_kmeans = MiniBatchKMeans(n_clusters=5, random_state=42)
minibatch_kmeans.fit(X)
데이터셋이 메모리에 들어가지 않는 경우 가장 간단한 방법은 memmap 클래스를 사용하는 거나, MiniBatchKMeans 클래스의 partial_fit() 메서드에 한 번에 하나의 미니배치를 전달하는 것이다. 그러나 초기화를 여러 번 수행하고 만들어진 결과에서 가장 좋은 것을 직접 골라야 해서 번거롭다.
미니배치 k-평균 알고리즘이 일반 k-평균 알고리즘보다 훨씬 빠르지만 이너셔는 일반적으로 조금 더 나쁘다.
아래는 미니 배치 K-평균과 일반 K-평균 간의 이너셔 비율과 훈련 시간 비율을 시각화한 코드다.
from timeit import timeit
max_k = 100
times = np.empty((max_k, 2))
inertias = np.empty((max_k, 2))
for k in range(1, max_k + 1):
# 사이킷런 1.3에서 'full'이 deprecated 된다는 경고를 피하기 위해 'lloyd'로 지정함
kmeans_ = KMeans(n_clusters=k, algorithm="lloyd", n_init=10, random_state=42)
minibatch_kmeans = MiniBatchKMeans(n_clusters=k, n_init=10, random_state=42)
print(f"\r{k}/{max_k}", end="") # \r returns to the start of line
times[k - 1, 0] = timeit("kmeans_.fit(X)", number=10, globals=globals())
times[k - 1, 1] = timeit("minibatch_kmeans.fit(X)", number=10,
globals=globals())
inertias[k - 1, 0] = kmeans_.inertia_
inertias[k - 1, 1] = minibatch_kmeans.inertia_
plt.figure(figsize=(10, 4))
plt.subplot(121)
plt.plot(range(1, max_k + 1), inertias[:, 0], "r--", label="K-평균")
plt.plot(range(1, max_k + 1), inertias[:, 1], "b.-", label="미니 배치 K-평균")
plt.xlabel("$k$")
plt.title("이너셔")
plt.legend()
plt.axis([1, max_k, 0, 100])
plt.grid()
plt.subplot(122)
plt.plot(range(1, max_k + 1), times[:, 0], "r--", label="K-평균")
plt.plot(range(1, max_k + 1), times[:, 1], "b.-", label="미니 배치 K-평균")
plt.xlabel("$k$")
plt.title("훈련 시간 (초)")
plt.axis([1, max_k, 0, 4])
plt.grid()
plt.show()
★ 성능이 비슷한 이유
두 알고리즘의 이너셔와 훈련시간이 비슷해진 이유는 다음과 같은 여러 요인이 있을 수 있다:
- 알고리즘의 개선: 시간이 지남에 따라 두 알고리즘 모두 최적화 및 개선이 이루어졌기 때문에, 성능 격차가 줄어들 수 있다.
- 하드웨어 성능 향상: 최신 하드웨어는 데이터 병렬 처리와 빠른 계산을 지원하여, 훈련 속도의 차이가 줄어들 수 있다.
- 데이터셋의 최적화: 사용되는 데이터셋의 특징에 따라 미니배치 k-평균의 효율이 k-평균과 비슷해질 수 있다.
두 알고리즘 사이의 성능 차이가 줄어드는 것은 일반적인 현상일 수 있으며, 이는 알고리즘 개선, 하드웨어 발전, 데이터셋 최적화 등 여러 요인이 작용한 결과일 수 있다.
최적의 클러스터 개수 찾기
일반적으로 클러스터 개수 k를 어떻게 설정할 지 쉽게 알 수 없으며, 만약 올바르게 지정하지 않으면 결과는 매우 나쁠 수 있다.
아래는 데이터셋에서 k를 각 3, 5, 8로 지정한 후 결정 경계를 시각화한 코드다.
def plot_clusterer_comparison_1(clusterer1, clusterer2, clusterer3, X, title1, title2, title3):
# 클러스터링 수행
clusterer1.fit(X)
clusterer2.fit(X)
clusterer3.fit(X)
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(18, 5), sharey=True) # 한 줄에 3개 서브플롯
plt.sca(ax1)
plot_decision_boundaries(clusterer1, X, show_centroids=True, show_xlabels=True)
ax1.set_title(title1)
plt.sca(ax2)
plot_decision_boundaries(clusterer2, X, show_centroids=True, show_xlabels=True)
ax2.set_title(title2)
plt.sca(ax3)
plot_decision_boundaries(clusterer3, X, show_centroids=True, show_xlabels=True)
ax3.set_title(title3)
plt.show()
클러스터링 결정 경계 시각화
# k-평균 클러스터링을 위한 객체 생성
kmeans_k3 = KMeans(n_clusters=3, n_init=10, random_state=42)
kmeans_k5 = KMeans(n_clusters=5, n_init=10, random_state=42)
kmeans_k8 = KMeans(n_clusters=8, n_init=10, random_state=42)
# 클러스터링 결과를 비교하는 플롯 함수 호출
plot_clusterer_comparison_1(kmeans_k3, kmeans_k5, kmeans_k8, X, "$k=3$", "$k=5$", "$k=8$")
plt.show()
▶ k가 너무 작으면 별개의 클러스터가 합쳐지고, k가 너무 크면 하나의 클러스터가 여러 개로 나뉜다.
1) 엘보우 포인트
그렇다면 이러한 경우 가장 작은 이너셔를 가진 모델을 선택하는 것도 좋은 해결책은 아니다.
이너셔는 k가 증가함에 따라 점점 작아지므로 k를 선택할 때 좋은 성능 지표가 아니며, 실제로 클러스터가 늘어날수록 각 샘플은 가까운 센트로이드에 더 가깝게 된다.
이너셔를 k의 함수로 그래프를 그려본다.
kmeans_per_k = [KMeans(n_clusters=k, n_init=10, random_state=42).fit(X)
for k in range(1, 10)]
inertias = [model.inertia_ for model in kmeans_per_k]
plt.figure(figsize=(8, 3.5))
plt.plot(range(1, 10), inertias, "bo-")
plt.xlabel("$k$")
plt.ylabel("이너셔")
plt.annotate("", xy=(4, inertias[3]), xytext=(4.45, 650),
arrowprops=dict(facecolor='black', shrink=0.1))
plt.text(4.5, 650, "엘보", horizontalalignment="center")
plt.axis([1, 8.5, 0, 1300])
plt.grid()
plt.show()
▶ 그림을 통해 이너셔는 k가 4까지 증가할 때 빠르게 줄어들며, k가 계속 증가하면 이너셔는 훨씬 느리게 감소한다.
따라서 이 그래프를 보면 k=4 지점이 엘보우며, k에 대한 정답을 모른다면 엘보우 포인트인 4는 좋은 선택이 된다.
※ 엘보우(Elbow)
성능 평가지표를 이용하여 최적의 군집수를 선택하는 방법 중 하나로 엘보우 포인트(Elbow Point)에서 최적 군집수를 결정하는 방식이다. 엘보우 포인트는 그래프가 원만한 경사를 이루는 지점을 말한다.
2) 실루엣 점수
엘보우 포인트보다 더 정확하지만 계산 비용이 많이 드는 방법은 실루엣 점수이다.
이 값은 모든 샘플에 대한 실루엣 계수의 평균이다.
[실루엣 점수 관련 내용]
실루엣 점수를 계산하려면 사이킷런의 silhouette_score() 함수를 사용한다.
from sklearn.metrics import silhouette_score
silhouette_score(X, kmeans.labels_)
실루엣 계수는 -1에서 +1까지 바뀔 수 있다.
- +1에 가까우면 자신의 클러스터 안에 잘 속해있고 다른 클러스터와는 멀리 떨어져 있다는 의미
- 0에 가까우면 클러스터 경계에 위치
- -1에 가까우면 샘플이 잘못된 클러스터에 할당되었다는 의미
클러스터 개수를 달리하여 실루엣 점수를 비교해 본다.
silhouette_scores = [silhouette_score(X, model.labels_)
for model in kmeans_per_k[1:]]
plt.figure(figsize=(8, 3))
plt.plot(range(2, 10), silhouette_scores, "bo-")
plt.xlabel("$k$")
plt.ylabel("실루엣 점수")
plt.axis([1.8, 8.5, 0.55, 0.7])
plt.grid()
plt.show()
▶ 이너셔만 비교했을 때와는 달리 더 많은 정보를 준다. k=4와 k=5가 좋은 선택임을 알려주고, k=6이나 k=7보다 훨씬 좋다는 것을 알려준다.
[실루엣 다이어그램]
모든 샘플의 실루엣 계수를 할당된 클러스터와 계수 값으로 정렬하여 (실루엣 다어그램으로) 그리면 더 많은 정보가 있는 그래프를 얻을 수 있다.
- 실루엣 다이어그램은 클러스터마다 칼 모양의 그래프가 그려짐.
- 이 그래프의 높이는 클러스터가 포함하고 있는 샘플의 개수를 의미.
- 너비는 이 클러스터에 포함된 샘플의 정렬된 실루엣 계수를 나타내며 넓을수록 좋음.
- 수직 파선은 각 클러스터 개수에 해당하는 평균 실루엣 점수를 나타내며, 한 클러스터의 샘플 대부분이 이 점수보다 낮은 계수를 가지면 클러스터의 샘플이 다른 클러스터랑 너무 가깝다는 것을 의미하므로 나쁜 클러스터이다.
from sklearn.metrics import silhouette_samples
from matplotlib.ticker import FixedLocator, FixedFormatter
plt.figure(figsize=(11, 9))
for k in (3, 4, 5, 6):
plt.subplot(2, 2, k - 2)
y_pred = kmeans_per_k[k - 1].labels_
silhouette_coefficients = silhouette_samples(X, y_pred)
padding = len(X) // 30
pos = padding
ticks = []
for i in range(k):
coeffs = silhouette_coefficients[y_pred == i]
coeffs.sort()
color = plt.cm.Spectral(i / k)
plt.fill_betweenx(np.arange(pos, pos + len(coeffs)), 0, coeffs,
facecolor=color, edgecolor=color, alpha=0.7)
ticks.append(pos + len(coeffs) // 2)
pos += len(coeffs) + padding
plt.gca().yaxis.set_major_locator(FixedLocator(ticks))
plt.gca().yaxis.set_major_formatter(FixedFormatter(range(k)))
if k in (3, 5):
plt.ylabel("클러스터")
if k in (5, 6):
plt.gca().set_xticks([-0.1, 0, 0.2, 0.4, 0.6, 0.8, 1])
plt.xlabel("실루엣 계수")
else:
plt.tick_params(labelbottom=False)
plt.axvline(x=silhouette_scores[k - 2], color="red", linestyle="--")
plt.title(f"$k={k}$")
plt.show()
▶ 수직 파선을 통해 k=3과 k=6에서 나쁜 클러스터(많은 샘플이 파선의 왼쪽에서 멈춤) 를 볼 수 있고,
k=4나 k=5일 때는 대부분의 샘플이 파선을 넘어 뻗어 있고 1.0에 근접해 있어 클러스터가 좋아보인다.
k=4일 때 인덱스 2의 클러스터, k=5일 때는 인덱스 0의 클러스터가 매우 크다.
이런 경우에는 실루엣 점수가 전반적으로 높고, 비슷한 크기의 클러스터를 얻을 수 있는 k=4를 선택하는 것이 좋다.
plot_decision_boundaries(kmeans_per_k[4 - 1], X)
plt.show()
k=4와 k=5 일 때의 클러스터 결정 경계 시각화를 통한 비교
def plot_clusterer_comparison(clusterer1, clusterer2, X, title1, title2):
# 클러스터링 수행
clusterer1.fit(X)
clusterer2.fit(X)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5), sharey=True)
plt.sca(ax1)
plot_decision_boundaries(clusterer1, X, show_centroids=True, show_xlabels=True)
ax1.set_title(title1)
plt.sca(ax2)
plot_decision_boundaries(clusterer2, X, show_centroids=True, show_xlabels=True)
ax2.set_title(title2)
plt.show()
kmeans_k4 = KMeans(n_clusters=4, n_init=10, random_state=42)
kmeans_k5 = KMeans(n_clusters=5, n_init=10, random_state=42)
plot_clusterer_comparison(kmeans_k4, kmeans_k5, X, "$k=4$", "$k=5$")
plt.show()
k-평균을 실행하기 전에 입력 특성의 스케일을 맞추는 것이 중요한데, 그렇지 않으면 클러스터가 길쭉해지고 k-평균의 결과가 좋지 않다. 특성의 스케일을 맞추어도 모든 클러스터가 잘 구분되고 원형의 형태를 가진다고 보장할 수는 없지만 일반적으로 k-평균에 도움이 된다.
다음 내용
[출처]
핸즈 온 머신러닝
위키백과
https://bommbom.tistory.com/
https://blog.naver.com/silverbjin/223627717077
https://flowersayo.tistory.com/137
lets-start-data.tistory.com
https://blog.naver.com/vita1202/223339370844
https://blog.naver.com/softbluecloud/223652313937
'[파이썬 Projects] > <파이썬 머신러닝>' 카테고리의 다른 글
[머신러닝] 텐서플로 모델 훈련과 배포 (0) | 2024.12.07 |
---|---|
[머신러닝] 군집: 군집 사례 (1) | 2024.11.16 |
[머신러닝] 머신러닝 기반 분석 모형 선정 (1) | 2024.11.16 |
[머신러닝] 차원 축소: 랜덤 투영, 지역 선형 임베딩 (3) | 2024.11.15 |
[머신러닝] 차원 축소: 주성분 분석 (추가) (3) | 2024.11.15 |