{ "cells": [ { "cell_type": "markdown", "id": "c3edcae5", "metadata": { "editable": true }, "source": [ "" ] }, { "cell_type": "markdown", "id": "4102577e", "metadata": { "editable": true }, "source": [ "# Clustering and Unsupervised Learning\n", "\n", "In general terms cluster analysis, or clustering, is the task of grouping a\n", "data-set into different distinct categories based on some measure of equality of\n", "the data. This measure is often referred to as a **metric** or **similarity\n", "measure** in the literature (note: sometimes we deal with a **dissimilarity\n", "measure** instead). Usually, these metrics are formulated as some kind of\n", "distance function between points in a high-dimensional space.\n", "\n", "The simplest, and also the most\n", "common is the **Euclidean distance**.\n", "\n", "The simplest of all clustering algorithms is the **k-means algorithm**\n", ", sometimes also referred to as *Lloyds algorithm*. It is the simplest and also\n", "the most common. From its simplicity it obtains both strengths and weaknesses.\n", "These will be discussed in more detail later. The $k$-means algorithm is a\n", "**centroid based** clustering algorithm.\n", "\n", "Assume, we are given $n$ data points and we wish to split the data into $K < n$\n", "different categories, or clusters. We label each cluster by an integer" ] }, { "cell_type": "markdown", "id": "0deb3255", "metadata": { "editable": true }, "source": [ "$$\n", "k\\in\\{1, \\cdots, K \\}.\n", "$$" ] }, { "cell_type": "markdown", "id": "cfd8fe00", "metadata": { "editable": true }, "source": [ "In the basic k-means algorithm each point is assigned to only\n", "one cluster $k$, and these assignments are *non-injective* i.e. many-to-one. We\n", "can think of these mappings as an encoder $k = C(i)$, which assigns the $i$-th\n", "data-point $\\bf x_i$ to the $k$-th cluster.\n", "\n", "$k$-means algorithm in words:\n", "1. We start with guesses / random initializations of our $k$ cluster centers/centroids\n", "\n", "2. For each centroid the points that are most similar are identified\n", "\n", "3. Then we move / replace each centroid with a coordinate average of all the points that were assigned to that centroid.\n", "\n", "4. Iterate 2-3 until the centroids no longer move (to some tolerance)\n", "\n", "We assume we have $n$ data-points" ] }, { "cell_type": "markdown", "id": "a29b7459", "metadata": { "editable": true }, "source": [ "\n", "
\n", "\n", "$$\n", "\\begin{equation}\\label{eq:kmeanspoints} \\tag{1}\n", " \\boldsymbol{x_i} = \\{x_{i, 1}, \\cdots, x_{i, p}\\}\\in\\mathbb{R}^p.\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "98f9e37b", "metadata": { "editable": true }, "source": [ "which we wish to group into $K < n$ clusters. For our dissimilarity measure we\n", "use the *squared Euclidean distance*" ] }, { "cell_type": "markdown", "id": "d3c32572", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\\label{eq:squaredeuclidean} \\tag{2}\n", " d(\\boldsymbol{x_i}, \\boldsymbol{x_i'}) = \\sum_{j=1}^p(x_{ij} - x_{i'j})^2\n", " = ||\\boldsymbol{x_i} - \\boldsymbol{x_{i'}}||^2\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "29d24648", "metadata": { "editable": true }, "source": [ "We define the so called *within-cluster point scatter* which gives us a\n", "measure of how close each data point assigned to the same cluster tends to be to\n", "the all the others." ] }, { "cell_type": "markdown", "id": "fce5c797", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\\label{eq:withincluster} \\tag{3}\n", " W(C) = \\frac{1}{2}\\sum_{k=1}^K\\sum_{C(i)=k}\n", " \\sum_{C(i')=k}d(\\boldsymbol{x_i}, \\boldsymbol{x_{i'}}) =\n", " \\sum_{k=1}^KN_k\\sum_{C(i)=k}||\\boldsymbol{x_i} - \\boldsymbol{\\overline{x_k}}||^2\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "674a26b7", "metadata": { "editable": true }, "source": [ "where $\\boldsymbol{\\overline{x_k}}$ is the mean vector associated with the $k$-th\n", "cluster, and $N_k = \\sum_{i=1}^nI(C(i) = k)$, where the $I()$ notation is\n", "similar to the Kronecker delta (*Commonly used in statistics, it just means that\n", "when $i = k$ we have the encoder $C(i)$*). In other words, the within-cluster\n", "scatter measures the compactness of each cluster with respect to the data points\n", "assigned to each cluster. This is the quantity that the $k$-means algorithm aims\n", "to minimize. We refer to this quantity $W(C)$ as the within cluster scatter\n", "because of its relation to the *total scatter*.\n", "\n", "We have" ] }, { "cell_type": "markdown", "id": "f200e7ff", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\\label{eq:totalscatter} \\tag{4}\n", " T = W(C) + B(C) = \\frac{1}{2}\\sum_{i=1}^n\n", " \\sum_{i'=1}^nd(\\boldsymbol{x_i}, \\boldsymbol{x_{i'}})\n", " = \\frac{1}{2}\\sum_{k=1}^K\\sum_{C(i)=k}\n", " \\Big(\\sum_{C(i') = k}d(\\boldsymbol{x_i}, \\boldsymbol{x_{i'}})\n", " + \\sum_{C(i')\\neq k}d(\\boldsymbol{x_i}, \\boldsymbol{x_{i'}})\\Big).\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "5471a94d", "metadata": { "editable": true }, "source": [ "This is a quantity that is conserved throughout the $k$-means algorithm. It can\n", "be thought of as the total amount of information in the data, and it is composed\n", "of the aforementioned within-cluster scatter and the *between-cluster scatter*\n", "$B(C)$. In methods such as principle component analysis the total scatter is not\n", "conserved.\n", "\n", "Given a cluster mean $\\boldsymbol{m_k}$ we define the **total cluster variance**" ] }, { "cell_type": "markdown", "id": "299a99ce", "metadata": { "editable": true }, "source": [ "\n", "\n", "\n", "$$\n", "\\begin{equation}\\label{eq:totalclustervariance} \\tag{5}\n", " \\min_{C, \\{\\boldsymbol{m_k}\\}_1^K}\\sum_{k=1}^KN_k\\sum||\\boldsymbol{x_i} - \\boldsymbol{m_k}||^2\n", "\\end{equation}\n", "$$" ] }, { "cell_type": "markdown", "id": "bdfa54ee", "metadata": { "editable": true }, "source": [ "Now we have all the pieces necessary to formally revisit the $k$-means algorithm.\n", "\n", "The $k$-means clustering algorithm goes as follows \n", "\n", "1. For a given cluster assignment $C$, and $k$ cluster means $\\left\\{m_1, \\cdots, m_k\\right\\}$. We minimize the total cluster variance with respect to the cluster means $\\{m_k\\}$ yielding the means of the currently assigned clusters.\n", "\n", "2. Given a current set of $k$ means $\\{m_k\\}$ the total cluster variance is minimized by assigning each observation to the closest (current) cluster mean. That is $$C(i) = \\underset{1\\leq k\\leq K}{\\mathrm{argmin}} ||\\boldsymbol{x_i} - \\boldsymbol{m_k}||^2$$\n", "\n", "3. Steps 1 and 2 are repeated until the assignments do not change." ] }, { "cell_type": "markdown", "id": "f5def86c", "metadata": { "editable": true }, "source": [ "## Codes and Approaches\n", "\n", "1. Before we start we specify a number $k$ which is the number of clusters we want to try to separate our data into.\n", "\n", "2. We initially choose $k$ random data points in our data as our initial centroids, *or means* (this is where the name comes from).\n", "\n", "3. Assign each data point to their closest centroid, based on the squared Euclidean distance.\n", "\n", "4. For each of the $k$ cluster we update the centroid by calculating new mean values for all the data points in the cluster.\n", "\n", "5. Iteratively minimize the within cluster scatter by performing steps (3, 4) until the new assignments stop changing (can be to some tolerance) or until a maximum number of iterations have passed.\n", "\n", "Let us now program the most basic version of the algorithm using nothing but\n", "Python with numpy arrays. This code is kept intentionally simple to gradually\n", "progress our understanding. There is no vectorization of any kind, and even most\n", "helper functions are not utilized.\n", "\n", "We need first a dataset to do our cluster analysis on. In our case\n", "this is a plain *vanilla* data set using random numbers using a\n", "Gaussian distribution." ] }, { "cell_type": "code", "execution_count": 1, "id": "b0260188", "metadata": { "collapsed": false, "editable": true }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/Users/mhjensen/miniforge3/envs/myenv/lib/python3.9/site-packages/jax/_src/lib/__init__.py:32: UserWarning: JAX on Mac ARM machines is experimental and minimally tested. Please see https://github.com/google/jax/issues/5501 in the event of problems.\n", " warnings.warn(\"JAX on Mac ARM machines is experimental and minimally tested. \"\n" ] }, { "ename": "AttributeError", "evalue": "module 'jaxlib.pocketfft' has no attribute 'pocketfft'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "Input \u001b[0;32mIn [1]\u001b[0m, in \u001b[0;36m