{ "cells": [ { "cell_type": "markdown", "id": "e1e5a2b7", "metadata": {}, "source": [ "# Analiza skupień metodą k-medoids (PAM) " ] }, { "cell_type": "markdown", "id": "80d5deaf", "metadata": {}, "source": [ "### Co to jest klasteryzacja? " ] }, { "cell_type": "markdown", "id": "4040df16", "metadata": {}, "source": [ "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." ] }, { "cell_type": "markdown", "id": "3dc57d21", "metadata": {}, "source": [ "W naszym projekcie przedstawimy metodę k-medoid i porównamy ją z metodą k-średnich." ] }, { "cell_type": "markdown", "id": "f7c684c9", "metadata": {}, "source": [ "### Algorytm k-medoid" ] }, { "cell_type": "markdown", "id": "af45d7c7", "metadata": {}, "source": [ "1. Inicjalizacja: wybierz k losowych punktów spośród n punktów danych jako medoidy.\n", "2. Przyporządkuj każdy punkt danych do najbliższego medoidu, używając dowolnych popularnych metod metryki odległości.\n", "3. Podczas gdy koszt maleje:\n", " Dla każdej medoidy m, dla każdego punktu danych o, który nie jest medoidą: \n", " i. Zamień punkty m i o, przyporządkuj każdy punkt danych do najbliższej medoidy, ponownie oblicz koszt. \n", " ii. Jeśli całkowity koszt jest większy niż w poprzednim kroku, cofnij zamianę." ] }, { "cell_type": "markdown", "id": "d8f6dd1e", "metadata": {}, "source": [ "**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: " ] }, { "cell_type": "markdown", "id": "ab825d40", "metadata": {}, "source": [ "

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

\n" ] }, { "cell_type": "markdown", "id": "f6d344cf", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": 7, "id": "73cffc81", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Sylwetka (ang. Silhouette) dla algorytmu k-medoid dla k = 2 ---------- 0.6114567207221335\n" ] } ], "source": [ "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from sklearn import preprocessing\n", "import random\n", "import math\n", "import copy\n", "from sklearn.preprocessing import MinMaxScaler\n", "from sklearn.metrics import silhouette_samples, silhouette_score\n", "\n", "\n", "class TrainModel:\n", " def __init__(self, data, k_value):\n", " self.data = data\n", " scaler = MinMaxScaler()\n", " self.data = scaler.fit_transform(self.data)\n", " self.k_value = k_value\n", " self.kmedoids(self.data)\n", "\n", " def get_random_medoids(self, data):\n", " points = random.sample(range(0, len(data)), self.k_value)\n", " medoids = []\n", " for i in range(self.k_value):\n", " medoids.append(data[i])\n", " return medoids\n", "\n", " def get_closest_medoids(self, sample_point, medoids):\n", " min_distance = float('inf')\n", " closest_medoid = None\n", " for i in range(len(medoids)):\n", " distance = self.calculateDistance(sample_point, medoids[i])\n", " if distance < min_distance:\n", " min_distance = distance\n", " closest_medoid = i\n", " return closest_medoid\n", "\n", " def get_clusters(self, data_points, medoids):\n", " clusters = [[] for _ in range(self.k_value)]\n", " for i in range(len(data_points)):\n", " x = self.get_closest_medoids(data_points[i], medoids)\n", " clusters[x].append(data_points[i])\n", " return clusters\n", "\n", " def calculate_cost(self, data_points, clusters, medoids):\n", " cost = 0\n", " for i in range(len(clusters)):\n", " for j in range(len(clusters[i])):\n", " cost += self.calculateDistance(medoids[i], clusters[i][j])\n", " return cost\n", "\n", " def get_non_medoids(self, data_points, medoids):\n", " non_medoids = []\n", " for sample in data_points:\n", " flag = False\n", " for m in medoids:\n", " if (sample == m).all():\n", " flag = True\n", " if flag == False:\n", " non_medoids.append(sample)\n", " return non_medoids\n", "\n", " def get_clusters_label(self, data_points, clusters):\n", " labels = []\n", " for i in range(len(data_points)):\n", " labels.append(0)\n", " for i in range(len(clusters)):\n", " cluster = clusters[i]\n", " for j in range(len(cluster)):\n", " for k in range(len(data_points)):\n", " if (cluster[j] == data_points[k]).all():\n", " labels[k] = i\n", " break\n", " return labels\n", "\n", " def kmedoids(self, data):\n", " medoids = self.get_random_medoids(data)\n", " clusters = self.get_clusters(data, medoids)\n", " initial_cost = self.calculate_cost(data, clusters, medoids)\n", " while True:\n", " best_medoids = medoids\n", " lowest_cost = initial_cost\n", " for i in range(len(medoids)):\n", " non_medoids = self.get_non_medoids(data, medoids)\n", " for j in range(len(non_medoids)):\n", " new_medoids = medoids.copy()\n", " for k in range(len(new_medoids)):\n", " if (new_medoids[k] == medoids[i]).all():\n", " new_medoids[k] = non_medoids[j]\n", " new_clusters = self.get_clusters(data, new_medoids)\n", " new_cost = self.calculate_cost(data, new_clusters, new_medoids)\n", " if new_cost < lowest_cost:\n", " lowest_cost = new_cost\n", " best_medoids = new_medoids\n", " if lowest_cost < initial_cost:\n", " initial_cost = lowest_cost\n", " medoids = best_medoids\n", " else:\n", " break\n", " final_clusters = self.get_clusters(data, medoids)\n", " cluster_labels = self.get_clusters_label(data, final_clusters)\n", " silhouette_avg = silhouette_score(data, cluster_labels)\n", "\n", " print('Sylwetka (ang. Silhouette) dla algorytmu k-medoid dla k =', self.k_value, 10*'-', silhouette_avg)\n", "\n", " def calculateDistance(self, x, y):\n", " return np.linalg.norm(x-y)\n", "\n", "\n", "dataset = pd.read_csv('iris.csv')\n", "dataset = dataset.iloc[:,:-1]\n", "dataset = dataset.iloc[: , 1:]\n", "dataset = dataset.values\n", "model = TrainModel(dataset, 2)" ] }, { "cell_type": "code", "execution_count": null, "id": "3a8a592f", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.13" } }, "nbformat": 4, "nbformat_minor": 5 }