web-dev-qa-db-fra.com

Comment implémenter k-means avec TensorFlow?

Le didacticiel d'introduction, qui utilise l'optimiseur de descente de gradient intégré, a beaucoup de sens. Cependant, k-means n'est pas simplement quelque chose que je peux intégrer à la descente de gradient. Il semble que je devrais écrire mon propre type d’optimiseur, mais je ne sais pas trop comment le faire étant donné les primitives TensorFlow.

Quelle approche devrais-je prendre?

18

(note: vous pouvez maintenant obtenir une version plus raffinée de ce code sous forme de Gist sur github .)

vous pouvez le faire, mais vous devez définir vos propres critères d'optimisation (pour k-means, il s'agit généralement du nombre maximal d'itérations et du moment où l'assignation se stabilise). Voici un exemple de la façon dont vous pourriez le faire (il existe probablement des moyens plus optimaux de le mettre en œuvre et certainement de meilleurs moyens de sélectionner les points initiaux). C'est comme si vous le faisiez numpy si vous essayiez vraiment de ne pas faire les choses de façon itérative en python:

import tensorflow as tf
import numpy as np
import time

N=10000
K=4
MAX_ITERS = 1000

start = time.time()

points = tf.Variable(tf.random_uniform([N,2]))
cluster_assignments = tf.Variable(tf.zeros([N], dtype=tf.int64))

# Silly initialization:  Use the first two points as the starting                
# centroids.  In the real world, do this better.                                 
centroids = tf.Variable(tf.slice(points.initialized_value(), [0,0], [K,2]))

# Replicate to N copies of each centroid and K copies of each                    
# point, then subtract and compute the sum of squared distances.                 
rep_centroids = tf.reshape(tf.tile(centroids, [N, 1]), [N, K, 2])
rep_points = tf.reshape(tf.tile(points, [1, K]), [N, K, 2])
sum_squares = tf.reduce_sum(tf.square(rep_points - rep_centroids),
                            reduction_indices=2)

# Use argmin to select the lowest-distance point                                 
best_centroids = tf.argmin(sum_squares, 1)
did_assignments_change = tf.reduce_any(tf.not_equal(best_centroids,
                                                    cluster_assignments))

def bucket_mean(data, bucket_ids, num_buckets):
    total = tf.unsorted_segment_sum(data, bucket_ids, num_buckets)
    count = tf.unsorted_segment_sum(tf.ones_like(data), bucket_ids, num_buckets)
    return total / count

means = bucket_mean(points, best_centroids, K)

# Do not write to the assigned clusters variable until after                     
# computing whether the assignments have changed - hence with_dependencies
with tf.control_dependencies([did_assignments_change]):
    do_updates = tf.group(
        centroids.assign(means),
        cluster_assignments.assign(best_centroids))

sess = tf.Session()
sess.run(tf.initialize_all_variables())

changed = True
iters = 0

while changed and iters < MAX_ITERS:
    iters += 1
    [changed, _] = sess.run([did_assignments_change, do_updates])

[centers, assignments] = sess.run([centroids, cluster_assignments])
end = time.time()
print ("Found in %.2f seconds" % (end-start)), iters, "iterations"
print "Centroids:"
print centers
print "Cluster assignments:", assignments

(Notez qu'une implémentation réelle devrait être plus prudente lors de la sélection initiale du cluster, en évitant les problèmes avec tous les points allant à un cluster, etc. Ceci est juste une démonstration rapide. plus clair et "exemple-digne".)

27
dga

La plupart des réponses que j'ai vues jusqu'à présent concernent uniquement la version 2d (lorsque vous devez regrouper des points en 2 dimensions). Voici mon implémentation du clustering dans des dimensions arbitraires.


Idée de base de algorithme k-means in n dims:

  • générer des k points de départ aléatoires
  • faites-le jusqu'à ce que vous dépassiez la patience ou que l'affectation de cluster ne change pas:
    • assigner chaque point au point de départ le plus proche
    • recalculer l'emplacement de chaque point de départ en prenant la moyenne parmi son groupe

Pour pouvoir en quelque sorte valider les résultats, je vais essayer de regrouper des images MNIST. 

import numpy as np
import tensorflow as tf
from random import randint
from collections import Counter
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("MNIST_data/")
X, y, k = mnist.test.images, mnist.test.labels, 10

Donc ici X sont mes données pour le cluster (10000, 784), y est le nombre réel, et k est le nombre de cluster (qui est identique au nombre de chiffres. Maintenant, le algorithme actuel:

# select random points as a starting position. You can do better by randomly selecting k points.
start_pos = tf.Variable(X[np.random.randint(X.shape[0], size=k),:], dtype=tf.float32)
centroids = tf.Variable(start_pos.initialized_value(), 'S', dtype=tf.float32)

# populate points
points           = tf.Variable(X, 'X', dtype=tf.float32)
ones_like        = tf.ones((points.get_shape()[0], 1))
prev_assignments = tf.Variable(tf.zeros((points.get_shape()[0], ), dtype=tf.int64))

# find the distance between all points: http://stackoverflow.com/a/43839605/1090562
p1 = tf.matmul(
    tf.expand_dims(tf.reduce_sum(tf.square(points), 1), 1),
    tf.ones(shape=(1, k))
)
p2 = tf.transpose(tf.matmul(
    tf.reshape(tf.reduce_sum(tf.square(centroids), 1), shape=[-1, 1]),
    ones_like,
    transpose_b=True
))
distance = tf.sqrt(tf.add(p1, p2) - 2 * tf.matmul(points, centroids, transpose_b=True))

# assign each point to a closest centroid
point_to_centroid_assignment = tf.argmin(distance, axis=1)

# recalculate the centers
total = tf.unsorted_segment_sum(points, point_to_centroid_assignment, k)
count = tf.unsorted_segment_sum(ones_like, point_to_centroid_assignment, k)
means = total / count

# continue if there is any difference between the current and previous assignment
is_continue = tf.reduce_any(tf.not_equal(point_to_centroid_assignment, prev_assignments))

with tf.control_dependencies([is_continue]):
    loop = tf.group(centroids.assign(means), prev_assignments.assign(point_to_centroid_assignment))

sess = tf.Session()
sess.run(tf.global_variables_initializer())

# do many iterations. Hopefully you will stop because of has_changed is False
has_changed, cnt = True, 0
while has_changed and cnt < 300:
    cnt += 1
    has_changed, _ = sess.run([is_continue, loop])

# see how the data is assigned
res = sess.run(point_to_centroid_assignment)

Il est maintenant temps de vérifier la qualité de nos grappes. Pour ce faire, nous allons regrouper tous les nombres réels apparus dans la grappe. Après cela, nous verrons les choix les plus populaires dans ce cluster. Dans le cas d'une mise en cluster parfaite, nous n'aurons qu'une valeur dans chaque groupe. En cas de cluster aléatoire, chaque valeur sera représentée à peu près également dans le groupe.

nums_in_clusters = [[] for i in xrange(10)]
for cluster, real_num in Zip(list(res), list(y)):
    nums_in_clusters[cluster].append(real_num)

for i in xrange(10):
    print Counter(nums_in_clusters[i]).most_common(3)

Cela me donne quelque chose comme ça:

[(0, 738), (6, 18), (2, 11)]
[(1, 641), (3, 53), (2, 51)]
[(1, 488), (2, 115), (7, 56)]
[(4, 550), (9, 533), (7, 280)]
[(7, 634), (9, 400), (4, 302)]
[(6, 649), (4, 27), (0, 14)]
[(5, 269), (6, 244), (0, 161)]
[(8, 646), (5, 164), (3, 125)]
[(2, 698), (3, 34), (7, 14)]
[(3, 712), (5, 290), (8, 110)]

C'est très bien parce que la majorité des comptes est dans le premier groupe. Vous voyez que le clustering confond 7 et 9, 4 et 5. Mais 0 est très bien groupé.

Quelques approches pour améliorer ceci:

  • exécutez l'algorithme plusieurs fois et sélectionnez le meilleur (en fonction de la distance aux grappes)
  • gérer les cas lorsque rien n'est attribué à un cluster. Dans mon cas, vous obtiendrez Nan dans la variable means car count est égal à 0.
  • initialisation aléatoire de points.
4
Salvador Dali

De nos jours, vous pouvez directement utiliser (ou vous inspirer de) l’estimateur KMeansClustering . Vous pouvez jeter un oeil sur son implémentation sur GitHub .

0
Vicente Reyes-Puerta