{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "\n# Image denoising using kernel PCA\n\nThis example shows how to use :class:`~sklearn.decomposition.KernelPCA` to\ndenoise images. In short, we take advantage of the approximation function\nlearned during `fit` to reconstruct the original image.\n\nWe will compare the results with an exact reconstruction using\n:class:`~sklearn.decomposition.PCA`.\n\nWe will use USPS digits dataset to reproduce presented in Sect. 4 of [1]_.\n\n.. rubric:: References\n\n.. [1] [Bak\u0131r, G\u00f6khan H., Jason Weston, and Bernhard Sch\u00f6lkopf.\n    \"Learning to find pre-images.\"\n    Advances in neural information processing systems 16 (2004): 449-456.](https://papers.nips.cc/paper/2003/file/ac1ad983e08ad3304a97e147f522747e-Paper.pdf)\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "# Authors: The scikit-learn developers\n# SPDX-License-Identifier: BSD-3-Clause"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Load the dataset via OpenML\n\nThe USPS digits datasets is available in OpenML. We use\n:func:`~sklearn.datasets.fetch_openml` to get this dataset. In addition, we\nnormalize the dataset such that all pixel values are in the range (0, 1).\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import numpy as np\n\nfrom sklearn.datasets import fetch_openml\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import MinMaxScaler\n\nX, y = fetch_openml(data_id=41082, as_frame=False, return_X_y=True)\nX = MinMaxScaler().fit_transform(X)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "The idea will be to learn a PCA basis (with and without a kernel) on\nnoisy images and then use these models to reconstruct and denoise these\nimages.\n\nThus, we split our dataset into a training and testing set composed of 1,000\nsamples for the training and 100 samples for testing. These images are\nnoise-free and we will use them to evaluate the efficiency of the denoising\napproaches. In addition, we create a copy of the original dataset and add a\nGaussian noise.\n\nThe idea of this application, is to show that we can denoise corrupted images\nby learning a PCA basis on some uncorrupted images. We will use both a PCA\nand a kernel-based PCA to solve this problem.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "X_train, X_test, y_train, y_test = train_test_split(\n    X, y, stratify=y, random_state=0, train_size=1_000, test_size=100\n)\n\nrng = np.random.RandomState(0)\nnoise = rng.normal(scale=0.25, size=X_test.shape)\nX_test_noisy = X_test + noise\n\nnoise = rng.normal(scale=0.25, size=X_train.shape)\nX_train_noisy = X_train + noise"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In addition, we will create a helper function to qualitatively assess the\nimage reconstruction by plotting the test images.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n\n\ndef plot_digits(X, title):\n    \"\"\"Small helper function to plot 100 digits.\"\"\"\n    fig, axs = plt.subplots(nrows=10, ncols=10, figsize=(8, 8))\n    for img, ax in zip(X, axs.ravel()):\n        ax.imshow(img.reshape((16, 16)), cmap=\"Greys\")\n        ax.axis(\"off\")\n    fig.suptitle(title, fontsize=24)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In addition, we will use the mean squared error (MSE) to quantitatively\nassess the image reconstruction.\n\nLet's first have a look to see the difference between noise-free and noisy\nimages. We will check the test set in this regard.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plot_digits(X_test, \"Uncorrupted test images\")\nplot_digits(\n    X_test_noisy, f\"Noisy test images\\nMSE: {np.mean((X_test - X_test_noisy) ** 2):.2f}\"\n)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Learn the `PCA` basis\n\nWe can now learn our PCA basis using both a linear PCA and a kernel PCA that\nuses a radial basis function (RBF) kernel.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "from sklearn.decomposition import PCA, KernelPCA\n\npca = PCA(n_components=32, random_state=42)\nkernel_pca = KernelPCA(\n    n_components=400,\n    kernel=\"rbf\",\n    gamma=1e-3,\n    fit_inverse_transform=True,\n    alpha=5e-3,\n    random_state=42,\n)\n\npca.fit(X_train_noisy)\n_ = kernel_pca.fit(X_train_noisy)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Reconstruct and denoise test images\n\nNow, we can transform and reconstruct the noisy test set. Since we used less\ncomponents than the number of original features, we will get an approximation\nof the original set. Indeed, by dropping the components explaining variance\nin PCA the least, we hope to remove noise. Similar thinking happens in kernel\nPCA; however, we expect a better reconstruction because we use a non-linear\nkernel to learn the PCA basis and a kernel ridge to learn the mapping\nfunction.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "X_reconstructed_kernel_pca = kernel_pca.inverse_transform(\n    kernel_pca.transform(X_test_noisy)\n)\nX_reconstructed_pca = pca.inverse_transform(pca.transform(X_test_noisy))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "plot_digits(X_test, \"Uncorrupted test images\")\nplot_digits(\n    X_reconstructed_pca,\n    f\"PCA reconstruction\\nMSE: {np.mean((X_test - X_reconstructed_pca) ** 2):.2f}\",\n)\nplot_digits(\n    X_reconstructed_kernel_pca,\n    (\n        \"Kernel PCA reconstruction\\n\"\n        f\"MSE: {np.mean((X_test - X_reconstructed_kernel_pca) ** 2):.2f}\"\n    ),\n)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "PCA has a lower MSE than kernel PCA. However, the qualitative analysis might\nnot favor PCA instead of kernel PCA. We observe that kernel PCA is able to\nremove background noise and provide a smoother image.\n\nHowever, it should be noted that the results of the denoising with kernel PCA\nwill depend of the parameters `n_components`, `gamma`, and `alpha`.\n\n"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "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.14"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}