{ "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": 1, "id": "73cffc81", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " Długość kielicha Szerokość kielicha Długość płatka Szerokość płatka \\\n", "0 0.222222 0.625000 0.067797 0.041667 \n", "1 0.166667 0.416667 0.067797 0.041667 \n", "2 0.111111 0.500000 0.050847 0.041667 \n", "3 0.083333 0.458333 0.084746 0.041667 \n", "4 0.194444 0.666667 0.067797 0.041667 \n", ".. ... ... ... ... \n", "145 0.666667 0.416667 0.711864 0.916667 \n", "146 0.555556 0.208333 0.677966 0.750000 \n", "147 0.611111 0.416667 0.711864 0.791667 \n", "148 0.527778 0.583333 0.745763 0.916667 \n", "149 0.444444 0.416667 0.694915 0.708333 \n", "\n", " Wartość medoidu 0 \\\n", "0 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "1 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "2 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "3 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "4 [0.19444444444444442, 0.5833333333333333, 0.08... \n", ".. ... \n", "145 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "146 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "147 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "148 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "149 [0.19444444444444442, 0.5833333333333333, 0.08... \n", "\n", " Wartość medoidu 1 Medoid \n", "0 [0.5277777777777779, 0.33333333333333326, 0.64... 0 \n", "1 [0.5277777777777779, 0.33333333333333326, 0.64... 0 \n", "2 [0.5277777777777779, 0.33333333333333326, 0.64... 0 \n", "3 [0.5277777777777779, 0.33333333333333326, 0.64... 0 \n", "4 [0.5277777777777779, 0.33333333333333326, 0.64... 0 \n", ".. ... ... \n", "145 [0.5277777777777779, 0.33333333333333326, 0.64... 1 \n", "146 [0.5277777777777779, 0.33333333333333326, 0.64... 1 \n", "147 [0.5277777777777779, 0.33333333333333326, 0.64... 1 \n", "148 [0.5277777777777779, 0.33333333333333326, 0.64... 1 \n", "149 [0.5277777777777779, 0.33333333333333326, 0.64... 1 \n", "\n", "[150 rows x 7 columns]\n", "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", " data = {'Długość kielicha': [data[i][0] for i in range(0, len(data))],\n", " 'Szerokość kielicha': [data[i][1] for i in range(0, len(data))],\n", " 'Długość płatka': [data[i][2] for i in range(0, len(data))],\n", " 'Szerokość płatka': [data[i][3] for i in range(0, len(data))],\n", " 'Wartość medoidu 0': [medoids[0] for i in range(150)],\n", " 'Wartość medoidu 1': [medoids[1] for i in range(150)],\n", " 'Medoid': cluster_labels}\n", " df = pd.DataFrame(data)\n", " print(df)\n", " df.to_csv('data.csv')\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 }