주요 컨텐츠로 이동

Databricks의 TensorFlow™

Illustration

클러스터링 및 k-평균

이제 k-평균 알고리즘을 사용하여 클러스터링하는 첫 번째 애플리케이션을 살펴보겠습니다. 클러스터링은 다양한 데이터를 살펴보고 서로 유사한 특성을 가진 그룹을 찾는 데이터 마이닝 작업입니다. k-평균은 다양한 유형의 데이터세트에서 클러스터를 찾는 데 유용한 알고리즘입니다.

클러스터 및 k-평균에 대한 자세한 내용은 k-평균 알고리즘에 대한 scikit-learn 문서를 참조하거나 다음 비디오를 시청하세요.

샘플 생성

먼저 몇 가지 샘플을 생성해야 합니다. 샘플을 무작위로 생성할 수도 있지만, 이 경우 매우 희소한 포인트를 제공하거나 하나의 큰 그룹만 제공할 가능성이 높으므로 클러스터링에 유용하지 않습니다.

대신, 3개의 중심을 생성하는 것부터 시작한 다음 해당 포인트를 중심으로 (정규 분포를 사용하여) 무작위로 선택해 보겠습니다. 먼저, 이 작업을 수행하는 방법은 다음과 같습니다.

 

python
import tensorflow as tf
import numpy as np


def create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed):
    np.random.seed(seed)
    slices = []
    centroids = []
    # Create samples for each cluster
    for i in range(n_clusters):
        samples = tf.random_normal((n_samples_per_cluster, n_features),
                                   mean=0.0, stddev=5.0, dtype=tf.float32, seed=seed, name="cluster_{}".format(i))
        current_centroid = (np.random.random((1, n_features)) * embiggen_factor) - (embiggen_factor/2)
        centroids.append(current_centroid)
        samples += current_centroid
        slices.append(samples)
    # Create a big "samples" dataset
    samples = tf.concat(slices, 0, name='samples')
    centroids = tf.concat(centroids, 0, name='centroids')
    return centroids, samples

 

코드를 functions.py에 포함

작동 방식은 n_clusters의 서로 다른 중심을 무작위로 생성하고(np.random.random((1, n_features)) 사용), 이를 tf.random_normal의 중심 포인트로 사용하는 것입니다. tf.random_normal 함수는 정규 분포된 난수 값을 생성한 다음, 이 값을 현재 중심 포인트에 추가합니다. 그러면 이 중심 포인트 주변에 포인트 blob이 생성됩니다. 이제 중심(centroids.append)과 생성된 샘플(slices.append(samples))을 기록합니다. 마지막으로 tf.concat을 사용하여 "하나의 큰 샘플 목록"을 생성하고, 다시 tf.concat을 사용하여 중심을 TensorFlow 변수로 변환합니다.

이 create_samples 메서드를 functions.py라는 파일에 저장하면 이러한 메서드를 이번(및 다음!) 단원을 위한 스크립트로 가져올 수 있습니다. 다음 코드가 포함된 generate_samples.py라는 파일을 새로 만듭니다.

 

python
import tensorflow as tf
import numpy as np

from functions import create_samples

n_features = 2
n_clusters = 3
n_samples_per_cluster = 500
seed = 700
embiggen_factor = 70

np.random.seed(seed)

centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed)

model = tf.global_variables_initializer()
with tf.Session() as session:
    sample_values = session.run(samples)
    centroid_values = session.run(centroids)

 

위의 코드는 클러스터 및 특성 수(나중에 시각화할 수 있도록 특성 수를 2로 유지)와 생성할 샘플 수를 설정합니다. embiggen_factor를 늘리면 클러스터의 '분산' 또는 크기가 늘어납니다. 여기서는 시각적으로 식별 가능한 클러스터를 생성하므로 유용한 학습 기회를 제공하는 값을 선택했습니다.

결과를 시각화하기 위해 matplotlib를 사용하여 플롯팅 함수를 만들어 보겠습니다. 다음 코드를 functions.py에 추가합니다.

 

python
def plot_clusters(all_samples, centroids, n_samples_per_cluster):
     import matplotlib.pyplot as plt
    #Plot out the different clusters
     #Choose a different colour for each cluster
     colour = plt.cm.rainbow(np.linspace(0,1,len(centroids)))
    for i, centroid in enumerate(centroids):
         #Grab just the samples fpr the given cluster and plot them out with a new colour
         samples = all_samples[i*n_samples_per_cluster:(i+1)*n_samples_per_cluster]
         plt.scatter(samples[:,0], samples[:,1], c=colour[i])
         #Also plot centroid
         plt.plot(centroid[0], centroid[1], markersize=35, marker="x", color='k', mew=10)
         plt.plot(centroid[0], centroid[1], markersize=30, marker="x", color='m', mew=5)
     plt.show()

 

코드를 functions.py에 포함

이 코드는 서로 다른 색상을 사용하여 각 클러스터의 샘플로 플롯을 작성하고 중심 위치에 마젠타 색의 큰 X를 만듭니다. 중심은 인수로 제공되는데, 이는 나중에 유용하게 사용됩니다.

from functions import plot_clusters를 파일 상단에 추가하여 이 함수를 가져오도록 generate_samples.py를 업데이트합니다. 그리고 맨 아래에 다음 코드 줄을 추가합니다.

 

plot_clusters(sample_values, centroid_values, n_samples_per_cluster)

 

generate_samples.py를 실행하면 다음과 같은 플롯이 생성됩니다.

초기화

k-평균 알고리즘은 초기 중심을 선택하는 것에서 시작되는데, 이는 데이터의 실제 중심을 무작위로 추측하는 것입니다. 다음 함수는 데이터세트에서 무작위로 여러 샘플을 선택하여 이 초기 추측을 수행합니다.

 

python
def choose_random_centroids(samples, n_clusters):
    # Step 0: Initialisation: Select `n_clusters` number of random points
    n_samples = tf.shape(samples)[0]
    random_indices = tf.random_shuffle(tf.range(0, n_samples))
    begin = [0,]
    size = [n_clusters,]
    size[0] = n_clusters
    centroid_indices = tf.slice(random_indices, begin, size)
    initial_centroids = tf.gather(samples, centroid_indices)
    return initial_centroids
    

 

코드를 functions.py에 포함

이 코드는 먼저 tf.range(0, n_samples)를 사용하여 각 샘플에 대한 인덱스를 생성한 다음 무작위로 섞습니다. 여기에서 tf.slice를 사용하여 고정된 수의 (n_clusters) 인덱스를 선택합니다. 이러한 인덱스는 초기 중심과 상관관계가 있었으므로, tf.gather를 사용하여 그룹화되어 초기 중심 배열을 형성합니다.

이 새로운 choose_random_centorids 함수를 functions.py에 추가하고, 다음과 같이 새 스크립트를 생성하거나 이전 스크립트를 업데이트합니다.

 

python
import tensorflow as tf
import numpy as np

from functions import create_samples, choose_random_centroids, plot_clusters

n_features = 2
n_clusters = 3
n_samples_per_cluster = 500
seed = 700
embiggen_factor = 70

centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed)
initial_centroids = choose_random_centroids(samples, n_clusters)

model = tf.global_variables_initializer()
with tf.Session() as session:
    sample_values = session.run(samples)
    updated_centroid_value = session.run(initial_centroids)

plot_clusters(sample_values, updated_centroid_value, n_samples_per_cluster)

 

여기서 가장 큰 변화는 이러한 초기 중심에 대한 변수를 생성하고 세션에서 해당 값을 계산한다는 것입니다. 그런 다음 데이터를 생성하는 데 사용된 실제 중심이 아닌 plot_cluster에 대한 첫 번째 추측으로 플롯을 작성합니다.

이 함수를 실행하면 위와 비슷한 이미지가 생성되지만 중심은 임의의 위치에 있게 됩니다. 이 스크립트를 몇 번 실행해보면 중심이 꽤 많이 이동한다는 것을 알 수 있습니다.

중심 업데이트

k-평균 알고리즘은 중심 위치에 대한 몇 가지 추측으로 시작한 다음 데이터를 기반으로 해당 추측을 업데이트합니다. 이 프로세스에서는 각 샘플에 가장 가까운 중심을 나타내는 클러스터 번호를 할당합니다. 그러면 중심이 해당 클러스터에 할당된 모든 샘플의 평균이 되도록 업데이트됩니다. 다음 코드는 가장 가까운 클러스터 할당 단계를 처리합니다.

 

python
def assign_to_nearest(samples, centroids):
    # Finds the nearest centroid for each sample

    # START from https://esciencegroup.com/2016/01/05/an-encounter-with-googles-tensorflow/
    expanded_vectors = tf.expand_dims(samples, 0)
    expanded_centroids = tf.expand_dims(centroids, 1)
    distances = tf.reduce_sum( tf.square(
               tf.subtract(expanded_vectors, expanded_centroids)), 2)
    mins = tf.argmin(distances, 0)
    # END from https://esciencegroup.com/2016/01/05/an-encounter-with-googles-tensorflow/
    nearest_indices = mins
    return nearest_indices

 

참고로, 이 페이지에서 다양한 유형의 k-평균 알고리즘과 기타 유용한 정보가 포함된 일부 코드를 차용했습니다.

작동 방식은 각 샘플과 각 중심 사이의 거리를 계산하는 것인데, distances = 줄에서 이루어집니다. 여기서 거리 계산이란 유클리드 거리입니다. 여기서 중요한 점은 tf.subtract가 두 인수의 크기를 자동으로 확장한다는 것입니다. 즉, 샘플을 행렬로 사용하고 중심을 열 벡터로 사용하면 둘 사이의 쌍 뺄셈이 이루어집니다. 이를 위해 tf.expand_dims를 사용하여 샘플과 중심 모두에 대한 추가 차원을 생성하고 tf.subtract의 이 동작을 강제로 실행합니다.

다음 코드는 중심 업데이트를 처리합니다.

python
def update_centroids(samples, nearest_indices, n_clusters):
    # Updates the centroid to be the mean of all samples associated with it.
    nearest_indices = tf.to_int32(nearest_indices)
    partitions = tf.dynamic_partition(samples, nearest_indices, n_clusters)
    new_centroids = tf.concat([tf.expand_dims(tf.reduce_mean(partition, 0), 0) for partition in partitions], 0)
    return new_centroids

 

이 코드는 각 샘플에 가장 가까운 인덱스를 찾은 다음 tf.dynamic_partition을 사용하여 별도의 그룹으로 가져옵니다. 여기에서 단일 그룹에 대해 tf.reduce_mean을 사용하여 해당 그룹의 평균을 찾아 새로운 중심을 형성합니다. 그리고 새로운 중심을 형성하기 위해 tf.concat을 사용하여 이들을 서로 연결합니다.

이제 코드가 준비되었으므로 다음 호출을 스크립트에 추가하거나 새 스크립트를 만들 수 있습니다.

 

python
import tensorflow as tf
import numpy as np

from functions import *

n_features = 2
n_clusters = 3
n_samples_per_cluster = 500
seed = 700
embiggen_factor = 70


data_centroids, samples = create_samples(n_clusters, n_samples_per_cluster, n_features, embiggen_factor, seed)
initial_centroids = choose_random_centroids(samples, n_clusters)
nearest_indices = assign_to_nearest(samples, initial_centroids)
updated_centroids = update_centroids(samples, nearest_indices, n_clusters)

model = tf.global_variables_initializer()
with tf.Session() as session:
    sample_values = session.run(samples)
    updated_centroid_value = session.run(updated_centroids)
    print(updated_centroid_value)

plot_clusters(sample_values, updated_centroid_value, n_samples_per_cluster)

 

이 코드는 다음을 수행합니다.

  1. 초기 중심에서 샘플 생성
  2. 초기 중심을 무작위로 선택
  3. 각 샘플을 가장 가까운 중심에 연결
  4. 각 중심을 연결된 샘플의 평균이 되도록 업데이트

이것이 바로 k-평균의 단일 반복입니다. 이 단일 반복을 반복 버전을 전환하는 실습을 수행할 것을 권장합니다.

1) generate_samples에 전달된 시드 옵션은 '무작위'로 생성된 샘플이 스크립트를 실행할 때마다 일관되게 유지되도록 보장합니다. 여기서 choose_random_centroids 함수에 시드를 전달하지는 않았는데, 이는 스크립트가 실행될 때마다 초기 중심이 다르다는 것을 의미합니다. 임의의 중심에 대한 새 시드를 포함하도록 스크립트를 업데이트합니다.

2) k-평균 알고리즘은 반복적으로 수행되며, 이전 반복에서 업데이트된 중심을 사용하여 클러스터를 할당한 다음 중심을 업데이트합니다. 즉, 알고리즘은 assign_to_nearest 호출과 update_centroids 호출을 번갈아 수행합니다. 코드 실행을 중단하기 전에 10번 반복하도록 코드를 업데이트합니다. k-평균을 더 많이 반복하면 결과 중심이 평균에 훨씬 더 가까워진다는 것을 알 수 있습니다. (k-평균에 익숙한 사용자를 위해 향후 튜토리얼에서 수렴 함수 및 기타 중단 기준을 살펴볼 것입니다.)