Main Content

군집 분석

이 예제에서는 Statistics and Machine Learning Toolbox™에서 군집 분석을 사용하여 관측값 또는 객체의 유사성과 비유사성을 검토하는 방법을 보여줍니다. 데이터는 대개 기본적으로 관측값의 그룹(또는 군집)에 속하며, 동일한 군집에 속하는 객체의 특성은 유사하고 서로 다른 군집에 속하는 객체의 특성은 서로 다릅니다.

K-평균 군집화 및 계층적 군집화

Statistics and Machine Learning Toolbox에는 K-평균 군집화와 계층적 군집화를 수행하는 함수가 포함되어 있습니다.

K-평균 군집화는 데이터의 관측값을 위치와 거리가 서로 다른 객체로 처리하는 분할 방법입니다. 이 방법은 객체가 각 군집 내에서는 최대한 서로 가까이 있고 다른 군집 내의 객체와는 최대한 멀리 있도록 객체를 K개의 상호 배타적인 군집으로 분할합니다. 각 군집은 군집의 중심, 즉 중심점으로 특징지을 수 있습니다. 물론, 군집화에 사용된 거리는 대개의 경우 공간 거리를 나타내지 않습니다.

계층적 군집화는 군집 트리를 생성하여 다양한 거리 스케일에 대해 데이터의 그룹화를 동시에 조사하는 방법입니다. 이 트리는 K-평균에서처럼 여러 군집으로 구성된 단일 세트가 아니라 다중 수준 계층입니다. 여기서 한 수준에 있는 하나 또는 여러 군집은 다음 상위 수준의 군집으로 결합됩니다. 이를 통해 현재 응용 사례에 가장 적합한 군집화 스케일 또는 수준을 결정할 수 있습니다.

이 예제에서 사용하는 일부 함수는 MATLAB®의 난수 생성 함수를 호출합니다. 이 예제에 표시된 결과를 정확히 다시 재현하려면 아래 명령을 실행하여 난수 생성기를 알려진 상태로 설정해야 합니다. 상태를 설정하지 않으면 결과가 소소하게 달라질 수 있으며, 예를 들어, 군집의 번호가 다른 순서로 지정될 수 있습니다. 준최적 군집 해가 결과로 나타날 가능성도 있습니다(이 예제에는 준최적 해를 방지할 수 있는 방법을 포함하여 준최적 해에 대한 설명이 포함되어 있음).

rng(6,'twister')

피셔(Fisher)의 붓꽃 데이터

1920년대에 식물학자들은 세 개의 종에서 각각 50개씩, 총 150개의 붓꽃 표본을 대상으로 꽃받침 길이, 꽃받침 너비, 꽃잎 길이, 꽃잎 너비를 측정한 값을 수집했습니다. 이 측정값은 피셔(Fisher)의 붓꽃 데이터 세트라고 알려졌습니다.

이 데이터 세트의 각 관측값은 알려진 종에서 추출한 것이며, 따라서 데이터를 그룹화할 뚜렷한 방법이 이미 있습니다. 여기서는 종에 관한 정보를 무시하고 오직 원시 측정값만 사용하여 데이터를 군집화하겠습니다. 작업을 마치고 나면 결과로 생성된 군집을 실제 종과 비교하여 세 가지 붓꽃 유형이 고유한 특성을 가지는지 확인할 수 있습니다.

K-평균 군집화를 사용하여 피셔(Fisher)의 붓꽃 데이터 군집화하기

함수 kmeans는 모든 군집에 대해 각 객체에서 해당 군집 중심까지의 거리의 합이 최소가 되도록 객체를 군집에 할당하는 반복 알고리즘을 사용하여 K-평균 군집화를 수행합니다. 피셔의 붓꽃 데이터에 사용된 이 함수는 붓꽃의 꽃받침 및 꽃잎 측정값을 기준으로 붓꽃 표본에서 자연적 그룹화를 찾습니다. K-평균 군집화에서는 생성하려는 군집 개수를 지정해야 합니다.

먼저, 데이터를 불러오고 원하는 군집 개수를 2로 설정하고 제곱 유클리드 거리를 사용하여 kmeans를 호출합니다. 실루엣 플롯을 생성함으로써 결과로 생성된 군집이 얼마나 잘 분리되었는지 파악할 수 있습니다. 실루엣 플롯은 하나의 군집에 포함된 각 점이 인접 군집에 포함된 점에 얼마나 가까운지를 측정한 값을 표시합니다.

load fisheriris
[cidx2,cmeans2] = kmeans(meas,2,'dist','sqeuclidean');
[silh2,h] = silhouette(meas,cidx2,'sqeuclidean');

실루엣 플롯에서 두 군집 내의 대부분의 점이 큰 실루엣 값(0.8보다 큼)을 가지는 것을 알 수 있습니다. 이는 대부분의 점이 인접 군집으로부터 잘 분리되었음을 나타냅니다. 그러나 각 군집에는 낮은 실루엣 값을 갖는 점도 몇 개 포함되어 있습니다. 이 결과는 이러한 점이 다른 군집의 점에 가까이 있음을 나타냅니다.

이를 통해 이 데이터의 네 번째 측정값인 꽃잎 너비가 세 번째 측정값인 꽃잎 길이와 밀접한 상관관계를 가지며, 따라서 4차원에 의존하지 않고도 처음 세 개 측정값에 대한 3차원 플롯으로 데이터를 효과적으로 표현할 수 있다는 것을 알 수 있습니다. kmeans로 생성된 각 군집에 각기 다른 기호를 사용하여 데이터를 플로팅하면 작은 실루엣 값을 가지는 점 즉, 다른 군집의 점에 가까운 점을 식별할 수 있습니다.

ptsymb = {'bs','r^','md','go','c+'};
for i = 1:2
    clust = find(cidx2==i);
    plot3(meas(clust,1),meas(clust,2),meas(clust,3),ptsymb{i});
    hold on
end
plot3(cmeans2(:,1),cmeans2(:,2),cmeans2(:,3),'ko');
plot3(cmeans2(:,1),cmeans2(:,2),cmeans2(:,3),'kx');
hold off
xlabel('Sepal Length');
ylabel('Sepal Width');
zlabel('Petal Length');
view(-137,10);
grid on

각 군집의 중심은 동그라미 쳐진 X를 사용하여 플롯에 그려졌습니다. 삼각형으로 그려진 하부 군집의 점에서 세 점이 정사각형으로 그려진 상부 군집의 점과 매우 가깝습니다. 상부 군집이 상당히 분산되어 있기 때문에 이 세 점은 소속 군집에서 대부분의 점과 많이 떨어져 있다 하더라도 상부 군집의 중심보다는 하부 군집의 중심에 더 가깝습니다. K-평균 군집화는 밀도가 아니라 거리만 고려하므로 이러한 결과가 발생할 수 있습니다.

군집 개수를 늘려서 kmeans가 데이터를 더 잘 그룹화할 수 있는지 살펴봅니다. 이번에는 'Display' 이름-값 쌍의 인수를 사용하여 군집화 알고리즘의 각 반복 과정에 대한 정보를 출력해 보겠습니다.

[cidx3,cmeans3] = kmeans(meas,3,'Display','iter');
  iter	 phase	     num	         sum
     1	     1	     150	     146.424
     2	     1	       5	     144.333
     3	     1	       4	     143.924
     4	     1	       3	      143.61
     5	     1	       1	     143.542
     6	     1	       2	     143.414
     7	     1	       2	     143.023
     8	     1	       2	     142.823
     9	     1	       1	     142.786
    10	     1	       1	     142.754
Best total sum of distances = 142.754

각 반복마다 kmeans 알고리즘(알고리즘 참조)은 군집 간에 점을 재할당하여 점-중심 간 거리의 합을 줄인 후 새로운 군집을 구성하고 그 군집에 대한 군집 중심값을 재계산합니다. 알고리즘이 최솟값에 도달할 때까지 각 반복 과정마다 거리의 총 합과 재할당 수가 줄어드는 것을 알 수 있습니다. kmeans에 사용되는 알고리즘은 두 단계로 구성됩니다. 여기에 나온 예제에서는 알고리즘의 두 번째 단계에서 어떠한 재할당도 수행하지 않았으며, 이는 첫 번째 단계에서 단지 몇 번의 반복만 수행한 후 최솟값에 도달했음을 의미합니다.

기본적으로, kmeans는 일련의 초기 중심 위치값을 임의로 선택하여 군집화 과정을 시작합니다. kmeans 알고리즘은 국소 최솟값인 해로 수렴할 수 있습니다. 즉, kmeans는 단일 점을 다른 군집으로 이동할 경우 거리의 총합이 증가하도록 데이터를 분할할 수 있습니다. 그러나 다른 여러 유형의 수치적 최소화 기법과 마찬가지로 kmeans가 도달하는 해는 시작 점에 따라 결정되기도 합니다. 따라서 데이터에서 거리 총합이 더 낮은 다른 해(국소 최솟값)가 있을 수 있습니다. 선택 사항인 'Replicates' 이름-값 쌍의 인수를 사용하여 여러 해를 테스트해 볼 수 있습니다. 둘 이상의 반복 실험을 지정하면 kmeans가 각 반복 실험마다 다른 중심들을 임의로 선택하여 군집화를 반복합니다. 그러면 kmeans가 모든 반복 실험 중에서 거리 총합이 가장 낮은 해를 반환합니다.

[cidx3,cmeans3,sumd3] = kmeans(meas,3,'replicates',5,'display','final');
Replicate 1, 9 iterations, total sum of distances = 78.8557.
Replicate 2, 10 iterations, total sum of distances = 78.8557.
Replicate 3, 8 iterations, total sum of distances = 78.8557.
Replicate 4, 8 iterations, total sum of distances = 78.8557.
Replicate 5, 1 iterations, total sum of distances = 78.8514.
Best total sum of distances = 78.8514

이러한 비교적 단순한 문제에서도 출력값에 비전역 최솟값이 존재하는 것을 알 수 있습니다. 5개 반복 실험은 각각 다른 초기 중심들에서 시작되었습니다. 시작된 위치에 따라 kmeans는 두 개의 다른 해 중 하나에 도달했습니다. 그러나, kmeans가 반환하는 최종 해는 모든 반복 실험에 대해 가장 작은 거리 총합을 갖는 해입니다. 세 번째 출력 인수는 이 최적의 해에 대한 각 군집 내 거리의 합을 포함합니다.

sum(sumd3)
ans =

   78.8514

이 3-군집 해에 대한 실루엣 플롯을 통해 한 군집은 잘 분리되었지만 나머지 두 군집은 잘 분리되지 않은 것을 알 수 있습니다.

[silh3,h] = silhouette(meas,cidx3,'sqeuclidean');

이번에도 원시 데이터를 플로팅하여 kmeans가 점을 군집에 어떻게 할당했는지를 볼 수 있습니다.

for i = 1:3
    clust = find(cidx3==i);
    plot3(meas(clust,1),meas(clust,2),meas(clust,3),ptsymb{i});
    hold on
end
plot3(cmeans3(:,1),cmeans3(:,2),cmeans3(:,3),'ko');
plot3(cmeans3(:,1),cmeans3(:,2),cmeans3(:,3),'kx');
hold off
xlabel('Sepal Length');
ylabel('Sepal Width');
zlabel('Petal Length');
view(-137,10);
grid on

kmeans가 2-군집 해에서 상부 군집을 분할했으며 이 두 군집이 서로 아주 가깝다는 것을 확인할 수 있습니다. 군집화한 후 이 데이터에 수행하려는 작업에 따라 이 3-군집 해가 앞에서 설명한 2-군집 해보다 더 유용할 수도 있고 덜 유용할 수도 있습니다. silhouette의 첫 번째 출력 인수는 각 점에 대한 실루엣 값을 포함합니다. 이 값을 사용하여 두 해를 정량적으로 비교할 수 있습니다. 평균 실루엣 값은 2-군집 해에서 더 컸으며, 이는 완전히 구별되는 군집을 생성한다는 관점에서 순수하게 더 나은 답임을 나타냅니다.

[mean(silh2) mean(silh3)]
ans =

    0.8504    0.7357

또한, 다른 거리를 사용하여 이러한 데이터를 군집화할 수도 있습니다. 코사인 거리는 측정값에 대한 절대적인 크기를 무시하고 상대적인 크기만 고려하므로 이 데이터에 적합할 수 있습니다. 따라서, 크기가 서로 다르지만 꽃잎과 꽃받침의 모양이 유사한 두 꽃이 제곱 유클리드 거리에서는 가깝지 않은 반면, 코사인 거리에서는 가까울 수 있습니다.

[cidxCos,cmeansCos] = kmeans(meas,3,'dist','cos');

실루엣 플롯에서 이 군집은 제곱 유클리드 거리를 사용하여 구한 것보다 분리 상태가 조금 더 양호한 것으로 나타납니다.

[silhCos,h] = silhouette(meas,cidxCos,'cos');
[mean(silh2) mean(silh3) mean(silhCos)]
ans =

    0.8504    0.7357    0.7491

참고로, 이 군집의 순서는 이전 실루엣 플롯과 다릅니다. 그 이유는 kmeans가 무작위로 초기 군집 할당을 하기 때문입니다.

원시 데이터를 플로팅하면 두 개의 서로 다른 거리를 사용하여 생성된 군집 모양의 차이를 확인할 수 있습니다. 두 해는 서로 유사하지만, 코사인 거리를 사용하는 경우 두 개의 상부 군집이 원점 방향으로 길게 늘어져 있습니다.

for i = 1:3
    clust = find(cidxCos==i);
    plot3(meas(clust,1),meas(clust,2),meas(clust,3),ptsymb{i});
    hold on
end
hold off
xlabel('Sepal Length');
ylabel('Sepal Width');
zlabel('Petal Length');
view(-137,10);
grid on

이 플롯에는 군집 중심이 포함되어 있지 않습니다. 그 이유는 코사인 거리에 대한 중심은 원시 데이터 공간에서 원점에서의 반직선에 해당하기 때문입니다. 그러나 정규화된 데이터 점에 대한 평행좌표 플롯을 생성하여 군집 중심 간의 차이를 시각화할 수 있습니다.

lnsymb = {'b-','r-','m-'};
names = {'SL','SW','PL','PW'};
meas0 = meas ./ repmat(sqrt(sum(meas.^2,2)),1,4);
ymin = min(min(meas0));
ymax = max(max(meas0));
for i = 1:3
    subplot(1,3,i);
    plot(meas0(cidxCos==i,:)',lnsymb{i});
    hold on;
    plot(cmeansCos(i,:)','k-','LineWidth',2);
    hold off;
    title(sprintf('Cluster %d',i));
    xlim([.9, 4.1]);
    ylim([ymin, ymax]);
    h_gca = gca;
    h_gca.XTick = 1:4;
    h_gca.XTickLabel = names;
end

이 플롯에서는 세 개의 군집 각각에서 표본의 꽃잎과 꽃받침의 상대적인 크기가 평균적으로 완전히 다른 것을 확실히 알 수 있습니다. 첫 번째 군집은 꽃잎이 꽃받침보다 더 작습니다. 두 번째 두 군집에서는 꽃잎과 꽃받침의 크기가 겹치지만 세 번째 군집의 꽃잎과 꽃받침이 두 번째보다 더 많이 겹칩니다. 두 번째 군집과 세 번째 군집에 서로 매우 유사한 몇몇 표본이 포함되어 있음을 확인할 수도 있습니다.

데이터에 포함된 각 관측값의 종을 알고 있으므로 kmeans로 파악된 군집을 실제 종과 비교하여 세 개 종에 확실히 다른 물리적 특성이 있는지 확인할 수 있습니다. 실제로, 다음 플롯에 표시된 것처럼 코사인 거리를 사용하여 생성된 군집이 5개 꽃에 대해서만 종 그룹과 다릅니다. 별로 플로팅된 이 5개 점은 모두 두 개의 상부 군집의 경계 근처에 있습니다.

subplot(1,1,1);
for i = 1:3
    clust = find(cidxCos==i);
    plot3(meas(clust,1),meas(clust,2),meas(clust,3),ptsymb{i});
    hold on
end
xlabel('Sepal Length');
ylabel('Sepal Width');
zlabel('Petal Length');
view(-137,10);
grid on
sidx = grp2idx(species);
miss = find(cidxCos ~= sidx);
plot3(meas(miss,1),meas(miss,2),meas(miss,3),'k*');
legend({'setosa','versicolor','virginica'});
hold off

계층적 군집화를 사용하여 피셔(Fisher)의 붓꽃 데이터 군집화하기

K-평균 군집화는 붓꽃 데이터의 단일 분할을 생성했지만, 사용자는 데이터에서 다른 차원의 그룹화를 조사하고자 할 수도 있습니다. 계층적 군집화를 통해 계층적 군집 트리를 생성하면 이를 시도해 볼 수 있습니다.

먼저, 붓꽃 데이터의 관측값 간 거리를 사용하여 군집 트리를 생성합니다. 유클리드 거리를 사용하여 시작하겠습니다.

eucD = pdist(meas,'euclidean');
clustTreeEuc = linkage(eucD,'average');

코페네틱 상관은 군집 트리가 원래 거리와 일치하는지를 확인할 수 있는 한 가지 방법입니다. 값이 클 경우 이는 관측값 간의 쌍별 연결이 실제 쌍별 거리와 상관관계가 있다는 면에서 트리가 거리를 잘 피팅함을 나타냅니다. 이 트리는 거리에 대한 꽤 양호한 피팅인 것처럼 보입니다.

cophenet(clustTreeEuc,eucD)
ans =

    0.8770

군집의 계층 구조를 시각화하려면 덴드로그램을 플로팅하면 됩니다.

[h,nodes] = dendrogram(clustTreeEuc,0);
h_gca = gca;
h_gca.TickDir = 'out';
h_gca.TickLength = [.002 0];
h_gca.XTickLabel = [];

이 트리의 루트 노드는 나머지 노드보다 훨씬 더 높으며, 이로써 K-평균 군집화에서 나타난 결과, 즉 고유한 두 개의 대규모 관측값 그룹이 있다는 것이 확인되었습니다. 이 두 그룹 각각에서는 더 작은 거리 스케일을 고려할수록 더 낮은 수준의 그룹이 나타나는 것을 볼 수 있습니다. 크기와 변별도가 각기 다른, 서로 다른 수준의 그룹이 많이 있습니다.

K-평균 군집화의 결과에 따라, 거리 측정에 코사인을 선택해도 유용할 수 있습니다. 결과로 생성되는 계층적 트리가 완전히 다르며, 이는 붓꽃 데이터의 그룹 구조를 살펴볼 수 있는 완전히 다른 방법입니다.

cosD = pdist(meas,'cosine');
clustTreeCos = linkage(cosD,'average');
cophenet(clustTreeCos,cosD)
ans =

    0.9360

[h,nodes] = dendrogram(clustTreeCos,0);
h_gca = gca;
h_gca.TickDir = 'out';
h_gca.TickLength = [.002 0];
h_gca.XTickLabel = [];

이 트리의 가장 높은 수준은 붓꽃 표본을 두 개의 매우 다른 그룹으로 분리합니다. 이 덴드로그램에서는 유클리드 거리에 비해 코사인 거리에서 그룹 내 차이가 그룹 간 차이보다 상대적으로 훨씬 더 작다는 것을 보여줍니다. 코사인 거리가 원점으로부터 동일한 "방향"에 있는 객체에 대한 쌍별 거리를 0으로 계산하므로 이는 이 데이터에 대해 정확히 예상할 수 있는 사항입니다.

150개 관측값을 사용하는 경우 복잡한 플롯이 생성되므로 가장 낮은 수준의 트리를 표시하지 않도록 하여 단순화된 덴드로그램을 생성할 수 있습니다.

[h,nodes] = dendrogram(clustTreeCos,12);

이 트리에서 가장 높은 세 노드는 동일한 크기의 그룹 세 개와 서로 근처에 있지 않은 단일 표본(리프 노드 5로 레이블이 지정됨)을 분리합니다.

[sum(ismember(nodes,[11 12 9 10])) sum(ismember(nodes,[6 7 8])) ...
                  sum(ismember(nodes,[1 2 4 3])) sum(nodes==5)]
ans =

    54    46    49     1

여러 용도에서, 덴드로그램은 충분한 결과가 될 수 있습니다. 그러나, K-평균과 마찬가지로 한 스텝 더 나아가 cluster 함수를 사용하여 트리를 절단하고 관측값을 특정 군집으로 명시적으로 분할할 수 있습니다. 코사인 거리의 계층 구조를 사용하여 군집을 생성하는 경우, 가장 높은 세 개의 노드 아래에서 트리를 절단하도록 연결 높이를 지정하고 군집 네 개를 생성한 후 군집화된 원시 데이터를 플로팅합니다.

hidx = cluster(clustTreeCos,'criterion','distance','cutoff',.006);
for i = 1:5
    clust = find(hidx==i);
    plot3(meas(clust,1),meas(clust,2),meas(clust,3),ptsymb{i});
    hold on
end
hold off
xlabel('Sepal Length');
ylabel('Sepal Width');
zlabel('Petal Length');
view(-137,10);
grid on

이 플롯은 코사인 거리를 사용한 계층적 군집화에서 얻은 결과가 세 개의 군집을 사용한 K-평균에서 얻은 결과와 정성적으로 유사하다는 것을 보여줍니다. 그러나, 계층적 군집 트리를 생성하면 K-평균 군집화에서는 여러 다른 K 값을 사용하여 상당한 실험을 수행해야 얻을 수 있는 결과를 한 번에 모두 시각화할 수 있습니다.

또한 여러 연결을 사용하여 실험할 수도 있습니다. 예를 들어, 평균 거리보다 더 큰 거리에 대해 객체를 함께 연결하는 경향이 있는 단일 연결로 붓꽃 데이터를 군집화하면 데이터의 구조에 대한 매우 다른 해석을 얻을 수 있습니다.

clustTreeSng = linkage(eucD,'single');
[h,nodes] = dendrogram(clustTreeSng,0);
h_gca = gca;
h_gca.TickDir = 'out';
h_gca.TickLength = [.002 0];
h_gca.XTickLabel = [];