Introduction a l’algorithme des kmeans

L'algorithme des kmeans permet de regrouper les informations (points) au sein de cluster distincts en utilisant une estimation de distance, ici ce seras une simple distance euclidien. Pour ce faire, nous allons fixer dans l’algorithme les centres de départs aléatoirement et leurs nombres donnés au début du programme. Ce sont des informations à priori que nous avons sur les données. Ensuite nous allons effectuer plusieurs itérations jusqu'a ce que deux itérations successive n’améliore pas suffisamment le résultat (avec une tolérance).

Ainsi à chaque itération, nous allons réattribuer les centres à chaque point, en fonction de la distance du point (la donnée) avec le centre le plus proche ce seras le rôle de la fonction updateClusterId. Ensuite, on recalcule le centre de tous les clusters grâce à la fonction updateCenters et finalement, on calcule le « score » global (la somme des distances des clusters) avec une autre fonction computeSSE. Si la différence entre le score courant et l’ancien est inférieurs à la tolérance donné, il y a stabilisation et ont arrête. À l'issue de son exécution, l’algorithme des kmeans à alors associé chaque donnée à un groupe.

Pour tester chaque partie de l'algorithme nous allos utiliser quelques données simple :

data = [(1,1),(1,2),(1,3),(5,4),(4,5),(4.5,4.5)]
centers = [(1,2),(4.5,4.5)]
clusertID = [0,0,0,1,1,1]

Associations des points au centres les plus proches

Dans cette étape ont cherche à attribuer un numéro de cluster à chaque point. Chaque cluster a sont « tampon » pour construire la liste des distances et venir prendre le minimum facilement :

def updateClusterID(data, centers):
    clusterID = np.zeros(len(data))
    L = np.zeros(len(centers))
    
    for idx,elm in enumerate(data):
        for jdx,cent in enumerate(centers):
            L[jdx] = np.linealg.norm(elm,cent,2)
        clusterID[idx] = np.argmin(L)

    return clusterID

Avec les données précédemment obtenue, nous pouvons constater et vérifié (à la main) le résultat

updateClusterID(data, centers): [0, 0, 1, 1, 1, 0]

Mise à jours des centres des clusters

Finalement, ont recalcul les coordonnées du centre de chaque cluster

def updateCenters(data, clusterID, R):
    centers = np.zeros((len(data[0]),R))
    count = np.zeros(R)
    
    for idx,elm in enumerate(data):
        centers[clusterID[idx]] = np.sum(elm)
        count[clusterID[idx]] += 1

    return np.nan_to_num(centers / count)

Calcule du score

Pour vérifier l'association nous alons définir une fonction qui correspond à ma somme des erreurs quadratique entre les points et le centre de leur cluster associées :

def computeSSE(data, centers, clusterID):
    somme = 0;
    for i in range(len(data)):
        somme += np.linealg.norm(centers[clusterID[i]], data[i], 2);
    return somme

Par exemple on peu défini quelques données et tester:

computeSSE(data, centers, clusertID): 3.0

Appliquer chaque étapes

Pour résumer, ont affecte chaque point au centre le plus proche et donc le numero de cluster associé. Ont recalcul les centres des clusters et ont calcule la nouvelle erreur. S'il n'y a pas d'amélioration qui dépasse un certain seuil alors ont est dans un minimum local et il y a stabilisation. Bien entendu dans certain cas ont peu tourné autour de ce minimum local sans converger et il suffi de limiter le nombre d'itération ! L'algorithme ce traduit alors par la fonction kmeans :

def kmeans(data, centers, maxIter = 100, tol = 1e-6):
    nData, R = len(data), len(centers)
    
    if nData == 0 or R >= nData:
        return [], []

    clusterID = [0] * nData
    stabilise = False
    oldDistance = float("inf");
    iter = 0
    
    while not stabilise and iter < maxIter:
        clusterID = updateClusterID(data, centers)
        centers = updateCenters(data, clusterID, R)
        curDistance = computeSSE(data, centers, clusterID)
        print("# it ", iter, " SSE = ", curDistance)
        stabilise = abs(curDistance-oldDistance) < tol
        oldDistance = curDistance
        iter += 1

    print("# of iterations:", iter)
    print("SSE = ", oldDistance)
    return clusterID, centers

Utilisation

# Nombre de clusters a priori
centers = [data[randint(0,len(data)-1)] for i in range(3)]
results, centers = kmeans(data, centers)

On peut alors simplement lancer l’application avec différent jeux de testes (2d, 4d) et venir et tester le résultat de l’algorithme avec différents paramètres : tel que le nombre de center/cluster (K).

  • preview

    premier jeux avec 2 clusters

  • preview

    premier jeux avec 3 clusters

  • preview

    premier jeux avec 4 clusters

  • preview

    premier jeux avec 5 clusters

  • preview

    second jeux avec 2 clusters

  • preview

    second jeux avec 3 clusters

  • preview

    second jeux avec 4 clusters

  • preview

    second jeux avec 5 clusters

Comme nous pouvons le constater algorithmie n’est pas stable puisque le choix des centres de départ sont choisi aléatoirement au lancement de l’application ce qui en résulte en une association différente pour chaque éxécution.

  • preview

    éxécution 1

  • preview

    éxécution 2

Évidement ce type d'analyse existe déjà, par exemple le module sklearn la propose :

from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=2, random_state=0).fit(X)
kmeans.labels_
kmeans.cluster_centers_