Direkt zum Hauptinhalt

TensorFlow™ auf Databricks

Illustration

Clustering und k-means

Wir wagen uns nun an die Erstellung unserer ersten Anwendung, nämlich Clustering mit dem k-means-Algorithmus. Clustering ist ein Data-Mining-Vorgang, bei dem wir anhand einer Reihe von Daten Gruppen von Punkten ausfindig machen, die einander ähnlich sind. k-means ist ein Algorithmus, der sich hervorragend zum Auffinden von Clustern in vielen Arten von Datasets eignet.

Weitere Informationen zu Clustern und k-means finden Sie in der scikit-learn-Dokumentation zum k-means-Algorithmus oder in diesem Video:

Stichproben generieren

Zunächst müssen wir einige Stichproben generieren. Wir könnten die Stichproben zufällig generieren, aber das wird uns wahrscheinlich entweder sehr wenige Punkte oder nur eine große Gruppe liefern – was für Clustering nicht sehr aufregend ist.

Stattdessen beginnen wir mit der Generierung von drei Schwerpunkten und wählen dann zufällig (mit einer Normalverteilung) um diesen Punkt herum aus. Hier ist zunächst eine Methode dafür:

 

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

 

Diesen Code in functions.py einfügen

Dies funktioniert so, dass n_clusters mit verschiedenen Schwerpunkten nach dem Zufallsprinzip erstellt werden (unter Verwendung von np.random.random((1, n_features))) und diese als Mittelpunkte für tf.random_normal verwendet werden. Die Funktion tf.random_normal generiert normalverteilte Zufallswerte, die wir dann zum aktuellen Mittelpunkt hinzufügen. Dadurch entsteht ein „Punktklumpen“ um diesen Mittelpunkt herum. Anschließend zeichnen wir die Schwerpunkte (centroids.append) und die generierten Stichproben (slices.append(samples)) auf. Schließlich erstellen wir mit tf.concat eine „eine große Liste von Stichproben“ und konvertieren die Schwerpunkte ebenfalls mit tf.concat in eine TensorFlow-Variable.

Wenn wir diese create_samples-Methode in einer Datei namens functions.py speichern, können wir diese Methoden für diese (und die nächste!) Lektion in unsere Skripte importieren. Erstellen Sie eine neue Datei mit dem Namen generate_samples.py, die den folgenden Code enthält:

 

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)

 

Dadurch wird lediglich die Anzahl der Cluster und Features (ich empfehle, die Anzahl der Features bei 2 zu belassen, damit wir sie später visualisieren können) und die Anzahl der zu generierenden Stichproben festgelegt. Durch Erhöhen von embiggen_factor wird die Verteilung oder die Größe der Cluster erhöht. Ich habe hier einen Wert gewählt, der gute Lernmöglichkeiten bietet, da er visuell identifizierbare Cluster generiert.

Um die Ergebnisse zu visualisieren, erstellen wir mit matplotlib eine Plottingfunktion. Fügen Sie diesen Code zu functions.py hinzu:

 

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()

 

Diesen Code in functions.py einfügen

Dieser Code plottet lediglich die Stichproben aus jedem Cluster mit einer anderen Farbe und erstellt ein großes magentafarbenes X an der Stelle, an der sich der Schwerpunkt befindet. Der Schwerpunkt wird als Argument angegeben, was später nützlich sein wird.

Aktualisieren Sie generate_samples.py, um diese Funktion zu importieren, indem Sie am Anfang der Datei from functions import plot_clusters hinzufügen. Fügen Sie dann diese Codezeile unten hinzu:

 

plot_clusters(sample_values, centroid_values, n_samples_per_cluster)

 

Das Ausführen von generate_samples.py sollte Ihnen nun den folgenden Plot liefern:

Initialisierung

Der k-means-Algorithmus beginnt mit der Auswahl der anfänglichen Schwerpunkte, bei denen es sich lediglich um zufällige Schätzungen der tatsächlichen Schwerpunkte in den Daten handelt. Die folgende Funktion wählt zufällig eine Anzahl von Stichproben aus dem Dataset aus, die als erste Schätzung dienen:

 

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
    

 

Diesen Code in functions.py einfügen

Dieser Code erstellt zunächst einen Index für jede Probe (mithilfe von tf.range(0, n_samples) und mischt ihn dann nach dem Zufallsprinzip. Von dort aus wählen wir mit tf.slice eine feste Anzahl (n_clusters) von Indizes aus. Diese Indizes korrelierten mit unseren anfänglichen Schwerpunkten, die dann mithilfe von tf.gather gruppiert wurden, um unser Array anfänglicher Schwerpunkte zu bilden.

Fügen Sie diese neue Funktion choose_random_centorids zu functions.py hinzu und erstellen Sie ein neues Skript (oder aktualisieren Sie Ihr vorheriges) wie folgt:

 

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)

 

Die wichtigste Änderung besteht darin, dass wir eine Variable für diese anfänglichen Schwerpunkte erstellen und ihren Wert in der Sitzung berechnen. Anschließend tragen wir diese ersten Schätzungen in plot_cluster ein und nicht in die eigentlichen Schwerpunkte, die zur Generierung der Daten verwendet wurden.

Bei der Ausführung erhalten Sie ein ähnliches Bild wie oben, die Schwerpunkte befinden sich jedoch an zufälligen Positionen. Versuchen Sie, dieses Skript ein paar Mal auszuführen. Beachten Sie dabei, dass sich die Schwerpunkte ziemlich stark verschieben.

Schwerpunkte aktualisieren

Nachdem er mit einigen Schätzungen für die Schwerpunktpositionen begonnen hat, aktualisiert der k-means-Algorithmus diese Schätzungen dann basierend auf den Daten. Der Prozess besteht darin, jeder Probe eine Clusternummer zuzuweisen, die den Schwerpunkt darstellt, dem sie am nächsten liegt. Danach werden die Schwerpunkte so aktualisiert, dass sie die Mittelwerte aller diesem Cluster zugewiesenen Stichproben darstellen. Der folgende Code verarbeitet den Schritt Dem nächstgelegenen Cluster zuweisen:

 

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

 

Beachten Sie, dass ich Code von dieser Seite verwendet habe, auf der ein anderer Typ des k-means-Algorithmus und viele andere nützliche Information enthalten sind.

Dies funktioniert so, dass der Abstand zwischen jeder Probe und jedem Schwerpunkt berechnet wird, der durch die Zeile distances = entsteht. Die Entfernungsberechnung ist hier die euklidische Entfernung. Ein wichtiger Punkt hierbei ist, dass tf.subtract die Größe der beiden Argumente automatisch erweitert. Das bedeutet, dass wir unsere Stichproben als Matrix haben, und die Schwerpunkte als Spaltenvektor ergeben die paarweise Subtraktion zwischen ihnen. Zu diesem Zweck verwenden wir tf.expand_dims, um eine zusätzliche Dimension sowohl für Stichproben als auch für Schwerpunkte zu erstellen und so dieses Verhalten von tf.subtract zu erzwingen.

Der nächste Codeausschnitt übergibt update-centroids:

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

 

Dieser Code verwendet für jede Stichprobe die nächstgelegenen Indizes und erfasst diese mithilfe von tf.dynamic_partition als separate Gruppen. Von hier aus verwenden wir tf.reduce_mean für eine einzelne Gruppe, um den Durchschnitt dieser Gruppe zu ermitteln und so ihren neuen Schwerpunkt zu bilden. Von hier aus verketten wir sie einfach mit tf.concat, um unsere neuen Schwerpunkte zu bilden.

Nachdem wir diesen Teil fertig haben, können wir die folgenden Aufrufe zu unserem Skript hinzufügen (oder ein neues erstellen):

 

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)

 

Dieser Code wird:

  1. Stichproben aus den anfänglichen Schwerpunkten generieren
  2. Zufällig die Anfangsschwerpunkte wählen
  3. Jede Probe ihrem nächstgelegenen Schwerpunkt zuordnen
  4. Jeden Schwerpunkt so aktualisieren, dass er dem Mittelwert der ihm zugeordneten Stichproben entspricht

Dies ist eine einzelne Iteration von k-means! Probieren Sie die Übungen einmal aus, wodurch daraus eine iterative Version wird.

1) Die an generate_samples übergebene Startwertoption stellt sicher, dass die zufällig generierten Stichproben bei jeder Ausführung des Skripts konsistent sind. Wir haben den Startwert nicht an die Funktion choose_random_centroids übergeben, was bedeutet, dass diese anfänglichen Schwerpunkte bei jeder Ausführung des Skripts unterschiedlich sind. Aktualisieren Sie das Skript, um einen neuen Startwert für zufällige Schwerpunkte einzuschließen.

2) Der k-means-Algorithmus wird iterativ ausgeführt, wobei die aktualisierten Schwerpunkte aus der vorherigen Iteration zum Zuweisen von Clustern verwendet werden, die dann zum Aktualisieren der Schwerpunkte usw. verwendet werden. Mit anderen Worten: Der Algorithmus wechselt zwischen dem Aufruf von assign_to_nearest und update_centroids hin und her. Aktualisieren Sie den Code, um diese Iteration zehnmal durchzuführen, bevor Sie sie stoppen. Sie werden feststellen, dass die resultierenden Schwerpunkte bei mehr Iterationen von k-means im Durchschnitt viel näher beieinander liegen. (Für diejenigen, die Erfahrung mit k-means haben, wird sich ein zukünftiges Tutorial mit Konvergenzfunktionen und anderen Stoppkriterien befassen.)