diff --git a/.gitignore b/.gitignore index 3ec3ce2..b826598 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*.pkl + .vscode/ __pycache__ .ipynb_checkpoints diff --git a/RCV1 Dataset Visualization/Self Organizing Map.ipynb b/RCV1 Dataset Visualization/Self Organizing Map.ipynb new file mode 100644 index 0000000..e179079 --- /dev/null +++ b/RCV1 Dataset Visualization/Self Organizing Map.ipynb @@ -0,0 +1,461 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "
\n", + "

In The Name Of GOD

\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# RCV1 Dataset Visualization with Self Organizing Map" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Import Libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [], + "source": [ + "import itertools\n", + "import numpy as np\n", + "import pickle as pkl\n", + "from numpy.ma.core import ceil \n", + "from numpy import linalg as LA\n", + "from joblib import Parallel, delayed, effective_n_jobs\n", + "from numpy import argmin, unravel_index, sqrt, ogrid, newaxis\n", + "from sklearn.metrics import DistanceMetric #distance calculation\n", + "from sklearn.utils import resample #resampling\n", + "from sklearn.preprocessing import MinMaxScaler, StandardScaler #normalization\n", + "from sklearn.pipeline import Pipeline #pipeline\n", + "from sklearn.model_selection import train_test_split #split data\n", + "from sklearn.metrics import accuracy_score #scoring\n", + "from sklearn.metrics import confusion_matrix\n", + "import matplotlib.pyplot as plt\n", + "from matplotlib import animation, colors\n", + "from tqdm import tqdm\n", + "import pprint" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hyper parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [], + "source": [ + "NUM_SAMPLES = 10000 # number of samples to use\n", + "NUM_NEURONS = (5 * np.sqrt(NUM_SAMPLES)) #number of neurons in the SOM rectangular grid\n", + "GRID_SIZE = (ceil(np.sqrt(NUM_NEURONS)).astype(np.int32), ceil(np.sqrt(NUM_NEURONS)).astype(np.int32)) #size of the grid\n", + "NUM_ITERS = 3000 #number of iterations to run the SOM\n", + "BETA0 = 0.5 #initial learning rate\n", + "MU = 2 # initial mu for normal distribution\n", + "SIGMA0 = 11 # initial sigma for normal distribution\n", + "N_JOBS = effective_n_jobs() #number of jobs to run in parallel\n", + "MIN_BETA = 0.05 #minimum learning rate\n", + "MIN_SIGMA = 1 #minimum sigma for normal distribution" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Self Organizing Map (SOM) Implementation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Helper Functions" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": {}, + "outputs": [], + "source": [ + "def normal(x, mu, sigma): #calculate the normal distribution\n", + " p = np.divide(1, np.sqrt(2 * np.pi * sigma**2))\n", + " return np.multiply(p, np.exp(-0.5 / sigma**2 * (x - mu)**2))\n", + "\n", + "def get_beta(epoch): #get the learning rate for the SOM\n", + " # return max(BETA0 * np.exp(-5 * np.divide(epoch, NUM_ITERS)), MIN_BETA)\n", + " return max(MIN_BETA, (BETA0 - MIN_BETA) * (1 - np.divide(epoch, NUM_ITERS)) + MIN_BETA)\n", + "\n", + "def get_sigma(epoch): #get the sigma for the normal distribution\n", + " # return max(SIGMA0 * np.exp(-5 * np.divide(epoch, NUM_ITERS)), MIN_SIGMA)\n", + " return max(MIN_SIGMA, (SIGMA0 - MIN_SIGMA) * (1 - np.divide(epoch, NUM_ITERS)) + MIN_SIGMA)\n", + "\n", + "def expand(x, shape): #expand the normal distribution to the grid size\n", + " return np.tile(x[:, :, newaxis], (1, 1, shape))\n", + "\n", + "def update_neurons(grid, best_match_idx, w, epoch): #update the neurons\n", + " x0, y0 = best_match_idx #get the coordinates of the best match\n", + " x, y = ogrid[0:GRID_SIZE[0], 0:GRID_SIZE[1]] #create a grid of coordinates\n", + " distance_to_best_idx = sqrt(np.power((x - x0), 2) + np.power((y - y0), 2)) #calculate the distance between the neurons and the best match\n", + " \n", + " sigma = get_sigma(epoch) #get the sigma for the normal distribution\n", + " # ns_values = normal(distance_to_best_idx, MU, sigma) #calculate the normal distribution\n", + " ns_values = 1 / (distance_to_best_idx + 1)\n", + " coefficient = expand(ns_values, grid.shape[-1]) #expand the normal distribution to the grid size\n", + " \n", + " # coefficient = np.tile(normal(ns_values, MU, get_sigma(epoch)), grid.shape) #calculate the coefficient for the neurons\n", + " distances = coefficient * (grid - w) #calculate the distance between the neurons and the input\n", + " \n", + " grid = grid + get_beta(epoch) * distances #update the neurons\n", + "\n", + "def find_winning_neuron(grid, x): #find the winning neuron\n", + " distances = LA.norm(grid - x, axis=-1) #calculate the distance between the neurons and the input\n", + " return unravel_index(argmin(distances), grid.shape[0:-1])\n", + "\n", + "def get_pipeline(scaler=StandardScaler()): #create a pipeline for the data\n", + " return Pipeline([\n", + " ('scaler', scaler)\n", + " ])\n", + "\n", + "def get_best_matching_units(grid, X: list): #get the best matching units for the input list\n", + " return Parallel(n_jobs=N_JOBS)(delayed(find_winning_neuron)(grid, X[i]) for i in tqdm(range(len(X)), desc='Finding BMUs'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Import Dataset" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.datasets import fetch_rcv1 #fetch the RCV1 dataset\n", + "rcv1 = fetch_rcv1() \n", + "\n", + "X, Y = rcv1.data, rcv1.target" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'X.shape: (804414, 47236)'\n", + "'Y.shape: (804414, 103)'\n", + "'num_samples: 804414'\n", + "'num_features: 47236'\n", + "'num_classes: 103'\n" + ] + } + ], + "source": [ + "pprint.pprint(f'X.shape: {X.shape}')\n", + "pprint.pprint(f'Y.shape: {Y.shape}')\n", + "pprint.pprint(f'num_samples: {X.shape[0]}')\n", + "pprint.pprint(f'num_features: {X.shape[1]}')\n", + "pprint.pprint(f'num_classes: {Y.shape[1]}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Data Preprocessing" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [], + "source": [ + "X_resampled, Y_resampled = resample(X, Y, n_samples=NUM_SAMPLES, random_state=42) #resample data\n", + "\n", + "X_train, X_test, Y_train, Y_test = train_test_split(X_resampled, Y_resampled, test_size=0.2, random_state=42) #split data into training and testing sets\n", + "\n", + "pipeline = get_pipeline()\n", + "X_train_pipelined = pipeline.fit_transform(X_train.toarray()) #scale data\n", + "X_test_pipelined = pipeline.transform(X_test.toarray()) #scale data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Self Organizing Map (SOM) Initialization" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'Grid rectangle is of width and height of (23, 23)'\n", + "'Neuron grid is of shape (23, 23, 47236)'\n" + ] + } + ], + "source": [ + "grid = np.random.rand(*GRID_SIZE, X_train_pipelined.shape[1]) #initialize the grid with random values\n", + "\n", + "pprint.pprint(f'Grid rectangle is of width and height of {GRID_SIZE}')\n", + "pprint.pprint(f'Neuron grid is of shape {grid.shape}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Training Self Organizing Map (SOM)" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Epochs: 100%|██████████| 3000/3000 [10:17<00:00, 4.86it/s]\n" + ] + } + ], + "source": [ + "for epoch in tqdm(range(NUM_ITERS), desc='Epochs', leave=True): #train the SOM\n", + " rnd_idx = np.random.randint(0, X_train_pipelined.shape[0]) #get a random index\n", + " x = X_train_pipelined[rnd_idx] #get the data\n", + " best_match_idx = find_winning_neuron(grid, x) #find the index of the neuron with the smallest distance to the input\n", + " update_neurons(grid, best_match_idx, x, epoch) #update the neurons" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Saving weights of SOM" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'SOM saved to model/som.pkl'\n" + ] + } + ], + "source": [ + "with open('model/som.pkl', 'wb') as f: #save the SOM\n", + " pkl.dump(grid, f)\n", + " pprint.pprint(f'SOM saved to model/som.pkl')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading weights of SOM" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "'Grid has been loaded from model/som.pkl'\n" + ] + } + ], + "source": [ + "with open('model/som.pkl', 'rb') as f: #load the SOM\n", + " grid = pkl.load(f)\n", + " pprint.pprint(f'Grid has been loaded from model/som.pkl')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualizing SOM" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Helper Functions" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [], + "source": [ + "dx = [0, 0, 1, -1] #neighborhood offsets\n", + "dy = [1, -1, 0, 0] #neighborhood offsets\n", + "\n", + "def calc_u_matrix(grid): #calculate the U matrix\n", + " u_matrix = np.zeros(GRID_SIZE) #initialize the U matrix\n", + " for pos in itertools.product(range(grid.shape[0]), range(grid.shape[1])):\n", + " num_neighbors = 0\n", + " for i in range(len(dx)):\n", + " x = pos[0] + dx[i]\n", + " y = pos[1] + dy[i]\n", + " if x >= 0 and x < grid.shape[0] and y >= 0 and y < grid.shape[1]:\n", + " u_matrix[pos] += LA.norm(grid[pos] - grid[x, y], axis=-1)\n", + " num_neighbors += 1\n", + " u_matrix[pos] /= num_neighbors\n", + " return u_matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Plotting U-Matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "u_matrix = calc_u_matrix(grid) #calculate the U matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "cmap=\"CMRmap\"\n", + "fontsize=12\n", + "_, ax = plt.subplots(figsize=(10, 8))\n", + "img = ax.imshow(u_matrix, cmap=cmap)\n", + "\n", + "# ticks and labels\n", + "for tick in ax.xaxis.get_major_ticks():\n", + " tick.label.set_fontsize(fontsize)\n", + "for tick in ax.yaxis.get_major_ticks():\n", + " tick.label.set_fontsize(fontsize)\n", + "ax.set_ylabel(\"SOM rows\", fontsize=fontsize)\n", + "ax.set_xlabel(\"SOM columns\", fontsize=fontsize)\n", + "\n", + "# colorbar\n", + "cbar = plt.colorbar(img, ax=ax, fraction=0.04, pad=0.04)\n", + "cbar.ax.set_ylabel(\n", + " \"Distance measure (a.u.)\", rotation=90, fontsize=fontsize, labelpad=20\n", + ")\n", + "cbar.ax.tick_params(labelsize=fontsize)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### calculating best matching units for each test data point\n", + "\n", + "the performance of getting the best matching unit is very good compared to the performance of the training part because of the **parallelization** of the algorithm.(it is about 10 times faster than the training part on my 10 core cpu machine)" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Finding BMUs: 100%|██████████| 2000/2000 [00:39<00:00, 50.41it/s]\n" + ] + } + ], + "source": [ + "bmus = get_best_matching_units(grid, X_test_pipelined) " + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.9.12", + "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.9.12" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "465bf8bf39eb6d29a2a62c1b7df28f314b583a714539b89eff15d00bb86422b4" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/RCV1 Dataset Visualization/model/README.md b/RCV1 Dataset Visualization/model/README.md new file mode 100644 index 0000000..2446e46 --- /dev/null +++ b/RCV1 Dataset Visualization/model/README.md @@ -0,0 +1,3 @@ +
+

This directory is empty because I didn't want to increase the amount of storage that the repository has to an unreasonable amount.

+