[PYTHON] J'ai fait un processus d'initialisation KMEANS rapide avec une meilleure précision que aléatoire

Qu'est-ce que le processus d'initialisation de KMEANS?

KMEANS détermine d'abord une position initiale appropriée et répète l'estimation et le calcul de la valeur centrale jusqu'à la convergence.Plus la position initiale est bonne, plus la précision est élevée et moins il y a de répétitions jusqu'à convergence. Il existe deux méthodes d'initialisation, RANDOM et k-means ++. Comme son nom l'indique, RANDOM sélectionne au hasard des échantillons pour le nombre de clusters et les utilise comme positions initiales. Dans k-means ++, le premier point est sélectionné au hasard, mais 2 À partir de la seconde, créez une distribution de probabilité avec D (x) ^ 2 de sorte que plus la distance est longue, plus la probabilité est élevée, et sélectionnez autant d'objets que possible que le nombre de clusters. L'explication ici est très facile à comprendre → https://www.medi-08-data-06.work/entry/kmeans

Motivation

Comme j'ai pu faire KMEANS avec un micro-ordinateur, il est nécessaire d'effectuer un traitement d'initialisation aussi léger que KMEANS ++ et ne consomme pas de mémoire.

La façon dont je suis venu avec

① Calculez la valeur moyenne de toutes les distances entre les valeurs centrales ② Estimer la grappe à laquelle appartient l'échantillon ③ Si la distance de ② est supérieure à la moitié de ① (la distance est considérée comme le diamètre et son rayon), elle est définie comme nouvelle valeur centrale. ④ Si le cluster est mis à jour avec un nouvel échantillon selon ③, passez à ①, sinon passez à ② Répétez ① à ④ pour toutes les données cibles

Le fait que la distance au moment de l'estimation par grappes d'un échantillon soit supérieure à la distance moyenne entre les valeurs centrales signifie que l'échantillon est une nouvelle grappe et que la distribution doit être plus large que la valeur centrale actuelle, donc jusqu'à ce que la distance moyenne entre les valeurs centrales soit élargie. L'idée est de l'élargir. C'est difficile à comprendre, mais je ne peux pas l'expliquer correctement, alors jetez un œil à la source.

1000 résultats de tests par iris et graines

iris

En traitement Itération moyenne Distribution d'itération Bonne réponse moyenne Bonne dispersion des réponses
random 6.418 2.174 0.763 0.1268
k-means++ 5.38 1.63 0.804 0.09
Nouvelle méthode 6.104 2.088 0.801 0.09

seeds

En traitement Itération moyenne Distribution d'itération Bonne réponse moyenne Bonne dispersion des réponses
random 9.109 3.237 0.921 0.0049
k-means++ 7.096 2.4284 0.921 0.0051
Nouvelle méthode 7.368 2.56 0.921 0.0046

Bien que ce ne soit pas aussi bon que k-means ++, je pense que le résultat est clairement meilleur que aléatoire, le nombre d'itérations est petit, la précision est élevée et la dispersion est faible, donc la variation du résultat semble faible. Ce serait bien s'il y en avait un peu plus et qu'il y avait un échantillon d'environ 8 grappes, mais il n'y en avait pas de pratique, alors j'ai décidé de tester uniquement l'iris et les graines. À propos, il semble que la méthode soit hautement compatible avec chaque échantillon car elle a une plus grande précision et un nombre réduit d'itérations que le test réel utilisé dans le travail réel. Le code source est affiché ci-dessous.

Code source pour le processus d'initialisation

def Kmeans_Predict(means, x):
    distances = []
    for m in means:
        distances.append(np.linalg.norm(m - x))
    predict = np.argmin(distances)
    return distances[predict], predict

#Calculer la distance moyenne entre les valeurs centrales
def KmeansInit_CalcRadiusAverageDistance(means):
    length = len(means)
    avrDistance = 0
    cnt = 0
    for i in range(length):
        for j in range(i):
            if j == i: continue
            avrDistance += np.linalg.norm(means[i] - means[j])
            cnt += 1
    return (avrDistance / cnt/ 2)

def KmeansInit_FarawayCentroids(n_clusters, x):
    means = np.zeros((n_clusters, x.shape[1]))
    distanceThreshold = 0
    for cnt in range(1):
        for ix in x:
            distance, predict = Kmeans_Predict(means, ix)
            if distance > distanceThreshold:
                #S'il est plus grand que la distance moyenne entre les centres, alors l'échantillon est une nouvelle grappe.
                means[predict] = ix
                distanceThreshold = KmeansInit_CalcRadiusAverageDistance(means)
            else:
                #Si elle est égale ou inférieure à la distance moyenne entre les valeurs centrales, il s'agit d'une position raisonnable et la valeur centrale n'est pas mise à jour.
                pass
    return means

Code source complet du test

import math
import numpy as np
import sklearn.cluster
import sklearn.preprocessing
import sys

def Kmeans_Predict(means, x):
    distances = []
    for m in means:
        distances.append(np.linalg.norm(m - x))
    predict = np.argmin(distances)
    return distances[predict], predict

def KmeansInit_CalcRadiusAverageDistance(means):
    length = len(means)
    avrDistance = 0
    cnt = 0
    for i in range(length):
        for j in range(i):
            if j == i: continue
            avrDistance += np.linalg.norm(means[i] - means[j])
            cnt += 1
    return (avrDistance / cnt/ 2)

def KmeansInit_FarawayCentroids(n_clusters, x):
    means = np.zeros((n_clusters, x.shape[1]))
    distanceThreshold = 0
    for cnt in range(1):
        for ix in x:
            distance, predict = Kmeans_Predict(means, ix)
            if distance > distanceThreshold:
                means[predict] = ix
                distanceThreshold = KmeansInit_CalcRadiusAverageDistance(means)
    return means

def loadIris():
    data = np.loadtxt("./iris_dataset.txt", delimiter="\t", dtype=str)
    length = len(data)
    x = data[:,0:4].astype(np.float)
    names = data[:,4]
    nameList = np.unique(data[:,4])
    y = np.zeros(length)
    for i, name in enumerate(nameList):
        y[names == name] = i
    return x, y

def loadSeeds():
    data = np.loadtxt("./seeds_dataset.txt", delimiter="\t", dtype=str)
    length = len(data)
    x = data[:,0:7].astype(np.float)
    y = data[:,7].astype(float)
    return x, y


def KmeansModel(init, n_clusters):
    return sklearn.cluster.KMeans(
        n_clusters=n_clusters,
        init=init,
        n_init=1,
        max_iter=100,
        tol=1e-5,
        verbose=3
    )

def CalcAccuracy(y, predicts):
    answers = np.unique(y)
    clusters = np.sort(np.unique(predicts))
    accuracy = []
    ignoreCluster = []
    for ans in answers:
        pred = predicts[y == ans]
        total = []
        totalClusters = []
        for c in clusters:
            if c in ignoreCluster:
                continue
            total.append(np.sum(pred == c))
            totalClusters.append(c)
        maxIdx = np.argmax(total)
        ignoreCluster.append(totalClusters[maxIdx])
        acc = total[maxIdx] / len(pred)
        accuracy.append(acc)
    return accuracy

def KmeansTestSub(init, n_clusters, x, y):
    model = KmeansModel(init, n_clusters)
    model.fit(x)
    predicts = model.predict(x)
    accuracy = CalcAccuracy(y, predicts)
    return [model.n_iter_, np.mean(accuracy)] + list(accuracy)

def shuffleData(x, y):
    idxs = np.arange(len(x))
    np.random.shuffle(idxs)
    x, y = x[idxs], y[idxs]
    return x, y

def KmeansTest(dataset, n_clusters, prefix="", test_count=1000):
    x, y = dataset
    scaler = sklearn.preprocessing.StandardScaler()
    scaler.fit(x)
    x = scaler.transform(x)
    
    # farway
    report = []
    for i in range(test_count):
        x, y = shuffleData(x, y)
        init = KmeansInit_FarawayCentroids(n_clusters, x)
        rep = KmeansTestSub(init, n_clusters, x, y)
        report.append(rep)
    report = np.vstack([report, np.mean(report, axis=0)])
    report = np.vstack([report, np.std(report, axis=0)])
    np.savetxt("./{}report_farway.txt".format(prefix), report, delimiter="\t")
    
    # random
    report = []
    for i in range(test_count):
        rep = KmeansTestSub("random", n_clusters, x, y)
        report.append(rep)
    report = np.vstack([report, np.mean(report, axis=0)])
    report = np.vstack([report, np.std(report, axis=0)])
    np.savetxt("./{}report_random.txt".format(prefix), report, delimiter="\t")
    
    # k-means++
    report = []
    for i in range(test_count):
        rep = KmeansTestSub("k-means++", n_clusters, x, y)
        report.append(rep)
    report = np.vstack([report, np.mean(report, axis=0)])
    report = np.vstack([report, np.std(report, axis=0)])
    np.savetxt("./{}report_kmeansPP.txt".format(prefix), report, delimiter="\t")

def run():
    KmeansTest(loadIris(), prefix="iris_", n_clusters=3)
    KmeansTest(loadSeeds(), prefix="seeds_", n_clusters=3)

if __name__ == "__main__":
    run()
# python kmeans_test.py

c'est tout

c'est tout

Recommended Posts

J'ai fait un processus d'initialisation KMEANS rapide avec une meilleure précision que aléatoire
J'ai fait une commande lo qui est plus utile que ls
J'ai fait en sorte que l'IA patrouille sur le net et créé un service Web de classement des gadgets mis à jour une fois par semaine
[Python] J'ai créé un LINE Bot qui détecte les visages et effectue le traitement de la mosaïque.
J'ai fait un graphique de nombres aléatoires avec Numpy
J'ai créé et publié une image Docker qui lit RSS et tweete automatiquement régulièrement.
Création d'une application Web qui mappe les informations sur les événements informatiques avec Vue et Flask
[Petite histoire] En Python, i = i + 1 est légèrement plus rapide que i + = 1.
J'ai effectué un processus de connexion / déconnexion en utilisant Python's Bottle.
J'ai créé une VM qui exécute OpenCV pour Python
J'ai essayé de faire LINE BOT avec Python et Heroku
Création d'un toolver qui crache le système d'exploitation, Python, les modules et les versions d'outils à Markdown
J'ai créé un outil qui facilite un peu la création et l'installation d'une clé publique.