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

311 lines
17 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"id": "e1e5a2b7",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"# Analiza skupień metodą k-medoids (PAM) "
]
},
{
"cell_type": "markdown",
"id": "80d5deaf",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Co to jest klasteryzacja? "
]
},
{
"cell_type": "markdown",
"id": "4040df16",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"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": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"W naszym projekcie przedstawimy metodę k-medoid i porównamy ją z metodą k-średnich."
]
},
{
"cell_type": "markdown",
"id": "f7c684c9",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"### Algorytm k-medoid"
]
},
{
"cell_type": "markdown",
"id": "af45d7c7",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"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": {
"pycharm": {
"name": "#%% md\n"
}
},
"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": {
"pycharm": {
"name": "#%% md\n"
}
},
"source": [
"<h1><center>$s(x_i) = \\frac{b(x_i)-a(x_i)}{max(a(x_i),b(x_i))}$</center></h1>\n"
]
},
{
"cell_type": "markdown",
"id": "f6d344cf",
"metadata": {
"pycharm": {
"name": "#%% md\n"
}
},
"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": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [],
"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": 2,
"id": "3a8a592f",
"metadata": {
"pycharm": {
"name": "#%%\n"
}
},
"outputs": [
{
"ename": "KeyboardInterrupt",
"evalue": "",
"output_type": "error",
"traceback": [
"\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
"\u001B[0;31mKeyboardInterrupt\u001B[0m Traceback (most recent call last)",
"Input \u001B[0;32mIn [2]\u001B[0m, in \u001B[0;36m<cell line: 5>\u001B[0;34m()\u001B[0m\n\u001B[1;32m 3\u001B[0m dataset \u001B[38;5;241m=\u001B[39m pd\u001B[38;5;241m.\u001B[39mread_csv(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124miris.csv\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[1;32m 4\u001B[0m dataset \u001B[38;5;241m=\u001B[39m dataset\u001B[38;5;241m.\u001B[39miloc[:,:\u001B[38;5;241m-\u001B[39m\u001B[38;5;241m1\u001B[39m]\n\u001B[0;32m----> 5\u001B[0m dataset \u001B[38;5;241m=\u001B[39m \u001B[43mdataset\u001B[49m\u001B[38;5;241m.\u001B[39miloc[: , \u001B[38;5;241m1\u001B[39m:]\n\u001B[1;32m 6\u001B[0m dataset \u001B[38;5;241m=\u001B[39m dataset\u001B[38;5;241m.\u001B[39mvalues\n\u001B[1;32m 7\u001B[0m model \u001B[38;5;241m=\u001B[39m TrainModel(dataset, \u001B[38;5;241m2\u001B[39m)\n",
"Input \u001B[0;32mIn [2]\u001B[0m, in \u001B[0;36m<cell line: 5>\u001B[0;34m()\u001B[0m\n\u001B[1;32m 3\u001B[0m dataset \u001B[38;5;241m=\u001B[39m pd\u001B[38;5;241m.\u001B[39mread_csv(\u001B[38;5;124m'\u001B[39m\u001B[38;5;124miris.csv\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[1;32m 4\u001B[0m dataset \u001B[38;5;241m=\u001B[39m dataset\u001B[38;5;241m.\u001B[39miloc[:,:\u001B[38;5;241m-\u001B[39m\u001B[38;5;241m1\u001B[39m]\n\u001B[0;32m----> 5\u001B[0m dataset \u001B[38;5;241m=\u001B[39m \u001B[43mdataset\u001B[49m\u001B[38;5;241m.\u001B[39miloc[: , \u001B[38;5;241m1\u001B[39m:]\n\u001B[1;32m 6\u001B[0m dataset \u001B[38;5;241m=\u001B[39m dataset\u001B[38;5;241m.\u001B[39mvalues\n\u001B[1;32m 7\u001B[0m model \u001B[38;5;241m=\u001B[39m TrainModel(dataset, \u001B[38;5;241m2\u001B[39m)\n",
"File \u001B[0;32m/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py:747\u001B[0m, in \u001B[0;36mPyDBFrame.trace_dispatch\u001B[0;34m(self, frame, event, arg)\u001B[0m\n\u001B[1;32m 745\u001B[0m \u001B[38;5;66;03m# if thread has a suspend flag, we suspend with a busy wait\u001B[39;00m\n\u001B[1;32m 746\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m info\u001B[38;5;241m.\u001B[39mpydev_state \u001B[38;5;241m==\u001B[39m STATE_SUSPEND:\n\u001B[0;32m--> 747\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mdo_wait_suspend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mthread\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mframe\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mevent\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43marg\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 748\u001B[0m \u001B[38;5;66;03m# No need to reset frame.f_trace to keep the same trace function.\u001B[39;00m\n\u001B[1;32m 749\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mtrace_dispatch\n",
"File \u001B[0;32m/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydevd_bundle/pydevd_frame.py:144\u001B[0m, in \u001B[0;36mPyDBFrame.do_wait_suspend\u001B[0;34m(self, *args, **kwargs)\u001B[0m\n\u001B[1;32m 143\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mdo_wait_suspend\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[0;32m--> 144\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_args\u001B[49m\u001B[43m[\u001B[49m\u001B[38;5;241;43m0\u001B[39;49m\u001B[43m]\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mdo_wait_suspend\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
"File \u001B[0;32m/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py:1155\u001B[0m, in \u001B[0;36mPyDB.do_wait_suspend\u001B[0;34m(self, thread, frame, event, arg, send_suspend_message, is_unhandled_exception)\u001B[0m\n\u001B[1;32m 1152\u001B[0m from_this_thread\u001B[38;5;241m.\u001B[39mappend(frame_id)\n\u001B[1;32m 1154\u001B[0m \u001B[38;5;28;01mwith\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_threads_suspended_single_notification\u001B[38;5;241m.\u001B[39mnotify_thread_suspended(thread_id, stop_reason):\n\u001B[0;32m-> 1155\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_do_wait_suspend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mthread\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mframe\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mevent\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43marg\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43msuspend_type\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mfrom_this_thread\u001B[49m\u001B[43m)\u001B[49m\n",
"File \u001B[0;32m/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py:1170\u001B[0m, in \u001B[0;36mPyDB._do_wait_suspend\u001B[0;34m(self, thread, frame, event, arg, suspend_type, from_this_thread)\u001B[0m\n\u001B[1;32m 1167\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_call_mpl_hook()\n\u001B[1;32m 1169\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mprocess_internal_commands()\n\u001B[0;32m-> 1170\u001B[0m \u001B[43mtime\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msleep\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m0.01\u001B[39;49m\u001B[43m)\u001B[49m\n\u001B[1;32m 1172\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mcancel_async_evaluation(get_current_thread_id(thread), \u001B[38;5;28mstr\u001B[39m(\u001B[38;5;28mid\u001B[39m(frame)))\n\u001B[1;32m 1174\u001B[0m \u001B[38;5;66;03m# process any stepping instructions\u001B[39;00m\n",
"\u001B[0;31mKeyboardInterrupt\u001B[0m: "
]
}
],
"source": [
"import pandas as pd\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)"
]
}
],
"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
}