svd_mpsic/kmedoids.ipynb
2022-06-15 22:16:06 +02:00

17 KiB
Raw Blame History

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)
        
        data = {'Długość kielicha': [data[i][0] for i in range(0, len(data))],
                'Szerokość kielicha': [data[i][1] for i in range(0, len(data))],
                'Długość płatka': [data[i][2] for i in range(0, len(data))],
                'Szerokość płatka': [data[i][3] for i in range(0, len(data))],
                'Wartość medoidu 0': [medoids[0] for i in range(150)],
                'Wartość medoidu 1': [medoids[1] for i in range(150)],
                'Medoid': cluster_labels}
        df = pd.DataFrame(data)
        print(df)
        df.to_csv('data.csv')
        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)
import pandas as pd

dataset = pd.read_csv('iris.csv')
dataset = dataset.iloc[:,:-1]
dataset = dataset.iloc[: , 1:]
dataset = dataset.values
model = TrainModel(dataset, 2)
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Input In [2], in <cell line: 5>()
      3 dataset = pd.read_csv('iris.csv')
      4 dataset = dataset.iloc[:,:-1]
----> 5 dataset = dataset.iloc[: , 1:]
      6 dataset = dataset.values
      7 model = TrainModel(dataset, 2)

Input In [2], in <cell line: 5>()
      3 dataset = pd.read_csv('iris.csv')
      4 dataset = dataset.iloc[:,:-1]
----> 5 dataset = dataset.iloc[: , 1:]
      6 dataset = dataset.values
      7 model = TrainModel(dataset, 2)

File /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py:747, in PyDBFrame.trace_dispatch(self, frame, event, arg)
    745 # if thread has a suspend flag, we suspend with a busy wait
    746 if info.pydev_state == STATE_SUSPEND:
--> 747     self.do_wait_suspend(thread, frame, event, arg)
    748     # No need to reset frame.f_trace to keep the same trace function.
    749     return self.trace_dispatch

File /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py:144, in PyDBFrame.do_wait_suspend(self, *args, **kwargs)
    143 def do_wait_suspend(self, *args, **kwargs):
--> 144     self._args[0].do_wait_suspend(*args, **kwargs)

File /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py:1155, in PyDB.do_wait_suspend(self, thread, frame, event, arg, send_suspend_message, is_unhandled_exception)
   1152         from_this_thread.append(frame_id)
   1154 with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, stop_reason):
-> 1155     self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread)

File /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py:1170, in PyDB._do_wait_suspend(self, thread, frame, event, arg, suspend_type, from_this_thread)
   1167             self._call_mpl_hook()
   1169         self.process_internal_commands()
-> 1170         time.sleep(0.01)
   1172 self.cancel_async_evaluation(get_current_thread_id(thread), str(id(frame)))
   1174 # process any stepping instructions

KeyboardInterrupt: