svd_mpsic/kmedoids.ipynb

9.9 KiB

Analiza skupień metodą k-medoids (PAM)

Co to jest klasteryzacja?

Analiza skupień lub klasteryzacja to zadanie polegające na grupowaniu zbioru obiektów w taki sposób, aby obiekty w tej samej grupie lub klastrze były do siebie bardziej podobne niż obiekty w innych grupach lub klastrach. Sama analiza skupień nie jest jednym konkretnym algorytmem, lecz ogólnym zadaniem do rozwiązania. Można je zrealizować za pomocą różnych algorytmów (algorytm k-średnich, algorytm k-medoid), które różnią się znacznie w rozumieniu tego, czym jest klaster i jak skutecznie je znaleźć. Popularne pojęcia klastrów obejmują grupy o małych odległościach między elementami klastra. Klastrowanie można zatem sformułować jako wieloprzedmiotowy problem optymalizacyjny. Wybór odpowiedniego algorytmu grupowania i ustawień parametrów zależy od indywidualnego zbioru danych i przeznaczenia wyników. Analiza skupień jako taka nie jest zadaniem automatycznym, lecz iteracyjnym procesem odkrywania wiedzy lub interaktywnej optymalizacji wieloprzedmiotowej, który wymaga prób i błędów. Często konieczne jest modyfikowanie wstępnego przetwarzania danych i parametrów modelu, aż do uzyskania pożądanych właściwości.

W naszym projekcie przedstawimy metodę k-medoid i porównamy ją z metodą k-średnich.

Algorytm k-medoid

  1. Inicjalizacja: wybierz k losowych punktów spośród n punktów danych jako medoidy.
  2. Przyporządkuj każdy punkt danych do najbliższego medoidu, używając dowolnych popularnych metod metryki odległości.
  3. Podczas gdy koszt maleje: Dla każdej medoidy m, dla każdego punktu danych o, który nie jest medoidą:
    i. Zamień punkty m i o, przyporządkuj każdy punkt danych do najbliższej medoidy, ponownie oblicz koszt.
    ii. Jeśli całkowity koszt jest większy niż w poprzednim kroku, cofnij zamianę.

Rozwiązanie: Implementacja algorytmu k-medoid w Pythonie. Do wykonania algorytmu k-medoidy potrzebne jest wstępne przetworzenie danych. W naszym rozwiązaniu przeprowadziliśmy wstępne przetwarzanie danych w celu zaimplementowania algorytmu k-medoid. Dodatkowo oceniliśmy jaka jest jakość naszego grupowania. Posłużyliśmy się tzw. sylwetką (ang. silhouette) $s(x_i)$ obliczaną dla każdego obiektu $x_i$. Najpierw dla $x_i$ znajduje się jego średnią odległość $a(x_i)$ od pozostałych obiektów grupy, do której został przydzielony, a następnie wybiera się minimalną wartość $b(x_i)$ spośród obliczonych odległości od $x_i$ do każdej spośród pozostałych grup osobno. Odległość $x_i$ od danej grupy oblicza się jako średnią odległość od $x_i$ do wszystkich elementów tej grupy. Obie wielkości zestawia się we wzorze:

$s(x_i) = \frac{b(x_i)-a(x_i)}{max(a(x_i),b(x_i))}$

otrzymując wartość sylwetki dla danego obiektu $x_i$. Ma ona prostą interpretację: obiekty, dla których wskaźnik jest bliski 1, zostały trafnie zgrupowane, pozostałe (o wartości ok. 0 i ujemnej) prawdopodobnie trafiły do złych grup.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import preprocessing
import random
import math
import copy
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import silhouette_samples, silhouette_score


class TrainModel:
    def __init__(self, data, k_value):
        self.data = data
        scaler = MinMaxScaler()
        self.data = scaler.fit_transform(self.data)
        self.k_value = k_value
        self.kmedoids(self.data)

    def get_random_medoids(self, data):
        points = random.sample(range(0, len(data)), self.k_value)
        medoids = []
        for i in range(self.k_value):
            medoids.append(data[i])
        return medoids

    def get_closest_medoids(self, sample_point, medoids):
        min_distance = float('inf')
        closest_medoid = None
        for i in range(len(medoids)):
            distance = self.calculateDistance(sample_point, medoids[i])
            if distance < min_distance:
                min_distance = distance
                closest_medoid = i
        return closest_medoid

    def get_clusters(self, data_points, medoids):
        clusters = [[] for _ in range(self.k_value)]
        for i in range(len(data_points)):
            x = self.get_closest_medoids(data_points[i], medoids)
            clusters[x].append(data_points[i])
        return clusters

    def calculate_cost(self, data_points, clusters, medoids):
        cost = 0
        for i in range(len(clusters)):
            for j in range(len(clusters[i])):
                cost += self.calculateDistance(medoids[i], clusters[i][j])
        return cost

    def get_non_medoids(self, data_points, medoids):
        non_medoids = []
        for sample in data_points:
            flag = False
            for m in medoids:
                if (sample == m).all():
                    flag = True
            if flag == False:
                non_medoids.append(sample)
        return non_medoids

    def get_clusters_label(self, data_points, clusters):
        labels = []
        for i in range(len(data_points)):
            labels.append(0)
        for i in range(len(clusters)):
            cluster = clusters[i]
            for j in range(len(cluster)):
                for k in range(len(data_points)):
                    if (cluster[j] == data_points[k]).all():
                        labels[k] = i
                        break
        return labels

    def kmedoids(self, data):
        medoids = self.get_random_medoids(data)
        clusters = self.get_clusters(data, medoids)
        initial_cost = self.calculate_cost(data, clusters, medoids)
        while True:
            best_medoids = medoids
            lowest_cost = initial_cost
            for i in range(len(medoids)):
                non_medoids = self.get_non_medoids(data, medoids)
                for j in range(len(non_medoids)):
                    new_medoids = medoids.copy()
                    for k in range(len(new_medoids)):
                        if (new_medoids[k] == medoids[i]).all():
                            new_medoids[k] = non_medoids[j]
                    new_clusters = self.get_clusters(data, new_medoids)
                    new_cost = self.calculate_cost(data, new_clusters, new_medoids)
                    if new_cost < lowest_cost:
                        lowest_cost = new_cost
                        best_medoids = new_medoids
            if lowest_cost < initial_cost:
                initial_cost = lowest_cost
                medoids = best_medoids
            else:
                break
        final_clusters = self.get_clusters(data, medoids)
        cluster_labels = self.get_clusters_label(data, final_clusters)
        silhouette_avg = silhouette_score(data, cluster_labels)

        print('Sylwetka (ang. Silhouette) dla algorytmu k-medoid dla k =', self.k_value, 10*'-', silhouette_avg)

    def calculateDistance(self, x, y):
        return np.linalg.norm(x-y)


dataset = pd.read_csv('iris.csv')
dataset = dataset.iloc[:,:-1]
dataset = dataset.iloc[: , 1:]
dataset = dataset.values
model = TrainModel(dataset, 2)
Sylwetka (ang. Silhouette) dla algorytmu k-medoid dla k = 2 ---------- 0.6114567207221335