{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "slide"
    },
    "tags": []
   },
   "source": [
    "# Lecture 18: Dimensionality reduction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "skip"
    },
    "tags": []
   },
   "source": [
    "![](https://www.tensorflow.org/images/colab_logo_32px.png)\n",
    "[Run in colab](https://colab.research.google.com/drive/19J80Hg1ZLxLrpHWbgxFZoyPfql_AdeGN)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:48.075087Z",
     "iopub.status.busy": "2025-02-27T23:21:48.074841Z",
     "iopub.status.idle": "2025-02-27T23:21:48.081076Z",
     "shell.execute_reply": "2025-02-27T23:21:48.080546Z"
    },
    "slideshow": {
     "slide_type": "skip"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Last executed: 2025-02-27 23:21:48\n"
     ]
    }
   ],
   "source": [
    "import datetime\n",
    "now = datetime.datetime.now()\n",
    "print(\"Last executed: \" + now.strftime(\"%Y-%m-%d %H:%M:%S\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "slide"
    },
    "tags": []
   },
   "source": [
    "## Overview of dimensionality reduction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "* Many problems have thousands or millions of features.\n",
    "* Want to reduce the number of dimensions, i.e. features.\n",
    "* Dimensionality reduction is (typically) lossy."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Disadvantages\n",
    "\n",
    "* It may (or may not) speed up training but (almost certainly) can degrade resulting performance.\n",
    "* Also makes pipelines more complex.\n",
    "\n",
    "$\\Rightarrow$ Should always try using original data before considering dimensionality reduction."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Advantages\n",
    "\n",
    "* Can make problems possible, which otherwise would have been intractible.\n",
    "* Very useful for visualization (2D, 3D representations are more intuitive)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Curse of Dimensionality\n",
    "\n",
    "Many things behave differently in high dimensional space.\n",
    "\n",
    "Our intuition is built from our experience of the 3D world and often fails in higher dimensions."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "For example:\n",
    "- Pick a random point in a unit square (a 1 × 1 square), it will have only about a 0.4% chance of being located less than 0.001 from a border (in other words, it is very unlikely that a random point will be “extreme” along any dimension).\n",
    "- But in a 10,000-dimensional unit hypercube (a 1 × 1 × ⋯ × 1 cube, with ten thousand 1s), this probability is greater than 99.999999%. Most points in a high-dimensional hypercube are very close to the border."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Two main approaches for dimensionality reduction\n",
    "\n",
    "1. Projection\n",
    "2. Manifold learning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "#### Projection \n",
    "\n",
    "- In most real-world problems, training instances are not spread out uniformly across all dimensions. \n",
    "- Many features are almost constant, while others are highly correlated. \n",
    "- As a result, all training instances actually lie within (or close to) a much lower-dimensional subspace"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "For example, consider the following 3D data-set lying close to a 2D subspace.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/astro-informatics/course_mlbd_images/master/Lecture18_Images/Projection.png\" alt=\"data-layout\" width=\"700\" style=\"display:block; margin:auto\"/>\n",
    "\n",
    "[Source: Geron]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "Data can be projected to a 2D plane without losing too much information.  \n",
    "\n",
    "We've reduced the dimension of the data from 3 to 2.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/astro-informatics/course_mlbd_images/master/Lecture18_Images/projection_2d.png\" alt=\"data-layout\" width=\"700\" style=\"display:block; margin:auto\"/>\n",
    "\n",
    "[Source: Geron]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "#### Manifold learning"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "However, projection is not always the best approach for dimensionality reduction.  \n",
    "\n",
    "Some data-sets may live of spaces (manifolds) that twist and turn.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/astro-informatics/course_mlbd_images/master/Lecture18_Images/swiss_roll.png\" alt=\"data-layout\" width=\"500\" style=\"display:block; margin:auto\"/>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "##### Projection versus unrolling\n",
    "\n",
    "Projecting would squash layers of the swiss roll (left plot), whereas better to \"unroll\" the swiss roll (right plot).\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/astro-informatics/course_mlbd_images/master/Lecture18_Images/swiss_roll_unrolled.png\" alt=\"data-layout\" width=\"800\" style=\"display:block; margin:auto\"/>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "##### Manifold hypothesis\n",
    "\n",
    "Many dimensionality reduction algorithms work by modeling the manifold on which the training instances lie; this is called *Manifold Learning*. \n",
    "\n",
    "It relies on the manifold hypothesis.\n",
    "\n",
    "> Manifold hypothesis: most real-world high-dimensional data-sets lie close to a much lower dimensional manifold. This assumption is very often empirically observed.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "slide"
    },
    "tags": []
   },
   "source": [
    "## Principal component analysis (PCA) \n",
    "\n",
    "Most popular dimensionality reduction algorithm.\n",
    "\n",
    "1. Find hyperplane that lies closest to the data.\n",
    "2. Projects data onto it.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Principal components"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Choose directions in the data that preserve the maximum variability.\n",
    "\n",
    "<img src=\"https://raw.githubusercontent.com/astro-informatics/course_mlbd_images/master/Lecture18_Images/pca_variance.png\" alt=\"data-layout\" width=\"800\" style=\"display:block; margin:auto\"/>\n",
    "\n",
    "[Source: Geron]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "PCA finds the direction that accounts for the largest amount of *variance* in the data-set.\n",
    "\n",
    "It then finds the next direction, orthogonal to the first, that accounts for largest amount of remaining variance.\n",
    "\n",
    "Process repeats, up until the full dimension of the data.  But typically the majority of variance in the data is captured in many fewer dimensions than the maximum number.\n",
    "\n",
    "Each axis vector is called a principal component."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "Principal components found by Singular Value Decomposition (SVD), a matrix factorization technique, of the feature matrix $X$.\n",
    "\n",
    "The principal components are the eigenvectors of the covariance matrix of the data $X^\\text{T} X$\n",
    "\n",
    "SVD of $X$ is given by:\n",
    "\n",
    "$$X=U \\Sigma V^T,$$\n",
    " \n",
    "where the columns of $V$ give the principal components (which are normalised and orthogonal).\n",
    "\n",
    "See the [tutorial here](https://arxiv.org/abs/1404.1100) for further details about PCA."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "**Note: PCA assumes data is centered around origin. Scikit-Learn PCA will adjust data for you if needed.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "#### Example\n",
    "\n",
    "Generate example data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:48.115817Z",
     "iopub.status.busy": "2025-02-27T23:21:48.115633Z",
     "iopub.status.idle": "2025-02-27T23:21:48.339410Z",
     "shell.execute_reply": "2025-02-27T23:21:48.338849Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from scipy.spatial.transform import Rotation\n",
    "\n",
    "m = 60\n",
    "X = np.zeros((m, 3))  # initialize 3D dataset\n",
    "np.random.seed(42)\n",
    "angles = (np.random.rand(m) ** 3 + 0.5) * 2 * np.pi  # uneven distribution\n",
    "X[:, 0], X[:, 1] = np.cos(angles), np.sin(angles) * 0.5  # oval\n",
    "X += 0.28 * np.random.randn(m, 3)  # add more noise\n",
    "X = Rotation.from_rotvec([np.pi / 29, -np.pi / 20, np.pi / 4]).apply(X)\n",
    "X += [0.2, 0, 0.2]  # shift a bit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:48.342580Z",
     "iopub.status.busy": "2025-02-27T23:21:48.341611Z",
     "iopub.status.idle": "2025-02-27T23:21:49.227869Z",
     "shell.execute_reply": "2025-02-27T23:21:49.227246Z"
    },
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "from sklearn.decomposition import PCA\n",
    "\n",
    "pca = PCA(n_components=2)\n",
    "X2D = pca.fit_transform(X)  # dataset reduced to 2D\n",
    "X3D_inv = pca.inverse_transform(X2D)  # 3D position of the projected samples\n",
    "X_centered = X - X.mean(axis=0)\n",
    "U, s, Vt = np.linalg.svd(X_centered)\n",
    "\n",
    "axes = [-1.4, 1.4, -1.4, 1.4, -1.1, 1.1]\n",
    "x1, x2 = np.meshgrid(np.linspace(axes[0], axes[1], 10),\n",
    "                     np.linspace(axes[2], axes[3], 10))\n",
    "w1, w2 = np.linalg.solve(Vt[:2, :2], Vt[:2, 2])  # projection plane coefs\n",
    "z = w1 * (x1 - pca.mean_[0]) + w2 * (x2 - pca.mean_[1]) - pca.mean_[2]  # plane\n",
    "X3D_above = X[X[:, 2] >= X3D_inv[:, 2]]  # samples above plane\n",
    "X3D_below = X[X[:, 2] < X3D_inv[:, 2]]  # samples below plane"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.230095Z",
     "iopub.status.busy": "2025-02-27T23:21:49.229790Z",
     "iopub.status.idle": "2025-02-27T23:21:49.520446Z",
     "shell.execute_reply": "2025-02-27T23:21:49.519862Z"
    },
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 172240 characters]",
      "text/plain": [
       "<Figure size 900x900 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(9, 9))\n",
    "ax = fig.add_subplot(111, projection=\"3d\")\n",
    "\n",
    "# plot samples and projection lines below plane first\n",
    "ax.plot(X3D_below[:, 0], X3D_below[:, 1], X3D_below[:, 2], \"ro\", alpha=0.3)\n",
    "for i in range(m):\n",
    "    if X[i, 2] < X3D_inv[i, 2]:\n",
    "        ax.plot([X[i][0], X3D_inv[i][0]],\n",
    "                [X[i][1], X3D_inv[i][1]],\n",
    "                [X[i][2], X3D_inv[i][2]], \":\", color=\"#F88\")\n",
    "\n",
    "ax.plot_surface(x1, x2, z, alpha=0.1, color=\"b\")  # projection plane\n",
    "ax.plot(X3D_inv[:, 0], X3D_inv[:, 1], X3D_inv[:, 2], \"b+\")  # projected samples\n",
    "ax.plot(X3D_inv[:, 0], X3D_inv[:, 1], X3D_inv[:, 2], \"b.\")\n",
    "\n",
    "# now plot projection lines and samples above plane\n",
    "for i in range(m):\n",
    "    if X[i, 2] >= X3D_inv[i, 2]:\n",
    "        ax.plot([X[i][0], X3D_inv[i][0]],\n",
    "                [X[i][1], X3D_inv[i][1]],\n",
    "                [X[i][2], X3D_inv[i][2]], \"r--\")\n",
    "\n",
    "ax.plot(X3D_above[:, 0], X3D_above[:, 1], X3D_above[:, 2], \"ro\")\n",
    "\n",
    "def set_xyz_axes(ax, axes):\n",
    "    ax.xaxis.set_rotate_label(False)\n",
    "    ax.yaxis.set_rotate_label(False)\n",
    "    ax.zaxis.set_rotate_label(False)\n",
    "    ax.set_xlabel(\"$x_1$\", labelpad=8, rotation=0)\n",
    "    ax.set_ylabel(\"$x_2$\", labelpad=8, rotation=0)\n",
    "    ax.set_zlabel(\"$x_3$\", labelpad=8, rotation=0)\n",
    "    ax.set_xlim(axes[0:2])\n",
    "    ax.set_ylim(axes[2:4])\n",
    "    ax.set_zlim(axes[4:6])\n",
    "\n",
    "set_xyz_axes(ax, axes)\n",
    "ax.set_zticks([-1, -0.5, 0, 0.5, 1]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "Compute SVD"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.523152Z",
     "iopub.status.busy": "2025-02-27T23:21:49.522594Z",
     "iopub.status.idle": "2025-02-27T23:21:49.526229Z",
     "shell.execute_reply": "2025-02-27T23:21:49.525813Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "X_centered = X - X.mean(axis=0)\n",
    "U, s, Vt = np.linalg.svd(X_centered)\n",
    "V = Vt.T\n",
    "c1 = V[:,0]\n",
    "c2 = V[:,1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.527883Z",
     "iopub.status.busy": "2025-02-27T23:21:49.527709Z",
     "iopub.status.idle": "2025-02-27T23:21:49.531736Z",
     "shell.execute_reply": "2025-02-27T23:21:49.531386Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([0.67857588, 0.70073508, 0.22023881]),\n",
       " array([-0.72817329,  0.6811147 ,  0.07646185]))"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "c1, c2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Reconstruct $\\Sigma$ from `s`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.533494Z",
     "iopub.status.busy": "2025-02-27T23:21:49.533326Z",
     "iopub.status.idle": "2025-02-27T23:21:49.536249Z",
     "shell.execute_reply": "2025-02-27T23:21:49.535878Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "m, n = X.shape\n",
    "Σ = np.zeros_like(X_centered)\n",
    "Σ[:n, :n] = np.diag(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Check recover `X_centered`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.537981Z",
     "iopub.status.busy": "2025-02-27T23:21:49.537785Z",
     "iopub.status.idle": "2025-02-27T23:21:49.540650Z",
     "shell.execute_reply": "2025-02-27T23:21:49.540108Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "assert np.allclose(X_centered, U @ Σ @ Vt)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Projecting down to d dimensions\n",
    "\n",
    "Once principal components identified, can project data onto the hyperplane defined by first $d$ principal components.\n",
    "\n",
    "Can be performed by the dot product between the data principal components.\n",
    "\n",
    "1. Construct project matrix $P$ by taking first $d$ columns of $V$.\n",
    "2. Multiple feature matrix by projection matrix to recover lower dimensional representation of the data: $X_d = X P$.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "#### Check sizes\n",
    "\n",
    "For $X$ of size $m \\times n$, i.e. $n$ features and $m$ data instances, the matrices have the following sizes:\n",
    "\n",
    "- $V$ is of size $n \\times n$.\n",
    "- $P$ (first $d$ columns of $V$) is of size $n \\times d$.\n",
    "- $X_d = X P$ is of size $m \\times d$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.542460Z",
     "iopub.status.busy": "2025-02-27T23:21:49.542294Z",
     "iopub.status.idle": "2025-02-27T23:21:49.545152Z",
     "shell.execute_reply": "2025-02-27T23:21:49.544566Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "W2 = Vt[:2].T\n",
    "X2D = X_centered @ W2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.546946Z",
     "iopub.status.busy": "2025-02-27T23:21:49.546752Z",
     "iopub.status.idle": "2025-02-27T23:21:49.550054Z",
     "shell.execute_reply": "2025-02-27T23:21:49.549458Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original dimension is 3\n",
      "Reduced dimension is 2\n"
     ]
    }
   ],
   "source": [
    "print(f\"Original dimension is {X_centered.shape[-1]}\")\n",
    "print(f\"Reduced dimension is {X2D.shape[-1]}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Scikit-Learn PCA\n",
    "\n",
    "Scikit-Learn's PCA support also uses SVD but makes things even easier.\n",
    "\n",
    "You can access each principal component using ```components_``` variable"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.551841Z",
     "iopub.status.busy": "2025-02-27T23:21:49.551672Z",
     "iopub.status.idle": "2025-02-27T23:21:49.555266Z",
     "shell.execute_reply": "2025-02-27T23:21:49.554662Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.decomposition import PCA\n",
    "\n",
    "pca = PCA(n_components=2)\n",
    "X2D = pca.fit_transform(X)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.557016Z",
     "iopub.status.busy": "2025-02-27T23:21:49.556827Z",
     "iopub.status.idle": "2025-02-27T23:21:49.560687Z",
     "shell.execute_reply": "2025-02-27T23:21:49.560112Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0.67857588,  0.70073508,  0.22023881],\n",
       "       [ 0.72817329, -0.6811147 , -0.07646185]])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pca.components_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Explained variance ratio\n",
    "\n",
    "*Explained variance ratio* is a very useful metric.\n",
    "\n",
    "Gives the proportion of dataset variance along the axis of each principal component."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.562418Z",
     "iopub.status.busy": "2025-02-27T23:21:49.562252Z",
     "iopub.status.idle": "2025-02-27T23:21:49.565839Z",
     "shell.execute_reply": "2025-02-27T23:21:49.565454Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.7578477 , 0.15186921])"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pca.explained_variance_ratio_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "The first dimension explains about 76% of the variance, while the second explains about 15%."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "By projecting down to 2D, we lost about 9% of the variance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.567654Z",
     "iopub.status.busy": "2025-02-27T23:21:49.567484Z",
     "iopub.status.idle": "2025-02-27T23:21:49.571274Z",
     "shell.execute_reply": "2025-02-27T23:21:49.570904Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.float64(0.09028309326742034)"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "1 - pca.explained_variance_ratio_.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Choosing the right number of dimensions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Compute the explained variance cumulatively to see how much captured by a given number of principal components."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:49.573126Z",
     "iopub.status.busy": "2025-02-27T23:21:49.572952Z",
     "iopub.status.idle": "2025-02-27T23:21:53.659798Z",
     "shell.execute_reply": "2025-02-27T23:21:53.659297Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.int64(154)"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.datasets import fetch_openml\n",
    "\n",
    "mnist = fetch_openml('mnist_784', as_frame=False, parser=\"auto\")\n",
    "X_train, y_train = mnist.data[:60_000], mnist.target[:60_000]\n",
    "X_test, y_test = mnist.data[60_000:], mnist.target[60_000:]\n",
    "\n",
    "pca = PCA()\n",
    "pca.fit(X_train)\n",
    "cumsum = np.cumsum(pca.explained_variance_ratio_)\n",
    "d = np.argmax(cumsum >= 0.95) + 1\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "So we capture $\\geq95$% of the variance in $154$ dimensions.\n",
    "\n",
    "Compared to original dimensionality of $28 \\times 28 =784$), we compress to $\\sim 20$% of the original size."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "Scikit-Learn also has built in functionality to automatically select number of principal components that capture a given level of variance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:53.662280Z",
     "iopub.status.busy": "2025-02-27T23:21:53.662094Z",
     "iopub.status.idle": "2025-02-27T23:21:55.179585Z",
     "shell.execute_reply": "2025-02-27T23:21:55.179013Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "pca = PCA(n_components=0.95)\n",
    "X_reduced = pca.fit_transform(X_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:55.183080Z",
     "iopub.status.busy": "2025-02-27T23:21:55.182254Z",
     "iopub.status.idle": "2025-02-27T23:21:55.187758Z",
     "shell.execute_reply": "2025-02-27T23:21:55.187347Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.int64(154)"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pca.n_components_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### PCA for compression"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "We can also reconstruct high dimensional representation from compressed representation.\n",
    "\n",
    "Compression is lossy, so we won't recover the original high dimensional representation perfectly.\n",
    "\n",
    "Example applying PCA to MNIST dataset with 95% preservation = results in ~150 features (original = 28x28 = 784)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Since principal components are orthogonal, we can estimate a recovered high dimensional representation by\n",
    "\n",
    "$$X_\\text{recovered} = X_{d} P^\\text{T}.$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:55.190225Z",
     "iopub.status.busy": "2025-02-27T23:21:55.190043Z",
     "iopub.status.idle": "2025-02-27T23:21:55.643751Z",
     "shell.execute_reply": "2025-02-27T23:21:55.643184Z"
    },
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "X_recovered = pca.inverse_transform(X_reduced)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:55.647099Z",
     "iopub.status.busy": "2025-02-27T23:21:55.646185Z",
     "iopub.status.idle": "2025-02-27T23:21:55.864486Z",
     "shell.execute_reply": "2025-02-27T23:21:55.863997Z"
    },
    "scrolled": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 118428 characters]",
      "text/plain": [
       "<Figure size 700x400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(7, 4))\n",
    "for idx, X in enumerate((X_train[::2100], X_recovered[::2100])):\n",
    "    plt.subplot(1, 2, idx + 1)\n",
    "    plt.title([\"Original\", \"Compressed\"][idx])\n",
    "    for row in range(5):\n",
    "        for col in range(5):\n",
    "            plt.imshow(X[row * 5 + col].reshape(28, 28), cmap=\"binary\",\n",
    "                       vmin=0, vmax=255, extent=(row, row + 1, col, col + 1))\n",
    "            plt.axis([0, 5, 0, 5])\n",
    "            plt.axis(\"off\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Randomized PCA \n",
    "\n",
    "*Randomized PCA* quickly find an approximation of the first first $d$ principal components.\n",
    "\n",
    "It is much quicker than the full SVD approach.\n",
    "\n",
    "Its computational complexity is ${\\mathcal O}( m\\times d^2) + {\\mathcal O}( d^3)$, instead of ${\\mathcal O}( m \\times n^2) + {\\mathcal O}( n^3)$, so it is dramatically faster than the previous algorithms when $d$ is much smaller than $n$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:21:55.866516Z",
     "iopub.status.busy": "2025-02-27T23:21:55.866345Z",
     "iopub.status.idle": "2025-02-27T23:22:02.100911Z",
     "shell.execute_reply": "2025-02-27T23:22:02.100391Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "rnd_pca = PCA(n_components=154, svd_solver=\"randomized\", random_state=42)\n",
    "X_reduced = rnd_pca.fit_transform(X_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:02.103482Z",
     "iopub.status.busy": "2025-02-27T23:22:02.103301Z",
     "iopub.status.idle": "2025-02-27T23:22:02.108558Z",
     "shell.execute_reply": "2025-02-27T23:22:02.108081Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "154"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rnd_pca.n_components_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Incremental PCA \n",
    "\n",
    "One problem with PCA is that it requires the entire training set to be read into memory at once.  Thus, cannot apply to very large training data-sets. \n",
    "\n",
    "Instead *Incremental PCA (IPCA)* splits the data into batches, and allows for combination. \n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:02.110486Z",
     "iopub.status.busy": "2025-02-27T23:22:02.110313Z",
     "iopub.status.idle": "2025-02-27T23:22:20.395636Z",
     "shell.execute_reply": "2025-02-27T23:22:20.395095Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.decomposition import IncrementalPCA\n",
    "\n",
    "n_batches = 100\n",
    "inc_pca = IncrementalPCA(n_components=154)\n",
    "for X_batch in np.array_split(X_train, n_batches):\n",
    "    inc_pca.partial_fit(X_batch)\n",
    "\n",
    "X_reduced = inc_pca.transform(X_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:20.398654Z",
     "iopub.status.busy": "2025-02-27T23:22:20.397666Z",
     "iopub.status.idle": "2025-02-27T23:22:20.403297Z",
     "shell.execute_reply": "2025-02-27T23:22:20.402792Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "154"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "inc_pca.n_components_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "**Exercises:** *You can now complete Exercise 1 in the exercises associated with this lecture.*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "slide"
    },
    "tags": []
   },
   "source": [
    "## Random projections"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Even with randomized or incremental PCA, PCA can be too computationally expensive for very large data-sets.\n",
    "\n",
    "An alternative it to consider *random* projections to lower dimensional spaces through linear projections."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "Random projections can actually preserve distances reasonably well.  Hence, two similar data instances remain similar after projection.  Similarly, two very different data instances remain very different after projection.\n",
    "\n",
    "The more dimensions that are dropped, the more information is lost, and hence the more distances get distorted."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Selecting the reduced dimension size\n",
    "\n",
    "There is some deep mathematical theory that specifies how much dimensions can be compressed in order to ensure distances are distorted by less than some tolerance $\\epsilon$.  \n",
    "\n",
    "This is called the *Johnson-Lindenstrauss* minimum dimension:\n",
    "\n",
    "$$d \\geq \\frac{4 \\log(m)}{\\epsilon^2/2 - \\epsilon^3/3}.$$\n",
    "\n",
    "(Doesn't depend on number of features $n$.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:20.405853Z",
     "iopub.status.busy": "2025-02-27T23:22:20.405675Z",
     "iopub.status.idle": "2025-02-27T23:22:20.409905Z",
     "shell.execute_reply": "2025-02-27T23:22:20.409455Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "5920"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m, ε = 1_000, 0.1\n",
    "\n",
    "d = int(4 * np.log(m) / (ε ** 2 / 2 - ε ** 3 / 3))\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Or using Scikit-Learn:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:20.413424Z",
     "iopub.status.busy": "2025-02-27T23:22:20.412448Z",
     "iopub.status.idle": "2025-02-27T23:22:20.419642Z",
     "shell.execute_reply": "2025-02-27T23:22:20.419208Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "np.int64(5920)"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.random_projection import johnson_lindenstrauss_min_dim\n",
    "\n",
    "\n",
    "d = johnson_lindenstrauss_min_dim(m, eps=ε)\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "Then can compute a random projection down to dimension $d$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:20.421721Z",
     "iopub.status.busy": "2025-02-27T23:22:20.421549Z",
     "iopub.status.idle": "2025-02-27T23:22:23.802948Z",
     "shell.execute_reply": "2025-02-27T23:22:23.802408Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1000, 5920)"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "n = 8_000\n",
    "np.random.seed(42)\n",
    "\n",
    "# Radom projection matrix\n",
    "P = np.random.randn(d, n) / np.sqrt(d)  # std dev = square root of variance\n",
    "\n",
    "X = np.random.randn(m, n)  # generate a fake dataset\n",
    "X_reduced = X @ P.T\n",
    "X_reduced.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Or use Scikit-Learn built in functionality:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:23.805852Z",
     "iopub.status.busy": "2025-02-27T23:22:23.804842Z",
     "iopub.status.idle": "2025-02-27T23:22:27.060605Z",
     "shell.execute_reply": "2025-02-27T23:22:27.060048Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1000, 5920)"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.random_projection import GaussianRandomProjection\n",
    "\n",
    "gaussian_rnd_proj = GaussianRandomProjection(eps=ε, random_state=42)\n",
    "X_reduced = gaussian_rnd_proj.fit_transform(X)  \n",
    "X_reduced.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "slide"
    },
    "tags": []
   },
   "source": [
    "## Locally linear embedding (LLE)\n",
    "\n",
    "Locally linear embedding (LLE) is a powerful non-linear dimensionality reduction tool.\n",
    "\n",
    "It is a form of *manifold learning* and doesn't rely on projections.\n",
    "\n",
    "LLE measures how each instance relates to closest neighbors, then looks for low-D representation where local relations are best preserved."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### First Step: linearly modelling local relationships\n",
    "\n",
    "- First, for each training instance $x^{(i)}$, the algorithm identifies its $k$ closest neighbours.\n",
    "- Then tries to reconstruct $x^{(i)}$ as a linear weighted sum of these neighbors.\n",
    "\n",
    "More specifically, it finds the weights $w_{ij}$ such that the squared distance between $x^{(i)}$ and $\\sum_{j=1}^m w_{ij}x^{(j)}$ is as small as possible, assuming $w_{ij}=0$ if $x^{(j)}$ is not one of the $k$-nearest neighbours of $x^{(i)}$.\n",
    "\n",
    "That is, solve the minimisation problem\n",
    "\n",
    "$$\n",
    "\\text{argmin}_{W} \\sum_{i=1}^m \\left( x^{(i)} - \\sum_{j=1}^m w_{ij}x^{(j)} \\right)^2 ,\n",
    "$$\n",
    "\n",
    "where $W$ is the matrix of weights.  Note that weights are constrained to be normalised such that $\\sum_j w_{ij}=1$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Second step: reducing dimensionality while preserving relationships\n",
    "\n",
    "The second step is to map the training instances into a $d$-dimensional space (where $d < n$) while preserving these local relationships as much as possible.\n",
    "\n",
    "If $z^{(i)}$ is the d-space equivalent of $x^{(i)}$ then we want the squared distance between $z^{(i)}$ and $\\sum_{i=1}^m w_{ij}z^{(j)}$ to be as small as possible, given **the fixed set of weights** from step 1. \n",
    "\n",
    "That is, solve the minimisation problem\n",
    "\n",
    "$$\n",
    "\\text{argmin}_{Z} \\sum_{i=1}^m \\left( z^{(i)} - \\sum_{j=1}^m \\hat{w}_{ij}z^{(j)} \\right)^2 ,\n",
    "$$\n",
    "\n",
    "where $Z$ is the matrix of lower dimensional positions and $\\hat{w}_{ij}$ are the weights determined in step 1.\n",
    "\n",
    "Note that step 2 optimises positions of $z$ for fixed $w$, while step 1 optimises the $w$ for fixed positions $x$\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "### Example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "#### Plot swiss roll"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:27.063998Z",
     "iopub.status.busy": "2025-02-27T23:22:27.062959Z",
     "iopub.status.idle": "2025-02-27T23:22:27.067705Z",
     "shell.execute_reply": "2025-02-27T23:22:27.067256Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.datasets import make_swiss_roll\n",
    "\n",
    "X_swiss, t = make_swiss_roll(n_samples=1000, noise=0.2, random_state=42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:27.070034Z",
     "iopub.status.busy": "2025-02-27T23:22:27.069836Z",
     "iopub.status.idle": "2025-02-27T23:22:27.289425Z",
     "shell.execute_reply": "2025-02-27T23:22:27.288912Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 183104 characters]",
      "text/plain": [
       "<Figure size 800x700 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from matplotlib.colors import ListedColormap\n",
    "\n",
    "darker_hot = ListedColormap(plt.cm.hot(np.linspace(0, 0.8, 256)))\n",
    "\n",
    "axes = [-11.5, 14, -2, 23, -12, 15]\n",
    "\n",
    "fig = plt.figure(figsize=(8, 7))\n",
    "ax = fig.add_subplot(111, projection='3d')\n",
    "\n",
    "ax.scatter(X_swiss[:, 0], X_swiss[:, 1], X_swiss[:, 2], c=t, cmap=darker_hot)\n",
    "ax.view_init(10, -70)\n",
    "set_xyz_axes(ax, axes)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": "subslide"
    },
    "tags": []
   },
   "source": [
    "#### LLE"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:27.291420Z",
     "iopub.status.busy": "2025-02-27T23:22:27.291245Z",
     "iopub.status.idle": "2025-02-27T23:22:27.383133Z",
     "shell.execute_reply": "2025-02-27T23:22:27.382651Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.manifold import LocallyLinearEmbedding\n",
    "\n",
    "lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10, random_state=42)\n",
    "X_unrolled = lle.fit_transform(X_swiss)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "editable": true,
    "execution": {
     "iopub.execute_input": "2025-02-27T23:22:27.385572Z",
     "iopub.status.busy": "2025-02-27T23:22:27.385146Z",
     "iopub.status.idle": "2025-02-27T23:22:27.570413Z",
     "shell.execute_reply": "2025-02-27T23:22:27.569904Z"
    },
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'Unrolled swiss roll using LLE')"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 132512 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.scatter(X_unrolled[:, 0], X_unrolled[:, 1],\n",
    "            c=t, cmap=darker_hot)\n",
    "plt.xlabel(\"$z_1$\")\n",
    "plt.ylabel(\"$z_2$\", rotation=0)\n",
    "plt.axis([-0.055, 0.060, -0.070, 0.090])\n",
    "plt.grid(True)\n",
    "plt.title(\"Unrolled swiss roll using LLE\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "editable": true,
    "slideshow": {
     "slide_type": ""
    },
    "tags": []
   },
   "source": [
    "Swiss roll is unrolled and distances between instances are well preserved locally.\n",
    "\n",
    "However, distances not preserved on a larger scale."
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Slideshow",
  "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.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
