9-3. 군집(Clustering) - 준지도 학습

2021. 5. 21. 22:10Machine Learning

준지도 학습이란 비지도 학습과 지도 학습에 중간 단계에 있는 학습 방법으로

 

레이블된 샘플의 수가 그렇지 않은 샘플 수에 비해 적을 경우 사용한다.

 

준지도 학습을 실습을 통해 알아보겠다.

 

    X_digits, y_digits = load_digits(return_X_y=True)
    X_train, X_test, y_train, y_test = train_test_split(X_digits, y_digits)

먼저 digit 데이터를 호출하고 훈련 세트와 테스트 세트로 나눠준다. 

 

    n_labled = 50
    log_reg = LogisticRegression()
    log_reg.fit(X_train[:n_labled], y_train[:n_labled])
    print(log_reg.score(X_test, y_test))
    
    result:
    0.8488888888888889

그 다음 전체 훈련 세트에서 일부(50개)만 로지스틱 회귀를 통해 훈련시킨 결과, 84%의 정확도를 나타낸다. 

 

이를 향상시키기 위해 Kmean 클러스터를 이용해보자.

    from sklearn.cluster import KMeans
    
    k = 50
    kmeans = KMeans(n_clusters=k)
    X_digits_dist = kmeans.fit_transform(X_train) #(1347, 50)
    representative_digit_idx = np.argmin(X_digits_dist, axis=0) #(50,)
    X_representative_digits = X_train[representative_digit_idx]

kmean 클러스터링을 위해 전체 훈련 세트를 50개의 클러스터로 나누어 변환한 뒤 X_digits_dist에 저장한다. 

 

64차원의 1347개의 샘플을 가지고 있던 훈련세트가 샘플 당 50차원으로 줄어들었다. 

 

넘파이의 argmin 함수를 통해 각 세로축에서 가장 작은 값(의 인덱스)을 찾는다.

 

Kmean 클러스터는 데이터를 50개의 클러스터로 나누었는데, 각 클러스터 별로 가장 거리가 가까운 샘플이 

 

센트로이드일 것이다.  훈련세트에서 이 인덱스에 해당하는 샘플이 대표샘플로 처리된다. 

 

    print(X_digits_dist.shape)
    print(X_representative_digits.shape)
        
    results : 
    (1347, 50)
    (50, 64)

대표 샘플로 뽑힌 데이터를 출력해보자.

    plt.figure(figsize=(20,10))

    for idx in range(50):
        plt.subplot(5,10,idx+1)
        plt.imshow(X_representative_digits[idx].reshape(8,8), 'gray')
        plt.axis('off')

    plt.show()

대표 샘플

이 샘플들이 Kmean 클러스터링 과정에서 센트로이드로 선정된 샘플들이다. 이 외의 샘플들은 이 센트로이드를 중심으로

 

클러스터링이 이뤄졌다. 

 

y_representive_digits = np.array([8, 8, 9, 6, 0, 7, 4, 2, 5, 9, 0, 6, 7, 2, 3, 4, 8, 9, 6, 1, 5, 8,
       8, 3, 3, 5, 2, 4, 2, 1, 7, 1, 0, 1, 4, 5, 1, 1, 7, 0, 7, 9, 2, 4,
       6, 5, 9, 1, 7, 3])

추출한 이미지를 바탕으로 수동으로 레이블을 부여하였다. 

 

    log_reg = LogisticRegression()
    log_reg.fit(X_representative_digits, y_representative_digits)
    log_reg.score(X_test,y_test)
    
    result:
    0.9066666666666666

확실히 일부 50개의 샘플로만 훈련시킨 로지스틱 모델보다는 대표 샘플로만 훈련시킨 모델이 더 정확도가 높다.

 

일부에 레이블을 할당하는 방법을 취할 때는 이와 같이 대표 샘플에 할당하는 것이 좋은 방법이다. 

 

이 수동으로 부여한 레이블을 같은 클러스터 속한 샘플들에게 전파하도록 하겠다. 이를 레이블 전파라고 한다.

 

     y_train_propagated = np.empty(len(X_train), dtype=np.int32)
     for i in range(k):
         y_train_propagated[kmeans.labels_ == i] = y_representative_digits[i]

전체 훈련 세트에서 kmean이 부여한 레이블(50개 클러스터 중 하나)이 0 ~ 49까지 각각에 해당하는 샘플들의 타깃값에

 

우리가 수동으로 부여한 레이블을 주입한다. 

 

    log_reg = LogisticRegression()
    log_reg.fit(X_train, y_train_propagated)
    log_reg.score(X_test, y_test)
    
    result:
    0.9333333333333333333

레이블 전파 후 평가한 점수가 대표 샘플들로만 훈련시킨 점수보다 높다.

 

센트로이드로 모여든 샘플들 중 잘못 분류된 샘플들도 있겠지만, 훈련시킬 샘플 수가 1347개로 

 

증가했기 때문에 정확도가 더 증가했다. (양질 전환의 법칙 - 헤겔)

 

하지만 클러스터에 속한 샘플 센트로이드와 가장 멀리 떨어진 샘플들은 잘못 분류될 가능성이 높은데

 

이 샘플들을 배제하고 훈련시킨다면 더 정확도가 올라가지 않을까?

 

    percentile_closest = 20
    
    X_cluster_dist = X_digits_dist[np.arange(len(X_train)), kmeans.labels_]
    #(1347,)

먼저 50차원으로 축소된 데이터에서 각 샘플별로 kmean 객체가 레이블링한 값(센트로이드의 인덱스)를 추출한다.

 

    for i in range(k):
     	in_cluster = (kmeans.labels_ == i)
     	cluster_dist = X_cluster_dist[in_cluster]
    	cutoff_distance = np.percentile(cluster_dist, percentile_clostest)
     	above_cutoff = (X_cluster_dist > cutoff_distance)
     	X_cluster_dist[in_cluster & above_cutoff] = -1

모든 센트로이드의 인덱스에 대해서 i번째 센트로이드로 분류된 샘플을 추출하고, 여기서 상위 20퍼센트의 값을 추출한다.

 

(애초에 50차원으로 축소된 데이터인 X_digits_dist는 각 차원(대표 샘플이라 생각해도 좋다)별 거리를 나타냈기 때문이다)

 

X_cluster_dist에서 이 상위 20%의 값보다 큰 값을 가진(다시말해 센트로이드와 더 멀 경우) 샘플 인덱스를 추출하고

 

X_cluster_dist 값을 -1로 변경한다. 

    partially_propagated = (X_cluster_dist != -1)
    X_train_partially_propagated = X_train[partially_propagated]
    y_train_partially_propagated = y_train_propagated[partially_propagated]

이렇게 각 클러스터 별로 상위 20%로 가까운 샘플들의 인덱스만 살려두고 이를 전파된 타깃값에도 동일한 논리를 적용한다.

    log_reg = LogisticRegression()
    log_reg.fit(X_train_pratially_propagated, y_train_partially_propagated)
    log_reg.score(X_test, y_test)
    
    result:
    0.94