9장 비지도학습

주요 내용

참고: 핵심 설명과 코드는 🔑로 표시되었으며 굳이 알아둘 필요가 없는 코드는 ✋로 표시되었다.

기본 설정

9.1 군집화

분류 대 군집화

아래 코드는 분류와 군집화의 차이를 보여주는 그림을 그린다.

꽃잎 길이와 너비만으로는 두 개의 군집으로만 구분이 가능해 보인다. 하지만 꽃잎의 길이와 너비와 더불어 꽃받침의 길이와 너비까지 포함한 네 개의 특성을 모두 사용하여 후반부에서 가우시안 혼합 모델을 이용하여 세 개의 군집으로 나눌 수 있다.

실제 타깃을 이용한 분류와 직접 비교하면 다음과 같으며, 버시컬러와 버지니카 품종의 군집이 거의 정확하게 구분된 것을 확인할 수 있다.

군집화의 정확도를 확인하기 위해 군집별로 가장 많이 포함된 품종, 즉, 품종의 최빈값(mode)을 확인한다.

아래 코드는 사이파이(scipy)의 통계 모듈에 포함되어 있는 mode() 함수를 이용하여 각 군집별 최빈값을 확인한 후에 해당 최빈값과 군집 인덱스를 연결(mapping)한다.

최종 결과는 다음과 같다.

mapping을 이용하여 군집 인덱스를 품종 인덱스로 변경한 후에 군집화의 정확도를 측정하면 96.7% 가 나온다.

참고: 사용하는 사이킷런 버전에 따라 조금씩 다른 결과가 나올 수 있다.

9.1.1 k-평균

먼저 2,000개의 데이터 샘플을 생성한다. 생성되는 데이터는 지정된 5개의 센터를 중심으로 지정된 표준편차를 따르는 원 모양의 데이터 군집을 이룬다. 또한 각각의 군집은 거의 동일한 크기를 갖는다.

make_blobs() 함수가 앞서 설명한 방식으로 데이터를 생성한다.

참고: 각 샘플에 대한 실제 군집 인덱스가 타깃으로 함께 제공되지만 여기서는 사용하지 않는다.

산점도를 그리면 다음과 같다.

군집화 훈련과 예측

사이킷런 KMeans 모델의 fit() 메서드는 각 군집의 중심(센트로이드)을 잡은 다음에 모든 샘플에 대해 가장 가까운 센트로이드를 중심으로 하는 군집을 이루도록 한다. 다만, 센트로이드를 몇 개로 할 것인지 먼저 지정해야 하며, 여기서는 5개를 사용하여 훈련시킨다.

훈련 결과 kmeans.labels_ 속성에 각 훈련 샘플에 대한 군집 인덱스가 저장된다.

참고: 앞서 붓꽃 데이터 군집화에서 살펴본 것처럼 데이터가 생성될 때 지정된 실제 군집 인덱스가 아닌 훈련 과정에서 무작위로 지정된 인덱스임에 주의해야 한다.

각 군집의 중심, 즉 5개의 센트로이드의 좌표는 다음과 같다.

predict() 메서드를 이용하여 새로운 데이터에 대한 군집 예측도 가능하다.

결정경계

군집을 나누는 결정경계를 그리면 보로노이 다이어그램이 생성된다.

plot_data() 함수는 여기서만 사용되는 기본값이 지정된 산점도를 그린다.

plot_centroids() 함수는 센트로이드를 시각화한다.

plot_decision_boundaries() 함수는 결정경계를 시각화한다.

경계 근처의 일부 데이터가 잘못된 군집에 포함되긴 하였지만 전반적으로 군집이 잘 형성되었다.

군집화와 차원축소

지금까지 살펴 보았듯이 k-평균 모델 객체의 labels_ 속성은 각 샘플에 대해 가장 가까운 센트로이드를 중심으로 하는 군집의 (작위적으로 지정된) 인덱스를 저장하며, 이를 이용하여 predict() 메서드는 샘플이 속하는 군집의 인덱스를 반환한다. 이런 방식의 군집화가 하드 군집화(hard clustering)이다.

반면에 소프트 군집화(soft clustering)는 샘플과 각 군집 사이의 관계를 점수로 부여한다. 점수는 예를 들어 각 군집과 샘플사이의 거리 또는 5장에서 소개한 가우시안 방사기저 함수를 이용한 유사도 점수 등이 사용될 수 있다. k-평균 모델 객체의 transform() 메서드는 샘플과 각 센트로이드 사이의 (유클리드) 거리를 점수로 사용한다.

아래 코드는 네 개의 새 데이터를 변환하는 것을 보여준다.

✋ 앞서 설명한 대로 각 샘플에 대한 반환값은 각 센트로이드로부터의 (유클리드) 거리임을 아래와 같이 확인할 수 있다.

k-평균 모델의 transform() 메서드는 결론적으로 기존 $n$-차원의 데이터셋을 비선형적으로 $k$-차원의 데이터셋으로 변환하는 비선형 차원축소 기법으로 사용될 수 있다.

참고: 국소적 선형 임베딩 처럼 차원축소 기법을 군집화에 활용할 수도 있다.

k-평균 알고리즘

k-평균 알고리즘은 가장 빠르며 가장 간단한 군집화 알고리즘 중 하나이다.

사이킷런의 KMeans 클래스

KMeans 클래스의 기본 옵션 중 몇 가지는 다음과 같다.

여기서는 예시를 위해 아래 옵션을 대신 사용한다.

각 단계별 결정경계와 센트로이드의 변화는 다음과 같다.

센트로이드 초기화 문제와 해결법

초기화 문제

초기화가 무작위로 이루어질 경우 적절하지 않은 군집화를 얻을 수 있다. 아래 코드는 두 개의 나쁜 경우를 잘 보여준다.

plot_clusterer_comparison() 함수는 두 개의 결정경계 그래프를 동시에 그려준다.

아래 두 개의 그래프는 바로 이전의 경우처럼 센트로이드 초기화를 무작위로 딱 한 번 사용한 결과를 보여준다. 두 경우 모두 적절치 않는 모델을 생성한다.

관성(inertia, 이너셔)

비지도 학습인 k-평균 모델의 성능을 관성(inertia)을 이용하여 측정한다.

✋ 아래 코드는 관성이 각 훈련 샘플과 가장 가까운 센트로이드 사이의 거리를 모두 합한 값임을 확인해준다.

score() 메서드는 관성의 음숫값을 반환한다. 이유는 "더 높은 점수가 더 좋다"의 원칙을 따라야 하기 때문이다.

초기화 반복

무작위 초기와 문제를 해결하기 위해 k-평균 알고리즘의 초기화를 여러 번 실행한 다음에 가장 낮은 관성을 보이는 모델을 최종 모델로 선택하면 되며, 실제로 이 옵션(n_init=10)이 앞서 설명한 대로 KMeans 모델의 기본 하이퍼파라미터 값으로 설정되어 있다.

아래에서 확인할 수 있듯이 기본 하이퍼파라미터를 사용한 kmeans의 관성이 한 번의 센트로이드 초기화를 사용하는 모델들의 관성보다 낮다.

실제로 n_init=10로 설정할 경우 앞서 살펴본 좋은 모델과 비슷한 결과를 얻는다.

관성 점수도 거의 최저다.

K-Means++ 알고리즘

센트로이드 무작위 초기화 문제의 보다 근본적인 해결책이 아서(David Arthur)와 바실비츠기(Sergei Vassilvitskii)의 2006년 논문에서 제시되었다. 기본 아이디어는 기존 센트로이드들과의 거리가 멀 수록 다음 센트로이드로 선택될 확률이 높아지도록 하는 것이다.

  1. 균등분포를 따르면서 임의로 하나의 센트로이드 $c_1$ 선택 후 $k$ 개의 센트로이드를 지정할 때까지 아래 과정 반복
  2. $c_1, \dots, c_{i-1}$이 이미 선택되었가고 가정.

    1. 아래의 확률로 새로운 센트로이드 $c_i$로 $\mathbf{x}_i$ 선택:

      $$\frac{D(\mathbf{x}_i)^2}{\sum\limits_{j=1}^{m}{D(\mathbf{x}_j)}^2}$$

    2. $D(\mathbf{x}_j)$: $\mathbf{x}_j$와 이미 선택된 $c_1, \dots, c_{i-1}$ 중에서 가장 가까운 센트로이드 사이의 거리

      $$D(\mathbf{x}_j) = \min_{k<i} \| x_j - c_k \|$$

확률 계산으로 인해 초기화 비용이 좀 더 많이 들어가긴 하지만 결과적으로 초기화 횟수(n_init)를 획기적으로 줄일 수 있는 장점이 보다 크다. 따라서 사이킷런의 KMeans 모델의 기본값으로 사용된다.

데이터를 생성할 때 사용된 군집의 실제 중심과 kmeans 모델이 찾아낸 군집의 센트로이드가 매우 비슷함을 아래 코드에서 확인할 수 있다.

주의사항: 좌표의 순서가 다름에 주의하라.

init 하이퍼파라미터 활용

센트로이드에 대한 좋은 후보값을 알 수 있다면 init 하이퍼파라미터의 값으로 센트로이드의 좌표를 지정하여 훈련하면 보다 좋은 결과를 얻게됨을 아래 코드가 잘 보여준다.

주의사항: 아래 5개의 좌표가 앞서 언급한 blob_centers의 좌표와 유사하다. 여기서도 순서는 중요하지 않다.

개선된 k-평균 알고리즘과 미니배치 k-평균

개선된 k-평균 알고리즘

k-평균 알고리즘은 각 훈련 샘플과 센트로이드 사이의 거리르 계산하여 가장 짧은 거리의 센트로이드를 중심으로 하는 군집에 해당 샘플을 연결한다. 하지만 엘칸(Charles Elkan)의 2003 논문이 거리 계산을 획기적으로 줄이는 개선된 알고리즘을 제시한다.

사이킷런의 KMeans 모델은 algorithm='elkan'을 기본 옵션으로 사용한다. 그런데 엘칸 알고리즘은 희소 데이터(sparse data)에 대해서는 잘 작동하지 않아 모든 거리를 계산하는 algorithm='full' 옵션이 희소 데이터에 대해 자동으로 선택된다.

아래 코드는 두 방식의 시간차이를 보여준다. 하지만 데이터셋이 크지 않기 때문에 시간차이가 크지 않아 보인다.

미니배치 k-평균

사이킷런의 MiniBatchKMeans 모델은 미니배치 학습을 지원한다.

참고: Web-Scale K-Means Clustering

훈련 세트가 많이 큰 경우: memmap 클래스 활용

8장 주성분 분석에서 소개한 넘파이 memmap 클래스를 이용하여 MNIST 데이터셋을 대상으로 미니배치 k-평균 모델을 훈련해보자.

먼저 MNIST 데이터셋을 불러온다.

훈련 세트와 테스트 세트로 구분한다.

memmap 객체로 지정한다.

MiniBatchKMeans 모델을 훈련한다.

훈련 세트가 너무 큰 경우: partial_fit() 활용

데이터셋이 너무 크면 memmap 클래스조차 활용하지 못할 수 있다. 이럴 때는 메모리가 아닌 다른 저장 장치로부터 필요한 만큼의 데이터 배치(묶음)를 불러오는 함수를 이용하여 수동으로 미니배치 학습을 구현해야 한다. 즉, 아래 내용을 직접 구현해야 한다.

아래 함수는 지정된 크기 만큼의 데이터를 무작위로 선택해서 전달한다.

다음 조건에 맞게 미니배치 모델을 훈련한다.

아래 코드는 초기화를 반복하면서 최적의 모델을 업데이트한다. partial_fit() 메서드를 사용함에 주의해야 한다.

훈련된 모델의 점수는 매우 높은 편이다.

미니배치 k-평균 알고리즘이 일반 k-평균 알고림즘 보다 훨씬 빠르다.

반면에 성능은 많이 떨어진다. 이 사실을 확인해야 할 군집수와 관련해서 확인해보자.

먼저 군집수를 1에서 100까지 변화시키면서 일반 k-평균 모델과 미니배치 k-평균 모델이 훈련에 필요한 시간과 훈련된 모델의 관성을 측정한다.

군집수를 늘릴 때 훈련시간과 관성의 변화를 그래프로 그리면 다음과 같다.

최적의 군집수

지금까지 사용한 예제에 대해 군집수를 5보다 작거나 크게 하면 아래와 같은 일이 발생한다.

관성 활용

별로 좋아보이지 않는다. 그런데 관성은 군집수가 커질수록 줄어든다.

실제로 군집수가 많아질 수록 관성은 줄어든다. 이유는 센트로이드 수가 늘어날 수록 각 샘플과 센트로이드 사이의 거리는 줄어들 수밖에 없기 때문이다. 아래 코드가 이 사실을 잘 보여준다.

팔꿈치(elbow)에 해당하는 위치인 $k=4$, 즉, 네 개의 군집이 좋아 보인다. 군집이 네 개보다 작으면 별로이고, 4개보다 많아도 별로 좋아지지 않아 보인다. 하지만 아래 그림에서 볼 수 있듯이 왼쪽 하단 두 개의 군집이 하나의 군집으로 처리되기 때문이다. 그럼에도 불구하고 꽤 좋은 군집화 모델임엔 틀림없다.

실루엣 점수 활용

실루엣 점수(silhouette score)는 각 훈련 샘플에 대한 실루엣 계수(silhouette coefficient)의 평균값이다. 실루엣 계수는 아래와 같이 계산된다.

$$\frac{b - a}{\max(a, b)}$$

실루엣 계수는 -1과 1 사이의 값이며, 의미는 다음과 같다.

아래 코드는 군집수가 증가할 때 실루엣 점수의 변화를 보여준다.

$k=4$가 여전히 매우 좋아 보인다. 하지만 관성의 경우와는 달리 $k=5$도 역시 꽤 좋다는 것을 알 수 있다.

군집별로 각 샘플의 실루엣 계수를 오름차순으로 정렬한 그래프인 실루엣 다이어그램(silhouette diagram)이 보다 많은 정보를 전달한다.

$k=5$인 경우가 가장 좋아 보인다. 이유는 모든 군집이 거의 비슷한 크기이고, 모든 군집의 칼날이 실루엣 점수(빨강 파선)넘어서고 있기 때문이다.

9.1.2 k-평균의 한계

k-평균의 가장 큰 단점은 최적의 군집수를 확인하기 위해 알고리즘을 여러 번 실행해야 한다는 점이다. 또한 군집의 크기와 밀도가 서로 다른거나 원형이 아닌 경우 k-평균 모델이 제대로 작동하지 않을 수 있다.

아래 코드는 타원 모양의 군집으로 이루어진 데이터셋을 생성한다.

산점도를 그리면 아래와 같다.

먼저 알고 있는 센트로이드 정보를 이용하여 좋은 k-평균 모델을 훈련한다.

이번엔 센트로이드를 무작위로 지정한다.

두 모델의 훈련 결과는 다음과 같다. 오른편 모델은 형편없다. 반면에 왼편 모델은 보다 좋지만 그래도 25%정도의 데이터가 오른쪽 군집에 잘못 할당되었다.

특성 스케일링 활용

특성 스케일링을 하면 군집 구분이 보다 명확해지고 보다 원형에 가까운 군집이 생성되어 k-평균 모델의 성능이 보다 좋아질 수 있다.

9.1.3 군집화 활용: 이미지 색상 분할

이미지 색상 분할은 유사한 색상을 동일한 군집에 연결하는 기법을 의미한다. 색상 분할 과정을 살펴보기 위해 무당벌레 이미지를 하나 다운로드한다.

다운로드된 이미지는 533 x 800 픽셀 크기의 칼라 사진이다.

k-평균 모델 훈련을 위해 이미지 픽셀을 일차원으로 변환한다. 즉, (533, 800, 3) 모양의 어레이를 (426400, 3) 모양의 어레이로 변환한다.

참고: 533 * 800 = 426,400

이미지 색상 분할 과정

아래 코드는 8개의 군집을 이용한 색상 분할과정을 보여준다. 먼저, k-평균 모델을 설정한다.

찾아낸 8개의 센트로이드는 다음과 같다.

각 훈련 샘플에 대한 레이블(kmeans.labels_)을 이용하여 군집별 센트로이드의 색상으로 통일시킨다. 이를 위해 넘파이 어레이에 대한 팬시 인덱싱을 활용한다.

이미지를 원래의 크기로 다시 변환한다.

군집수에 따른 비교

아래 코드는 앞서 설명한 방식을 다양한 군집수에 대해 적용한 결과를 비교할 수 있는 그림을 보여준다.

9.1.4 군집화 활용: 차원축소

훈련된 k-평균 객체의 transform() 메서드는 주어진 데이터에 대해 각 센트로이드부터의 거리로 이루어진 어레이를 생성한다. 즉, n 차원의 데이터셋을 k 차원의 데이터셋으로 변환한다. 만약에 k < n 이라면 이는 비선형 차원축소로 간주될 수 있으며 이어지는 지도학습에 유용하게 활용될 수 있다.

여기서는 MNIST 손글씨 데이터셋을 이용하여 군집화를 차원축소 기법으로 활용하는 방식을 소개한다. 다만, 여기서 사용되는 데이터셋은 8 x 8 크기의 픽셀을 갖는 1,797개의 축소된 MNIST 데이터셋을 사용한다.

먼저 훈련 세트와 테스트 세트로 구분한다.

로지스틱 회귀 적용

로지스틱 회귀 모델을 훈련한 다음에 테스트 세트에 대한 성능을 평가하면, 정확도 평균값이 96.8888% 정도로 나온다.

군집화 전처리 후 성능 평가

아래 파이프라인은 50개의 군집으로 먼저 군집화를 한 다음에 로지스틱 회귀 모델을 훈련한다.

주의사항: 파이파라인으로 구성된 예측기의 fit() 메서드를 호출하면 최종 예측기를 제외한 나머지 예측기의 fit_transform() 메서드가 호출된다. 또한 k-평균 모델의 transform() 메서드는 앞서 설명한 방식으로 차원축소를 진행한다.

파이프라인 모델의 정확도 평균값은 97.7%로 28%의 성능향상이 발생한다.

그리드 탐색 활용

그리드 탐색을 이용하여 최적의 군집수를 알아낼 수 있다. 즉, 파이프라인의 성능이 최고가 되도록 하는 군집수는 다음과 같다.

아래 코드는 그리드 참색을 이용하여 2개부터 100개 사이의 군집을 사용할 때 이어지는 로지스틱 회귀의 성능을 평가하여 최적의 군집수를 알아낸다.

경고: 사용하는 컴퓨터 성능에 따라 아래 코드는 20분 이상 걸릴 수 있다.

최적의 군집수는 57이며, 로지스틱 회귀의 정확도는 98%가 된다.

9.1.5 군집화 활용: 준지도학습

준지도학습

준지도학습은 약간의 레이블이 있는 샘플이 있고 대부분의 샘플엔 레이블이 없는 데이터셋에 대한 지도학습 기법이다. 준지도학습 설명을 위해 미니 MNIST 데이터셋을 계속 이용한다.

먼저 무작위로 선정된 50개의 샘플만을 대상으로 로지스틱 회귀모델을 훈련하면 정확도 평균값이 83.33% 정도로 낮게 나온다. 이유는 훈련 세트가 작아서 훈련이 제대로 되지 않기 때문이다.

참고: train_test_split() 함수는 무작위성을 이용한다.

대표이미지 활용

무작위로 50개의 샘플을 선택해서 훈련하는 대신에 50개의 군집으로 군집화한 뒤에 각 군집의 센트로이드에 가장 가까운 이미지 50개를 대상으로 훈련해보았을 때 로지스틱 회귀 모델의 성능을 확인해보자.

참고: 센트로이드에 가장 가까운 이미지를 대표이미지라 부른다.

먼저 50개의 군집으로 나눈 후 변환한다.

변환된 훈련세트는 50개의 특성을 가지며, 샘플별로 50개 군집의 센트로이드 사이의 거리를 특성값으로 갖는다. 따라서 특성별로 최소값을 갖는 인덱스가 50개 군집의 센트로이드에 가장 가까운 샘플을 가리킨다. 이 성질을 이용하여 대표이미지를 아래와 같이 선정한다.

✋ 선정된 50개의 대표이미지에 포함된 숫자는 다음과 같다.

실제 타깃(레이블)은 다음과 같다.

위 정보를 이용하여 50개의 대표이미지에 대한 레이블을 지정한다.

50개의 대표이미지를 이용하여 로지스틱 회귀 모델을 훈련시킨 결과 성능이 92.4% 이상으로 좋아졌다. 이를 통해 무작위로 선정된 샘플의 레이블을 이용하는 것 보다 대표(샘플)을 군집화를 이용하여 선정한 후에 학습하면 보다 좋은 성능의 모델이 생성됨을 확인하였다.

레이블 전파 1

동일한 군집에 속하는 샘플의 레이블을 대표이미지의 레이블로 지정하는 레이블 전파 방식을 활용할 때의 성능을 확인해보자.

아래 코드는 군집별로 샘플의 레이블을 대표이미지의 레이블로 지정한다.

로지스틱 회귀 모델의 훈련후 성능은 93.8% 정도로 조금 향상된다.

레이블 전파 2

군집 전체에 레이블을 전파하면 이상치 등에 대한 레이블 전파도 이루어지기에 모델의 성능을 약화시킬 수 있다. 따라서 군집별로 센트로이드에 가까운 20%의 샘플에 대해서만 레이블을 전파하고, 나머지 샘플은 무시한다.

아래 코드는 각 샘플에 대해 해당 샘플이 속한 군집 센트로이드까지의 거리를 확인한다.

아래 코드는 군집별로 센트로이드 근접도가 상위 20%가 아닌 샘플들을 센트로이드와의 거리를 -1로 지정하는 방식을 이용하여 제외시킨다.

아래 코드는 군집별로 센트로이드 근접도가 상위 20% 안에 드는 샘플만 훈련세트로 추출한다.

선정된 샘플만을 이용하여 로지스틱 회귀 모델을 훈련한 후에 성능을 평가해보자.

성능이 (책 설명과는 달리) 오히려 좀 떨어졌다.

참고: 아마도 샘플 수가 너무 적기 때문일 것으로 추정된다. 원래 훈련 세트가 보다 크다면 성능이 올라갈 것으로 기대된다.

레이블 전파 정확도

센트로이드 근접도가 상위 20% 이내인 샘플들을 대상으로 전파된 레이블의 정확도는 98.9%에 달한다.

반면에 전체 훈련 세트에 대해 레이블 전파를 했을 경우의 정확도는 94.4% 정도이다.

9.1.6 DBSCAN

DBSCAN 알고리즘은 데이터셋의 밀도를 이용하여 군집을 구성하며, 하나의 군집은 연속된 핵심 샘플(core samples)들의 $\varepsilon$-이웃을 모두 합친 결과이다.

참고: $\varepsilon$은 그리스어 알파벳이며 엡실론이라 읽는다.

핵심 샘플인지 여부는 반경 $\varepsilon$과 min_samples 두 개의 하이퍼파라미터에 의해 결정되며, 하나의 핵심 샘플이 다른 핵심 샘플의 $\varepsilon$ 반경 안에 들어갈 때 연속된 핵심 샘플이라 한다. 따라서 하나의 군집은 핵심 샘플의 이웃의 이웃 관계로 이루어진 샘플들의 집합이다.

DBSCAN 알고리즘은 모든 군집이 충분한 밀도의 샘플들로 구성되고 군집 사이는 저밀도 지역으로 구분될 수 있을 때 잘 작동한다. 아래 예제는 moons 데이터셋을 이용하여 DBSCAN 알고리즘의 작동과정을 보여준다.

총 7개의 군집이 형성되었다.

일부 샘플의 군집 인덱스가 -1인 이유는 해당 샘플들이 이상치(outlier)로 간주되었음을 의미한다.

핵심 샘플은 총 808개로 지정되었다.

처음 10개의 핵심 샘플의 인덱스는 다음과 같다.

핵심 샘플 자체는 components_ 속성이 기억한다. 처음 3개의 핵심 샘플은 다음과 같다.

이제 이웃의 반경을 0.2로 키워보자.

그러면 군집수가 2로 줄어들고 이상치도 사라진다.

plot_dbscan() 함수는 군집화된 샘플을 보여준다. 군집은 색으로 구분되며 각 샘플을 핵심 샘플, 이상치, 기타(non-core samples)로 구분한다.

아래 코드는 위 두 경우를 비교하는 그림을 그려준다. 오른편 그림에서는 기타의 경우는 존재하지 않는다. 즉, 모든 샘플이 핵심 샘플이다. 반면에 왼편 그림에서는 세 종류의 샘플이 표시된다.

DBSCAN과 예측하기

DBSCAN 모델을 predict() 메서드를 지원하지 않으며, 다만 fit_predict() 메서드만 지원한다. 이유는 DBSCAN 모델의 특성상 예측을 하려면 예측에 사용되는 데이터 샘플을 포함하여 핵심 샘플을 다시 계산해야 하기 때문인데, 이는 예측할 때마다 매 번 학습을 다시 해야 한다는 것을 의미한다.

반면에 주어진 샘플의 가장 가까운 군집을 예측하는 일은 간단하게 구현된다. 예를 들어, k-최근접 이웃 분류기인 KNeighborsClassifier 모델을 훈련하면된다. 이 방식을 설명하기 위해 앞서 훈련한 dbscan2 모델(위 그림의 오른편 모델)을 사용한다.

k-최근접 이웃 분류기

k-최근접 이웃 분류 모델은 주어진 샘플 근처의 k개의 샘플에 대한 타깃값들의 최빈값(mode)을 주어진 샘플의 예측값으로 지정한다.

아래 코드는 학습된 DBSCAN 모델의 핵심 샘플만을 이용하여 k-최근겆 이웃 분류기를 훈련한다. 이웃 샘플의 수는 50으로 정한다.

훈련된 k-최근접 이웃 분류기를 이용하여 새로운 샘플들에 대한 클래스를 예측한다.

k-최근접 이웃 분류기는 각 클래스에 속할 확률도 계산한다. 아래 결과에서 볼 수 있듯이 predict_proba() 메서드는 0번과 1번 클래스에 속할 확률을 각 샘플에 대해 계산한다.

아래 코드는 네 개의 새로운 샘플에 대한 예측값을 포함하여 두 군집의 결정경계를 잘 보여준다.

이상치 처리

앞서 사용된 훈련 세트에는 이상치가 없어서 모든 샘플에 대해 군집(클래스)을 할당한다. 하지만 모든 군집으로부터 일정 거리 이상 떨어진 샘플을 간단하게 이상치로 처리할 수 있다.

아래 코드는 k-최근접 이웃 분류기의 kneighbors() 메서드를 이용하여 가장 가까운 이웃 샘플로부터의 거리를 계산한다.

주의사항: 저자의 주피터 노트북에 사용된 아래 코드는 엄밀히 말해 정확하지 않음. 이유는 dbscan.core_sample_indices_dbscan.labels_ 가 다른 경우 엉뚱한 답을 내 놓을 수도 있기 때문임.

y_pred = dbscan.labels_[dbscan.core_sample_indices_][y_pred_idx]

계층적 DBSCAN: HDBSCAN

DBSCAN 모델은 군집의 밀집도가 변하는 데이터셋에 대해서는 잘 작동하지 않는다. 이런 한계를 극복하기 위해 다양한 $\varepsilon$을 이용하여 최적의 군집을 찾는 HDBSCAN 모델을 활용할 수 있다. 간단한 사용법은 다음과 같으며 보다 자세한 사항은 scikit-learn-contrib: hdbscan 프로젝트를 참고한다.

주의사항 hdbscan 모듈을 먼저 설치해야 한다.

9.1.7 기타 군집화 알고리즘

스펙트럼 군집화(Spectral Clustering)

샘플 사이의 유사도 행렬을 이용하여 저차원으로 차원축소를 진행한 후에 k-평균 등의 군집화를 진행하는 기법이다. 유사도 행렬은 가우시안 rbf(방사기저함수) 등을 이용하여 계산하며, 차원축소는 8장에서 소개한 커널 PCA와 유사한 방식으로 이루어진다.

사이킷런의 SpectralClustering 모델은 군집수를 지정해야 하며, 간단한 사용법은 다음과 같다.

gamma는 8장에서 소개한 rbf 함수에 사용되는 $\gamma$ 계수에 해당한다. gamma 값이 커질 수록 보다 가까운 샘플들 사이이의 유사도가 강조된다.

아래 코드는 유사도를 달리한 군집화의 결과를 보여준다.

오른편 그래프의 경우 gamma=1에 해당하며 잘못된 군집화를 보여준다. 반면에 왼편 그래프는 gamma=100에 해당하며 제대로된 군집화를 보여준다.

병합 군집화(Agglomerative Clustering)

병합 군집화는 계층 군집화(hierchical clustering)의 주요 기법이다. 가장 작은 단위의 군집은 개별 샘플 하나로 이루어지며, 두 개의 군집을 합치며 점점 군집의 수를 줄여 나간다. 군집화 대상인 두 개의 군집은 병합했을 때 두 샘플 사이의 연결 거리(linkage distance)가 최소가 되도록 유도하며 이를 위해 탐욕 알고리즘을 사용한다.

참고: 사이킷런의 계층 군집화

아래 코드는 사이킷런의 AgglomerativeClustering 클래스를 이용하여 병합 군집화 모델을 훈련하는 과정을 간단하게 보여준다. 연결 거리는 다음 옵션 종류에 따라 다르게 측정되며 기본 옵션은 'ward'이다.

아래 함수는 훈련된 군집화 모델이 제공하는 속성의 리스트를 생성한다.

병합 군집 모델의 경우 아래 속성이 제공된다.

예를 들어, children_ 속성은 병합과정을 묘사하는 트리(tree)를 $(m-1, 2)$ 모양의 어레이로 설명한다. 어레이에 사용된 숫자 $i$ 의 의미는 다음과 같다.

위 결과의 의미는 다음과 같다.

두 개의 군집을 생성하였기에 4번, 5번 노드가 각각 서로 다른 군집을 가리킨다.

9.2 가우시안 혼합(Gaussian Mixtures)

가우시안 혼합 모델은 데이터셋이 여러 개의 가우시안 분포가 혼합된 분포를 따른다는 가정하는 확률 모델이다. 하나의 가우시안 분포를 따르는 샘플들이 하나의 군집을 생성하며, 일반적으로 타원형 모습을 띈다. 타원의 모양, 크기, 밀집도, 방향이 다양한 여러 개의 가우시안 분포를 따르는 데이터 셋을 군집화한다.

아래 코드는 가우시안 혼합 모델을 지원하는 사이킷런의 GaussianMixture 클래스의 활용법을 설명하기 위해 사용되는 데이터셋을 생성한다.

GaussianMixture 모델을 적용한다.

갸중치(weights_)는 군집별 크기 비를 나타내며, 거의 제대로 학습되었다.

학습된 군집별 평균값과 공분산은 다음과 같다.

기댓값-최대화(EM, expectation-maximization) 알고리즘이 4번 반복만에 수렴을 확인할 수 있다.

predict()predict_proba()

생성모델

생성모델은 학습된 정보를 이용하여 새로운 샘플을 생성할 수 있으며, 속한 군집에 대한 정보도 함께 제공한다.

군집 인덱스 순서대로 샘플을 생성한다.

생성된 샘플들의 군집별 비는 군집별 가중치를 따른다.

score_samples() 와 확률밀도

샘플별로 확률밀도함수(PDF, probability density function)의 로그를 계산한다.

아래 코드는 확률밀도함수와 x축으로 감싸인 면적이 1임을 증명한다. 증명 방식은 구분구적법(segmentation method)을 이용한 정적분 계산이다.

먼저 x축과 y축 모두 -10부터 10 사이의 구간을 1/100 단위로 구분하여 가로세로 20인 정사각형 영역을 2000x2000개의 작은 격자로 잘개 쪼갠다. -10부터 10 사이의 구간을 택한 이유는 해당 영역 이외에서는 확률밀도가 거의 0기 때문이다.

이제 각 격자(정사각형)의 한쪽 모서리에서의 확률밀도를 해당 격자의 면적과 곱한 값을 모두 더하면 거의 1이다.

결정경계와 밀도 등고선

plot_gaussian_mixture() 함수는 가우시안 혼합 모델이 알아낸 군집 결정경계와 샘플의 로그밀도 등고선(density contours)을 그린다.

군집 경계와 로그밀도 등고선이 제대로 그려진다.

covariance_type 속성과 군집 형태

군집에 속하는 데이터들의 가우시안 분포를 결정하는 공분산 행렬은 공분산 유형(covariance_type) 옵션에 따라 달라진다.

아래 코드는 다양한 방식으로 가우시안 혼합 모델을 훈련시킨다.

compare_gaussian_mixtures() 함수는 서로 다른 옵션을 사용하는 두 모델의 결과를 비교하는 그래프를 그린다.

"tied" 방식과 "spherical" 방식 사이의 차이는 다음과 같다.

"full" 방식과 "diag" 방식 사이의 차이는 다음과 같다.

9.2.1 가우시안 혼합과 이상치 탐지

지정된 밀도보다 낮은 저밀도 지역에 위치한 샘플을 이상치로 간주할 수 있다. 밀도 기준은 경우에 따라 다르게 지정된다. 예를 들어, 아래 코드는 제조 결함률이 4% 정도라고 알려져 있다면 밀도 임곗값을 4%의 제조 결함률이 이상치로 판명되도록 정한다.

아래 코드는 밀도 4% 이하의 지역에 위차한 이상치를 표시한다.

9.2.2 군집수 선택

k-평균 모델의 경우와는 달리 군집이 임의의 타원 형태를 띌 수 있기 때문에 적절한 군집수를 선택하기 위해 관성(이너셔), 실루엣 점수 등을 사용할 수 없다. 대신에 BIC 또는 AIC와 같은 이론적 정보 기준(theoretical information criterion)을 사용할 수 있다.

$$ \begin{align*} \text{BIC} &= {\log(m)\, p - 2\log({\hat L})}\\ \text{AIC} &= 2p - 2\log(\hat L) \end{align*} $$

BIC(베이즈 정보 기준, Bayesian Information criterion)와 AIC(아카이케 정보 기준, Akaike information criterion) 모두 값이 낮을 수록 좋은 모델이다. 이는 모델이 학습해야 하는 파라미터가 적을 수록 불리함을 의미하며 결국 군집수를 적게하는 방향으로 유도한다. 또한 모델의 가능도 함수의 값이 클 수록 좋으며 따라서 데이터를 보다 잘 대표하는 모델일 수록 두 기준값이 낮아진다.

학습된 모델의 BIC와 AIC는 각각 bic(), aic() 메서드가 계산한다.

✋ BIC와 AIC

모델이 학습해야 하는 파라미터는 아래 세 종류이다.

참고: $n\times n$ 공분산 행렬의 자유도(파라미터 수)는 $n^2$이 아니라 $1 + 2 + \dots + n = \dfrac{n (n+1)}{2}$이다.

군집수에 따른 BIC와 AIC

군집수를 달리하면서 BIC와 AIC의 변화를 관측해보자. 아래 코드는 군집수를 1 개에서 10 개까지 변화시키면서 10개의 가우안 혼합 모델을 훈련한다.

훈련된 모델의 BIC와 AIC의 변화를 그래프로 그리면 다음과 같다.

아래 코드는 군집수뿐만 아니라 공분산 유형(covariance_type)을 달리하면서 가장 좋은 BIC를 갖는 가우시안 혼합 모델을 찾는다.

3개의 군집과 full 공분산 유형이 가장 좋은 모델을 생성한다.

✋ 가능도(likelihood) 함수

가능도(likelihood) 함수는 파라미터가 알려지지 않은 분포에서 특정 확률변수 값 $\mathbf{x}$가 주어졌을 때 파라미터의 변화에 따라 해당 값이 나타날 확률을 계산한다. 반면에 최대 가능도 추정치(MLE, maximal likelihood estimate)는 주어진 확률변수 값이 발생할 확률을 최대로 하는 확률분포의 파라미터이다.

가능도 함수의 최댓값을 찾는 대신에 가능도 함숫값의 로그값을 최대화시키는 작업을 사용하곤 한다. 이유는 두 함숫값을 최대화 하는 파라미터가 동일한 반면에 함수의 로그값의 최댓값을 구하는 일이 경우에 따라 훨씬 쉬어지기 때문이다.

아래 코드는 두 개의 가우시안 분포가 혼합된 확률분포 모델을 생성한다.

아래 코드는 책의 그림 9-20 (338쪽)을 그린다.

9.2.3 베이즈 가우시안 혼합 모델

베이즈 가우시안 혼합 모델은 군집수를 미리 정하기 보다는 필요한 만큼의 군집만을 선택해서 사용한다. 사이킷런의 BayesianGaussianMixture 모델은 불필요한 군집에 0 또는 거의 0에 가까운 가중치(weights)를 가하는 방식으로 필요한 만큼의 군집만 사용한다.

모델을 생성할 때 군집수를 충분히 크다고 생각하는 수로 지정해야 한다.

모델이 적절한 군집화를 완료하지 못했다는 경고가 뜨는 경우에는 초기화 횟수(n_init) 또는 기댓값-최대화(EM) 알고리즘 반복 횟수(max_iter)를 키운다.

3개의 군집만 필요하단는 사실을 아래와 같이 확인한다. 또한 군집 크기의 비가 4:4:2로 제대로 예측되었다.

군집 결정경계는 다음과 같다.

사전 확률

사전 믿음(prior belief), 즉 군집수에 대한 사전 지식에 대한 믿음을 weight_concentration_prior 속성으로 지정할 수 있다. 0보다 큰 값을 사용하며 0에 가까울 수록 적은 군집수가 사용되었음을 암시한다. 지정하지 않으면 1/n_components가 기본값으로 사용된다. 하지만 훈련세트가 크면 별 의미가 없으며, 아래와 같은 그림은 사전 믿음의 차이가 매우 크고 훈련세트가 작은 경우에만 그려진다.

다음 코드는 사전확률을 극단적으로 차이를 두어 두 모델을 생성한다. 또한 73개의 샘플만을 이용하여 훈련한다.

사전 믿음이 0.01인 경우 두 개의 군집만 찾는다.

사전 믿음이 10,000인 경우 세, 네의 군집을 찾는다.

주의사항: 실행결과가 책과 다르다. 또한 훈련세트 크기를 조금만 다르게 해도 전혀 다른 결과를 얻는다. 이는 버그가 아니며 앞서 언급한 대로 사전 믿음이 주는 불안정성을 반영할 뿐이다.

가우시안 혼합 모델의 한계

가우시안 혼합 모델은 데이터셋을 타원 형탱의 군집으로 분할하려 한다. 따라서 moons 데이터셋과 같은 경우 제대로된 군집화를 진행하지 못한다.

아래 코드는 moons 데이터셋에 베이즈 가우시안 혼합 모델을 적용하였더니 8개의 군집을 찾는 것을 보여준다. 하지만 밀도 고등선은 이상치 탐지에 활용되기에는 충분하다.

연습문제

1. to 9.

부록 A 참조

10. Cluster the Olivetti Faces Dataset

Exercise: The classic Olivetti faces dataset contains 400 grayscale 64 × 64–pixel images of faces. Each image is flattened to a 1D vector of size 4,096. 40 different people were photographed (10 times each), and the usual task is to train a model that can predict which person is represented in each picture. Load the dataset using the sklearn.datasets.fetch_olivetti_faces() function.

Exercise: Then split it into a training set, a validation set, and a test set (note that the dataset is already scaled between 0 and 1). Since the dataset is quite small, you probably want to use stratified sampling to ensure that there are the same number of images per person in each set.

To speed things up, we'll reduce the data's dimensionality using PCA:

Exercise: Next, cluster the images using K-Means, and ensure that you have a good number of clusters (using one of the techniques discussed in this chapter).

It looks like the best number of clusters is quite high, at 120. You might have expected it to be 40, since there are 40 different people on the pictures. However, the same person may look quite different on different pictures (e.g., with or without glasses, or simply shifted left or right).

The optimal number of clusters is not clear on this inertia diagram, as there is no obvious elbow, so let's stick with k=100.

Exercise: Visualize the clusters: do you see similar faces in each cluster?

About 2 out of 3 clusters are useful: that is, they contain at least 2 pictures, all of the same person. However, the rest of the clusters have either one or more intruders, or they have just a single picture.

Clustering images this way may be too imprecise to be directly useful when training a model (as we will see below), but it can be tremendously useful when labeling images in a new dataset: it will usually make labelling much faster.

11. Using Clustering as Preprocessing for Classification

Exercise: Continuing with the Olivetti faces dataset, train a classifier to predict which person is represented in each picture, and evaluate it on the validation set.

Exercise: Next, use K-Means as a dimensionality reduction tool, and train a classifier on the reduced set.

Yikes! That's not better at all! Let's see if tuning the number of clusters helps.

Exercise: Search for the number of clusters that allows the classifier to get the best performance: what performance can you reach?

We could use a GridSearchCV like we did earlier in this notebook, but since we already have a validation set, we don't need K-fold cross-validation, and we're only exploring a single hyperparameter, so it's simpler to just run a loop manually:

Oh well, even by tuning the number of clusters, we never get beyond 80% accuracy. Looks like the distances to the cluster centroids are not as informative as the original images.

Exercise: What if you append the features from the reduced set to the original features (again, searching for the best number of clusters)?

That's a bit better, but still worse than without the cluster features. The clusters are not useful to directly train a classifier in this case (but they can still help when labelling new training instances).

12. A Gaussian Mixture Model for the Olivetti Faces Dataset

Exercise: Train a Gaussian mixture model on the Olivetti faces dataset. To speed up the algorithm, you should probably reduce the dataset's dimensionality (e.g., use PCA, preserving 99% of the variance).

Exercise: Use the model to generate some new faces (using the sample() method), and visualize them (if you used PCA, you will need to use its inverse_transform() method).

Exercise: Try to modify some images (e.g., rotate, flip, darken) and see if the model can detect the anomalies (i.e., compare the output of the score_samples() method for normal images and for anomalies).

The bad faces are all considered highly unlikely by the Gaussian Mixture model. Compare this to the scores of some training instances:

13. Using Dimensionality Reduction Techniques for Anomaly Detection

Exercise: Some dimensionality reduction techniques can also be used for anomaly detection. For example, take the Olivetti faces dataset and reduce it with PCA, preserving 99% of the variance. Then compute the reconstruction error for each image. Next, take some of the modified images you built in the previous exercise, and look at their reconstruction error: notice how much larger the reconstruction error is. If you plot a reconstructed image, you will see why: it tries to reconstruct a normal face.

We already reduced the dataset using PCA earlier: