9. 비지도 학습#

감사의 글

자료를 공개한 저자 오렐리앙 제롱과 강의자료를 지원한 한빛아카데미에게 진심어린 감사를 전합니다.

소스코드

본문 내용의 일부를 파이썬으로 구현한 내용은 (구글코랩) 비지도 학습에서 확인할 수 있다.

슬라이드

본문 내용을 요약한 슬라이드 1부슬라이드 2부를 다운로드할 수 있다.

소개

비지도 학습은 라벨이 없는 데이터를 학습하는 기법이다. 8장에서 다룬 차원 축소 기법도 비지도 학습의 전형적인 예제이다. 여기서는 다음 주제와 관련된 비지도 학습을 다룬다.

  • 군집화: 비슷한 샘플끼리의 군집 형성

    • 고객 분류

    • 추천 시스템

    • 데이터 분석

    • 차원 축소

    • 특성 공학

    • 준지도 학습

    • 검색 엔진

    • 이미지 분할

  • 이상치 탐지: 정상 테이터와 이상치 구분

    • 생산라인에서 결함 제품 탐지

    • 시계열데이터에서 새로운 트렌드 찾기

  • 데이터 밀도 추정: 데이터셋의 확률 밀도를 추정

    • 이상치 분류: 밀도가 낮은 지역에 위치한 샘플

    • 데이터 시각화

    • 데이터 분석

9.1. 분류 대 군집화#

군집cluster은 유사한 대상들의 모음을 가리킨다. 예를 들어, 산이나 공원에서 볼 수 있는 이름 모르는 동일 품종의 꽃으로 이루어진 군집을 생각할 수 있다. 군집화clustering는 특정 기준으로 대상을 여러 개의 군집으로 나누는 과정을 가리킨다.

분류와 군집화는 각 샘플에 하나의 그룹을 할당한다는 점에서 유사하다. 하지만 분류는 미리 지정된 라벨(타깃)을 최대한 정확하게 예측하는 과정을 의미하는 반면에, 군집화는 미리 지정된 라벨(타깃)이 없음에도 불구하고 예측기 스스로 찾아낸 특정 기준을 이용해서 여러 개의 군집으로 나누는 과정을 가리킨다.

다음 세 종류의 군집화 알고리즘을 자세히 소개한다. 알고리즘에 따라 생성되는 군집의 특성과 모양이 다르다.

  • k-평균: 센트로이드(중심)라는 특정 샘플을 중심으로 모인 샘플들의 집합

  • DBSCAN: 밀집된 샘플들의 연속으로 이루어진 집합

  • 가우스 혼합: 특정 가우스 분포를 따르는 샘플들의 집합

이외에 군집의 군집 등 다양한 군집의 모양과 특성이 존재한다.

예제: 붓꽃 데이터셋 군집화

아래 왼쪽 그림은 붓꽃의 꽃잎 길이와 너비를 특성으로 사용해서 품종을 분류한 결과를 보여주지만, 오른쪽 그림은 어떤 품종인지는 모르지만 노랑 동그라미아 검정 별표로 구분된 두 개의 군집을 보여준다. 분류는 세 개의 품종을 매우 잘 분류하지만 군집은 세토사 군집과 나머지 군집으로 구분할 뿐이다.

반면에 가우스 혼합 모델Gaussian Mixture Model(GMM)을 꽃잎의 길이와 너비 뿐만 아니라 꽃받침의 길이와 너비 특성까지 특성으로 사용하는 붓꽃 데이터셋에 대해 적용하면 세 개의 군집을 매우 정확하게 생성한다.

9.2. k-평균#

군집의 중심인 센트로이드centroid 몇 개를 찾은 다음 각 센트로이드에 가깝게 위치한 샘플들로 구성된 군집을 형성하는 기법이다.

사이킷런의 KMeans 모델

아래 그림은 다섯 개의 샘플 덩어리로 이루어진 데이터셋을 보여준다.

위 데이터셋에 대해 다섯 개의 군집을 형성하는 k-평균 알고리즘은 다음과 같이 적용한다. 군집 수를 몇 개로 지정하는 게 가장 좋은지는 미리 알 수 없다. 나중에 몇 개의 군집이 적절한가를 판단하는 여러 방식을 살펴볼 것이다.

아래 코드에서 X는 위 산점도에 포함된 데이터 샘플들로 구성된 훈련셋을 가리킨다.

from sklearn.cluster import KMeans

k = 5
kmeans = KMeans(n_clusters=k, n_init=10, random_state=42)
y_pred = kmeans.fit_predict(X)

예측값

predict() 함수의 반환값은 0, 1, 2, 3, 4 등 정수로 구성된다. 하지만 이는 임의로 지정된 군집의 인덱스를 가리킬 뿐이며 클래스 분류와는 아무 상관 없다.

>>> y_pred
array([4, 0, 1, ..., 2, 1, 0])

센트로이드 정보

KMeans 모델이 찾아낸 센트로이드 정보는 cluster_centers_ 속성에 저장된다.

>>> kmeans.cluster_centers_
array([[-2.80389616,  1.80117999],
       [ 0.20876306,  2.25551336],
       [-2.79290307,  2.79641063],
       [-1.46679593,  2.28585348],
       [-2.80037642,  1.30082566]])

보로노이 다이어그램

보로노이 다이어그램Voronoi diagram은 평면을 특정 점(센트로이드)까지의 거리가 가장 가까운 점들의 집합으로 분할한 그림이다. 점들이 군집을 잘 구성하는지 여부를 쉽게 확인할 수 있다.

왼쪽 상단 군집에 포함된 샘플들 중에서 군집 경계 근처에 있는 샘플들의 군집이 잘못 지정됐다. 이유는 그 오른편에 위치한 군집의 직경이 보다 크기에 사실 그 군집에 속해야 하는 샘플이 왼쪽 센트로이드와의 거리가 단지 보다 가깝다는 이유로 왼쪽 군집으로 판정되었다. 이렇듯 군집의 직경이 서로 많이 다르면 군집화가 잘 작동하지 않을 수 있다.

하드 군집화 대 소프트 군집화

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

소프트 군집화(soft clustering)는 샘플과 각 군집 사이의 관계를 점수로 부여한다. 점수는 예를 들어 각 군집과 샘플사이의 거리 또는 2장5장에서 활용한 가우스 방사 기저 함수를 이용한 유사도 점수 등이 사용될 수 있다. 생성된 점수는 데이터셋의 새로운 특성으로 지정되어 모델 훈련에 활용되기도 한다.

여기서 사용하는 사이킷런의 KMeans 모델의 transform() 메서드는 샘플과 각 센트로이드 사이의 (유클리드) 거리를 점수로 사용한다 (아래 코드 참고).

>>> kmeans.transform(X_new).round(2)
array([[2.81, 0.33, 2.9 , 1.49, 2.89],
       [5.81, 2.8 , 5.85, 4.48, 5.84],
       [1.21, 3.29, 0.29, 1.69, 1.71],
       [0.73, 3.22, 0.36, 1.55, 1.22]])

반면에 2장에서 Kmeans 모델을 상속하는 형식으로 정의된 ClusterSimilarity 클래스의 transform() 메서드는 가우스 방사 기저 함수인 rbf_kernel() 함수를 이용하여 각 샘플에 대해 모든 센트로이드들과의 유사도 점수를 계산한다. 계산된 점수는 캘리포니아 주택 가격 예측 모델의 훈련에 사용되도록 새로운 특성으로 추가되었다.

class ClusterSimilarity(BaseEstimator, TransformerMixin):
    def __init__(self, n_clusters=10, gamma=1.0, random_state=None):
        self.n_clusters = n_clusters
        self.gamma = gamma
        self.random_state = random_state

    def fit(self, X, y=None, sample_weight=None): # sample_weight: 샘플별로 가중치 적용
        self.kmeans_ = KMeans(self.n_clusters, n_init=10, random_state=self.random_state)
        self.kmeans_.fit(X, sample_weight=sample_weight)
        return self  # 항상 self 반환

    # 구역 데이터 샘플과 각 센트로이드 사이의 유사도 측정
    def transform(self, X):
        return rbf_kernel(X, self.kmeans_.cluster_centers_, gamma=self.gamma)
    
cluster_simil = ClusterSimilarity(n_clusters=10, gamma=1., random_state=42)

# 구역별 중앙 주택 가격을 샘플 가중치로 지정한 후에 군집화 실행. 
# 즉, 비싼 주택 가격을 갖는 구역을 중요시하며 따라서 센트로이드로 지정될 가능성을 높임.
# transform() 메서드는 각 샘플과 10개의 센트로이드 사이의 유사도 계산

housing_labels = strat_train_set["median_house_value"].copy() # 중앙 주택 가격
similarities = cluster_simil.fit_transform(housing[["latitude", "longitude"]], 
                                           sample_weight=housing_labels)

처음 5 개 샘플과 각 센트로이드에 대한 유사도 점수는 다음과 같다.

>>> similarities[:5].round(2)
array([[0.  , 0.14, 0.  , 0.  , 0.  , 0.08, 0.  , 0.99, 0.  , 0.6 ],
       [0.63, 0.  , 0.99, 0.  , 0.  , 0.  , 0.04, 0.  , 0.11, 0.  ],
       [0.  , 0.29, 0.  , 0.  , 0.01, 0.44, 0.  , 0.7 , 0.  , 0.3 ],
       [0.65, 0.  , 0.21, 0.  , 0.  , 0.  , 0.51, 0.  , 0.  , 0.  ],
       [0.86, 0.  , 0.89, 0.  , 0.  , 0.  , 0.14, 0.  , 0.03, 0.  ]])

아래 그림에서 🗙는 각 군집의 중심 구역을 나타내며, 색상은 센트로이드 구역과의 유사도를 가리킨다. 빨강색의 구역이 센트로이드 구역과의 유사도가 1에 가깝다.

가우스 방사 기저 함수

rbf_kernel()가우스 방사 기저 함수Gaussian radial basis function를 가리키며 다음과 같이 정의된다. 특정 지점을 가리키는 랜드마크landmark\(\mathbf{m}\)으로부터 조금만 멀어져도 함숫값이 급격히 작아진다.

\[ \phi(\mathbf{x},\mathbf{m}) = \exp \left( -\gamma \|\mathbf{x} - \mathbf{m} \|^2 \right) \]

하이퍼파라미터인 감마(\(\gamma\), gamma)는 데이터 샘플이 랜드마크로부터 멀어질 때 가우스 RBF 함수의 반환값이 얼마나 빠르게 0에 수렴하도록 하는가를 결정한다. 감마 값이 클 수록 랜드마크로부터 조금만 멀어져도 보다 빠르게 0에 수렴한다. 따라서 가우스 RBF 함수의 그래프가 보다 좁은 종 모양을 띤다.

아래 그래프는 감마가 1일 때와 0.01 때의 차이를 명확하게 보여준다. 즉 랜드마크인 \(\mathbf{m}=\) 0으로부터 거리가 멀어질 때 감마가 1이면 매우 급격하게 함숫값이 0으로 줄어든다. 즉, 랜드마크로부터 조금만 멀어저도 유사도가 매우 약해진다.


예를 들어, 유사도를 도심 상권의 발달 정도로 해석할 때 도시 중심으로 조금만 멀어져도 상권이 좋지 않음을 의미한다. 대표적으로 소도시의 상권을 잘 반영한다. 반면에 서울의 경우 도시 중심으로부터 조금 떨어져 있다 하더라도 상권이 충분히 잘 발달되어 있을 수 있다. 따라서 그런 경우에는 감마를 0.01처럼 작게 지정해야 한다.

ClusterSimilarity 클래스의 transform() 메서드 또한 비슷한 방식으로 유사도를 계산한다. 이유는 샘플과 주택 가격이 비싼 센트로이드 구역 사이의 가우스 방사 기저 함숫값을 유사도로 사용하기 때문이다.

실제로 fit() 함수를 이용하여 군집화 정보를 계산할 때 비싼 가격의 주택이 모여 있는 구역에 보다 높은 가중치를 주기에 그런 구역이 군집의 센트로이드로 선택될 가능성이 높아진다. 각각의 센트로이드를 랜드마크로 보았을 때 주택 가격이 높은 센트로이드에 가깝게 위치한 구역일 수록 주택 가격이 상대적으로 비싼 게 일반적이다. 즉, 유사도가 높다고 볼 수 있다. 그런데 위 코드에서는 감마(gamma)를 1로 지정하였기에 센트로이드에 정말로 가까운 구역에 대해서만 높은 유사도를 부여한다.

9.2.1. k-평균 알고리즘#

먼저 몇 개의 군집으로 분류할지를 정하기 위해 k 값을 지정한다. 그런 다음 k 개의 센트로이드를 무작위로 선택한 다음에 센트로이드들의 위치가 수렴할 때까지 아래 과정을 반복한다.

  • 각 샘플을 가장 가까운 센트로이드에 할당하여 군집을 생성한다.

  • 군집별로 샘플의 평균을 계산하여 새로운 센트로이드로 지정한다.

무작위 초기화 문제

임의로 선택된 초기 센트로이드에 따라 매우 다른 모양과 성질의 군집이 생성될 수 있다. 아래 오른쪽 그림은 센트로이드 초기화가 다르면 최종 결과가 많이 다를 수 있음을 잘 보여준다.

9.2.2. 센트로이드 초기화 문제 해결 방안#

관성

관성intertia은 각 샘플과 가장 가까운 센트로이드와의 거리의 제곱의 합이며, 각 군집이 센트로이드에 얼마나 가까이 모여있는가를 측정한다. 따라서 관성이 작을 수록 군집이 잘 구성되었다고 평가한다.

훈련된 KMeans 모델의 경우 inertia_ 속성에 관성 값이 저징되며, score() 메서드가 관성의 음숫값을 반환한다. 이유는 점수(score)는 높을 수록 좋은 모델을 나타내도록 해야 하기 때문이다. KMeans 모델은 훈련 과정 중에 다양한 초기화 과정을 실험하고 그 중에 관성이 가장 작은 값이 되는 센트로이드를 선택한다.

센트로이드 초기화 반복 횟수

무작위 초기와 문제를 해결하기 위해 k-평균 알고리즘의 초기화를 여러 번 실행한 다음에 가장 낮은 관성을 보이는 모델을 최종 모델로 선택한다. 이전 코드에서 n_init=10으로 지정되어 있어서 센트로이드 초기화를 10번 진행한다.

k-평균++ 초기화 알고리즘

센트로이드 무작위 초기화 문제의 보다 근본적인 해결책이 아서(David Arthur)와 바실비츠키(Sergei Vassilvitskii)의 논문 k-means++: the advantages of careful seeding에서 제시되었다.

k-평균++ 초기화 알고리즘은 기존에 선택된 센트로이드들과의 거리가 먼 샘플일 수록 다음 센트로이드로 선택될 확률이 높아지도록 한다. 보다 구체적으로 다음 과정을 따른다.

  1. 임의로 하나의 센트로이드 \(c_1\) 선택 후 \(k\) 개의 센트로이드를 지정할 때까지 아래 과정을 반복한다.

  2. \(c_1, \dots, c_{i-1}\)이 이미 선택되었가고 가정했을 대, 각 샘플 \(\mathbf{x}_j\)가 아래의 확률로 새로운 센트로이드 \(c_i\)로 선택되도록 한다.

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

    단, \(m\)은 훈련셋의 크기를, \(D(\mathbf{x}_j)\)\(\mathbf{x}_j\)와 이미 선택된 \(c_1, \dots, c_{i-1}\) 중에서 가장 가까운 센트로이드 사이의 거리를 가리킨다.

    \[D(\mathbf{x}_j) = \min_{p<i} \| x_j - c_p \|\]

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

미니배치 k-평균

미니배치를 사용해서 센트로이드를 조금씩 이동하는 k-평균 알고리즘이다. 사이킷런의 MiniBatchMeans 모델이 지원한다.

minibatch_kmeans = MiniBatchKMeans(n_clusters=10, batch_size=10,
                                   random_state=42)
minibatch_kmeans.fit(X_memmap)

군집수가 많아질 수록 k-평균보다 서너 배 정도 빠르게 훈련되지만, 성능은 조금 낮다.

MiniBatchKMeansmemmap 클래스

8장에서 점진적 PCA를 소개하면서 언급한 넘파이 memmap 클래스를 이용하여 매우 큰 데이터셋을 조금씩 모델에 제공할 수 있다.

9.2.3. 최적의 군집수#

군집수가 적절하지 않으면 좋지 않은 모델로 수렴할 수 있다. 5개의 군집이 적절한 데이터셋에 대해 왼쪽 그림처럼 3개의 센트로이다만 사용하거나, 오른쪽 그램처럼 너무 많은 8개의 센트로이드를 사용하면 군집화가 적절하게 진행되지 않는다.

방법 1: 관성과 군집수

군집수 k가 증가할 수록 관성은 기본적으로 줄어들기에 관성만으로 모델을 평가하기엔 부족하다. 하지만 관성이 더 이상 획기적으로 줄어들지 않는 지점을 군집수 후보로 선정할 수는 있다. 예를 들어 아래 그래프는 k가 1부터 9까지 변하는 동안 훈련된 모델의 관성을 측정하며, 관성이 현격하게 줄어드는 현상이 약화되기 시작하는 k=4가 군집수 후보로 괜찮아 보인다.

이유는 군집이 네 개보다 작으면 별로이고, 4개보다 많아도 훨씬 좋아진다고 보기 어렵기 때문이다. 하지만 4개의 군집으로 구성하려 하면 아래 그림과 같이 왼쪽 하단 두 개의 군집이 하나의 군집으로 처리될 수 있기에 가장 좋은 군집화라고 말하기 어렵다.

방법 2: 실루엣 점수와 군집수

실루엣 점수silhouette score 샘플별 실루엣 계수의 평균값이다. 샘플의 실루엣 계수silhouette coefficient는 다음 식으로 계산된다.

\[\frac{b - a}{\max(a, b)}\]
  • \(a\): 동일 군집 내의 다른 샘플들과의 거리의 평균값

  • \(b\): 가장 가까운 타 군집에 속하는 샘플들과의 거리의 평균값

실루엣 계수는 -1과 1사이의 값이며, 다음 특성을 보여준다.

  • 1에 가까운 값: 적절한 군집에 포함됨. 이유는 \(b\)\(a\)보다 월등이 크기에 \(\frac{b - a}{\max(a, b)}\)\(\frac{b}{b}\)에 가까워짐.

  • 0에 가까운 값: 군집 경계에 위치. 이유는 \(a \simeq b\) 이기에 \(\frac{b - a}{\max(a, b)}\)\(0\)에 가까워짐.

  • -1에 가까운 값: 잘못된 군집에 포함됨. 이유는 \(a\)\(b\)보다 월등이 크기에 \(\frac{b - a}{\max(a, b)}\)\(\frac{-a}{a}\)에 가까워짐.

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

방법 3: 실루엣 다이어그램과 군집수

실루엣 다이어그램은 군집별로 실루엣 계수들을 모아 놓은 그래프다. 군집별로 실루엣 계수를 내림차순으로 정렬하면 아래 그림에서처럼 여러 개의 칼날 모양이 군집별로 형성된다.

  • 칼날 두께: 군집에 포함된 샘플 수

  • 칼날 길이: 군집에 포함된 각 샘플의 실루엣 계수

  • 빨강 파선: 실루엣 점수, 즉 실루엣 계수의 평균값이다.

좋은 군집 모델은 대부분의 칼날이 빨간 파선보다 길어야 한다. 즉 잘못 분류된 샘플이 적어야 한다. 또한 칼날의 두께가 서로 비슷해야 한다. 즉, 군집별 크기가 비슷해야 좋은 모델이다. 이런 기준으로 볼 때 k=5 가 가장 좋은 모델이다.

9.2.4. k-평균의 한계#

첫째, 최적의 모델을 구하기 위해 여러 번 학습해야 한다.

둘째, 군집수를 미리 지정해야 한다.

셋째, 군집의 크기나, 샘플의 밀도가 다르거나, 원형이 아닐 경우 잘 작동하지 않는다. 예를 들어, 아래 그림에 사용된 데이터 샘플들의 분포가 원형이 아니기에 양쪽 그림에서 보여지는 군집화 모두 적절하지 않다. 그리고 오른쪽 군집의 관성이 왼쪽 보다 작지만 군집화는 오히려 훨씬 나쁘다. 데이터 분포가 타원형인 경우 이어서 소개하는 가우스 혼합 모델(GMM)이 매우 잘 작동한다.

9.3. 군집화 활용#

9.3.1. 이미지 분할#

이미지 분할은 보통 다음 세 가지 중에 하나를 가리킨다.

  • 시맨틱 분할

  • 인스턴스 분할

  • 색상 분할

시맨틱 분할semantic segmentation은 사진에 들어 있는 사물들을 클래스별로 분할한다. 예를 들어 아래 왼쪽 사진에서 배경과 구분된 고양이들을 묶어서 cat 클래스로 분류한다. 다만 고양의 종류와 개수는 따지지 않는다.

인스턴스 분할instance segmentation은 클래스 뿐만 아니라 객체도 분할한다. 예를 들어 아래 오른쪽 사진에서 배경과 구분된 각각의 고양이를 cat1, cat2 등으로 구별한다.

색상 분할color segmentation은 유사 색상으로 이루어진 군집으로 분할하는 것을 의미한다. 아래 그림은 무당벌레가 포함된 이미지를 대상으로 색상 수를 다르게 하면서 색상 분할을 시도한 결과를 보여준다.

9.3.2. 준지도 학습#

준지도 학습semi-supervised learning은 라벨이 있는 데이터가 적고, 라벨이 없는 데이터가 많을 때 활용한다. 예를 들어, 8x8 크기의 손글씨 이미지 1,797 개로 구성된 미니 MNIST 데이터셋을 50개의 군집으로 나눈 후 각 군집에서 센트로이드에 가장 가까운 샘플 50개를 대표 이미지로 선정해보자. 그런 다음 선정된 50개 샘플만을 이용하여 분류 모델을 훈련해도 84.9%의 정확도가 달성된다.

라벨 전파

대표 이미지의 라벨을 해당 군집의 모든 샘플로 전파하는 기법을 라벨 전파label propagation라 한다. 라벨 전파를 이용하여 예를 들어 미니 MNIST 데이터셋의 50개 군집의 대표 이미지의 라벨을 각 군집의 전체 샘플에 전파한 다음에 전체 훈련셋을 대상으로 분류 모델을 훈련하면 89% 이상으로 정확도가 올라간다.

또한 센트로이드에 가장 멀리 떨어진 1%의 데이터를 이상치로 취급하여 각 군집에서 제외시킨 다음에 라벨 전파된 훈련셋을 이용하면 분류 모델의 성능이 조금이나마 향상된다.

sklearn.semi_supervised 패키지는 다양한 라벨 전파 클래스를 제공한다.

  • LabelSpreading

  • LabelPropagation

  • SelfTrainingClassifier

준지도 학습과 능동 학습

모델의 성능을 보다 높이기 위한 다음 단계로 능동 학습Active Learning 기법을 적용할 수 있다. 능동 학습은 기존에 훈련된 모델이 예측에 어려움을 겪는 일부 샘플의 라벨을 사람이 직접 새롭게 지정해서 모델의 성능을 개선하는 기법이다. 능동학습은 보통 모델의 성능이 더 이상 개선되지 않을 때까지 반복한다.

모델 성능을 개선하기 위한 샘플 선택 전략이 다양하게 알려져 있지만 불확실성 샘플링uncertainty sammpling 전략으로 알려진 아래 방식이 가장 많이 사용된다.

  1. 기존에 정리된 훈련셋을 이용하여 모델을 학습시킨다.

  2. 훈련된 모델이 예측에 대해 가장 불확실해 하는 샘플들을 대상으로 사람이 직접 라벨을 확인한다.

9.4. DBSCAN#

DBSCAN(density-based spatial clustering of applications with noise) 알고리즘은 데이터 샘플들의 밀도가 높게 연속적으로 이어진 지역을 군집으로 지정한다. 알고리즘이 작동하는 방식은 다음과 같다.

  • 각 샘플에 대해 \(\varepsilon\)-이웃에 자신을 포함하여 몇 개의 샘플이 있는지 확인한다. \(\varepsilon\)-이웃은 샘플을 중심으로 반경이 \(\varepsilon\)인 지역을 가리킨다.

  • 어떤 샘플의 \(\varepsilon\)-이웃 안에 min_samples 개수 이상의 샘플이 존재한다는 해당 샘플을 핵심 샘플core instance라 부른다.

  • 핵심 샘플의 \(\varepsilon\)-이웃에 포함된 샘플은 모두 동일한 군집에 속한다. \(\varepsilon\)-이웃에 포함된 다른 샘플 또한 핵심 샘플인 경우 해당 샘플의 \(\varepsilon\)-이웃에 포함된 샘플도 모두 동일한 군집에 속한다. 즉, 군집이 핵심 샘플들의 \(\varepsilon\)-이웃으로 이뤄진다.

  • 핵심 샘플이 아니면서 동시에 \(\varepsilon\)-이웃에 자신 이외의 다른 샘플이 없다면 그런 샘플은 이상치로 간주된다.

DBSCAN 알고리즘은 군집 각각이 밀도가 낮은 지역으로 구분될 때 잘 작동한다.

9.4.1. 사이킷런의 DBSCAN 모델#

앞서 설명한 \(\varepsilon\)-이웃의 반경 eps와 핵심 샘플의 eps 반경안에 포함되어야 할 샘플의 최소 개수를 가리키는 min_samples 두 개의 하이퍼파라미터를 사용한다.

예제: 초승달 데이터 군집화

from sklearn.cluster import DBSCAN
from sklearn.datasets import make_moons

X, y = make_moons(n_samples=1000, noise=0.05)
dbscan = DBSCAN(eps=0.05, min_samples=5)
dbscan.fit(X)

군집 라벨

DBSCAN 모델이 찾은 군집 정보는 0, 1, 2, … 등 정수 인덱스로 표기되며 각 샘들의 군집 정보는 labels_ 속성에 저장된다. 단, -1은 이상치로 간주되는 샘플을 가리킨다.

>>> dbscan.labels_[:10]
array([ 0,  2, -1, -1,  1,  0,  0,  0,  2,  5])

핵심 샘플들의 행 인덱스는 core_sample_indices_ 속성에 저장된다. 예를 들어, 첫 10개의 핵심 샘플의 행 인덱스는 다음과 같다.

>>> dbscan.core_sample_indices_[:10]
array([ 0,  4,  5,  6,  7,  8, 10, 11, 12, 13])

핵심 샘플로 구성된 데이터셋은 components_ 속성에 저장된다.

>>> dbscan.components_
array([[-0.02137124,  0.40618608],
       [-0.84192557,  0.53058695],
       [ 0.58930337, -0.32137599],
       ...,
       [ 1.66258462, -0.3079193 ],
       [-0.94355873,  0.3278936 ],
       [ 0.79419406,  0.60777171]])

군집화 결과

아래 그림은 \(\varepsilon\)-이웃의 반경을 0.05로 할 때(왼쪽)와 0.2로 할 때(오른쪽)의 차이를 보여준다.

특징

왼쪽 그림(이웃 반경 0.05)

오른쪽 그림(이웃 반경 0.2)

군집수

2개 초과

2개

이상치(빨강 🗙)

많음

없음



DBSCAN과 예측

DBSCAN 모델은 predict() 메서드를 지원하지 않는다. 즉, 새로운 샘플에 대한 군집 예측을 지원하지 않는다. 반면에 fit_predict() 메서드는 지원하여 훈련셋에 대한 군집 인덱스는 예측한다. predict() 메서드를 지원하지 않는 이유는 더 좋은 성능의 분류기를 대신 이용할 수 있기 때문이다. 예를 들어 아래 코드는 KNeighborsClassifier 분류 모델을 핵심 샘플들을 이용하여 지도학습을 진행한다.

아래 코드에서 dbscaneps=0.2로 훈련된 모델을 가리킨다. 즉, 위 오른쪽 그림에서 사용된 DBSCAN 모델이다. 훈련을 위해 핵심 샘플만 이용해서 훈련하지만 당연히 모든 샘플을 이용할 수도 있다.

from sklearn.neighbors import KNeighborsClassifier

knn = KNeighborsClassifier(n_neighbors=50)
knn.fit(dbscan.components_, dbscan.labels_[dbscan.core_sample_indices_])

knn은 분류 모델이기에 당연히 새로운 데이터 샘플에 대해 클래스를 예측하거나 클래스별 확률을 예측할 수 있다.

>>> X_new = np.array([[-0.5, 0], [0, 0.5], [1, -0.1], [2, 1]])
>>> knn.predict(X_new)
array([1, 0, 1, 0])
>>> knn.predict_proba(X_new)
array([[0.18, 0.82],
       [1. , 0. ],
       [0.12, 0.88],
       [1. , 0. ]])

결정 경계

아래 그림은 knn 모델을 이용하여 두 개의 그룹을 분류하는 결정 경계를 보여준다. 파랑 덧셈 기호 +는 이전 코드에서 지정한 X_new 어레이에 포함된 데이터 샘플 4 개를 가리킨다.

그런데 빨강 동그라미로 감싸진 두 샘플은 사실 이상치로 취급되어야 한다. 이유는 다른 데이터들로부터 너무 멀리 떨어져 있기 때문이다.

DBSCAN의 장단점

장점은 다음과 같다.

  • 단 2개의 하이퍼파라미터만을 사용하는 단순하지만 매우 강력한 알고리즘이다.

  • 군집의 모양과 개수에 상관없이 일반적으로 잘 작동한다.

  • 이상치에 별로 민감하지 않다. 즉, 이상치가 있어도 군집을 잘 생성한다.

반면에 단점은 다음과 같다.

  • 군집들의 밀도가 서로 크게 다르거나 두 군집 사이의 영역의 밀도가 충분히 낮지 않으면 서로 다른 군집을 제대로 분리하지 못할 수도 있다.

  • 알고리즘의 시간복잡도가 \(O(m^2 n)\)이기에 대용량 훈련셋을 이용한 훈련은 어렵다.

9.4.2. 기타 군집 알고리즘#

군집의 밀도가 서로 다른 경우 계층 DBSCAN 군집화를 지원하는 HDBSCAN 모델이 보다 잘 작동한다. 그런데 HDBSCAN 모델은 사이킷런에서 정식으로 지원되지 않기에 추가 설치해서 사용해야 한다. 보다 자세한 사항은 HDBSCAN 군집화 라이브러리 공식 문서를 참고한다.

사이킷런 라이브러리가 제공하는 기타 군집 알고리즘은 다음과 같다. 각 모델의 사용법은 공식 문서를 참고한다.

9.5. 가우스 혼합 모델#

가우스 혼합 모델Gaussian mixture model (GMM)은 데이터셋이 여러 개의 혼합된 가우스 분포를 따르는 샘플들로 구성되었다고 가정한다.

가우스 분포

데이터셋에 포함된 데이터들의 확률분포가 아래 그림에 있는 곡선들처럼 종 모양의 (정규분포) 확률밀도함수를 가질 경우 가우스 분포를 따른다라고 말한다.

예를 들어 아래 그림에 있는 데이터셋은 서로 다른 세 개의 가우스 분포를 따르는 세 개의 데이터 군집들의 혼합으로 구성된다. 세 개의 가우스 분포의 평균값, 표준편차, 샘플의 개수가 모두 다르며, 그에따라 각각의 군집을 나타내는 타원의 모양, 위치, 타원 내의 데이터 밀도 등이 모두 다르다.

임의의 데이터셋이 어떤 가우스 분포들의 혼합으로 이뤄졌는지를 알아내서 그에 따른 군집을 형성해야 한다. 하지만 일반적으로는 가우스 분포를 따른다는 보장도 없기에 다양한 군집 알고리즘을 활용해서 가장 좋은 알고리즘을 선택해야 한다.

여기서는 데이터셋이 여러 개의 가우스 분포로 혼합된 분포를 따른다고 가정하면서 가우스 혼합 모델의 작동 방식을 살펴 본다.

9.5.1. GMM 활용#

아래 코드는 위 그림에서 사용된 데이터셋에 대해 가우스 혼합 모델(GMM)을 훈련 시켜서 데이터셋에 포함된 군집 각각을 특징 짓는 가우스 분포를 찾는다.

  • GaussianMixture: 가우스 혼합 모델

  • n_components: k-평균 모델의 경우처럼 군집수는 미리 지정해야 함.

  • n_init: k-평균 모델의 경우처럼 군집의 파라미터(평균값, 공분산 등)를 무작위로 선택한 후 좋은 군집으로 수렴할 때까지 학습시킴. 아래 모델은 10번 시도.

from sklearn.mixture import GaussianMixture

gm = GaussianMixture(n_components=3, n_init=10, random_state=42)
gm.fit(X)

학습된 모델을 이용하여 아래 그림을 그릴 수 있다.

  • 군집 평균: 🗙 표시

  • 결정 경계: 빨강 파선

  • 밀도 등고선: 진한 파랑색에 가까울 수록 데이터 밀도 높음

위 모델이 찾은 세 군집의 분포는 다음과 같다.

  • 군집별 가중치: 군집별 데이터 수의 상대적 비율 가리킴

    >>> gm.weights_
    array([0.39025715, 0.40007391, 0.20966893])
    
  • 군집별 평균값

    >>> gm.means_
    array([[ 0.05131611, 0.07521837],
           [-1.40763156, 1.42708225],
           [ 3.39893794, 1.05928897]])
    
  • 군집별 공분산

    array([[[ 0.68799922, 0.79606357],
            [ 0.79606357, 1.21236106]],
           [[ 0.63479409, 0.72970799],
            [ 0.72970799, 1.1610351 ]],
           [[ 1.14833585, -0.03256179],
            [-0.03256179, 0.95490931]]])
    

하드/소프트 군집화

predict() 메서드는 샘플이 속하는 군집을, predict_proba() 메서드는 샘플이 각 군집에 속할 상대적 확률을 계산한다.

>>> gm.predict(X)
array([0, 0, 1, ..., 2, 2, 2])
>>> gm.predict_proba(X).round(3)
array([[0.977, 0. , 0.023],
       [0.983, 0.001, 0.016],
       [0. , 1. , 0. ],
       ...,
       [0. , 0. , 1. ],
       [0. , 0. , 1. ],
       [0. , 0. , 1. ]])

데이터 확률 밀도 측정

score_samples() 메서드는 임의의 위치에서의 확률 밀도의 로그값을 측정한다. 값이 클 수록 높은 밀도를 나타낸다.

>>> gm.score_samples(X).round(2)
array([-2.61, -3.57, -3.33, ..., -3.51, -4.4 , -3.81])

GMM 모델 공분산 규제

데이터셋의 차원이 크거나, 군집수가 많거나, 샘플이 적은 경우 최적의 군집화가 어려울 수 있다. 이런 경우엔 공분산 유형을 지정해서 학습을 도와줄 수 있다. 이를 위해 covariance_type 하이퍼파라미터를 full 대신 다른 값을 지정한다. 사용할 수 있는 값은 다음과 같다.

  • full: 공분산에 아무런 아무런 제한 없음. 기본값으로 지정됨.

  • spherical: 군집이 원형이라 가정. 지름(분산)은 다를 수 있음.

  • diag: 어떤 타원형도 가능. 단. 타원의 축이 좌표축과 평행하다고 가정.

  • tied: 모든 군집이 동일 모양, 동일 크기, 동일 방향을 갖는 타원형이라고 가정.

gm_full = GaussianMixture(n_components=3, n_init=10,
                          covariance_type="full", random_state=42)
gm_tied = GaussianMixture(n_components=3, n_init=10,
                          covariance_type="tied", random_state=42)
gm_spherical = GaussianMixture(n_components=3, n_init=10,
                               covariance_type="spherical", random_state=42)
gm_diag = GaussianMixture(n_components=3, n_init=10,
                          covariance_type="diag", random_state=42)

아래 왼쪽 그림은 "tied"를, 오른쪽 그림은 "spherical"covariance_type으로 지정한 결과를 보여준다.

아래 왼쪽 그림은 " full"를, 오른쪽 그림은 "diag"covariance_type으로 지정한 결과를 보여준다.

GMM 알고리즘 시간 복잡도

GaussianMixture 모델의 훈련 시간은 데이터셋의 크기 \(m\), 차원(특성 수) \(n\), 군집 수 \(k\), 그리고 공분산 규제 방식에 의존한다.

  • 'spherical' 또는 'diag' 방식: \(O(k m n)\)

  • 'tied' 또는 'full' 방식: \(O(k m n^2 + k n^3)\)

9.5.2. 이상치 탐지#

데이터 확률 밀도가 지정된 임곗값보다 낮은 지역에 있는 샘플을 이상치로 간주할 수 있다. 임곗값은 상황에 따라 다르게 지정된다.

예를 들어, 제품이 결함을 가질 확률이 2%로 알려진 경우 임곗값은 2%로 정할 수 있다. 따라서 군집화 결과 2% 보다 낮은 확률 밀도의 지역에 위치한 샘플은 결함이 있을 가능성이 높다. 기대했던 것보다 결함율이 낮거나 높은지에 딸 임곗값을 낮추거나 높혀야 한다.

아래 그림은 확률 밀도가 2% 이하인 지역에 위치한 샘플을 별표(★)를 이용하여 이상치로 표시하였다.

densities = gm.score_samples(X)
density_threshold = np.percentile(densities, 2)
anomalies = X[densities < density_threshold]

9.5.3. 군집수 지정#

적절한 군집수를 미리 지정해야 하는데 일반적으로 미리 알 수 없다. 앞서 k-평균에서 사용했던 관성 또는 실루엣 점수 방식은 사용할 수 없다. 대신에 이론적 정보 기준theoretical information criterion을 최소화 하는 모델을 생성하는 군집수를 선택한다.

이론적 정보 기준으로 보통 다음 두 기준 중 하나를 사용하며, 값이 작을 수록 좋은 모델로 간주된다.

  • BIC(Bayesian information criterion):

    \[ \log(m)\, p - 2 \log (\hat L)\]
  • AIC(Akaike information criterion):

    \[ 2\, p - 2 \log (\hat L)\]

위 식에 사용된 기호의 의미는 다음과 같다.

  • \(m\): 데이터셋 크기

  • \(p\): 모델이 학습해야 할 파라미터 수, 즉 모델의 복자도를 가리킴.

  • \(\hat L\): 모델의 가능도 함숫값을 최댓값

두 식 모두 의미하는 바는 동일하다. 첫째, 각 군집의 평균값, 표준편차 등 모델이 학습해야 할 파라미터가 많을 수록 벌칙이 가해진다. 둘째, 데이터셋에 잘 들어맞는 군집화 모델이 학습될 수록 잘 학습하는 모델일 수록 기준값을 낮춘다.

군집수와 이론적 정보 기준

아래 그림은 위 데이터셋에 대해 군집수 \(k\)와 AIC, BIC의 관계를 보여주며, \(k=3\)이 최적임을 확인해준다.

9.5.4. 베이즈 가우스 혼합 모델#

BayesianGaussianMixture 모델은 군집수를 미리 지정하지 않아도 적절한 군집수를 찾아준다. 단, n_components 하이퍼파라미터를 이용하여 가능한 최대 군집수를 충분히 크게 지정해줘야 한다. 그러면 훈련중에 자동으로 불필요한 군집에 0 또는 0에 매우 가까운 가중치를 지정하는 방식으로 해당 군집을 무시하도록 한다.

예를 들어 아래 코드는 최대 군집수를 10으로 지정하고 베이즈 가우스 혼합 모델을 훈련시킨다.

from sklearn.mixture import BayesianGaussianMixture

bgm = BayesianGaussianMixture(n_components=10, n_init=10, random_state=42)
bgm.fit(X)

weights_ 속성에 저장된 군집별 가중치를 확인하면 앞서 확인한 군집별 가중치 결과와 동일하게 세 개의 군집에 대해 4:2:4 비율로 가중치를 주며 나머지 7개의 군집에 대한 가중치는 모두 0으로 처리되었다.

>>> bgm.weights_.round(2)
array([0.4 , 0.21, 0.4 , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  ])

(베이즈) 가우스 혼합 모델의 장단점

타원형 군집으로 이뤄진 데이터셋에 대해 매우 잘 작동한다. 하지만 다른 모양을 가진 데이터셋에서는 성능이 좋지 않다. 예를 들어 초승달 데이터셋(그림 왼쪽)에 베이트 가우스 혼합 모델을 적용하면 아래 오른쪽 그림에서처럼 억지로 타원형 군집으로 맞추기 위해 필요 이상의 군집을 사용한다.

9.5.5. 이상치 탐지용 기타 알고리즘#

  • Fast-MCD

  • Isolation forest

  • Local outlier factor (LOF)

  • One-class SVM

  • inverse_transform() 메서드를 지원하는 PCA 등의 차원 축소 알고리즘: 이상치의 경우 재구성 오류가 크다는 성질 이용

9.6. 연습문제#

참고: (실습) 비지도 학습