From 2dbca713bdd85a57b34e37ebab04d2a8737b4060 Mon Sep 17 00:00:00 2001 From: Neil Lawrence Date: Mon, 26 Oct 2026 08:34:15 +0000 Subject: [PATCH] Add notebook smoke tests and rebuild lectures from current snippets. The GPSS-style harness executes numbered lecture notebooks in CI. Rebuilt notebooks pick up snippet loader rebinds and the basis-function design-matrix defines. --- .github/workflows/notebook-smoke.yml | 77 + .gitignore | 54 + README.md | 15 + _lamd/_lamd.yml | 1 + _lamd/basis-functions.md | 4 + _lamd/matrix-factorization.md | 2 +- _notebooks/01-intro-probability.ipynb | 413 ++- _notebooks/02-the-data-science-process.ipynb | 286 +- _notebooks/03-matrix-factorization.ipynb | 767 +++-- _notebooks/04-linear-regression.ipynb | 1885 +++++++---- _notebooks/06-basis-functions.ipynb | 828 +++-- _notebooks/07-generalization.ipynb | 586 ++-- _notebooks/08-bayesian-regression.ipynb | 1317 ++++---- _notebooks/09-dimensionality-reduction.ipynb | 2974 +++++++++++++----- _notebooks/10-naive-bayes.ipynb | 540 +++- _notebooks/11-logistic-and-glm.ipynb | 648 ++-- _notebooks/12-gaussian-processes.ipynb | 2068 ++++++------ requirements-smoke.txt | 28 + scripts/smoke_notebooks.py | 451 +++ 19 files changed, 8365 insertions(+), 4579 deletions(-) create mode 100644 .github/workflows/notebook-smoke.yml create mode 100644 .gitignore create mode 100644 requirements-smoke.txt create mode 100755 scripts/smoke_notebooks.py diff --git a/.github/workflows/notebook-smoke.yml b/.github/workflows/notebook-smoke.yml new file mode 100644 index 00000000..ee5ee86f --- /dev/null +++ b/.github/workflows/notebook-smoke.yml @@ -0,0 +1,77 @@ +name: Notebook smoke + +on: + pull_request: + paths: + - "_notebooks/**/*.ipynb" + - "scripts/smoke_notebooks.py" + - "requirements-smoke.txt" + - ".github/workflows/notebook-smoke.yml" + schedule: + # Weekly full suite (Sunday 04:00 UTC) + - cron: "0 4 * * 0" + workflow_dispatch: + inputs: + only: + description: "Optional notebook prefixes (space-separated), e.g. 01 12. Empty = all except skipped." + required: false + default: "" + +concurrency: + group: notebook-smoke-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke: + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: requirements-smoke.txt + + - name: Install smoke dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements-smoke.txt + + - name: Cache pods / notebook data downloads + uses: actions/cache@v4 + with: + path: | + ~/.pods_data + _notebooks/data + key: pods-data-${{ runner.os }}-${{ hashFiles('requirements-smoke.txt') }} + restore-keys: | + pods-data-${{ runner.os }}- + + - name: Run notebook smoke tests + env: + MPLBACKEND: Agg + JUPYTER_PLATFORM_DIRS: "1" + run: | + set -euo pipefail + ONLY="${{ github.event.inputs.only }}" + if [ -n "${ONLY}" ]; then + # shellcheck disable=SC2086 + python scripts/smoke_notebooks.py --only ${ONLY} + else + python scripts/smoke_notebooks.py + fi + + - name: Upload smoke results + if: always() + uses: actions/upload-artifact@v4 + with: + name: notebook-smoke-results + path: | + _notebooks/test-results/summary.json + _notebooks/test-results/*.ok + _notebooks/test-results/*.fail + if-no-files-found: ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e5e5522c --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Jupyter and notebook checkpoints +.ipynb_checkpoints/ + +# OS and editor files +.DS_Store +*.swp +*.swo +*~ +\#*\# +.\#* + +# Local workspace files +*.code-workspace + +# Python cache +__pycache__/ +*.pyc +*.pyo +*.pyd + +# Backups +*.bak + +# Build artefacts +*.o +*.so +*.out +*.log + +# LaMD intermediate build artefacts (maketalk) under _lamd/ +# Keep sources (*.md), _lamd.yml, compile.sh, lectures.csv tracked. +_lamd/*.gpp.markdown +_lamd/*.ipynb +_lamd/*.ipynb.markdown +_lamd/*.html +_lamd/*.html.markdown +_lamd/*.notes.* +_lamd/*.posts.* +_lamd/*.slides.* +_lamd/makefile +_lamd/*.log +_lamd/talk-people.gpp +_lamd/*-e +_lamd/*.tmp.markdown + +# Compiled Jekyll site +_site/ + +# Ruby bundler +.bundle/ + +# Local Python env and notebook smoke artefacts +.venv/ +_notebooks/test-results/ diff --git a/README.md b/README.md index 1faee0a2..10217d32 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,21 @@ The lecture notes will all be given in the form of Jupyter Notebooks and are ava * [Week 12](./week12.ipynb) Special Topic: Gaussian Processes +## Notebook smoke tests + +Execute numbered lecture notebooks under `_notebooks/` with CI-safe patches (Agg backend, mocked stdin, capped GPy `optimize`, no widget hangs). Lecture 05 is skipped by default because it needs a live MariaDB and credential widgets. + +```bash +python -m venv .venv +source .venv/bin/activate +pip install -r requirements-smoke.txt +python scripts/smoke_notebooks.py +# subset: python scripts/smoke_notebooks.py --only 01 12 +# include lecture 05: python scripts/smoke_notebooks.py --only 05 +``` + +GitHub Actions runs the same harness on pull requests that touch notebooks or the smoke files, weekly on a schedule, and via **workflow_dispatch** (optional `--only` input). Results land in `_notebooks/test-results/` (gitignored) and as a workflow artifact. + ## ToDo There are some refinements to the notebook slides required. Particularly for Week 8 (unsupervised learning) 9 and 10. diff --git a/_lamd/_lamd.yml b/_lamd/_lamd.yml index c317e236..70127db5 100755 --- a/_lamd/_lamd.yml +++ b/_lamd/_lamd.yml @@ -18,6 +18,7 @@ notesdir: ../_notes/ notebooksdir: ../_notebooks/ practicalsdir: ../_practicals/ snippetsdir: $HOME/lawrennd/snippets +macrosdir: $HOME/lawrennd/lamd/lamd/macros writediagramsdir: . scriptsdir: https://inverseprobability.com/talks/scripts/ diagramsdir: ../slides/diagrams/ diff --git a/_lamd/basis-functions.md b/_lamd/basis-functions.md index 0f7977fc..81edeca7 100755 --- a/_lamd/basis-functions.md +++ b/_lamd/basis-functions.md @@ -11,6 +11,10 @@ abstract: | \include{_mlai/includes/mlai-notebook-setup.md} \include{_ml/includes/non-linear-regression-intro.md} +\define{designVector}{\basisVector} +\define{designVariable}{Phi} +\define{designMatrix}{\basisMatrix} + \include{_ml/includes/basis-functions.md} \include{_ml/includes/basis-function-models.md} diff --git a/_lamd/matrix-factorization.md b/_lamd/matrix-factorization.md index 6d67dbbd..40dc0fdb 100755 --- a/_lamd/matrix-factorization.md +++ b/_lamd/matrix-factorization.md @@ -46,7 +46,7 @@ are going to take a slightly different tack.} \include{_software/includes/pods-software.md} -\include{_ml/includes/movie-body-count-data.md} +\include{_data-science/includes/movie-body-count-pandas.md} \writeassignment{Data ethics. If you find data available on the internet, can you simply use it without consequence? If you are given data by a diff --git a/_notebooks/01-intro-probability.ipynb b/_notebooks/01-intro-probability.ipynb index 6b8506b8..cec0616f 100644 --- a/_notebooks/01-intro-probability.ipynb +++ b/_notebooks/01-intro-probability.ipynb @@ -6,7 +6,7 @@ "source": [ "# Probability and an Introduction to Jupyter, Python and Pandas" ], - "id": "5003c3e6-1c4b-4f30-886f-c3d5fd74a1b0" + "id": "f50195c2-9bd0-40d6-aca4-fb87ee0df082" }, { "cell_type": "markdown", @@ -16,7 +16,7 @@ "learning*, review *probability* and begin familiarization with the\n", "Jupyter notebook, python and pandas." ], - "id": "440d3f5b-eeb5-45ba-a115-b5ba311f4636" + "id": "c3f7294a-7690-438d-b2cc-3d1255671777" }, { "cell_type": "markdown", @@ -25,17 +25,20 @@ "$$\n", "$$" ], - "id": "8d47d52b-fe86-4718-bb97-09163f719f23" + "id": "7f99ed97-b203-4ff6-8707-9abcf319afb0" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -43,7 +46,7 @@ "\n", "-->" ], - "id": "1b4bb6a3-39f9-4283-b3e7-75300ce6a5eb" + "id": "c0e188ae-db57-4a5d-85a3-338b26beeb9c" }, { "cell_type": "markdown", @@ -55,7 +58,7 @@ "style=\"\">\\[edit\\]" ], - "id": "595cd177-72fe-468a-86a3-d79e2dfa1c7e" + "id": "f371e08a-e689-4927-b951-40d65cb67209" }, { "cell_type": "markdown", @@ -67,12 +70,12 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", + "\n", "\n", "Figure: The main course text is “A First Course in Machine Learning”\n", "by Rogers and Girolami (2011)." ], - "id": "da4d323f-26f5-4397-8da5-0041f5ffecd0" + "id": "47ecc550-de3d-4fbe-bc99-3d039506c3e1" }, { "cell_type": "markdown", @@ -84,7 +87,7 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", + "\n", "\n", "Figure: For additional reading we will regularly refer to “Pattern\n", "Recognition and Machine Learning” by Bishop (2006)\n", @@ -137,7 +140,7 @@ "expended on writing those three lines is far greater than in standard\n", "code." ], - "id": "7a3685ff-d26d-4a0f-8f93-1e0b05448167" + "id": "44a1d33f-7ba0-48e0-8931-b6fd3e9fa9c5" }, { "cell_type": "markdown", @@ -149,7 +152,7 @@ "style=\"\">\\[edit\\]" ], - "id": "e8ae3d40-78e4-4835-bf0d-5a91a65b1317" + "id": "e3e74743-e88d-4430-985c-e03c699b1fcc" }, { "cell_type": "markdown", @@ -167,7 +170,7 @@ "understanding probability distributions over high dimensional\n", "distributions." ], - "id": "3bef7670-4052-4abd-9be9-162cb27f8c21" + "id": "78600908-df06-4f9c-8d7b-3098fc0d8b8c" }, { "cell_type": "markdown", @@ -191,7 +194,7 @@ "known as the IPython notebook) is available\n", "[here](http://ipython.org/ipython-%20doc/2/notebook/index.html)." ], - "id": "3f6fe3dc-55f4-449b-a43b-4060052abeab" + "id": "29ccb807-e2c4-407c-8d7a-b92de84f450e" }, { "cell_type": "markdown", @@ -203,7 +206,7 @@ "is the origin of the name “python”? Is the language a compiled language?\n", "Is it an object orientated language?" ], - "id": "3c2178dd-b4c4-4faa-af74-eca75b115f1b" + "id": "398255cb-2127-47ac-bf9a-07745bde3594" }, { "cell_type": "markdown", @@ -213,7 +216,7 @@ "\n", "Write your answer to Exercise 1 here" ], - "id": "494e883b-47e3-491a-973f-8bb2d6a89470" + "id": "1cbdba23-f7bb-483f-b535-0a2cd28bfccd" }, { "cell_type": "markdown", @@ -231,7 +234,7 @@ "communication of ideas. The box this text is written in is a *markdown*\n", "box. Below we have a *code* box." ], - "id": "cf87b9ef-df7c-4f28-b4cc-9c86200169a3" + "id": "df8adadc-530c-4295-94f4-82a5220e22a5" }, { "cell_type": "code", @@ -247,7 +250,7 @@ " shuffle(words)\n", " print(' '.join(words))" ], - "id": "b6971f66-cca6-4ee7-93ff-dbb333fbec09" + "id": "b8fb0e50-3f3f-42e7-b69b-c9785c032379" }, { "cell_type": "markdown", @@ -260,7 +263,7 @@ "code we can load in some data using the `pandas` library for data\n", "analysis." ], - "id": "ec1eb399-d248-4ea8-9744-9366227eb198" + "id": "2ff190ac-5dbe-438e-a172-8a4ba19529a0" }, { "cell_type": "markdown", @@ -273,7 +276,7 @@ "project? Name two languages involved in the Jupyter project other than\n", "python." ], - "id": "76b99115-7f06-4169-938d-cfe95573d709" + "id": "3b1ca0a0-179d-40e1-b54d-fef19cb47159" }, { "cell_type": "markdown", @@ -283,7 +286,7 @@ "\n", "Write your answer to Exercise 2 here" ], - "id": "37f470d4-b373-449f-9bd5-c93ac0bdc362" + "id": "be82e828-f1f8-4645-bc67-3bf0fc5ba39e" }, { "cell_type": "markdown", @@ -295,7 +298,7 @@ "style=\"\">\\[edit\\]" ], - "id": "62a10558-7e65-436b-b7fc-8efe8b63359b" + "id": "3d6e956e-d93f-4510-8b4a-595345807899" }, { "cell_type": "code", @@ -304,9 +307,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "5c895999-bea4-4e7e-ab4c-61b5942644a8" + "id": "e1c7fa8a-7f23-433c-82f1-9788b0f4d3a9" }, { "cell_type": "markdown", @@ -317,7 +330,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "6c175489-94fd-4427-9089-895dc18dffce" + "id": "2c9d6d66-ac6d-41d6-a4a0-bb01d4d70e18" }, { "cell_type": "markdown", @@ -334,7 +347,56 @@ "\n", "The software can be installed using" ], - "id": "9985e437-e32f-449f-b6d6-4b9bf20d798a" + "id": "3668562b-9227-4311-90b6-3de2d4da2ac5" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "b3d16df4-8999-416c-80af-d93daeee17ab" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" + ], + "id": "ba514e7c-54b7-429d-86d5-eb808a72483c" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "022602f5-6f9d-4bd5-abf2-ccfaa2b3e1f3" }, { "cell_type": "code", @@ -342,9 +404,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "%system {cmd}" ], - "id": "bde22be6-65a0-439d-bd87-3b85ea2714f5" + "id": "b01417fb-77fa-45e0-b0d7-85066d9956ae" }, { "cell_type": "markdown", @@ -357,7 +419,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "c145dee5-4a3b-41d6-aaa8-d32d189fbe0e" + "id": "b95dd2d1-78b4-4599-b855-1ea8cf9f1e3a" }, { "cell_type": "code", @@ -367,7 +429,7 @@ "source": [ "import notutils" ], - "id": "e46ee584-6d1b-446d-8de8-3f648b1412b9" + "id": "ac328482-8732-44e9-888a-870ac43edfdb" }, { "cell_type": "markdown", @@ -389,7 +451,17 @@ "\n", "The software can be installed using" ], - "id": "99745f27-d199-4017-86cf-3dbba50426bb" + "id": "735229c9-45cf-4883-b3fd-b3e662207058" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "ca739824-34f9-43d0-8675-60139588c9d6" }, { "cell_type": "code", @@ -397,9 +469,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "%system {cmd}" ], - "id": "dd9c6dfc-1fa6-4640-8145-d6b4754849fe" + "id": "d81c27ab-6353-4b21-b766-0eca8514866a" }, { "cell_type": "markdown", @@ -411,7 +483,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "c5397624-3b82-4a44-b5ee-6e4ef826ae32" + "id": "94d90393-4719-487c-b5c5-9d4f95f5735d" }, { "cell_type": "code", @@ -421,7 +493,7 @@ "source": [ "import pods" ], - "id": "ee4aaa88-810f-4811-bb56-824bdd20b27a" + "id": "e9b0d2c2-34c8-413a-b6f6-e30e770b34c9" }, { "cell_type": "markdown", @@ -439,7 +511,7 @@ "\n", "The software can be installed using" ], - "id": "9921892d-3673-40ab-bfc7-b507209561ef" + "id": "e36aff1c-a572-4ce1-bcaf-eba885c06c86" }, { "cell_type": "code", @@ -447,9 +519,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "cmd = install_command('mlai')" ], - "id": "2b77b53a-4c18-4dea-a6a4-fd4ec70b2ecc" + "id": "9861324d-e5ae-4ef3-bfae-ad35991bc3ce" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "dbebf411-0046-4887-b3a5-1cb51d26abfe" }, { "cell_type": "markdown", @@ -461,7 +543,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "fe36a62e-b033-4677-be6c-696fffcc6d98" + "id": "d3d72b3d-efa4-4f93-850b-72bb76b57069" }, { "cell_type": "code", @@ -469,9 +551,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "b974d40b-f04f-4e6c-8090-6d840416174c" + "id": "b6ceb632-77db-4337-b1bd-6ff5aabb2de3" }, { "cell_type": "markdown", @@ -529,7 +612,7 @@ "You can also check my post blog post on [What is Machine\n", "Learning?](http://inverseprobability.com/2017/07/17/what-is-machine-learning)." ], - "id": "69bfa567-0927-4109-9b24-651c9cee284d" + "id": "1bae0efb-e185-4ac8-bc81-879038457ac7" }, { "cell_type": "markdown", @@ -557,7 +640,7 @@ "The solution to these two simultaneous equations can be represented\n", "graphically as\n", "\n", - "\n", + "\n", "\n", "Figure: The solution of two linear equations represented as the fit\n", "of a straight line through two data\n", @@ -569,14 +652,14 @@ "2.5 = 2m + c\n", "$$\n", "\n", - "\n", + "\n", "\n", "Figure: A third observation of data is inconsistent with the solution\n", "dictated by the first two observations\n", "\n", "Now there are three candidate lines, each consistent with our data.\n", "\n", - "\n", + "\n", "\n", "Figure: Three solutions to the problem, each consistent with two\n", "points of the three observations\n", @@ -586,7 +669,7 @@ "model is a simplification of the real world, and the data we observe is\n", "therefore inconsistent with our model." ], - "id": "b49dd885-dd1b-4320-9198-f2d09d8757b0" + "id": "3854dfda-243d-4c52-8d56-d6a68e3ceb86" }, { "cell_type": "code", @@ -596,7 +679,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "23102006-85e6-444a-9e84-31dfb732fa15" + "id": "77ae8bf6-3693-4d8b-b939-9aaafbeb4011" }, { "cell_type": "code", @@ -606,7 +689,7 @@ "source": [ "plot.over_determined_system(diagrams='./ml')" ], - "id": "37b9141d-6aaa-4e38-95e1-e233c279751f" + "id": "455a5cc0-bf0a-496b-ba2d-19e23e58aec1" }, { "cell_type": "code", @@ -617,7 +700,7 @@ "from ipywidgets import IntSlider\n", "import notutils as nu" ], - "id": "3b4e1c41-a53f-4478-91a1-07c2ed1c8683" + "id": "0eab4664-bc22-4c50-bcd1-c51c3d436eaf" }, { "cell_type": "code", @@ -629,7 +712,7 @@ " directory='./ml', \n", " samp=IntSlider(1,1,7,1))" ], - "id": "32518aa0-891f-4c57-ad70-7b2abd9084a0" + "id": "0499da33-10a7-4276-93df-7b4e67ae1b59" }, { "cell_type": "markdown", @@ -646,7 +729,7 @@ "world, and the way it was incomplete is *unknown*. His idea was that\n", "such unknowns could be dealt with through probability." ], - "id": "2bc73ec2-9108-4b9e-8204-03c28853d7ed" + "id": "bd5cc9a3-0f99-44ed-8d45-6ed8025aa382" }, { "cell_type": "markdown", @@ -658,11 +741,23 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", + "\n", "\n", "Figure: Pierre-Simon Laplace 1749-1827." ], - "id": "58aeff23-d6b5-41b5-ad0b-253d0ae19d88" + "id": "dab27964-cb8b-499d-bcbd-a6c95fd6680c" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Laplace’s Determism\n", + "\n", + "\\[edit\\]" + ], + "id": "30a9bb86-f245-47a8-b9c5-9919ced6d8cc" }, { "cell_type": "code", @@ -673,12 +768,14 @@ "import notutils as nu\n", "nu.display_google_book(id='1YQPAAAAQAAJ', page='PR17-IA2')" ], - "id": "94c67841-ebbb-4f16-9bb0-8b12f4de0153" + "id": "d0c39712-0252-4eb0-a2fd-316e34e5efd7" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "Figure: Laplace’s determinism book reference.\n", + "\n", "Famously, Laplace considered the idea of a deterministic Universe, one\n", "in which the model is *known*, or as the below translation refers to it,\n", "“an intelligence which could comprehend all the forces by which nature\n", @@ -696,11 +793,11 @@ "\n", "This notion is known as *Laplace’s demon* or *Laplace’s superman*.\n", "\n", - "\n", + "\n", "\n", "Figure: Laplace’s determinsim in English translation." ], - "id": "442bd965-e934-4aac-ad94-353bb6574499" + "id": "5c02a0d5-eaa8-47e5-90c4-c12665b393f3" }, { "cell_type": "markdown", @@ -708,15 +805,10 @@ "source": [ "## Laplace’s Gremlin\n", "\n", - "\\[edit\\]\n", - "\n", "Unfortunately, most analyses of his ideas stop at that point, whereas\n", "his real point is that such a notion is unreachable. Not so much\n", "*superman* as *strawman*. Just three pages later in the “Philosophical\n", - "Essay on Probabilities” (**Laplace:essai14?**), Laplace goes on to\n", - "observe:\n", + "Essay on Probabilities” (Laplace, 1814), Laplace goes on to observe:\n", "\n", "> The curve described by a simple molecule of air or vapor is regulated\n", "> in a manner just as certain as the planetary orbits; the only\n", @@ -725,7 +817,7 @@ "> Probability is relative, in part to this ignorance, in part to our\n", "> knowledge." ], - "id": "e3ea0eca-2847-4135-8bf6-2f222ee455af" + "id": "6a16e585-2bd2-4d95-9706-9489b48e8719" }, { "cell_type": "code", @@ -736,13 +828,15 @@ "import notutils as nu\n", "nu.display_google_book(id='1YQPAAAAQAAJ', page='PR17-IA4')" ], - "id": "563a4c7e-d2ca-456c-9cd5-761a100c4af8" + "id": "b03e849d-c5d9-4a06-b394-a3e7459eb074" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "Figure: Laplace’s gremlin in the original French.\n", + "\n", + "\n", "\n", "Figure: To Laplace, determinism is a strawman. Ignorance of mechanism\n", "and data leads to uncertainty which should be dealt with through\n", @@ -763,7 +857,7 @@ "And *probability* is the tool we use to incorporate this ignorance\n", "leading to uncertainty or *doubt* in our predictions." ], - "id": "70ed39b3-a87c-4698-93f5-0eb4faf38a8e" + "id": "df69016e-c6b2-49c4-b343-7ee53e3c0034" }, { "cell_type": "markdown", @@ -797,7 +891,7 @@ "real world. Laplace’s idea is that we should represent that unknown\n", "corruption with a *probability distribution*." ], - "id": "2afff4ca-a270-4a00-a3ca-042d68ecf586" + "id": "248bdcb6-d978-4c5c-8a99-b810b9243b45" }, { "cell_type": "markdown", @@ -815,7 +909,7 @@ "a probabilistic or stochastic process, to distinguish it from a\n", "deterministic process." ], - "id": "5d67ceb5-ee4e-42d6-85c3-e86308f1e7a8" + "id": "ff5e3062-2bfc-4e5b-966a-c3ef03d41a3c" }, { "cell_type": "markdown", @@ -862,7 +956,7 @@ "\n", "For ease of use we’ve packaged this data set in the `pods` library" ], - "id": "1233873d-3d7f-4184-a4a5-abc0bce7c3f1" + "id": "1b3a806b-ab33-4601-b68d-537700b566a1" }, { "cell_type": "code", @@ -873,7 +967,7 @@ "data = pods.datasets.nigeria_nmis()['Y']\n", "data.head()" ], - "id": "a89b4b77-f810-4853-8d5f-bd734805a5e3" + "id": "168dbb25-be38-4685-b2ee-dfa6d18ae7ec" }, { "cell_type": "markdown", @@ -893,7 +987,7 @@ "Once it is loaded in the data can be summarized using the `describe`\n", "method in pandas." ], - "id": "854e4baa-cdb7-41e4-82bd-27059507114d" + "id": "f9a849ec-b72a-4371-a415-7eaaa0071071" }, { "cell_type": "code", @@ -903,7 +997,7 @@ "source": [ "data.describe()" ], - "id": "ae4f7919-e721-4921-ac56-87e91fdbe4d6" + "id": "09343cfb-6d37-4ef7-b6fe-ac19fd67e529" }, { "cell_type": "markdown", @@ -912,7 +1006,7 @@ "We can also find out the dimensions of the dataset using the `shape`\n", "property." ], - "id": "f05e04b0-06d3-4c34-950a-cf346a56a8d5" + "id": "e70052b6-eb7b-47b6-84b7-c2f032997d11" }, { "cell_type": "code", @@ -922,7 +1016,7 @@ "source": [ "data.shape" ], - "id": "cb97a6f2-63ef-4e04-a72d-be0fd6a6ad8c" + "id": "cd2b501c-8c9a-436a-b6e3-68c5b1a78d01" }, { "cell_type": "markdown", @@ -940,7 +1034,7 @@ "following the name with a question mark. This will open a box with\n", "documentation at the bottom which can be closed with the x button." ], - "id": "bac09314-6f65-4232-a073-425bf2753002" + "id": "3bdd2a49-9f85-448b-9e28-9399a9670453" }, { "cell_type": "code", @@ -950,7 +1044,7 @@ "source": [ "data.describe?" ], - "id": "85df7d57-b592-4ee2-9aec-ea9defdc52ee" + "id": "2065e07c-2b5e-4c94-b59f-47045346428a" }, { "cell_type": "code", @@ -962,7 +1056,7 @@ "import mlai\n", "import mlai.plot as plot" ], - "id": "58c688f9-ec99-4c86-b072-359d8d07516c" + "id": "dcc12923-f580-47b8-ac6e-8d35e9b58b62" }, { "cell_type": "code", @@ -977,19 +1071,19 @@ "\n", "mlai.write_figure('nigerian-health-facilities.png', directory='./ml')" ], - "id": "3f0ff8df-6a17-43b8-b650-154d5cc69d20" + "id": "8e56e4dd-e087-4d66-8035-4c94d8e13e3d" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Location of the over thirty-four thousand health facilities\n", "registered in the NMIS data across Nigeria. Each facility plotted\n", "according to its latitude and longitude." ], - "id": "c067233a-16fb-4326-a8e2-6ac1118413d8" + "id": "a10bc97f-d817-4fbd-b61f-1f1484313ee2" }, { "cell_type": "markdown", @@ -1028,7 +1122,27 @@ "P(Y=y) \\approx \\frac{n_y}{N}.\n", "$$" ], - "id": "ae1466bb-cda7-4669-aaa6-d78f7b9b351d" + "id": "9f0f12d2-f873-4312-8c58-7808284e9509" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pods" + ], + "id": "20702fb2-04f6-4669-b0b2-efee93245bc3" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.nigeria_nmis()['Y']" + ], + "id": "ae8f6cdf-25d7-4ff6-b215-0bfafe0a5749" }, { "cell_type": "markdown", @@ -1049,7 +1163,7 @@ "counts, means, standard deviations and percentiles for the values in\n", "those columns. To access a column directly we can write" ], - "id": "ffd62b2a-9c75-4647-8a6a-b8dbf58d3eb7" + "id": "d7a83398-a6dc-4f1c-b15e-90f0ee00baff" }, { "cell_type": "code", @@ -1060,7 +1174,7 @@ "print(data['num_doctors_fulltime'])\n", "#print(data['num_nurses_fulltime'])" ], - "id": "d52eb011-abba-4d05-bf66-306de0713d65" + "id": "b83cbd92-19ce-4f32-8f8b-90ef5e801762" }, { "cell_type": "markdown", @@ -1070,7 +1184,7 @@ "number of community health workers (CHEWS). We can plot the number of\n", "doctors against the number of nurses as follows." ], - "id": "baba6abf-5700-4d76-90ee-bf4a0d967454" + "id": "b92cc476-6688-48f7-8abc-f6d2968ed706" }, { "cell_type": "code", @@ -1080,7 +1194,7 @@ "source": [ "import matplotlib.pyplot as plt # this imports the plotting library in python" ], - "id": "60920d01-72f0-46c3-a2bd-236089f55f8d" + "id": "473ce205-c52e-478b-bc6c-6a28667e8dfa" }, { "cell_type": "code", @@ -1090,7 +1204,7 @@ "source": [ "_ = plt.plot(data['num_doctors_fulltime'], data['num_nurses_fulltime'], 'rx')" ], - "id": "5728f29c-823e-49b1-8b8c-0bb8f4966960" + "id": "aa11ed7a-fc64-476d-b480-58713b5f382b" }, { "cell_type": "markdown", @@ -1099,7 +1213,7 @@ "You may be curious what the arguments we give to `plt.plot` are for, now\n", "is the perfect time to look at the documentation" ], - "id": "6df5d2f1-669f-40da-ba0c-ea542e2c2381" + "id": "2251f702-6670-408e-9e5a-cded7832f981" }, { "cell_type": "code", @@ -1109,7 +1223,7 @@ "source": [ "plt.plot?" ], - "id": "7d9392ac-9b54-402b-a3b0-44c6b0ba1e97" + "id": "022d6b6f-7889-4b60-9d8b-7dd521498274" }, { "cell_type": "markdown", @@ -1119,7 +1233,7 @@ "prevent’s us seeing the detail of the main number of facilities. First\n", "lets identify the facilities with the most nurses." ], - "id": "1bdf3c0a-c2f2-456d-8f75-b4b19e51a7c5" + "id": "8fcdaa41-dcda-43ab-a833-dd8adda188bc" }, { "cell_type": "code", @@ -1129,7 +1243,7 @@ "source": [ "data[data['num_nurses_fulltime']>100]" ], - "id": "e3261207-a8a8-49a2-8a2e-746e73d8538b" + "id": "dc184a57-e11f-46c1-be03-3ed17689aa81" }, { "cell_type": "markdown", @@ -1145,7 +1259,7 @@ "the `num_nurses_fulltime` column in *descending* order we use the\n", "following command." ], - "id": "e41afd9f-b5cc-47ca-a71b-78d5f35d0b38" + "id": "418e8884-ea76-41b7-bd8e-7b73fa50e697" }, { "cell_type": "code", @@ -1155,7 +1269,7 @@ "source": [ "data[data['num_nurses_fulltime']>100].sort_values(by='num_nurses_fulltime', ascending=False)" ], - "id": "18041360-44cb-41ec-ad2d-a8fef1ac4fc1" + "id": "1caff6a5-5b6e-4aa5-9ddf-57e38448c851" }, { "cell_type": "markdown", @@ -1165,7 +1279,7 @@ "outlier with 513 nurses. We can try and determine how much of an outlier\n", "by histograming the data." ], - "id": "dfca77d4-6431-443b-bb4b-05e6c7fac74d" + "id": "37fb3956-6199-40c2-962d-bb7104b5aa7b" }, { "cell_type": "markdown", @@ -1173,7 +1287,7 @@ "source": [ "## Plotting the Data" ], - "id": "d3396811-1766-4ca4-938a-1a6ae0537f72" + "id": "14c4c691-2a3d-42f0-9fdc-a30e078a4fad" }, { "cell_type": "code", @@ -1184,7 +1298,7 @@ "data['num_nurses_fulltime'].hist(bins=20) # histogram the data with 20 bins.\n", "plt.title('Histogram of Number of Nurses')" ], - "id": "f89ce3c4-0b9d-4223-a21f-87145f3d128f" + "id": "210deb54-04b9-4e40-b051-2c9f1a2b8805" }, { "cell_type": "markdown", @@ -1195,7 +1309,7 @@ "hospitals with many nurses. We can try more bins and using a *log* scale\n", "on the $y$-axis." ], - "id": "214d44ab-ee55-479f-bb78-cf20e5ac2419" + "id": "be844f7d-492c-46a8-b4ed-626c7135e993" }, { "cell_type": "code", @@ -1208,7 +1322,7 @@ "ax = plt.gca()\n", "ax.set_yscale('log')" ], - "id": "d6345669-2685-4aa6-80cd-77f4848ab874" + "id": "d679ab89-748a-4195-be8a-da017df3267e" }, { "cell_type": "markdown", @@ -1217,7 +1331,7 @@ "Let’s try and see how the number of nurses relates to the number of\n", "doctors." ], - "id": "d879bfb5-e17e-49d1-ba4c-c5815c2bc61b" + "id": "884094fb-0849-44c1-b180-b5b6081ac3d5" }, { "cell_type": "code", @@ -1234,7 +1348,7 @@ "plt.ylabel('number of nurses')\n", "plt.xlabel('number of doctors')" ], - "id": "6773666f-59bf-4bd5-990e-f1d83b46127e" + "id": "34ed9ece-a308-44db-9597-6d5c620de513" }, { "cell_type": "markdown", @@ -1256,7 +1370,7 @@ "in a different order. The *state* of the program is always as we left it\n", "after running the previous part." ], - "id": "265ca7fc-b836-40d6-9103-465d04c11fa9" + "id": "ffe417d0-e061-493b-96eb-c6c37c2404f4" }, { "cell_type": "markdown", @@ -1271,7 +1385,7 @@ "Let’s use the sum rule to compute the estimate the probability that a\n", "facility has more than two nurses." ], - "id": "3dab814d-fc3c-4408-9e51-bb15f70fff4f" + "id": "ef1d2a0f-aeac-4d7f-8e21-29e970a86bf0" }, { "cell_type": "code", @@ -1285,7 +1399,7 @@ "prob_large = float(large)/float(total_facilities)\n", "print(\"Probability of number of nurses being greather than 2 is:\", prob_large)" ], - "id": "14715933-e771-46ce-938a-0cc623b18447" + "id": "4b228175-c67f-4bd1-a79e-1bce85cec2f3" }, { "cell_type": "markdown", @@ -1307,7 +1421,7 @@ "compare the result, for example to $P(Y>2|X\\leq 1)$ using our empirical\n", "estimate of the probability." ], - "id": "18923529-86e2-4909-b12c-12c93ab326b5" + "id": "7f2abb9f-04f8-459e-ad78-37a375272477" }, { "cell_type": "code", @@ -1320,7 +1434,7 @@ "prob_both_large = large/total_large_doctors\n", "print(\"Probability of number of nurses being greater than 2 given number of doctors is greater than 1 is:\", prob_both_large)" ], - "id": "0f66cb30-fdcc-4876-92d1-a524f0b59ad2" + "id": "a4f98ec8-2db6-40df-9c3c-328b1ff40669" }, { "cell_type": "markdown", @@ -1331,7 +1445,7 @@ "Write code that prints out the probability of nurses being greater than\n", "2 for different numbers of doctors." ], - "id": "5498e307-ea96-47c9-8352-3f3e6f0bce4a" + "id": "056f8e59-3ae9-4ac7-b890-90ed23af2ad1" }, { "cell_type": "code", @@ -1345,7 +1459,7 @@ "\n", "\n" ], - "id": "529664de-8925-4d33-ac75-1c0c6fb68075" + "id": "97f6b270-c722-4c5c-b20d-a676ecc4611c" }, { "cell_type": "markdown", @@ -1367,7 +1481,7 @@ "\n", "" ], - "id": "bb208640-1295-46d0-a275-e9e59ab98ceb" + "id": "38f0ba12-55a8-488e-8d4a-392827f7d607" }, { "cell_type": "markdown", @@ -1379,7 +1493,7 @@ "style=\"\">\\[edit\\]" ], - "id": "30d04a9e-fce1-429e-9a32-07869f3540af" + "id": "bcee5a6d-c5dc-4644-87ce-1bb14972f7e2" }, { "cell_type": "code", @@ -1389,7 +1503,7 @@ "source": [ "from mlai import plot" ], - "id": "142f9e7a-9f0a-4721-a7e1-768f8cd1023a" + "id": "70fd8400-9f82-4030-a737-7ccb84b1e359" }, { "cell_type": "code", @@ -1399,13 +1513,13 @@ "source": [ "plot.prob_diagram(diagrams='./mlai')" ], - "id": "daffc44e-aeaf-43a9-8428-e2d55b181107" + "id": "e2bc97d6-fe59-40bd-accc-4f3f00b6a78b" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Diagram representing the different probabilities, joint,\n", "marginal and conditional. This diagram was inspired by lectures given by\n", @@ -1413,7 +1527,7 @@ "\n", "Inspired by lectures from Christopher Bishop" ], - "id": "8a3558dd-4c73-4663-8eed-f7d0b38ca3d7" + "id": "92f3dbb7-e03f-4156-a963-d2917fa3d829" }, { "cell_type": "markdown", @@ -1427,7 +1541,7 @@ "| Marginal Probability | $\\lim_{N\\rightarrow\\infty}\\frac{n_{X=5}}{N}$ | $P\\left(X=5\\right)$ |\n", "| Conditional Probability | $\\lim_{N\\rightarrow\\infty}\\frac{n_{X=3,Y=4}}{n_{Y=4}}$ | $P\\left(X=3\\vert Y=4\\right)$ |" ], - "id": "d1437037-9630-4bb4-9abe-4860baf7188e" + "id": "7cf60eda-c9c7-4a68-81cf-d5f386fed92f" }, { "cell_type": "markdown", @@ -1459,7 +1573,7 @@ "was over 2 and the number of doctors was 1? Or any other question that\n", "may occur to us. Again we can easily use pandas to ask such questions." ], - "id": "bab8e91e-181e-4960-a9ab-0c45327f3f15" + "id": "67567546-3b2a-4a52-8871-2ba7e37a8d50" }, { "cell_type": "code", @@ -1473,7 +1587,7 @@ "prob_large = float(large)/float(total_facilities)\n", "print(\"Probability of nurses being greater than 2 and number of doctors being\", num_doctors, \"is:\", prob_large)" ], - "id": "89bc497d-024f-43eb-976a-ae3722ccc485" + "id": "1a53e598-22e6-4c8d-b8fa-e9e1a6040912" }, { "cell_type": "markdown", @@ -1501,7 +1615,7 @@ "$$ We can see the relation working in practice for our data above by\n", "computing the different values for $x=1$." ], - "id": "f347a560-03eb-443d-882b-840da4dae403" + "id": "ff5169a9-a092-48da-a117-37957e96a00d" }, { "cell_type": "code", @@ -1519,7 +1633,7 @@ "print(\"P(y|x) is\", p_y_given_x)\n", "print(\"P(y,x) is\", p_y_and_x)" ], - "id": "2adabbb3-e141-4afa-94be-0dbfc91abe4d" + "id": "b4ee6988-1cbb-4c00-b1d6-a7f035f2ea5b" }, { "cell_type": "markdown", @@ -1536,7 +1650,7 @@ "P(y) = \\sum_{x} P(y, x)\n", "$$" ], - "id": "4ad59ef9-c917-407c-830a-9f2e2d91bef2" + "id": "e8e65903-8b10-4198-8d68-a49bb083533c" }, { "cell_type": "markdown", @@ -1547,7 +1661,7 @@ "Write code that computes $P(y)$ by adding $P(y, x)$ for all values of\n", "$x$." ], - "id": "a10980b2-b392-4f1e-9842-f5b7a8f5b6d5" + "id": "255e4c30-e4be-434f-b5bc-7992d02a7c2e" }, { "cell_type": "code", @@ -1561,7 +1675,7 @@ "\n", "\n" ], - "id": "9db26dc8-02bb-4fc5-93e5-7d2a4bcc043a" + "id": "8cdc3ca7-a186-4331-90cc-985be5f6ea72" }, { "cell_type": "markdown", @@ -1581,9 +1695,18 @@ "importance is more to do with the semantic operation that it enables.\n", "Each of these probability distributions represents the answer to a\n", "question we have about the world. Bayes rule (via the product rule)\n", - "tells us how to *invert* the probability." + "tells us how to *invert* the probability.\n", + "\n", + "\n", + "\n", + "Figure: Two barrels: Barrel One holds 20 apples and 4 oranges; Barrel\n", + "Two holds 4 apples and 8 oranges. A barrel is chosen uniformly at random\n", + "and a single fruit is drawn.\n", + "\n", + "Work the numbers on the board if time allows. The same pattern applies\n", + "to coins, medical tests, or any two-hypothesis discrete update." ], - "id": "3e2bc4df-0095-4389-bff0-eeefcf32d1a8" + "id": "95553f2e-f1ac-47d2-ae9c-24b3681ace76" }, { "cell_type": "markdown", @@ -1591,9 +1714,13 @@ "source": [ "## Further Reading\n", "\n", + "\\[edit\\]\n", + "\n", "- Probability distributions: page 12–17 (Section 1.2) of Bishop (2006)" ], - "id": "46eb7470-b3be-4e89-8ba0-40d0c27ea23d" + "id": "ff856931-3f46-4ffd-a09c-095e761c9232" }, { "cell_type": "markdown", @@ -1601,9 +1728,13 @@ "source": [ "## Exercises\n", "\n", - "- Exercise 1.3 of Bishop (2006)" + "- Exercise 1.3 of Bishop (2006)\n", + "\n", + "That last number is Shannon entropy in nats:\n", + "$H(p)=\\mathbb{E}_{p}[-\\log p(y)]=-\\sum_y p(y)\\log p(y)$. Same arithmetic\n", + "under another name." ], - "id": "27fa9e48-ef46-4f27-86ca-81f7093a386e" + "id": "19bdbaa5-af0c-4167-a5b1-23a4d7da7719" }, { "cell_type": "markdown", @@ -1619,7 +1750,7 @@ "we might use the probabilities to do some decision making. Let’s look at\n", "the information data." ], - "id": "c2b6886c-071a-473f-b0df-22887d66c455" + "id": "3d96df7b-6189-48ea-8537-918f07b25a95" }, { "cell_type": "code", @@ -1629,7 +1760,7 @@ "source": [ "data.columns" ], - "id": "8673240a-5bba-414c-8554-f142ce17b1c9" + "id": "158949a3-4902-4658-a38c-30f09a2ca18b" }, { "cell_type": "markdown", @@ -1648,7 +1779,7 @@ "conditional, what should the distribution be over, and what should it be\n", "conditioned on?" ], - "id": "8bc63c96-4685-4dc5-b905-b565f6f4cfeb" + "id": "f6a9bd39-ce5a-4f80-8670-5cd2a8d5945c" }, { "cell_type": "markdown", @@ -1658,7 +1789,7 @@ "\n", "Write your answer to Exercise 5 here" ], - "id": "33a76e4c-e81c-4252-924f-3db0b234b2a5" + "id": "2b3116c1-dda2-4cc0-93ed-68f09085d150" }, { "cell_type": "code", @@ -1669,7 +1800,7 @@ "from IPython.lib.display import YouTubeVideo\n", "YouTubeVideo('GX8VLYUYScM')" ], - "id": "3363e4a8-bb0c-42e2-8ee0-252277ab88df" + "id": "827afe46-2e55-40a0-98dc-4014ec2a9784" }, { "cell_type": "markdown", @@ -1684,7 +1815,7 @@ "- If you are unfamiliar with probabilities you should complete the\n", " following exercises:" ], - "id": "747743d1-1e50-4d47-be68-6d78e5de3959" + "id": "7d26a9b5-cd34-4c4e-876c-68d08ac4bbd5" }, { "cell_type": "markdown", @@ -1695,6 +1826,7 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", "- book: [The Atomic\n", " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", @@ -1704,7 +1836,7 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "28a7c594-5003-427c-9c77-150592e3883a" + "id": "468c1aea-6621-4230-9a9d-f1d1aa7fb1b3" }, { "cell_type": "markdown", @@ -1731,7 +1863,7 @@ "- For material on information theory and KL divergence try Section 1.6\n", " & 1.6.1 (pg 48 onwards) of Bishop (2006)" ], - "id": "775dbbbf-3af9-414b-a657-5505fdb079a2" + "id": "236d2256-55bd-475b-8c24-e0f15632569c" }, { "cell_type": "markdown", @@ -1745,22 +1877,19 @@ "\n", "- Exercise 1.9 of Bishop (2006)" ], - "id": "b28ca0f9-13e9-4c91-ba9c-a257d4caaee6" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "18ce1659-6306-47b7-8fcf-5a1dd2340058" + "id": "eb6cc337-4ac8-4c79-9a04-6766562b44a0" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Bishop, C.M., 2006. Pattern recognition and machine learning. springer.\n", "\n", + "Laplace, P.S., 1814. Essai philosophique sur les probabilités, 2nd ed.\n", + "Courcier, Paris.\n", + "\n", "Rogers, S., Girolami, M., 2011. A first course in machine learning. CRC\n", "Press.\n", "\n", @@ -1768,7 +1897,7 @@ "Millennium Development Goals (OSSAP-MDGs), Columbia University, 2014.\n", "Nigeria NMIS facility database." ], - "id": "802a82b5-6801-4e3b-9fbb-e614ec5d6db4" + "id": "ac1f5538-913c-4d16-9880-382cc68e570a" } ], "nbformat": 4, diff --git a/_notebooks/02-the-data-science-process.ipynb b/_notebooks/02-the-data-science-process.ipynb index c44128fe..22fe38ca 100644 --- a/_notebooks/02-the-data-science-process.ipynb +++ b/_notebooks/02-the-data-science-process.ipynb @@ -6,7 +6,7 @@ "source": [ "# The Data Science Process" ], - "id": "19012f05-4bb7-4bcf-a8e1-a030fd83171e" + "id": "8ad27de1-10c1-4dca-9def-1980ec3ffe4c" }, { "cell_type": "markdown", @@ -15,7 +15,7 @@ "**Abstract**: In this session we look at some of what makes the data\n", "science process different from classical computer science." ], - "id": "76d92046-c2a6-4d19-b1fe-84edd9a06955" + "id": "59616dd7-633f-4b17-bfce-4441fced1569" }, { "cell_type": "markdown", @@ -24,17 +24,20 @@ "$$\n", "$$" ], - "id": "26d93271-6e82-4624-a823-b48be38264a8" + "id": "9017517a-5d9e-4a93-9a47-4b62869efb07" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -42,7 +45,7 @@ "\n", "-->" ], - "id": "0e2e5b35-8900-4e21-87b8-5bb40998f1c1" + "id": "571a2e13-eace-4590-a8b5-dcc742cccd2c" }, { "cell_type": "markdown", @@ -103,9 +106,12 @@ "\n", "You can also check my blog post on [System\n", "Zero](http://inverseprobability.com/2015/12/04/what-kind-of-ai). This\n", - "was also written in 2015." + "was also written in 2015.\n", + "\n", + "See Lawrence (2024) System Zero p. 242-7, 306, 309, 329, 350, 355, 359,\n", + "361, 363, 364." ], - "id": "bffb971f-626c-4fee-bd8f-3b17edacfb40" + "id": "e33860a1-2cd0-46fb-820e-1e19a5829909" }, { "cell_type": "markdown", @@ -125,7 +131,7 @@ "mathematical statistics was developed to help temper this tendency and\n", "understand when we are valid in drawing conclusions from data.\n", "\n", - "\n", + "\n", "\n", "Figure: The trinity of human, data, and computer, and highlights the\n", "modern phenomenon. The communication channel between computer and data\n", @@ -147,9 +153,14 @@ "which must not only deal with the same challenges that mathematical\n", "statistics faced in tempering our tendency to over interpret data but\n", "must also deal with the possibility that the machine has either\n", - "inadvertently or maliciously misrepresented the underlying data." + "inadvertently or maliciously misrepresented the underlying data.\n", + "\n", + "See Lawrence (2024) topography, information p. 34-9, 43-8, 57, 62, 104,\n", + "115-16, 127, 140, 192, 196, 199, 291, 334, 354-5. See Lawrence (2024)\n", + "anthropomorphization (‘anthrox’) p. 30-31, 90-91, 93-4, 100, 132, 148,\n", + "153, 163, 216-17, 239, 276, 326, 342." ], - "id": "01b43019-cafa-45c8-b6c5-1448ac41412d" + "id": "63b10e48-f4ea-4a83-912c-8a67760678fd" }, { "cell_type": "markdown", @@ -167,12 +178,12 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", @@ -196,17 +207,17 @@ "\n", "\n", "\n", - "billion
calculations/s\n", + "calculations/s\n", "\n", "\n", "\n", "\n", - "~100\n", + "~100 billion\n", "\n", "\n", "\n", "\n", - "a billion\n", + "a billion billion\n", "\n", "\n", "\n", @@ -223,7 +234,7 @@ "\n", "\n", "\n", - "5 billion years\n", + "~billion years\n", "\n", "\n", "\n", @@ -238,8 +249,7 @@ "Machine Intelligence” Lawrence (2017a)
\n", "\n", "There is a fundamental limit placed on our intelligence based on our\n", - "ability to communicate. Claude Shannon founded the field of information\n", - "theory. The clever part of this theory is it allows us to separate our\n", + "ability to communicate. Information theory allows us to separate our\n", "measurement of information from what the information pertains to.[1]\n", "\n", "Shannon measured information in bits. One bit of information is the\n", @@ -249,10 +259,10 @@ "language contains 12 bits of information.\n", "\n", "Given typical speaking rates, that gives us an estimate of our ability\n", - "to communicate of around 100 bits per second (Reed and Durlach, 1998).\n", - "Computers on the other hand can communicate much more rapidly. Current\n", - "wired network speeds are around a billion bits per second, ten million\n", - "times faster.\n", + "to communicate of between 10 and 60 bits per second Zheng and Meister\n", + "(2025). Computers on the other hand can communicate much more rapidly.\n", + "Current wired network speeds are around a billion bits per second, ten\n", + "million times faster.\n", "\n", "When it comes to compute though, our best estimates indicate our\n", "computers are slower. A typical modern computer can process make around\n", @@ -269,7 +279,10 @@ "world’s climate in the afternoon. It is a 16-petaflop machine,\n", "processing around 1,000 *trillion* bits per second.\n", "\n", - "\n", + "See Lawrence (2024) embodiment factor p. 13, 29, 35, 79, 87, 105, 197,\n", + "216-217, 249, 269, 353, 369.\n", + "\n", + "\n", "\n", "Figure: The Lotus 49, view from the rear. The Lotus 49 was one of the\n", "last Formula One cars before the introduction of aerodynamic aids.\n", @@ -288,7 +301,7 @@ "when you imagine the F1 car that represents a psyche, think of an F1 car\n", "with bicycle wheels.\n", "\n", - "\n", + "\n", "\n", "Figure: Marcel Renault races a Renault 40 cv during the Paris-Madrid\n", "race, an early Grand Prix, in 1903. Marcel died later in the race after\n", @@ -303,7 +316,7 @@ "with well-matched tires. They can communicate far more fluidly. They are\n", "more efficient, but somehow less extraordinary, less beautiful.\n", "\n", - "\n", + "\n", "\n", "Figure: Caleb McDuff driving for WIX Silence Racing.\n", "\n", @@ -413,7 +426,7 @@ "### Pigeonholing \n", "\n", "\n", - "\n", + "\n", "\n", "Figure: The machine learning systems decomposition process calls for separating a complex task into decomposable separate entities. A process we can think of as pigeonholing.\n", "\n", @@ -468,7 +481,7 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "Figure: The answer to the question which comes first, the chicken or the egg is simple, they co-evolve [@Popper:conjectures63]. Similarly, when we place components together in a complex machine learning system, they will tend to co-evolve and compensate for one another.\n", "\n", @@ -497,7 +510,7 @@ "we store 1.5 Megabytes of data (12 million bits). That would take around\n", "2000 hours, or nearly twelve weeks, to transmit verbally." ], - "id": "999a32c0-269b-438a-88da-4887b3b0fa59" + "id": "e4391fa9-aed7-4327-a919-9a9f93f8ee3a" }, { "cell_type": "markdown", @@ -526,7 +539,7 @@ "Despite data being half the equation, most University courses spend\n", "little to no time on its challenges." ], - "id": "0c0aae0b-ffaa-4091-81af-31abe0cb461a" + "id": "cd0f9e22-8164-4dfb-84f0-9394e4176847" }, { "cell_type": "markdown", @@ -552,7 +565,7 @@ "form. From the computer’s perspective software *is* data. The first wave\n", "of the data crisis was known as the *software crisis*." ], - "id": "e87a3933-14f6-4ad1-a704-d21a3ef70217" + "id": "313f39cb-9e5f-42cf-8329-c5cbda6a9257" }, { "cell_type": "markdown", @@ -611,7 +624,7 @@ "Three principal changes need to occur in response. They are cultural and\n", "infrastructural." ], - "id": "cab25b48-2ebd-4555-af5a-2349539ba1ce" + "id": "41e917d4-fe39-41f5-bc26-1caa5103a67b" }, { "cell_type": "markdown", @@ -639,7 +652,7 @@ "(Sculley et al., 2015), although we are trying to frame the solution\n", "here rather than the problem." ], - "id": "2ba05d55-37c2-4a00-9326-e419d62a57b9" + "id": "888cb2de-3ed0-41b7-ae1e-4366578bf685" }, { "cell_type": "markdown", @@ -655,7 +668,7 @@ "\n", "[^data-readiness-levels]: [Data Readiness Levels](http://inverseprobability.com/b2017/01/12/data-readiness-levels) [@Lawrence-drl17] are an attempt to develop a language around data quality that can bridge the gap between technical solutions and decision makers such as managers and project planners. They are inspired by Technology Readiness Levels which attempt to quantify the readiness of technologies for deployment.-->\\`{=html}" ], - "id": "c3977582-74ea-426a-8a7b-f8fd23a7a732" + "id": "8dfd0e99-7b01-4cc7-ac8c-fdce1a7ac159" }, { "cell_type": "markdown", @@ -678,7 +691,7 @@ "See this blog post on [Data Readiness\n", "Levels](http://inverseprobability.com/2017/01/12/data-readiness-levels)." ], - "id": "9da40d13-f1aa-4fe2-ab59-4c1b5e90e3da" + "id": "e324ad12-3b28-41f3-b9f2-7173a816d2ff" }, { "cell_type": "markdown", @@ -708,7 +721,7 @@ "readiness levels for use in planning documents and costing of data\n", "cleaning and the benefits of reusing cleaned data." ], - "id": "723392e1-d023-4251-b4fc-66979f2e79c9" + "id": "a31bee58-b4f9-4260-92dc-6ccdb81c1a24" }, { "cell_type": "markdown", @@ -725,7 +738,7 @@ "because of the need for programming skills, but the day to day problems\n", "faced are very different." ], - "id": "978b3606-e5ae-4550-a6e4-bae1b6e31e3f" + "id": "2a4a5c0a-fbb8-44e2-93ff-c185a97ee03d" }, { "cell_type": "markdown", @@ -737,7 +750,7 @@ "style=\"\">\\[edit\\]" ], - "id": "c7a8fe77-3659-4413-a3eb-787b821c9f1c" + "id": "aa4ee384-d9d1-40f1-b099-478354800f3e" }, { "cell_type": "markdown", @@ -811,7 +824,7 @@ "engineering mainly focusses on ensuring that code is written without\n", "bugs in the first place." ], - "id": "41c97988-db0f-4c38-9985-0aa9dd8cb1e7" + "id": "9cf56f0d-fc32-4e36-90c4-2292269c104a" }, { "cell_type": "markdown", @@ -819,39 +832,49 @@ "source": [ "### Lessons\n", "\n", - "1. When you begin an analysis, behave as a debugger.\n", - "\n", - "- Write test code as you go. Document those tests and ensure they are\n", - " accessible by others.\n", - "- Understand the landscape of your data. Be prepared to try several\n", - " different approaches to the data set.\n", - "- Be constantly skeptical.\n", - "- Use the best tools available, develop a deep understand how they\n", - " work.\n", - "- Share your experience of what challenges you’re facing. Have others\n", - " (software engineers, fellow data analysts, your manager) review your\n", - " work.\n", - "- Never go straight for the goal: you’d never try and write the API\n", - " call straight away on the discarded hard drive, so why are you\n", - " launching your classification algorithm before visualizing the data?\n", - "- Ensure your analysis is documented and accessible. If your code does\n", - " go wrong in production, you’ll need to be able to retrace to where\n", - " the error crept in.\n", - "\n", - "1. When managing the data science process, don’t treat it as standard\n", - " code development.\n", - "\n", - "- Don’t deploy a traditional agile development pipeline and expect it\n", - " to work the same way it does for standard code development. Think\n", - " about how you handle bugs, think about how you would handle very\n", - " many bugs.\n", - "- Don’t leave the data scientist alone to wade through the mess.\n", - "- Integrate the data analysis with your other team activities. Have\n", - " the software engineers and domain experts work closely with the data\n", - " scientists. This is vital for providing the data scientists with the\n", - " technical support they need, but also managing the expectations of\n", - " the engineers in terms of when and how the data will be able to\n", - " deliver.\n", + "When you begin a data analysis approach it with the mindset of a\n", + "debugger: write test code continuously as you go through your analysis,\n", + "ensure that the tests are documented and accessible to those who need to\n", + "review or build upon your work.\n", + "\n", + "Take time to thouroughly understand the context of your data before\n", + "diving in. Expore multiple approaches and continually revisit your\n", + "initial assumptions. Maintain a healtky skepticism about your results\n", + "through the process and proactively explore potential issues or\n", + "inconsistencies in the data.\n", + "\n", + "Leverage the best tools available and develop a deep understanding of\n", + "how they work. Don’t just treat the tools as black boxes, develop your\n", + "understanding of tehri strengths, limmitations and appropriate use.\n", + "Share challenges you are facing and progress you are making with\n", + "colleagues. Seek regular views from business stakholders, software\n", + "engineers, fellow data scientists and managers. Bringing in their\n", + "different perspectives can help identify potential issues early.\n", + "\n", + "Don’t rush to implementing your end-solution. Just as you wouldn’t\n", + "immediately write an API call for unknown code you’ve found on a\n", + "discarded USB code, you shouldn’t jump straght into running complex\n", + "classsification algorithms before gaining an intuition for your data\n", + "through some basic visualisation and ohter tests. The documentation of\n", + "the analysis process should be such that if issues do arise in\n", + "production the documentation can be used to support tracing back to\n", + "where the problems have originated and rapidly rectifying them.\n", + "\n", + "Always remember that the data science process differs fundamentally from\n", + "standard software development, so traditional agile development\n", + "pipelines may not be appropriate. You have to adapt your processes to\n", + "handle inherrent uncertainty and accomidate the iterative and recursive\n", + "nature of data science work. Think carefully about how you will handle\n", + "not just individual bugs but systematic issues that may emerge from your\n", + "data.\n", + "\n", + "Don’t work in isolation, the complexity of data work means that close\n", + "collaboration with domain experts, software engineers and other data\n", + "scientists is essention. The collaboration serves multiple purposes: it\n", + "provides you with the necessary technical support, helps maintain\n", + "realistic expectations among stakeholders in terms of both outcome and\n", + "delivery time lines. And it ensures that the solutions developed\n", + "actually address the underlying business problem.\n", "\n", "**Recommendation**: Anecdotally, resolving a machine learning challenge\n", "requires 80% of the resource to be focused on the data and perhaps 20%\n", @@ -861,13 +884,13 @@ "provide the understanding of how to data-wrangle. Companies must fill\n", "this gap.\n", "\n", - "\n", + "\n", "\n", "Figure: A reservoir of data has more value if the data is consumable.\n", "The data crisis can only be addressed if we focus on outputs rather than\n", "inputs.\n", "\n", - "\n", + "\n", "\n", "Figure: For a data first architecture we need to clean our data at\n", "source, rather than individually cleaning data for each task. This\n", @@ -880,7 +903,7 @@ "where applicable, but we need to develop them to become *data first*\n", "organizations. Data needs to be cleaned at *output* not at *input*." ], - "id": "42e470da-0507-4c4e-965f-1a1db013c570" + "id": "3f427bf2-d40c-46ca-81b0-f866ac0ebb78" }, { "cell_type": "markdown", @@ -942,7 +965,7 @@ "akin to that which one might have found in a feudal village (in the days\n", "before Houndkirk Moor was enclosed)." ], - "id": "d748f7ca-b1ba-49d2-9684-ba532421914a" + "id": "0654c2c8-3e13-4e92-9c91-15ec20d294f4" }, { "cell_type": "markdown", @@ -950,7 +973,7 @@ "source": [ "# How the GDPR May Help\n", "\n", - "\n", + "\n", "\n", "Figure: The convention for the protection of individuals with regard\n", "to the processing of personal data was opened for signature on 28th\n", @@ -993,7 +1016,7 @@ "those individuals, “good data practice” should become part of the DNA of\n", "the organization." ], - "id": "90b38eed-4649-4a70-b454-95f28665939f" + "id": "131f53d8-dd8e-44fd-9245-392e0487bc4b" }, { "cell_type": "markdown", @@ -1107,6 +1130,8 @@ "\n", "\n", "\n", + "\n", + "\n", "In a streaming architecture we shift from management of services, to management of data streams. Instead of worrying about availability of the services we shift to worrying about the quality of the data those services are producing.\n", "\n", "\n", @@ -1114,31 +1139,23 @@ "\n", "Historically we've been *software first*, this is a necessary but insufficient condition for *data first*. We need to move from software-as-a-service to data-as-a-service, from service oriented architectures to *data oriented architectures*.\n", "\n", + "\n", ":::\n", "\n", "\n", "\n", "::: {.cell .markdown}\n", "\n", - "## Streaming System \n", - "\n", - "Characteristics of a streaming system include a move from *pull* updates to *push* updates, i.e. the computation is driven by a change in the input data rather than the service calling for input data when it decides to run a computation. Streaming systems operate on 'rows' of the data rather than 'columns'. This is because the full column isn't normally available as it changes over time. As an important design principle, the services themselves are stateless, they take their state from the streaming ecosystem. This ensures the inputs and outputs of given computations are easy to declare. As a result, persistence of the data is also handled by the streaming ecosystem and decisions around data retention or recomputation can be taken at the systems level rather than the component level.\n", - "\n", - "\n", + "## Data Oriented Principles \n", "\n", "\n", + "\n", "\n", - "**Recommendation**: We should consider a major re-architecting of systems around our services. In particular we should scope the use of a *streaming architecture* (such as Apache Kafka) that ensures data persistence and enables asynchronous operation of our systems.[^data-orientated-architecture] This would enable the provision of QC streams, and real time dash boards as well as hypervisors.\n", + "Figure: For an overview of data oriented principles see @Cabrera-realworld23.\n", "\n", - "[^data-orientated-architecture]: These approaches are one area of focus for my own team's research. A data first architecture is a prerequisite for efficient deployment of machine learning systems.\n", + "Our work comes from surveying machine learning case studies [@Paleyes-challenges22] identifying the challenges and then surveying papers that focus on deployment [@Cabrera-realworld23] and identifying the principles they use.\n", "\n", - "Importantly a streaming architecture implies the services we build are\n", - "*stateless*, internal state is deployed on streams alongside external\n", - "state. This allows for rapid assessment of other services' data.\n", - "\n", - "\n", - "\n", - "The philosphy of DOA is also possible with more standard data infrastructures, such as SQL data bases, but more work has to be put into place to ensure that book-keeping around data provenance and origin is stored, as well as approaches for snapshotting the data ecosystem.\n", + "The philosphy of DOA is also possible with more standard data infrastructures, such as SQL data bases, but more work has to be put into place to ensure that book-keeping around data provenance and origin is stored, as well as approaches for snapshotting the data ecosystem. Our studies [@Paleyes-flow22] have made a lot of use of flow based programming [@Paleyes-flow22].\n", "\n", "\n", "\n", @@ -1199,7 +1216,7 @@ "\n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " Tom Borchert\n", + " Tom Borchert\n", "\n", " The Principle Engineer behind the Milan architecture has been Tom Borchert.Quoting from [Tom Borchert's blog\n", " on Milan](https://tborchertblog.wordpress.com/2020/02/13/28/):\n", @@ -1239,14 +1256,14 @@ "\n", "\n", "\n", - " \n", + " \n", "\n", " Figure: The Milan Software has a general purpose stream algebra at its core, the Milan IL.\n", "\n", "\n", "\n", "\n", - " \n", + " \n", "\n", " Figure: The Milan Software is designed for building modern AI systems. \n", "\n", @@ -1303,7 +1320,7 @@ " ## Meta Modelling \n", "\n", "\n", - " \n", + " \n", "\n", " Figure: The Emukit software is a set of software tools for emulation and surrogate modeling. \n", "\n", @@ -1363,7 +1380,7 @@ " import numpy as np\n", " import os" ], - "id": "c59d4418-8b5b-4c4d-9137-4a5c78b48124" + "id": "e80de7f4-73ec-4acd-a7c0-1b97dede4a18" }, { "cell_type": "code", @@ -1376,7 +1393,7 @@ "z = np.random.randn(len(days), 1)\n", "x = z.cumsum()+400" ], - "id": "1fc43441-2f78-41bd-a9f5-eb6eb1c1bf9d" + "id": "b2c200bf-d28c-40e3-b611-1ae88de55028" }, { "cell_type": "code", @@ -1389,7 +1406,7 @@ "real = prices.copy()\n", "real['21/5/2019':] = np.NaN" ], - "id": "834e66a7-6df4-42d1-a099-e5bb025c9827" + "id": "58c291e7-cee8-4866-9715-195cceca77e0" }, { "cell_type": "code", @@ -1401,7 +1418,7 @@ "import mlai.plot as plot\n", "import matplotlib.pyplot as plt" ], - "id": "77ac326f-a5b2-47aa-8691-87f9a95a6418" + "id": "eabad3ce-c6bc-4a8f-b1fe-527def6fce21" }, { "cell_type": "code", @@ -1411,7 +1428,7 @@ "source": [ "fontsize=16" ], - "id": "66565ad0-8d7e-4f2b-a45a-52c1ab4e924c" + "id": "8a72698a-2f8e-403c-831e-31ffd45c3f4f" }, { "cell_type": "code", @@ -1425,7 +1442,7 @@ "hypothetical.plot(color='b')\n", "ylim = ax.get_ylim()" ], - "id": "4b04a826-7d6e-41e6-8de1-e6eee772d923" + "id": "be81718e-203c-4e7d-9bfa-1f3e430bc1ea" }, { "cell_type": "markdown", @@ -1434,7 +1451,7 @@ "mlai.write_figure(‘hypothetical-prices.svg’,\n", "directory=‘./data-science/’)" ], - "id": "2df8af72-9ba7-4862-8a75-5b8aae4a57ce" + "id": "82b31b9b-fb5e-48ba-b3b2-1daaacd49ab4" }, { "cell_type": "code", @@ -1448,13 +1465,13 @@ "\n", "mlai.write_figure('real-prices.svg', directory='./data-science/')" ], - "id": "6e730c40-204f-4c0c-8a81-8054e21e82f4" + "id": "f0d6dec0-f5f6-4372-a969-09a9014dc9c4" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Anne has access to the share prices in the black stream but\n", "not in the blue stream. A hypothetical stream is the stream of future\n", @@ -1462,7 +1479,7 @@ "input etc). The need for a model is now exposed in the software\n", "infrastructure" ], - "id": "b73c4af9-9be9-462a-b1b3-bf5a8f060983" + "id": "e37be15b-c5fd-469e-a996-2a5547dcc5df" }, { "cell_type": "markdown", @@ -1496,7 +1513,7 @@ "downside risks are). These three constraints mean that we can only\n", "recover an approximation to the hypothetical." ], - "id": "85f7dbba-9459-4f0f-aa2a-dcb6b8209c28" + "id": "c138ab62-5bbe-4f34-97ee-216b35706a2c" }, { "cell_type": "markdown", @@ -1511,7 +1528,7 @@ "design criteria can be constantly evaluated triggering retraining when\n", "necessary." ], - "id": "61c487c6-03ba-4cdd-bb77-28b58ea04c1e" + "id": "a9726d80-c8e9-4709-b5a7-008d8b09205a" }, { "cell_type": "markdown", @@ -1529,7 +1546,7 @@ "the technical and intellectual debt incurred when maintaining such\n", "systems in a real-world environment.\n", "\n", - "\n", + "\n", "\n", "Figure: SafeBoda is a ride allocation system for Boda Boda drivers.\n", "Let’s imagine the capabilities we need for such an AI system.\n", @@ -1555,7 +1572,7 @@ "the more urgent and important problems that are faced in low-resource\n", "environments.\n", "\n", - "\n", + "\n", "\n", "Figure: Some software components in a ride allocation system. Circled\n", "components are hypothetical, rectangles represent actual data.\n", @@ -1590,7 +1607,7 @@ "\n", "This best guess may well be driven by previous data." ], - "id": "ea01dc41-fe76-4efe-af69-0499d3e0eadd" + "id": "fe92cf76-ae15-4bf4-9863-62541c8bccb9" }, { "cell_type": "markdown", @@ -1602,7 +1619,7 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", + "\n", "\n", "Figure: Service oriented architecture. The data access is buried in\n", "the cost allocation service. Data dependencies of the service cannot be\n", @@ -1614,7 +1631,7 @@ "API that accesses the service they own. Quality of service is maintained\n", "by rigorous standards around *testing* of software systems.\n", "\n", - "\n", + "\n", "\n", "Figure: Data oriented architecture. Now the joins and the updates are\n", "exposed within the streaming ecosystem. We can programmatically\n", @@ -1654,7 +1671,7 @@ "strong emphasis on *interpretability*. ML models are said to be ‘black\n", "boxes’ which make decisions that are not explainable.[1]\n", "\n", - "\n", + "\n", "\n", "Figure: Data-oriented programing. There is a requirement for an\n", "estimate of the driver allocation to give a rough cost estimate before\n", @@ -1699,7 +1716,7 @@ "[1] See for example [“The Dark Secret at the Heart of AI” in Technology\n", "Review](https://www.technologyreview.com/s/604087/the-dark-secret-at-the-heart-of-ai/)." ], - "id": "9b680ef6-9e55-4045-8516-f9a25dfe591f" + "id": "2c6a383d-27b7-4cf2-b858-3aa8bc23d2a3" }, { "cell_type": "markdown", @@ -1723,7 +1740,7 @@ "\n", "–\\> \\`\\`\\`" ], - "id": "106f17ec-3678-48b0-b46d-4cb87c4783d2" + "id": "99caf450-e38b-4ae1-bbf1-27b00342b3b9" }, { "cell_type": "markdown", @@ -1738,7 +1755,7 @@ "- Assess\n", "- Process" ], - "id": "0a64f8ca-c994-4052-8b0e-5cfcae01e5b1" + "id": "a7a246a7-a230-4c13-b846-aeec8ccf31a6" }, { "cell_type": "markdown", @@ -1749,7 +1766,7 @@ "See the template repository here:\n", "" ], - "id": "bcd82382-1de5-4a86-bfb9-a00b6821a911" + "id": "ccd97907-c4db-4729-bbe7-935111f82e37" }, { "cell_type": "markdown", @@ -1761,7 +1778,7 @@ "\n", "- Chapter 1 of Lawrence (2024)" ], - "id": "efe621cf-334c-40b0-8686-4a305a179a53" + "id": "35a3d0c0-f019-4c68-8f88-62cc6fe15a2b" }, { "cell_type": "markdown", @@ -1772,6 +1789,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -1779,28 +1799,24 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "15084748-273a-46a3-af71-cac8b23c7a8d" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "ccf6dcc7-3593-4022-9915-534e6eb215cf" + "id": "3f8f1277-c5cd-4b3e-93ec-30573b30b9c2" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Ananthanarayanan, R., Esser, S.K., Simon, H.D., Modha, D.S., 2009. The\n", "cat is out of the bag: Cortical simulations with $10^9$ neurons,\n", "$10^{13}$ synapses, in: Proceedings of the Conference on High\n", "Performance Computing Networking, Storage and Analysis - SC ’09.\n", "\n", "\n", - "Lawrence, N.D., 2024. The atomic human: Understanding ourselves in the\n", - "age of AI. Allen Lane.\n", + "Lawrence, N.D., 2024. [The atomic human: Understanding ourselves in the\n", + "age of\n", + "AI](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248).\n", + "Allen Lane.\n", "\n", "Lawrence, N.D., 2017b. Data readiness levels. ArXiv.\n", "\n", @@ -1821,9 +1837,13 @@ "systems](http://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf),\n", "in: Cortes, C., Lawrence, N.D., Lee, D.D., Sugiyama, M., Garnett, R.\n", "(Eds.), Advances in Neural Information Processing Systems 28. Curran\n", - "Associates, Inc., pp. 2503–2511." + "Associates, Inc., pp. 2503–2511.\n", + "\n", + "Zheng, J., Meister, M., 2025. The unbearable slowness of being: Why do\n", + "we live at 10 bit/s? Neuron 113, 192–204.\n", + "" ], - "id": "330d0168-344e-4d70-b16d-6658a2791685" + "id": "0cc59e1d-3224-4633-bce8-603bb0361d4c" } ], "nbformat": 4, diff --git a/_notebooks/03-matrix-factorization.ipynb b/_notebooks/03-matrix-factorization.ipynb index 040ad07b..a4aa0803 100644 --- a/_notebooks/03-matrix-factorization.ipynb +++ b/_notebooks/03-matrix-factorization.ipynb @@ -6,7 +6,7 @@ "source": [ "# Objective Functions: A Simple Example with Matrix Factorisation" ], - "id": "c9b60cd1-e101-45ef-af4a-9c2e6b538342" + "id": "1c0dae61-8317-4654-92ad-d0c0d7cfa77a" }, { "cell_type": "markdown", @@ -16,7 +16,7 @@ "functions and show how they can be used in a simple recommender system\n", "based on *matrix factorisation*." ], - "id": "77b9c81c-13f6-4148-9d0e-d91c10a0bf6a" + "id": "62ff1f88-ab38-4780-a684-e4c9d96807fe" }, { "cell_type": "markdown", @@ -25,17 +25,20 @@ "$$\n", "$$" ], - "id": "f927fef1-0e9f-489a-8e15-421207ef434d" + "id": "0b78ccb6-41bf-42b6-9b48-3abf16cd2518" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -43,7 +46,7 @@ "\n", "-->" ], - "id": "29db58ee-94f1-40a3-ac77-0e80e205472e" + "id": "6f194672-696d-4587-8a2c-34999b59b9b3" }, { "cell_type": "markdown", @@ -55,7 +58,7 @@ "style=\"\">\\[edit\\]" ], - "id": "259758c7-3ed7-4a92-8399-4797dbbffd6c" + "id": "5bac8cad-4ebd-4060-806d-171fcfe729e0" }, { "cell_type": "code", @@ -64,9 +67,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "6a46b7a6-0c71-48ed-b4f9-25506ff4b05e" + "id": "7872c159-6f01-4a5a-babf-e3753010692a" }, { "cell_type": "markdown", @@ -77,7 +90,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "9307b5a3-f011-4e78-8367-089817554391" + "id": "4abe2130-2b83-49b9-bea6-0955678a2787" }, { "cell_type": "markdown", @@ -94,7 +107,56 @@ "\n", "The software can be installed using" ], - "id": "f34ed269-4b35-429f-b262-03b7c958bc0c" + "id": "70bbc892-f94d-4ff7-bc1d-1d15a2d0e5d6" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "6f0cec10-86fc-44eb-bc55-6dea496422d3" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" + ], + "id": "a2d618b7-c151-4363-979d-2666eee5cb02" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "7e13ea86-1793-4146-8ac5-90f56fb0c31a" }, { "cell_type": "code", @@ -102,9 +164,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "%system {cmd}" ], - "id": "aba526a5-bab4-4830-8ca2-633f6161c34e" + "id": "55f031d0-ad33-4384-8461-79fb28f6c1dc" }, { "cell_type": "markdown", @@ -117,7 +179,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "25e06d7b-69bb-43d7-94c2-6bc8feb67cad" + "id": "dcfc0354-a9a3-4d99-8650-93eb734f508f" }, { "cell_type": "code", @@ -127,7 +189,7 @@ "source": [ "import notutils" ], - "id": "12e16ef9-8ed6-49ab-bc04-c17663001493" + "id": "5cefcd5d-c8e1-45fb-bb09-5f1b0ec293d5" }, { "cell_type": "markdown", @@ -149,7 +211,17 @@ "\n", "The software can be installed using" ], - "id": "39006e9a-a47f-4c26-a309-5e73e00edc26" + "id": "df769571-86cb-4f62-bbe8-f24bc4cb6af1" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "ce916158-ef6c-4715-a5b2-7879952a896e" }, { "cell_type": "code", @@ -157,9 +229,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "%system {cmd}" ], - "id": "c47a2131-af1c-4016-8caf-ef1c273af718" + "id": "d87127a8-dbe8-4729-b1e2-391675a7e7a1" }, { "cell_type": "markdown", @@ -171,7 +243,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "ab0da258-7f04-41f7-bf5e-901556dd2e5f" + "id": "07f31e3c-c828-4675-9411-66f6d15c1ae1" }, { "cell_type": "code", @@ -181,7 +253,7 @@ "source": [ "import pods" ], - "id": "ef6b854c-7d9a-4535-ae9b-37d82882901c" + "id": "23f8d6a5-2f56-46da-867e-ffb79a5e5514" }, { "cell_type": "markdown", @@ -199,7 +271,7 @@ "\n", "The software can be installed using" ], - "id": "3edf172a-86d7-438d-8dbf-bb3789d0d8f5" + "id": "adafaa54-0891-450e-9748-7b81f07ad526" }, { "cell_type": "code", @@ -207,9 +279,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "cmd = install_command('mlai')" ], - "id": "abb4a651-40e9-4d80-acd9-e33e9a7add6d" + "id": "797b9b35-4c03-4640-b0a3-16f1a7f4be67" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "8cdee872-ad80-4f5a-8df6-155236b7eb1d" }, { "cell_type": "markdown", @@ -221,7 +303,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "1661abf0-93bd-406a-8335-15b1f31f6b96" + "id": "ca2e8972-8559-4179-84a2-f2fd1ba9aab8" }, { "cell_type": "code", @@ -229,9 +311,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "9df9acc6-930d-4be8-ba6d-b1e7be377a33" + "id": "b1a8e2cb-7cc1-4834-81e0-1cf4ebfcedef" }, { "cell_type": "markdown", @@ -239,7 +322,7 @@ "source": [ "for loss functions." ], - "id": "0f83a077-c977-4807-aa65-c27b3950f49a" + "id": "71507def-991d-40c8-b7ca-24b3733d105d" }, { "cell_type": "markdown", @@ -279,17 +362,17 @@ "you to do is to install some software we’ve written for sharing\n", "information across google documents." ], - "id": "2cb87632-fe44-4a93-8699-96a62d01bb50" + "id": "2384a12c-5398-4cd6-b2b2-9e6b3a2cabf0" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Movie Body Count Example\n", + "# Movie Body Count Data Example\n", "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", "There is a crisis in the movie industry, deaths are occuring on a\n", "massive scale. In every feature film the body count is tolling up. But\n", @@ -309,15 +392,31 @@ "is available at [a github\n", "repository](https://github.com/sjmgarnier/R-vs-%20Python/tree/master/Deadliest%20movies%20scrape/code).\n", "Git is a version control system and github is a website that hosts code\n", - "that can be accessed through git. By sharing the code publicly through\n", - "github, the authors are licensing the code publicly and allowing you to\n", - "access and edit it. As well as accessing the code via github you can\n", - "also [download the zip\n", - "file](https://github.com/sjmgarnier/R-vs-Python/archive/master.zip).\n", + "that can be accessed through git. As well as accessing the code via\n", + "github you can also [download the zip\n", + "file](https://github.com/sjmgarnier/R-vs-Python/archive/master.zip)." + ], + "id": "fb278db5-843c-4560-9bb0-b6292531333e" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Movie Violence Data\n", + "\n", + "\\[edit\\]\n", "\n", - "For ease of use we’ve packaged this data set in the `pods` library" + "This is a data set created by Simon Garnier and Rany Olson for exploring\n", + "the differences between R and Python for data science. The data contains\n", + "information about different movies augmented by estimates about how many\n", + "on-screen deaths are contained in the movie. The data is craped from\n", + ". The data contains the following\n", + "featuers for each movie: `Year`, `Body_Count`, `MPAA_Rating`, `Genre`,\n", + "`Director`, `Actors`, `Length_Minutes`, `IMDB_Rating`." ], - "id": "74a4908c-7856-4d20-b04c-b42760c150a3" + "id": "bde3d18d-6b41-48c2-94b6-01c2f4017950" }, { "cell_type": "code", @@ -325,19 +424,30 @@ "metadata": {}, "outputs": [], "source": [ - "data = pods.datasets.movie_body_count()['Y']\n", - "data.head()" + "import pods" ], - "id": "cfcf80f7-a723-4c07-93df-1b34602175af" + "id": "a88b48ad-1e12-45ff-809e-35979419c9e9" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.movie_body_count()\n", + "movies = data['Y']" + ], + "id": "b8d1dde2-6879-41cf-93a8-f50d1dc46f36" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Once it is loaded in the data can be summarized using the `describe`\n", - "method in pandas." + "The data is provided to us in the form of a pandas data frame, we can\n", + "see the features we’re provided with by inspecting the columns of the\n", + "data frame." ], - "id": "c407ef3d-ab1e-4141-b66d-3b56fd96db52" + "id": "8e1d5ec1-b6b8-4bf2-9339-0c084049528d" }, { "cell_type": "code", @@ -345,14 +455,17 @@ "metadata": {}, "outputs": [], "source": [ - "data.describe()" + "print(', '.join(movies.columns))" ], - "id": "7d7860df-adcc-4f08-a0aa-3441635864f7" + "id": "d5fa31c3-0b2d-44b7-ba38-299da2a3ba12" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "Once it is loaded in the data can be summarized using the `describe`\n", + "method in pandas.\n", + "\n", "In jupyter and jupyter notebook it is possible to see a list of all\n", "possible functions and attributes by typing the name of the object\n", "followed by `.` for example in the above case if we type\n", @@ -364,7 +477,7 @@ "following the name with a question mark. This will open a box with\n", "documentation at the bottom which can be closed with the x button." ], - "id": "6d4a9ead-a3fb-438b-8adc-16e7b1119926" + "id": "e6a46919-47f9-4343-af5b-b46879dd9390" }, { "cell_type": "code", @@ -374,7 +487,7 @@ "source": [ "data.describe?" ], - "id": "c2cd7e41-fa04-4fd4-b2b8-28ac7cad7c43" + "id": "da9d2d69-efb5-4e14-898e-1e973c206dc7" }, { "cell_type": "markdown", @@ -389,7 +502,19 @@ "counts, means, standard deviations and percentiles for the values in\n", "those columns. To access a column directly we can write" ], - "id": "2a54341b-dd24-4a9f-a661-9a85622a1c40" + "id": "a0161e04-b157-4903-8d47-15a51adcdfef" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting the Data\n", + "\n", + "\\[edit\\]" + ], + "id": "5ef86ce4-babf-4134-86a7-7321473bd065" }, { "cell_type": "code", @@ -397,10 +522,10 @@ "metadata": {}, "outputs": [], "source": [ - "print(data['Year'])\n", - "#print(data['Body_Count'])" + "print(movies['Year'])\n", + "#print(movies['Body_Count'])" ], - "id": "55413c53-f7a9-4fc0-aa4a-6aa89d9eec72" + "id": "c2b3376d-4c7c-4e8d-975a-03d2af9c214e" }, { "cell_type": "markdown", @@ -409,7 +534,7 @@ "This shows the number of deaths per film across the years. We can plot\n", "the data as follows." ], - "id": "19683e9f-002a-457d-97f6-7efea6e093e5" + "id": "695e6902-6a62-441d-a6fe-577668c082cb" }, { "cell_type": "code", @@ -421,7 +546,7 @@ "%matplotlib inline \n", "import matplotlib.pyplot as plt # this imports the plotting library in python" ], - "id": "737f6db3-9699-4517-827b-bb2be4634a92" + "id": "e1b57b7e-1dbd-46a5-9d75-85e8ba27fb16" }, { "cell_type": "code", @@ -429,9 +554,9 @@ "metadata": {}, "outputs": [], "source": [ - "plt.plot(data['Year'], data['Body_Count'], 'rx')" + "plt.plot(movies['Year'], movies['Body_Count'], 'rx')" ], - "id": "84c7611c-cac8-4e47-9a6c-3de01df99330" + "id": "2299bd22-7542-4cbb-988c-3f2c34ee2a91" }, { "cell_type": "markdown", @@ -440,7 +565,7 @@ "You may be curious what the arguments we give to `plt.plot` are for, now\n", "is the perfect time to look at the documentation" ], - "id": "6b8a04d3-13f6-497b-8330-f7f56454c735" + "id": "83157d83-5d79-4c3c-8b37-df5cd1df8b86" }, { "cell_type": "code", @@ -450,7 +575,7 @@ "source": [ "plt.plot?" ], - "id": "79286e4c-f0ee-4dbe-a67a-61576ec64468" + "id": "8b843c71-2b47-4cb1-9860-c9dd004c8910" }, { "cell_type": "markdown", @@ -460,7 +585,7 @@ "us seeing the detail of the main body of films. First lets identify the\n", "films with the most deaths." ], - "id": "1d600f6a-fbdd-4732-a94f-3acbe9adf0f5" + "id": "adcb17f1-0f8a-44be-ad54-e60f1934fd8e" }, { "cell_type": "code", @@ -468,15 +593,15 @@ "metadata": {}, "outputs": [], "source": [ - "data[data['Body_Count']>200]" + "movies[movies['Body_Count']>200]" ], - "id": "1c664d3d-53bb-4474-b96e-4e8119e5fa12" + "id": "1f04c8a5-af26-4995-a9f1-a16cf4b8923f" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Here we are using the command `data['Kill_Count']>200` to index the\n", + "Here we are using the command `movies['Kill_Count']>200` to index the\n", "films in the pandas data frame which have over 200 deaths. To sort them\n", "in order we can also use the `sort` command. The result of this command\n", "on its own is a data series of `True` and `False` values. However, when\n", @@ -485,7 +610,7 @@ "also sort the result. To sort the result by the values in the\n", "`Kill_Count` column in *descending* order we use the following command." ], - "id": "5b6ad93c-73eb-4dc4-a9ec-d5b052b597a0" + "id": "1c535d71-335f-4a0d-a9b6-ab9c996ad125" }, { "cell_type": "code", @@ -493,9 +618,9 @@ "metadata": {}, "outputs": [], "source": [ - "data[data['Body_Count']>200].sort_values(by='Body_Count', ascending=False)" + "movies[movies['Body_Count']>200].sort_values(by='Body_Count', ascending=False)" ], - "id": "e3aa7f9b-cf76-4314-899b-9c4976415f3f" + "id": "41bf40a8-a4f9-47e0-9e63-79aed8ce1dbf" }, { "cell_type": "markdown", @@ -505,7 +630,7 @@ "large number of kills. We can try and determine how much of an outlier\n", "by histograming the data." ], - "id": "fb752f6b-0fc8-49b7-8fed-a52392dab4dd" + "id": "03910625-d544-45a9-9c4b-0c45da670912" }, { "cell_type": "markdown", @@ -513,7 +638,7 @@ "source": [ "## Plotting the Data" ], - "id": "f8579f2b-c161-4c63-a5db-18439a1c9bb5" + "id": "78e729b9-c7cf-4dc0-8213-03a217dcc142" }, { "cell_type": "code", @@ -521,10 +646,10 @@ "metadata": {}, "outputs": [], "source": [ - "data['Body_Count'].hist(bins=20) # histogram the data with 20 bins.\n", + "movies['Body_Count'].hist(bins=20) # histogram the data with 20 bins.\n", "plt.title('Histogram of Film Kill Count')" ], - "id": "d7592e8a-30fe-4f0c-86d2-5d88732e5283" + "id": "8b9f5116-382d-4361-a0b5-8e44913bd9be" }, { "cell_type": "markdown", @@ -537,7 +662,7 @@ "python. What is the `pylab` library and how does it relate to the other\n", "libraries?" ], - "id": "1d3684c8-d51a-46d0-b673-c548cb8e8b56" + "id": "047d7afc-a4ee-4205-b9bd-d0bab3b9ed97" }, { "cell_type": "markdown", @@ -550,7 +675,7 @@ "We could try and remove these outliers, but another approach would be\n", "plot the logarithm of the counts against the year." ], - "id": "1bcfebbf-b060-4e4f-868f-b5acf2955763" + "id": "2164f633-ff55-40e8-93cf-00375b038d9f" }, { "cell_type": "code", @@ -558,7 +683,7 @@ "metadata": {}, "outputs": [], "source": [ - "plt.plot(data['Year'], data['Body_Count'], 'rx')\n", + "plt.plot(movies['Year'], movies['Body_Count'], 'rx')\n", "ax = plt.gca() # obtain a handle to the current axis\n", "ax.set_yscale('log') # use a logarithmic death scale\n", "# give the plot some titles and labels\n", @@ -566,7 +691,7 @@ "plt.ylabel('deaths')\n", "plt.xlabel('year')" ], - "id": "3e6ddc6a-03a4-4565-83fe-d3a398ab880c" + "id": "e346d49c-cf55-440f-aca3-37048aeb340b" }, { "cell_type": "markdown", @@ -588,7 +713,7 @@ "in a different order. The *state* of the program is always as we left it\n", "after running the previous part." ], - "id": "3046c1e6-7872-44c3-bf96-c8eca614d275" + "id": "f579c5cf-6aab-4a4b-93e6-8b0000edaf55" }, { "cell_type": "markdown", @@ -600,7 +725,7 @@ "use it without consequence? If you are given data by a fellow researcher\n", "can you publish that data on line?" ], - "id": "1d582022-a129-4f30-9c6b-a5b3a0aeba48" + "id": "b97d8203-af87-481c-9080-f2389b14fbb8" }, { "cell_type": "markdown", @@ -610,7 +735,7 @@ "\n", "Write your answer to Exercise 2 here" ], - "id": "16a6ef4e-bab6-4c59-85ea-c525a3579656" + "id": "e6c73cfa-c017-44c3-b9f5-a178580336ef" }, { "cell_type": "markdown", @@ -660,7 +785,7 @@ "and later on Egypt. This image from Wikipedia shows a map of the\n", "Cathaginian Empire which fell after fighting with Rome.\n", "\n", - "\n", + "\n", "\n", "Figure: The Carthaginian Empire at its peak.\n", "\n", @@ -683,7 +808,7 @@ "best like to sit in the library to access easily the books they are most\n", "interested in." ], - "id": "56ecde9f-ba91-4b6b-935c-8eb14ee38b7b" + "id": "b2aa40f2-8369-42db-b90c-99fbf3b88879" }, { "cell_type": "markdown", @@ -712,7 +837,7 @@ " formalize it. In our library interpretation, that’s what we need to\n", " do next." ], - "id": "dbf8d744-551e-4ff9-ad54-6f6b30023f12" + "id": "095b1b46-be99-4e6a-9dee-d311984de453" }, { "cell_type": "markdown", @@ -755,79 +880,91 @@ "try and obtain such a lay out. To do this we first need to obtain the\n", "data." ], - "id": "29723ad6-aab6-4396-9904-14d4aa8d12e5" + "id": "e49379a1-24a1-42af-a808-e7b470ef057f" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Obtaining the Data\n", + "## Collecting the Data\n", "\n", "\\[edit\\]\n", "\n", - "We are using a functionality of the Open Data Science software library\n", - "to obtain the data. This functionality involves some prewritten code\n", - "which distributes to each of you a google spreadsheet where you can rate\n", - "movies that you’ve seen. For completeness the code follows. Try and read\n", - "and understand the code, but don’t run it! It has already been run\n", - "centrally by me.\n", + "This functionality involves some prewritten code which connects to a\n", + "central write server, where you can rate movies that you’ve seen.\n", "\n", - "``` python\n", - "import pods\n", - "import pandas as pd\n", - "import numpy as np\n", - "user_data = pd.DataFrame(index=movies.index, columns=['title', 'year', 'rating', 'prediction'])\n", - "user_data['title']=movies.Film\n", - "user_data['year']=movies.Year\n", - "\n", - "accumulator=pods.lab.distributor(spreadsheet_title='COM4509/6509 Movie Ratings:', user_sep='\\t')\n", - "# function to apply to data before giving it to user \n", - "# Select 50 movies at random and order them according to year.\n", - "max_movies = 50\n", - "function = lambda x: x.loc[np.random.permutation(x.index)[:max_movies]].sort(columns='year')\n", - "accumulator.write(data_frame=user_data, comment='Film Ratings', \n", - " function=function)\n", - "accumulator.write_comment('Rate Movie Here (score 1-5)', row=1, column=4)\n", - "accumulator.share(share_type='writer', send_notifications=True)\n", - "```\n", - "\n", - "In your Google drive account you should be able to find a spreadsheet\n", - "called ‘COM4509/6509 Movie Ratings: Your Name’. In the spreadsheet You\n", - "should find a number of movies listed according to year. In the column\n", - "titled ‘rating’ please place your rating using a score of 1-5 for *any*\n", - "of the movies you’ve seen from the list.\n", - "\n", - "Once you have placed your ratings we can download the data from your\n", - "spreadsheets to a central file where the ratings of the whole class can\n", - "be stored. We will build an algorithm on these ratings and use them to\n", - "make predictions for the rest of the class. Firstly, here’s the code for\n", - "reading the ratings from each of the spreadsheets.\n", - "\n", - "``` python\n", - "import numpy as np\n", + "Once you have placed your ratings, we can download the data to a file.\n", + "We will build an algorithm on these ratings and use them to make\n", + "predictions for the rest of the class. Firstly, here’s the code for\n", + "which movies we will be rating. Remember to change your name." + ], + "id": "d07aa3e9-659c-4065-aa17-bd23b6649f98" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "import pandas as pd\n", "import os\n", - "\n", - "accumulator = pods.lab.distributor(user_sep='\\t')\n", - "data = accumulator.read(usecols=['rating'], dtype={'index':int, 'rating':np.float64}, header=2)\n", - "\n", - "for user in data:\n", - " if data[user].rating.count()>0: # only add user if they rated something\n", - " # add a new field to movies with that user's ratings.\n", - " movies[user] = data[user]['rating']\n", - "\n", - "# Store the csv on disk where it will be shared through dropbox.\n", - "movies.to_csv(os.path.join(pods.lab.class_dir,'movies.csv'), skiprows=1, index_label='ResponseID')\n", - "```\n", - "\n", - "Now we will convert our data structure into a form that is appropriate\n", - "for processing. We will convert the `movies` object into a data base\n", - "which contains the movie, the user and the score using the following\n", - "command." + "movies_list = [\n", + " # Kenyan films\n", + " \"Nairobi Half Life\",\n", + " \"Rafiki\",\n", + " \"Sincerely Daisy\",\n", + " \"Supa Modo\",\n", + " \"Kati Kati\",\n", + " \"Mvera\",\n", + " \"NAWI: Dear Future Me\",\n", + " \"BOBO\",\n", + " \"The First Grader\",\n", + " \"Mother Mother\",\n", + " \"The Wall Street Boy\",\n", + " \"40 Sticks\",\n", + " \"TeraStorm\",\n", + "\n", + " # Global all-time blockbuster hits\n", + " \"Avatar\",\n", + " \"Avengers: Endgame\",\n", + " \"Titanic\",\n", + " \"The Lion King\",\n", + " \"Frozen\",\n", + " \"Spider-Man: No Way Home\",\n", + " \"Black Panther\",\n", + " \"Top Gun: Maverick\",\n", + " \"The Dark Knight\",\n", + " \"Inception\",\n", + " \"Jurassic World\",\n", + " \"Minions: The Rise of Gru\",\n", + " \"The Fate of the Furious\",\n", + " \"Doctor Strange in the Multiverse of Madness\",\n", + " \"Furious 7\",\n", + " \"Star Wars: The Force Awakens\",\n", + " \"The Avengers\",\n", + " \"Beauty and the Beast\",\n", + " \"Harry Potter and the Deathly Hallows – Part 2\",\n", + " \"Joker\",\n", + " \"Aladdin\",\n", + " \"Aquaman\"\n", + "]\n", + "\n", + "name = 'todo' # TODO: your name here - this will be somewhat public so you might not want to use full real names, nickname is fine\n", + "assert name!='todo'" + ], + "id": "c49649b6-79e9-49e8-bdef-dbdbeff0d1c6" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The below code starts an interactive Python Widget, which will submit\n", + "your responses. There’s also code for reading the ratings after they’ve\n", + "been submitted." ], - "id": "9e3806b2-f653-44c2-9299-c030067c96cc" + "id": "4334a0a5-8dbf-4560-a13f-13ab57131b94" }, { "cell_type": "code", @@ -835,10 +972,116 @@ "metadata": {}, "outputs": [], "source": [ - "import pandas as pd\n", - "import os" + "import random, requests, ipywidgets as W\n", + "from ipywidgets import Widget\n", + "from IPython.display import display, clear_output\n", + "import json\n", + "\n", + "# Config\n", + "topic, target = \"mlfc_movie_ratings\", 25\n", + "url = f\"https://ntfy.sh/{topic}\"\n", + "cache = \"rated_movies_cache.txt\"\n", + "\n", + "def responses(topic=topic, sep=\"|||\", since=\"all\", n=None):\n", + " url = f\"https://ntfy.sh/{topic}/json?poll=1&since={since}\"\n", + " rows = []\n", + " with requests.get(url, stream=True) as r:\n", + " for line in r.iter_lines():\n", + " if not line: continue\n", + " e = json.loads(line)\n", + " if e.get(\"event\") != \"message\": continue\n", + " p = (e.get(\"message\") or \"\").split(sep)\n", + " rows.append({\"user\": p[0], \"film\": p[1], \"rating\": pd.to_numeric(p[2], errors=\"coerce\")} if len(p)==3 else {\"raw\": e.get(\"message\")})\n", + " if n and len(rows) >= n: break\n", + " return pd.DataFrame(rows)\n", + "\n", + "def start_movie_rater(movies_list=movies_list, topic=topic, name=name, target=25):\n", + " import random, requests\n", + " import ipywidgets as W\n", + " from IPython.display import display\n", + "\n", + " url = f\"https://ntfy.sh/{topic}\"\n", + " movies = list(dict.fromkeys(map(str, movies_list)))\n", + " seen = set() # in-memory only\n", + " rated, cur, running = 0, None, True\n", + "\n", + " def rem(): return [m for m in movies if m not in seen]\n", + " def mark_seen(m):\n", + " if m not in seen:\n", + " seen.add(m)\n", + " def send(m, r):\n", + " try:\n", + " requests.post(url, data=f\"{name}|||{m}|||{r}\".encode(\"utf-8\"), timeout=3)\n", + " except Exception:\n", + " pass\n", + "\n", + " status = W.HTML()\n", + " btns = [W.Button(description=str(i)) for i in range(1, 11)]\n", + " skip = W.Button(description=\"Skip\", button_style=\"warning\")\n", + " finish = W.Button(description=\"Finish\", button_style=\"success\")\n", + " box = W.VBox([status, W.HBox(btns + [skip, finish])])\n", + "\n", + " def end(msg):\n", + " nonlocal running\n", + " running = False\n", + " for w in btns + [skip, finish]:\n", + " w.disabled = True\n", + " status.value = msg\n", + "\n", + " def pick_next():\n", + " nonlocal cur\n", + " r = rem()\n", + " cur = random.choice(r) if r else None\n", + " status.value = f\"Rate ({rated}/{target}): {cur}\" if cur else \"\"\n", + "\n", + " def on_rate(b):\n", + " nonlocal rated\n", + " if not running or cur is None:\n", + " return\n", + " send(cur, int(b.description))\n", + " mark_seen(cur)\n", + " rated += 1\n", + " if rated >= target:\n", + " end(f\"Done. Rated {rated}.\")\n", + " return\n", + " pick_next()\n", + " if cur is None:\n", + " end(f\"No unseen movies left. Rated {rated}.\")\n", + "\n", + " def on_skip(_):\n", + " if not running or cur is None:\n", + " return\n", + " mark_seen(cur)\n", + " pick_next()\n", + " if cur is None:\n", + " end(f\"No unseen movies left. Rated {rated}.\")\n", + "\n", + " def on_finish(_):\n", + " end(f\"Finished. Rated {rated}.\")\n", + "\n", + " for b in btns:\n", + " b.on_click(on_rate)\n", + " skip.on_click(on_skip)\n", + " finish.on_click(on_finish)\n", + "\n", + " display(box)\n", + " pick_next()\n", + " if cur is None:\n", + " end(\"No unseen movies to rate.\")" + ], + "id": "ef5f16be-a3bb-4ede-884d-c6b85d768bde" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# run this cell to provide your ratings\n", + "Widget.close_all()\n", + "start_movie_rater(target=5)" ], - "id": "539bdd57-30ef-43e2-9b1f-8bdda1849769" + "id": "fb19c838-c40e-4250-8154-ef8a54a8d059" }, { "cell_type": "code", @@ -846,12 +1089,19 @@ "metadata": {}, "outputs": [], "source": [ - "# uncomment the line below if you are doing this task by self study.\n", - "pods.access.download_url('https://raw.githubusercontent.com/lawrennd/datasets_mirror/main/movie_recommender/movies.csv', store_directory = 'class_movie', save_name='movies.csv')\n", - "#pods.access.download_url('https://www.dropbox.com/s/s6gqvp9b383b59y/movies.csv?dl=0&raw=1', store_directory = 'class_movie', save_name='movies.csv')\n", - "movies = pd.read_csv(os.path.join('class_movie', 'movies.csv'),encoding='latin-1').set_index('index')" + "# run this cell to collect everyone's responses\n", + "Y = responses()\n", + "Y" ], - "id": "3df54912-df31-4e20-a1d4-81fba879c74e" + "id": "e2af67c1-1605-40c0-9b45-9a0f35fef426" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Processing the Data" + ], + "id": "08112d9e-c7ea-4a71-b97d-08f1553eafde" }, { "cell_type": "markdown", @@ -859,11 +1109,10 @@ "source": [ "### Exercise 3\n", "\n", - "The movies data is now in a data frame which contains one column for\n", - "each user rating the movie. There are some entries that contain `NaN`.\n", - "What does the `NaN` mean in this context?" + "What is a pivot table? What does the `pandas` command `pd.pivot_table`\n", + "do? Subsequently, what does `pd.melt` do?" ], - "id": "b8f07991-5c5c-40bf-8711-3cb60604b71b" + "id": "3405494e-8d00-481a-8d7a-6cab6edf04d6" }, { "cell_type": "markdown", @@ -873,18 +1122,18 @@ "\n", "Write your answer to Exercise 3 here" ], - "id": "1ab359b7-7118-4101-a6ca-9ba221f2a5f3" + "id": "43187f62-4100-4318-b552-e8b1cecc60de" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Processing the Data\n", + "### Exercise 4\n", "\n", - "We will now prepare the data set for processing. To do this we are going\n", - "to conver the data into a new format using the `melt` command." + "Convert the above DataFrame `Y` to a pivot table, and then back again to\n", + "its current form." ], - "id": "f404cffa-1f11-47e0-8030-578175483a7f" + "id": "01185a4b-be1e-44ba-b460-4a874b239638" }, { "cell_type": "code", @@ -892,23 +1141,13 @@ "metadata": {}, "outputs": [], "source": [ - "user_names = list(set(movies.columns)-set(movies.columns[:9]))\n", - "Y = pd.melt(movies.reset_index(), id_vars=['Film', 'index'], \n", - " var_name='user', value_name='rating', \n", - " value_vars=user_names)\n", - "Y = Y.dropna(axis=0)" - ], - "id": "5c804676-92e4-47b7-8c88-4ff94db25d88" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## What is a pivot table? What does the `pandas` command\n", + "# Write your answer to Exercise 4 here\n", + "\n", "\n", - "`pd.melt` do?" + "\n", + "\n" ], - "id": "9432c2ff-5fc3-4fdc-90ca-08f1ca683387" + "id": "bf6e3091-0b29-425b-99d7-0b31a0e70e8f" }, { "cell_type": "markdown", @@ -947,7 +1186,7 @@ "user, $y_{i,j}$. To ensure this we will use a least squares objective\n", "function for all user ratings." ], - "id": "b306725e-3ec1-41a9-8e63-6991623591a6" + "id": "eebee54c-fc62-4536-b9e5-29ed757b2b4d" }, { "cell_type": "markdown", @@ -976,7 +1215,7 @@ "associated with the $j$th item and $m$ is the total number of items in\n", "the data set." ], - "id": "9f7e9cc1-b7e0-4fac-8389-f1864297454b" + "id": "b4c00cb5-3038-442b-9bc3-88985ab0409d" }, { "cell_type": "markdown", @@ -1002,13 +1241,13 @@ "$$ Similarly each parameter $v_{i,j}$ needs to be updated according to\n", "its gradient." ], - "id": "bbefd26b-8950-4148-91eb-1aa233e9ecb9" + "id": "ed0cd7c4-76ed-47ac-9144-f1d2f6ba1443" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 4\n", + "### Exercise 5\n", "\n", "What is the gradient of the objective function with respect to\n", "$v_{k, \\ell}$? Write your answer in the box below, and explain which\n", @@ -1016,17 +1255,17 @@ "to justify your answer in class by oral questioning. Create a function\n", "for computing this gradient that is used in the algorithm below." ], - "id": "687f5ff9-d6f4-4180-82c3-b2c9536d455f" + "id": "e9440c87-36ce-4cd3-ae30-11c1a13ba1c2" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 4 Answer\n", + "### Exercise 5 Answer\n", "\n", - "Write your answer to Exercise 4 here" + "Write your answer to Exercise 5 here" ], - "id": "b2febdfa-88da-4d28-8a9a-bd4687f2af55" + "id": "3f1aa14d-1508-4fa1-89c5-128434d95903" }, { "cell_type": "markdown", @@ -1038,7 +1277,7 @@ "function by subtacting the gradient of the objective function from the\n", "parameters." ], - "id": "0e715e12-c521-48be-8018-a58cccff7c07" + "id": "6cad537a-cea3-4158-81b4-f0db4556bbf5" }, { "cell_type": "markdown", @@ -1050,7 +1289,7 @@ "and the matrix $\\mathbf{V}$. Let’s create them as `pandas` data frames\n", "and initialise them randomly with small values." ], - "id": "74bcde7b-5974-46f0-809a-6555be6d50ef" + "id": "2bc61744-bd0f-47cf-9c96-61a53356ccc3" }, { "cell_type": "code", @@ -1060,7 +1299,7 @@ "source": [ "import numpy as np" ], - "id": "4621880d-da88-47a6-96c2-1860902543c2" + "id": "3802a006-deef-4edd-95f5-958221f5dc02" }, { "cell_type": "code", @@ -1070,10 +1309,10 @@ "source": [ "q = 2 # the dimension of our map of the 'library'\n", "learn_rate = 0.01\n", - "U = pd.DataFrame(np.random.normal(size=(len(user_names), q))*0.001, index=user_names)\n", - "V = pd.DataFrame(np.random.normal(size=(len(movies.index), q))*0.001, index=movies.index)" + "U = pd.DataFrame(np.random.normal(size=(len(Y['user'].unique()), q))*0.001, index=Y['user'].unique())\n", + "V = pd.DataFrame(np.random.normal(size=(len(Y['film'].unique()), q))*0.001, index=Y['film'].unique())" ], - "id": "75b1dc22-421e-4c26-af8a-a393355e09ad" + "id": "19361202-ced7-4088-867f-c4388298e3a2" }, { "cell_type": "markdown", @@ -1083,7 +1322,7 @@ "them predictions. Have a think about why this might be a good idea\n", "(*Hint*: what will the gradients be if we don’t subtract the mean?)." ], - "id": "7beb01e7-1496-49d1-b1be-76753553eda3" + "id": "a3e7c421-f80f-4514-bed9-9412a4d8d7b0" }, { "cell_type": "code", @@ -1093,7 +1332,7 @@ "source": [ "Y['rating'] -= Y['rating'].mean()" ], - "id": "580cc8a1-7589-4aa3-8d32-7e64f5610a87" + "id": "68406f5a-5d6a-4710-833c-cb5fb17df56f" }, { "cell_type": "markdown", @@ -1103,7 +1342,7 @@ "First we define a function for the gradient of the objective and the\n", "objective function itself." ], - "id": "01ea1162-4124-4902-aa48-1f1eb534bb07" + "id": "8728fc0f-5dd1-41b0-a91b-50609d98a06a" }, { "cell_type": "code", @@ -1116,7 +1355,7 @@ " gV = pd.DataFrame(np.zeros((V.shape)), index=V.index)\n", " obj = 0.\n", " for ind, series in Y.iterrows():\n", - " film = series['index']\n", + " film = series['film']\n", " user = series['user']\n", " rating = series['rating']\n", " prediction = np.dot(U.loc[user], V.loc[film]) # vTu\n", @@ -1126,7 +1365,7 @@ " gV.loc[film] += 2*diff*U.loc[user]\n", " return obj, gU, gV" ], - "id": "abcdf152-dfb5-4d1a-9235-9ac0907281d8" + "id": "2cfd2eee-8de5-4ce1-b523-ef92c6c12f7b" }, { "cell_type": "markdown", @@ -1135,7 +1374,7 @@ "Now we can write our simple optimisation route. This allows us to\n", "observe the objective function as the optimization proceeds." ], - "id": "aeccbfcb-fadf-4693-abf7-c643430ed1f3" + "id": "c131196c-9322-4436-a475-4c8e9c9e5396" }, { "cell_type": "code", @@ -1151,18 +1390,100 @@ " U -= learn_rate*gU\n", " V -= learn_rate*gV" ], - "id": "6d126c2b-46bf-44ce-aab0-2f07572d68d4" + "id": "f5b9467e-2d5e-4b79-a392-5d688e6739f8" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 5\n", + "Next we’ll use `plotly` to create a simple interactive visualisation of\n", + "the users and the films. In our map, we expect similar films to be close\n", + "to each other and similar users to be close to each other. Let’s look at\n", + "the user map first, which is stored in the dataframe `U`." + ], + "id": "8c509874-de90-45b6-921b-9343a415625b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import plotly.express as px" + ], + "id": "9fb50817-13cf-4a0b-bfc6-5286bfa02641" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The U dataframe with users as index, coordinates in columns 0 and 1\n", + "fig = px.scatter(\n", + " x=U[0], \n", + " y=U[1],\n", + " hover_name=U.index, # User names appear on hover\n", + " title=\"User Map Visualisation\"\n", + ")\n", + "fig.show()" + ], + "id": "00021d3c-5466-429e-97ba-7742444a6b8a" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let’s look at our map of the films which is stored in a dataframe\n", + "`V`." + ], + "id": "9d8e9264-94b0-42d0-8932-471f7047c90c" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import plotly.express as px" + ], + "id": "0696b9b3-df19-4b80-ade6-3724fe9ac543" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# The V dataframe with films as index, coordinates in columns 0 and 1\n", + "fig = px.scatter(\n", + " x=V[0], \n", + " y=V[1],\n", + " hover_name=V.index, # Film names appear on hover\n", + " title=\"Film Map Visualisation\"\n", + ")\n", + "fig.show()" + ], + "id": "95bd1c29-057d-495d-ab30-440e9f024709" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "That gives us a first attempt at visualising this rating data." + ], + "id": "5c575855-ca48-43e9-a0af-33cdbf37f93c" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 6\n", "\n", "What happens as you increase the number of iterations? What happens if\n", "you increase the learning rate?" ], - "id": "02063247-331a-4298-ac02-00700b83d84d" + "id": "b34e6267-fe1f-4bac-9e65-7f41c54f3b13" }, { "cell_type": "code", @@ -1170,13 +1491,13 @@ "metadata": {}, "outputs": [], "source": [ - "# Write your answer to Exercise 5 here\n", + "# Write your answer to Exercise 6 here\n", "\n", "\n", "\n", "\n" ], - "id": "ad5ee29b-84ed-440d-adbe-c2dd74c9230e" + "id": "f2ab8f7f-5dff-4aaa-9e17-b68d6ead218f" }, { "cell_type": "markdown", @@ -1184,28 +1505,29 @@ "source": [ "## Stochastic Gradient Descent or Robbins Monroe Algorithm\n", "\n", - "Stochastic gradient descent (Robbins and Monro, 1951) involves updating\n", - "separating each gradient update according to each separate observation,\n", - "rather than summing over them all. It is an approximate optimization\n", - "method, but it has proven convergence under certain conditions and can\n", - "be much faster in practice. It is used widely by internet companies for\n", - "doing machine learning in practice. For example, Facebook’s ad ranking\n", - "algorithm uses stochastic gradient descent." + "{In we saw how stochastic gradient descent (Robbins and Monro, 1951)\n", + "involves updating separating each gradient update according to each\n", + "separate observation, rather than summing over them all. It is an\n", + "approximate optimization method, but it has proven convergence under\n", + "certain conditions and can be much faster in practice. It is used widely\n", + "by internet companies for doing machine learning in practice. For\n", + "example, Facebook’s ad ranking algorithm uses stochastic gradient\n", + "descent." ], - "id": "c453a292-8d28-420f-aa31-320aea98ccf0" + "id": "673cb49f-273d-4ce6-9167-2d93db3679db" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 6\n", + "### Exercise 7\n", "\n", "Create a stochastic gradient descent version of the algorithm. Monitor\n", "the objective function after every 1000 updates to ensure that it is\n", "decreasing. When you have finished, plot the movie map and the user map\n", "in two dimensions. Label the plots with the name of the movie or user." ], - "id": "9b381a30-17bc-4fa7-91da-7556069c58f2" + "id": "1f95bf89-da45-48f6-9383-5230bc71c935" }, { "cell_type": "code", @@ -1213,13 +1535,13 @@ "metadata": {}, "outputs": [], "source": [ - "# Write your answer to Exercise 6 here\n", + "# Write your answer to Exercise 7 here\n", "\n", "\n", "\n", "\n" ], - "id": "4ab0746f-a889-4ff2-a49d-698d63a704ed" + "id": "ef9140c8-9e89-409a-aac9-06a075c409c7" }, { "cell_type": "markdown", @@ -1231,7 +1553,7 @@ "given user, $i$, for a given film, $j$, by simply taking the inner\n", "product between their vectors $\\mathbf{u}_i$ and $\\mathbf{v}_j$." ], - "id": "70a86065-fa09-40a4-8ea9-dd79cc43bb24" + "id": "b4900c56-1d21-413d-82d5-c741d68c3b6d" }, { "cell_type": "markdown", @@ -1259,7 +1581,7 @@ "justified by the amount of data we have? Can we really understand the\n", "facets of a film that only has at most three or four ratings?" ], - "id": "1bc50bb7-cfa6-413f-9e9a-aa20187e9da6" + "id": "6fc630f8-1255-487e-9db5-b792a411c224" }, { "cell_type": "markdown", @@ -1280,7 +1602,7 @@ "Now we’ll download the movielens 100k data and see if we can extract\n", "information about these movies." ], - "id": "13e27599-9762-48bd-863b-d3d92c5e1475" + "id": "0bd1f23d-4117-4d4b-a43d-8bc2167b1ab5" }, { "cell_type": "code", @@ -1290,7 +1612,7 @@ "source": [ "import pods" ], - "id": "e00b65a1-dd93-4655-99ca-11d0db43701e" + "id": "286a18d6-3264-4f86-aa9a-f4be73c5b76c" }, { "cell_type": "code", @@ -1301,18 +1623,18 @@ "d = pods.datasets.movielens100k()\n", "Y=d['Y']" ], - "id": "e884843b-df70-45d7-a9e1-5435191171b5" + "id": "b5e5125a-be97-4693-80e6-8f290c5e6c51" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 7\n", + "### Exercise 8\n", "\n", "Use stochastic gradient descent to make a movie map for the movielens\n", "data. Plot the map of the movies when you are finished." ], - "id": "8add4714-025c-4813-9629-ffdbb40094d7" + "id": "fa183994-9105-4eb5-8977-0b12e5aa6cb2" }, { "cell_type": "code", @@ -1320,13 +1642,13 @@ "metadata": {}, "outputs": [], "source": [ - "# Write your answer to Exercise 7 here\n", + "# Write your answer to Exercise 8 here\n", "\n", "\n", "\n", "\n" ], - "id": "5ed652a2-835d-46be-b539-f5a29340cb6a" + "id": "dfe9fb6a-2f34-4c2b-9e65-5d983150df70" }, { "cell_type": "markdown", @@ -1337,6 +1659,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -1344,27 +1669,21 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "c44f9c94-9a1a-46a9-ae23-a8c25b5f4cef" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "17b58422-1e8b-43d7-8f8b-594d320a50e3" + "id": "25cbaf6c-4499-4c6b-b725-3b170421a9c6" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Robbins, H., Monro, S., 1951. A stochastic approximation method. Annals\n", "of Mathematical Statistics 22, 400–407.\n", "\n", "Rogers, S., Girolami, M., 2011. A first course in machine learning. CRC\n", "Press." ], - "id": "8d01084e-fc6f-4f5b-8e51-1414e1178a62" + "id": "73422f2f-63a8-458b-b710-38737a44ed9d" } ], "nbformat": 4, diff --git a/_notebooks/04-linear-regression.ipynb b/_notebooks/04-linear-regression.ipynb index 5123509e..7b77f711 100644 --- a/_notebooks/04-linear-regression.ipynb +++ b/_notebooks/04-linear-regression.ipynb @@ -6,7 +6,7 @@ "source": [ "# Linear Algebra and Linear Regression" ], - "id": "7e032e88-7738-482f-8220-410536d8187e" + "id": "d002ef7c-a3e8-4e95-bf97-076977e3ba88" }, { "cell_type": "markdown", @@ -17,7 +17,7 @@ "motivate the importance of *linear algebra* by showing how much faster\n", "we can complete a linear regression using linear algebra." ], - "id": "b2873964-952b-4593-ac2f-cea30f7e81b9" + "id": "002dc4ba-b26f-4f8c-b5c5-3998e22bf5fa" }, { "cell_type": "markdown", @@ -26,17 +26,20 @@ "$$\n", "$$" ], - "id": "677ae830-cb83-46d9-8bc2-b5dd72d052e2" + "id": "6187a66d-a25f-4e73-b102-6955de74153a" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -44,7 +47,7 @@ "\n", "-->" ], - "id": "be58b849-6f5e-4545-aaad-be78fe1a6c8f" + "id": "781464de-7034-4708-8b2f-9acf7e513945" }, { "cell_type": "markdown", @@ -56,7 +59,7 @@ "style=\"\">\\[edit\\]" ], - "id": "0b457ddb-6da1-4960-9172-1b6774b7be2b" + "id": "7ca495d7-ed08-491a-8d69-dbc8a32572e4" }, { "cell_type": "code", @@ -65,9 +68,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "27905119-12dd-488d-abdf-8647819fea0c" + "id": "60bc95c9-5c3a-446a-b9e6-0d210dfe4a5e" }, { "cell_type": "markdown", @@ -78,7 +91,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "ddf5f6e9-3d27-4948-a121-77e6e54fbb18" + "id": "f826c68d-97a6-4169-9f3f-919d1d60f840" }, { "cell_type": "markdown", @@ -95,7 +108,17 @@ "\n", "The software can be installed using" ], - "id": "997f5a70-1166-4731-9008-e06556b030ce" + "id": "c378613a-b231-469f-bb1b-e3dd9875e901" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "9411268f-31ed-44fa-974a-7d53639754db" }, { "cell_type": "code", @@ -103,9 +126,48 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" ], - "id": "3a030ce2-1b51-4755-bac7-f107d1df7b2c" + "id": "dbca7eda-50ed-496f-8be2-87ef5b6ca200" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "5aa1b8ac-ac24-405d-80bd-20c8611f66ac" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "93a7d46f-4892-46c7-a5ab-6b979e8d6ca2" }, { "cell_type": "markdown", @@ -118,7 +180,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "a9ce3422-8ea6-40b0-bc67-2f90a62ff841" + "id": "8e6cb24b-457a-42be-b07a-cb158524c293" }, { "cell_type": "code", @@ -128,7 +190,7 @@ "source": [ "import notutils" ], - "id": "8d2e90e1-6d6b-422c-bdf2-4a8b7ddd0ff7" + "id": "f178589a-ba28-4bb3-b1ed-83f2fb07132d" }, { "cell_type": "markdown", @@ -150,7 +212,17 @@ "\n", "The software can be installed using" ], - "id": "a5cccfda-a7cd-4cf0-9a78-f5ce101a912e" + "id": "3429b84c-90b6-49bd-bbaa-e95400fcb05c" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "5f4fdc66-1387-408e-bdc3-79550ebc5d16" }, { "cell_type": "code", @@ -158,9 +230,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "%system {cmd}" ], - "id": "ebea6f8c-f1b1-4027-910c-159398f4eada" + "id": "e86989e3-6148-4aef-9ecc-8069eb39537b" }, { "cell_type": "markdown", @@ -172,7 +244,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "70ab3a90-ebd0-43a7-a22e-f4511901397a" + "id": "2316a1a6-c606-460d-9025-49a68a30616d" }, { "cell_type": "code", @@ -182,7 +254,7 @@ "source": [ "import pods" ], - "id": "80bf0a8e-9e6e-4719-8374-5449b520263b" + "id": "e0543935-deaa-46b8-be7f-906d23dbf864" }, { "cell_type": "markdown", @@ -200,7 +272,17 @@ "\n", "The software can be installed using" ], - "id": "9119814f-3b75-4019-83eb-396501a2b87d" + "id": "ae6f01df-17e2-40d4-9e9a-a42acdbeda82" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('mlai')" + ], + "id": "f17c63b2-15ea-4980-bb79-07b00232c496" }, { "cell_type": "code", @@ -208,9 +290,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "%system {cmd}" ], - "id": "f9717547-71f6-4ca2-83a3-2f752604b59f" + "id": "380648ab-64f3-4bc0-8c7c-3ae0e1e94f5a" }, { "cell_type": "markdown", @@ -222,7 +304,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "210994f2-1531-40ef-82d2-955fb08332bb" + "id": "249f82ab-d5ac-4bf1-87fc-82d423b1a125" }, { "cell_type": "code", @@ -230,9 +312,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "0cc2a373-6062-4444-bc89-e22c3c036a87" + "id": "7b8d2b8c-5edf-488e-9017-b1a25bf6df27" }, { "cell_type": "markdown", @@ -245,61 +328,160 @@ " stochastic gradients.\n", "- This time: explore least squares for regression." ], - "id": "a37cef1d-37c2-407d-a0b9-cf88a3c1c4ea" + "id": "42ea7fef-e6c3-4076-9c45-42639f3b7279" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Regression Examples\n", + "## Expected Loss\n", "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", + "\n", + "Our objective function so far has been the negative log likelihood,\n", + "which we have minimized (via the sum of squares error) to obtain our\n", + "model. However, there is an alternative perspective on an objective\n", + "function, that of a *loss function*. A loss function is a cost function\n", + "associated with the penalty you might need to pay for a particular\n", + "incorrect decision. One approach to machine learning involves specifying\n", + "a loss function and considering how much a particular model is likely to\n", + "cost us across its lifetime. We can represent this with an expectation.\n", + "If our loss function is given as $L(y, x, \\mathbf{ w})$ for a particular\n", + "model that predicts $y$ given $x$ and $\\mathbf{ w}$ then we are\n", + "interested in minimizing the expected loss under the likely distribution\n", + "of $y$ and $x$. To understand this formally we define the *true*\n", + "distribution of the data samples, $y$, $x$. This is a particular\n", + "distribution that we don’t typically have access to. To represent it we\n", + "define a variant of the letter ‘P’, $\\mathbb{P}(y, x)$. If we genuinely\n", + "pay $L(y, x, \\mathbf{ w})$ for every mistake we make, and the future\n", + "test data is genuinely drawn from $\\mathbb{P}(y, x)$ then we can define\n", + "our expected loss, or risk, to be, $$\n", + "R(\\mathbf{ w}) = \\int L(y, x, \\mathbf{ w}) \\mathbb{P}(y, x) \\text{d}y\n", + "\\text{d}x.\n", + "$$ Of course, in practice, this value can’t be computed *but* it serves\n", + "as a reminder of what it is we are aiming to minimise and under certain\n", + "circumstances it can be approximated.\n", + "\n", + "Here we have used $L(\\cdot)$ as our notation for the *loss* function.\n", + "The cost we expect to pay for making errors. This is a different\n", + "interpretation of the objective function, $E(\\cdot)$, fromt he idea of\n", + "negative log likelihood we see in Gauss’s probabilistic approach." + ], + "id": "ac48ce22-92f3-40c7-bf09-671406b0ea61" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sample-Based Approximations\n", + "\n", + "A sample-based approximation to an expectation involves replacing the\n", + "true expectation with a sum over samples from the distribution. $$\n", + "\\int f(z) p(z) \\text{d}z\\approx \\frac{1}{s}\\sum_{i=1}^s f(z_i).\n", + "$$ if $\\{z_i\\}_{i=1}^s$ are a set of $s$ independent and identically\n", + "distributed samples from the distribution $p(z)$. This approximation\n", + "becomes better for larger $s$, although the *rate of convergence* to the\n", + "true integral will be very dependent on the distribution $p(z)$ *and*\n", + "the function $f(z)$.\n", + "\n", + "That said, this means we can approximate our true integral with the sum,\n", + "$$\n", + "R(\\mathbf{ w}) \\approx \\frac{1}{n}\\sum_{i=1}^{n} L(y_i, x_i, \\mathbf{ w}).\n", + "$$\n", "\n", - "Regression involves predicting a real value, $y_i$, given an input\n", - "vector, $\\mathbf{ x}_i$. For example, the Tecator data involves\n", - "predicting the quality of meat given spectral measurements. Or in\n", - "radiocarbon dating, the C14 calibration curve maps from radiocarbon age\n", - "to age measured through a back-trace of tree rings. Regression has also\n", - "been used to predict the quality of board game moves given expert rated\n", - "training data." + "{" ], - "id": "a578dc1e-5053-44ec-844a-57007e4fd0e6" + "id": "6d70631b-bdcc-4bf1-986c-547d91364051" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Olympic 100m Data\n", + "## Laplace’s Idea\n", "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", - "\n", - "\n", - "` `{=html} \n", - "\n", - "
\n", + "Laplace had the idea to augment the observations by noise, that is\n", + "equivalent to considering a probability density whose mean is given by\n", + "the *prediction function*\n", + "$$p\\left(y_i|x_i\\right)=\\frac{1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{\\left(y_i-f\\left(x_i\\right)\\right)^{2}}{2\\sigma^2}\\right).$$\n", + "\n", + "This is known as *stochastic process*. It is a function that is\n", + "corrupted by noise. Laplace didn’t suggest the Gaussian density for that\n", + "purpose, that was an innovation from Carl Friederich Gauss, which is\n", + "what gives the Gaussian density its name." + ], + "id": "51fb3455-83fc-489c-83d2-a834acc35cd9" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Height as a Function of Weight\n", + "\n", + "In the standard Gaussian, parameterized by mean and variance, make the\n", + "mean a linear function of an *input*.\n", + "\n", + "This leads to a regression model. $$\n", + "\\begin{align*}\n", + " y_i=&f\\left(x_i\\right)+\\epsilon_i,\\\\\n", + " \\epsilon_i \\sim & \\mathscr{N}\\left(0,\\sigma^2\\right).\n", + " \\end{align*}\n", + "$$\n", "\n", - "- Gold medal times for Olympic 100 m runners since 1896.\n", - "- One of a number of Olypmic data sets collected by Rogers and\n", - " Girolami (2011). `{=html} `\n", + "Assume $y_i$ is height and $x_i$ is weight." + ], + "id": "06715b23-d375-45e7-88a6-2a0b8bef8c47" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Olympic Marathon Data\n", "\n", - "\n", + "\\[edit\\]\n", "\n", - "Figure: Start of the 2012 London 100m race. *Image from Wikimedia\n", - "Commons* \n", + "- Gold medal times for Olympic Marathon since 1896.\n", + "- Marathons before 1924 didn’t have a standardized distance.\n", + "- Present results using pace per km.\n", + "- In 1904 Marathon was badly organized leading to very slow times.\n", "\n", - "
\n", + "\n", + "Image from [Wikimedia\n", + "Commons](https://commons.wikimedia.org/wiki/File:Eliud_Kipchoge_in_Berlin_-_2015_(cropped).jpg)\n", "\n", - "The first thing we will do is load a standard data set for regression\n", - "modelling. The data consists of the pace of Olympic Gold Medal 100m\n", - "winners for the Olympics from 1896 to present. First we load in the data\n", + "The Olympic Marathon data is a standard dataset for regression\n", + "modelling. The data consists of the pace of Olympic Gold Medal Marathon\n", + "winners for the Olympics from 1896 to present. Let’s load in the data\n", "and plot." ], - "id": "9fe37be5-6df3-4f8d-9278-b54d715c3a5b" + "id": "3a709f44-b9bb-409b-95d0-9c1519cf47ff" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "5e50e262-c11e-4f10-94a3-9af97fc62ecb" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "cf04b656-e267-425a-b882-952b897bd85b" }, { "cell_type": "code", @@ -310,7 +492,7 @@ "import numpy as np\n", "import pods" ], - "id": "6546062d-79f4-4630-8397-2897795a58b4" + "id": "9441734b-dc5f-4ed4-a338-aaba3c47c0b5" }, { "cell_type": "code", @@ -318,14 +500,15 @@ "metadata": {}, "outputs": [], "source": [ - "data = pods.datasets.olympic_100m_men()\n", + "data = pods.datasets.olympic_marathon_men()\n", "x = data['X']\n", "y = data['Y']\n", "\n", "offset = y.mean()\n", - "scale = np.sqrt(y.var())" + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale" ], - "id": "08fffa93-9463-4cad-b3b8-1cf086f58927" + "id": "edefb64f-b27c-49bb-90d8-ac7584566297" }, { "cell_type": "code", @@ -337,19 +520,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "9ac9aa5f-2ac3-43c4-a1f5-68b53b330e62" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import pods\n", - "from matplotlib import pyplot as plt\n", - "%matplotlib inline" - ], - "id": "11be9984-1da4-4288-a860-fa0c5ebdf1cf" + "id": "98d445ae-0da1-4454-a012-4beaa812a737" }, { "cell_type": "code", @@ -358,9 +529,8 @@ "outputs": [], "source": [ "\n", - "xlim = (1875,2030)\n", - "ylim = (9, 12)\n", - "yhat = (y-offset)/scale\n", + "xlim = (1876,2044)\n", + "ylim = (2.5, 6.5)\n", "\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "_ = ax.plot(x, y, 'r.',markersize=10)\n", @@ -369,67 +539,63 @@ "ax.set_xlim(xlim)\n", "ax.set_ylim(ylim)\n", "\n", - "mlai.write_figure(filename='olympic-100m.svg', \n", + "mlai.write_figure(filename='olympic-marathon.svg', \n", " directory='./datasets')" ], - "id": "4c8b4068-ef06-4a53-a010-4f542410420e" + "id": "15bf04a0-3f33-4dff-b3dc-cd7b23af0212" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", + "\n", + "Figure: Olympic marathon pace times since 1896.\n", "\n", - "Figure: Olympic 100m wining times since 1896." + "Things to notice about the data include the outlier in 1904, in that\n", + "year the Olympics was in St Louis, USA. Organizational problems and\n", + "challenges with dust kicked up by the cars following the race meant that\n", + "participants got lost, and only very few participants completed. More\n", + "recent years see more consistently quick marathons." ], - "id": "564eb299-2622-48c5-bc19-83b2227d7016" + "id": "7cea6fae-94bf-4de8-bd2f-cd04a73d5734" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Olympic Marathon Data\n", + "## Running Example: Olympic Marathons\n", "\n", "\\[edit\\]\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "- Gold medal times for Olympic Marathon since 1896.\n", - "- Marathons before 1924 didn’t have a standardized distance.\n", - "- Present results using pace per km.\n", - "- In 1904 Marathon was badly organized leading to very slow times.\n", - "\n", - "\n", - "\n", - "\n", - "Image from Wikimedia Commons \n", + "style=\"\">edit\\]\n", "\n", - "
\n", + "Note that `x` and `y` are not `pandas` data frames for this example,\n", + "they are just arrays of dimensionality $n\\times 1$, where $n$ is the\n", + "number of data.\n", "\n", - "The first thing we will do is load a standard data set for regression\n", - "modelling. The data consists of the pace of Olympic Gold Medal Marathon\n", - "winners for the Olympics from 1896 to present. Let’s load in the data\n", - "and plot." + "The aim of this lab is to have you coding linear regression in python.\n", + "We will do it in two ways, once using iterative updates (coordinate\n", + "ascent) and then using linear algebra. The linear algebra approach will\n", + "not only work much better, it is also easy to extend to multiple input\n", + "linear regression and *non-linear* regression using basis functions." ], - "id": "caa8a997-3075-423e-8b77-319cc05ef039" + "id": "d2b41003-af13-4b64-b9cc-88b197facdb8" }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "import numpy as np\n", - "import pods" + "## Maximum Likelihood: Iterative Solution\n", + "\n", + "Now we will take the maximum likelihood approach we derived in the\n", + "lecture to fit a line, $y_i=mx_i + c$, to the data you’ve plotted. We\n", + "are trying to minimize the error function, $$\n", + "E(m, c) = \\sum_{i=1}^n(y_i-mx_i-c)^2,\n", + "$$ with respect to $m$, $c$ and $\\sigma^2$. We can start with an initial\n", + "guess for $m$," ], - "id": "e226a9a7-bb70-4397-a275-e95a65e81a7c" + "id": "5078a208-58a9-41f7-ab96-6a81f51392ad" }, { "cell_type": "code", @@ -437,27 +603,19 @@ "metadata": {}, "outputs": [], "source": [ - "data = pods.datasets.olympic_marathon_men()\n", - "x = data['X']\n", - "y = data['Y']\n", - "\n", - "offset = y.mean()\n", - "scale = np.sqrt(y.var())\n", - "yhat = (y - offset)/scale" + "m = -0.4\n", + "c = 80" ], - "id": "0c9e889f-609a-49ba-9513-ca29565e5bdf" + "id": "0183c827-8702-4884-9d65-f3da9effadf7" }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "import mlai.plot as plot\n", - "import mlai" + "Then we use the maximum likelihood update to find an estimate for the\n", + "offset, $c$." ], - "id": "5d1bccff-de44-4e9e-a5d3-8474f6176abb" + "id": "ae68326d-5da9-4302-acb2-06463ce1f51e" }, { "cell_type": "code", @@ -465,37 +623,25 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "xlim = (1875,2030)\n", - "ylim = (2.5, 6.5)\n", - "\n", - "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", - "_ = ax.plot(x, y, 'r.',markersize=10)\n", - "ax.set_xlabel('year', fontsize=20)\n", - "ax.set_ylabel('pace min/km', fontsize=20)\n", - "ax.set_xlim(xlim)\n", - "ax.set_ylim(ylim)\n", - "\n", - "mlai.write_figure(filename='olympic-marathon.svg', \n", - " directory='./datasets')" + "import numpy as np\n", + "import pods" ], - "id": "be596e54-326d-43ec-aa56-617e5b8e29a7" + "id": "91329a7b-1640-461a-a250-b1115d3c14cf" }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "\n", - "\n", - "Figure: Olympic marathon pace times since 1896.\n", - "\n", - "Things to notice about the data include the outlier in 1904, in that\n", - "year the Olympics was in St Louis, USA. Organizational problems and\n", - "challenges with dust kicked up by the cars following the race meant that\n", - "participants got lost, and only very few participants completed. More\n", - "recent years see more consistently quick marathons." + "data = pods.datasets.olympic_marathon_men()\n", + "x = data['X']\n", + "y = data['Y']\n", + "offset = y.mean()\n", + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale" ], - "id": "6631e4b1-dde5-4a01-9fc7-6f7482e924b7" + "id": "6574106a-3814-4089-b02e-748e8d661382" }, { "cell_type": "markdown", @@ -553,7 +699,7 @@ "You can also check my post blog post on [What is Machine\n", "Learning?](http://inverseprobability.com/2017/07/17/what-is-machine-learning)." ], - "id": "a913e757-c2d8-4ea9-a8b6-7327dd3014dd" + "id": "a10fb288-4a5b-4dc0-bf2b-d41c0aea87f0" }, { "cell_type": "markdown", @@ -594,7 +740,7 @@ "mainly inspired by the thinking of [Pierre-Simon\n", "Laplace](https://en.wikipedia.org/wiki/Pierre-Simon_Laplace)." ], - "id": "612ccbb3-e1b9-444c-af7f-d3d8e10ec9ed" + "id": "46f7d2a6-d27d-4970-a7e8-c26eb327606d" }, { "cell_type": "markdown", @@ -602,10 +748,6 @@ "source": [ "## Regression: Linear Releationship\n", "\n", - "\\[edit\\]\n", - "\n", "For many their first encounter with what might be termed a machine\n", "learning method is fitting a straight line. A straight line is\n", "characterized by two parameters, the scale, $m$, and the offset $c$.\n", @@ -619,7 +761,7 @@ "improvement of the olympic marathon pace on a yearly basis. And $c$ is\n", "the winning pace as estimated at year 0." ], - "id": "02cdafc7-f44d-489e-adae-b9fa56f1fe6d" + "id": "7949c7bb-ceab-43f0-b921-9b3edf15a54e" }, { "cell_type": "markdown", @@ -647,7 +789,7 @@ "The solution to these two simultaneous equations can be represented\n", "graphically as\n", "\n", - "\n", + "\n", "\n", "Figure: The solution of two linear equations represented as the fit\n", "of a straight line through two data\n", @@ -659,14 +801,14 @@ "2.5 = 2m + c\n", "$$\n", "\n", - "\n", + "\n", "\n", "Figure: A third observation of data is inconsistent with the solution\n", "dictated by the first two observations\n", "\n", "Now there are three candidate lines, each consistent with our data.\n", "\n", - "\n", + "\n", "\n", "Figure: Three solutions to the problem, each consistent with two\n", "points of the three observations\n", @@ -676,7 +818,7 @@ "model is a simplification of the real world, and the data we observe is\n", "therefore inconsistent with our model." ], - "id": "6d1596cd-ba7f-4f70-add9-fa4ce480ec48" + "id": "31ab9f94-5b98-4a1f-aa32-faf5f6dad603" }, { "cell_type": "code", @@ -686,7 +828,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "5629a6d7-a642-4a83-b1f3-ca5891beedc7" + "id": "75f953c3-9ada-4a75-957f-1283b570212a" }, { "cell_type": "code", @@ -696,7 +838,7 @@ "source": [ "plot.over_determined_system(diagrams='./ml')" ], - "id": "51c348a7-1fd1-44d4-a4ec-d434de805dbb" + "id": "b43b6898-a66b-47ad-81e7-b938812b45af" }, { "cell_type": "code", @@ -707,7 +849,7 @@ "from ipywidgets import IntSlider\n", "import notutils as nu" ], - "id": "e4c69bd7-dcfe-4f8f-b46d-4c6d3d773ec0" + "id": "cb57b879-6a47-4965-8490-4d7d0791dd41" }, { "cell_type": "code", @@ -719,7 +861,7 @@ " directory='./ml', \n", " samp=IntSlider(1,1,7,1))" ], - "id": "21820a0d-db84-449c-bb28-63c85d99d5f8" + "id": "dce4b0ea-152d-44ec-853c-e1e18d1c8bfd" }, { "cell_type": "markdown", @@ -736,7 +878,7 @@ "world, and the way it was incomplete is *unknown*. His idea was that\n", "such unknowns could be dealt with through probability." ], - "id": "5a5125e9-9554-4baa-bcf9-e168c90a7f6e" + "id": "f965e11f-0d40-4a8d-8279-d2e3b69730e2" }, { "cell_type": "markdown", @@ -748,11 +890,23 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", + "\n", "\n", "Figure: Pierre-Simon Laplace 1749-1827." ], - "id": "d1c4e1ae-10ec-466d-ad3f-f2eaaba75cc7" + "id": "df1b48f8-0230-4f99-bb88-24abf2803ad1" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Laplace’s Determism\n", + "\n", + "\\[edit\\]" + ], + "id": "b789b6ca-89e6-4024-837d-a66ed9814336" }, { "cell_type": "code", @@ -763,12 +917,14 @@ "import notutils as nu\n", "nu.display_google_book(id='1YQPAAAAQAAJ', page='PR17-IA2')" ], - "id": "21431780-3a2e-4650-9250-360bc5a43f8a" + "id": "e121c750-16ed-47ec-8b7d-9a5668b99248" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "Figure: Laplace’s determinism book reference.\n", + "\n", "Famously, Laplace considered the idea of a deterministic Universe, one\n", "in which the model is *known*, or as the below translation refers to it,\n", "“an intelligence which could comprehend all the forces by which nature\n", @@ -786,11 +942,11 @@ "\n", "This notion is known as *Laplace’s demon* or *Laplace’s superman*.\n", "\n", - "\n", + "\n", "\n", "Figure: Laplace’s determinsim in English translation." ], - "id": "1983e3c3-8f3d-4201-a8b9-0cceccfc691f" + "id": "128ff321-35f6-410d-a6a8-a5b16e2f9e28" }, { "cell_type": "markdown", @@ -798,10 +954,6 @@ "source": [ "## Laplace’s Gremlin\n", "\n", - "\\[edit\\]\n", - "\n", "Unfortunately, most analyses of his ideas stop at that point, whereas\n", "his real point is that such a notion is unreachable. Not so much\n", "*superman* as *strawman*. Just three pages later in the “Philosophical\n", @@ -814,7 +966,7 @@ "> Probability is relative, in part to this ignorance, in part to our\n", "> knowledge." ], - "id": "ac7024f0-6e57-41f7-84d5-75938f7eb61a" + "id": "a8490cc8-c48b-426d-9ee1-dd19c7301537" }, { "cell_type": "code", @@ -825,13 +977,15 @@ "import notutils as nu\n", "nu.display_google_book(id='1YQPAAAAQAAJ', page='PR17-IA4')" ], - "id": "ae4e7c99-6711-42ca-b08b-646f61c9c0be" + "id": "d8356dda-1ecb-4741-a816-a33f4e9faaea" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "Figure: Laplace’s gremlin in the original French.\n", + "\n", + "\n", "\n", "Figure: To Laplace, determinism is a strawman. Ignorance of mechanism\n", "and data leads to uncertainty which should be dealt with through\n", @@ -852,7 +1006,7 @@ "And *probability* is the tool we use to incorporate this ignorance\n", "leading to uncertainty or *doubt* in our predictions." ], - "id": "cb65da57-c603-4cf4-853b-d831c60d2a1b" + "id": "0e83f937-ac43-4b8a-80d2-787a54c17dea" }, { "cell_type": "markdown", @@ -872,8 +1026,13 @@ "But in the context Laplace uses it, the variable is so common that it\n", "has other names such as a “slack” variable or the *noise* in the system.\n", "\n", - "point 1: $x= 1$, $y=3$ \\[ 3 = m + c + \\_1 \\] point 2: $x= 3$, $y=1$ \\[ 1\n", - "= 3m + c + \\_2 \\] point 3: $x= 2$, $y=2.5$ \\[ 2.5 = 2m + c + \\_3 \\]\n", + "point 1: $x= 1$, $y=3$ $$\n", + "3 = m + c + \\epsilon_1\n", + "$$ point 2: $x= 3$, $y=1$ $$\n", + "1 = 3m + c + \\epsilon_2\n", + "$$ point 3: $x= 2$, $y=2.5$ $$\n", + "2.5 = 2m + c + \\epsilon_3\n", + "$$\n", "\n", "Laplace’s trick has converted the *overdetermined* system into an\n", "*underdetermined* system. He has now added three variables,\n", @@ -881,7 +1040,7 @@ "real world. Laplace’s idea is that we should represent that unknown\n", "corruption with a *probability distribution*." ], - "id": "b1c43c72-d9e5-44ca-bbc7-6222b8298afb" + "id": "c34d9b5a-76a0-4f75-bd0c-59e56261395b" }, { "cell_type": "markdown", @@ -899,7 +1058,7 @@ "a probabilistic or stochastic process, to distinguish it from a\n", "deterministic process." ], - "id": "47fe29a6-9ccc-4cd8-80af-f2ce24a34f32" + "id": "b5474fa6-ff9b-4828-8cf4-aa70bfb8f6f7" }, { "cell_type": "markdown", @@ -917,10 +1076,10 @@ "$\\sigma$.\n", "\n", "$$\\begin{align}\n", - " p(y| \\mu, \\sigma^2) & = \\frac{1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{(y- \\mu)^2}{2\\sigma^2}\\right)\\\\& \\buildrel\\triangle\\over = \\mathcal{N}\\left(y|\\mu,\\sigma^2\\right)\n", + " p(y| \\mu, \\sigma^2) & = \\frac{1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{(y- \\mu)^2}{2\\sigma^2}\\right)\\\\& \\buildrel\\triangle\\over = \\mathscr{N}\\left(y|\\mu,\\sigma^2\\right)\n", " \\end{align}$$" ], - "id": "8d0e3da4-4662-48dd-8299-42d6bb43f8c1" + "id": "277f1571-5bd8-4d39-83c8-e550f449f392" }, { "cell_type": "code", @@ -930,7 +1089,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "c85e54e3-2b7e-4288-9938-d59e090ebdf5" + "id": "7aaea5b5-d9a4-4aa7-aa5c-45671bb8b1e5" }, { "cell_type": "code", @@ -940,19 +1099,19 @@ "source": [ "plot.gaussian_of_height(diagrams='./ml')" ], - "id": "cff50b8e-d952-4b96-b056-23ecd805d4c4" + "id": "c925c4ea-363f-4035-98d9-0d0456b18293" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The Gaussian PDF with ${\\mu}=1.7$ and variance\n", "${\\sigma}^2=0.0225$. Mean shown as red line. It could represent the\n", "heights of a population of students." ], - "id": "74eaa951-ed73-4632-92a5-5dfdc71ee351" + "id": "c2d5bffe-7c40-431f-ac3f-f13558e2ca0e" }, { "cell_type": "markdown", @@ -967,7 +1126,7 @@ "The Gaussian density has many important properties, but for the moment\n", "we’ll review two of them." ], - "id": "a4179cdc-5e82-46e9-8a5a-5b4f3ff924b2" + "id": "0ed52d41-b2a2-495b-9531-3ad9e2587ab1" }, { "cell_type": "markdown", @@ -977,7 +1136,7 @@ "\n", "If we assume that a variable, $y_i$, is sampled from a Gaussian density,\n", "\n", - "$$y_i \\sim \\mathcal{N}\\left(\\mu_i,\\sigma_i^2\\right)$$\n", + "$$y_i \\sim \\mathscr{N}\\left(\\mu_i,\\sigma_i^2\\right)$$\n", "\n", "Then we can show that the sum of a set of variables, each drawn\n", "independently from such a density is also distributed as Gaussian. The\n", @@ -985,7 +1144,7 @@ "is the sum of the variances,\n", "\n", "$$\n", - "\\sum_{i=1}^{n} y_i \\sim \\mathcal{N}\\left(\\sum_{i=1}^n\\mu_i,\\sum_{i=1}^n\\sigma_i^2\\right)\n", + "\\sum_{i=1}^{n} y_i \\sim \\mathscr{N}\\left(\\sum_{i=1}^n\\mu_i,\\sum_{i=1}^n\\sigma_i^2\\right)\n", "$$\n", "\n", "Since we are very familiar with the Gaussian density and its properties,\n", @@ -997,7 +1156,7 @@ "theorem*](https://en.wikipedia.org/wiki/Central_limit_theorem) which is\n", "a major justification for the use of a Gaussian density." ], - "id": "c0e3fec4-9b09-4bd5-89b4-f28c5ddfcfcc" + "id": "dd9a60fa-8e98-4606-b168-a377fcdc272b" }, { "cell_type": "markdown", @@ -1008,66 +1167,24 @@ "Less unusual is the *scaling* property of a Gaussian density. If a\n", "variable, $y$, is sampled from a Gaussian density,\n", "\n", - "$$y\\sim \\mathcal{N}\\left(\\mu,\\sigma^2\\right)$$ and we choose to scale\n", + "$$y\\sim \\mathscr{N}\\left(\\mu,\\sigma^2\\right)$$ and we choose to scale\n", "that variable by a *deterministic* value, $w$, then the *scaled\n", "variable* is distributed as\n", "\n", - "$$wy\\sim \\mathcal{N}\\left(w\\mu,w^2 \\sigma^2\\right).$$ Unlike the summing\n", - "properties, where adding two or more random variables independently\n", - "sampled from a family of densitites typically brings the summed variable\n", - "*outside* that family, scaling many densities leaves the distribution of\n", - "that variable in the same *family* of densities. Indeed, many densities\n", - "include a *scale* parameter (e.g. the [Gamma\n", - "density](https://en.wikipedia.org/wiki/Gamma_distribution)) which is\n", - "purely for this purpose. In the Gaussian the standard deviation,\n", - "$\\sigma$, is the scale parameter. To see why this makes sense, let’s\n", - "consider, $$z \\sim \\mathcal{N}\\left(0,1\\right),$$ then if we scale by\n", - "$\\sigma$ so we have, $y=\\sigma z$, we can write,\n", - "$$y=\\sigma z \\sim \\mathcal{N}\\left(0,\\sigma^2\\right)$$" - ], - "id": "f278ec8f-22aa-41f8-83b7-d6b8bc838438" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Laplace’s Idea\n", - "\n", - "\\[edit\\]\n", - "\n", - "Laplace had the idea to augment the observations by noise, that is\n", - "equivalent to considering a probability density whose mean is given by\n", - "the *prediction function*\n", - "$$p\\left(y_i|x_i\\right)=\\frac{1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{\\left(y_i-f\\left(x_i\\right)\\right)^{2}}{2\\sigma^2}\\right).$$\n", - "\n", - "This is known as *stochastic process*. It is a function that is\n", - "corrupted by noise. Laplace didn’t suggest the Gaussian density for that\n", - "purpose, that was an innovation from Carl Friederich Gauss, which is\n", - "what gives the Gaussian density its name." - ], - "id": "9eeaf306-f082-40e3-b239-cb429c4da1be" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Height as a Function of Weight\n", - "\n", - "In the standard Gaussian, parameterized by mean and variance, make the\n", - "mean a linear function of an *input*.\n", - "\n", - "This leads to a regression model. $$\n", - "\\begin{align*}\n", - " y_i=&f\\left(x_i\\right)+\\epsilon_i,\\\\\n", - " \\epsilon_i \\sim & \\mathcal{N}\\left(0,\\sigma^2\\right).\n", - " \\end{align*}\n", - "$$\n", - "\n", - "Assume $y_i$ is height and $x_i$ is weight." + "$$wy\\sim \\mathscr{N}\\left(w\\mu,w^2 \\sigma^2\\right).$$ Unlike the summing\n", + "properties, where adding two or more random variables independently\n", + "sampled from a family of densitites typically brings the summed variable\n", + "*outside* that family, scaling many densities leaves the distribution of\n", + "that variable in the same *family* of densities. Indeed, many densities\n", + "include a *scale* parameter (e.g. the [Gamma\n", + "density](https://en.wikipedia.org/wiki/Gamma_distribution)) which is\n", + "purely for this purpose. In the Gaussian the standard deviation,\n", + "$\\sigma$, is the scale parameter. To see why this makes sense, let’s\n", + "consider, $$z \\sim \\mathscr{N}\\left(0,1\\right),$$ then if we scale by\n", + "$\\sigma$ so we have, $y=\\sigma z$, we can write,\n", + "$$y=\\sigma z \\sim \\mathscr{N}\\left(0,\\sigma^2\\right)$$" ], - "id": "36c7eddb-ac46-4a26-91cb-15dd9325cb7a" + "id": "c8f62c09-2e73-4b97-bdfe-ad48f1f885e0" }, { "cell_type": "markdown", @@ -1079,7 +1196,7 @@ "style=\"\">\\[edit\\]" ], - "id": "1e97f7e4-985c-4921-9a11-7b5e229a3c82" + "id": "7df55cad-3b56-481e-b59b-90a25406786a" }, { "cell_type": "markdown", @@ -1087,13 +1204,13 @@ "source": [ "## Legendre\n", "\n", - "Minimizing the sum of squares error was first proposed by\n", + "Minimising the sum of squares error was first proposed by\n", "[Legendre](http://en.wikipedia.org/wiki/Adrien-Marie_Legendre) in 1805\n", "(Legendre, 1805). His book, which was on the orbit of comets, is\n", - "available on google books, we can take a look at the relevant page by\n", + "available on Google books, we can take a look at the relevant page by\n", "calling the code below." ], - "id": "cbffb38a-33a6-4b51-9898-82335fe731a3" + "id": "7ea2f9c2-9d44-4736-bc6c-2398872f85e5" }, { "cell_type": "code", @@ -1104,7 +1221,7 @@ "import notutils as nu\n", "nu.display_google_book(id='spcAAAAAMAAJ', page='PA72')" ], - "id": "6bf0431a-e871-457d-9922-b3fead2159f5" + "id": "3efdb7df-7a5a-4ad7-bddf-ba2fb3e712d9" }, { "cell_type": "markdown", @@ -1175,73 +1292,20 @@ "products](http://en.wikipedia.org/wiki/Dot_product)) are similar. They\n", "allow us to write $$\n", "\\sum_{i=1}^q u_i v_i\n", - "$$ in a more compact notation, $\\mathbf{u}\\cdot\\mathbf{v}.$\n", + "$$ in vector notation, $\\mathbf{u}^\\top\\mathbf{v}.$\n", "\n", "Here we are using bold face to represent vectors, and we assume that the\n", "individual elements of a vector $\\mathbf{z}$ are given as a series of\n", "scalars $$\n", "\\mathbf{z} = \\begin{bmatrix} z_1\\\\ z_2\\\\ \\vdots\\\\ z_n\n", "\\end{bmatrix}\n", - "$$ which are each indexed by their position in the vector." - ], - "id": "9fbca963-7029-4732-9982-665a4631aeb0" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Running Example: Olympic Marathons\n", - "\n", - "\\[edit\\]\n", - "\n", - "Note that `x` and `y` are not `pandas` data frames for this example,\n", - "they are just arrays of dimensionality $n\\times 1$, where $n$ is the\n", - "number of data.\n", - "\n", - "The aim of this lab is to have you coding linear regression in python.\n", - "We will do it in two ways, once using iterative updates (coordinate\n", - "ascent) and then using linear algebra. The linear algebra approach will\n", - "not only work much better, it is also easy to extend to multiple input\n", - "linear regression and *non-linear* regression using basis functions." - ], - "id": "b4aa8ff3-eea9-436d-8207-214f0f09abd5" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Maximum Likelihood: Iterative Solution\n", + "$$ which are each indexed by their position in the vector.\n", "\n", - "Now we will take the maximum likelihood approach we derived in the\n", - "lecture to fit a line, $y_i=mx_i + c$, to the data you’ve plotted. We\n", - "are trying to minimize the error function: $$\n", - "E(m, c) = \\sum_{i=1}^n(y_i-mx_i-c)^2\n", - "$$ with respect to $m$, $c$ and $\\sigma^2$. We can start with an initial\n", - "guess for $m$," - ], - "id": "15b0425f-5f5a-42ff-a465-fa933ce1ce19" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "m = -0.4\n", - "c = 80" - ], - "id": "2175d910-65c3-4350-880f-6014efa7f2a9" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then we use the maximum likelihood update to find an estimate for the\n", - "offset, $c$." + "Unfortunately for Legendre, Gauss had already used the least squares\n", + "method in his recoveroy of Ceres. This led to a priority dispute which\n", + "Gauss’s unpublished use being established as predating Legendre’s." ], - "id": "54fd9b2a-a827-4dc9-81dd-e1312d9cac08" + "id": "cd01fa68-200e-4af8-bb75-4a90fd84cb5e" }, { "cell_type": "markdown", @@ -1267,7 +1331,7 @@ "gradient is zero. This can be found by taking the gradient of $E(m,c)$\n", "with respect to the parameter." ], - "id": "914c2ad6-e707-49eb-a824-55a4f40e7079" + "id": "3396ff14-6981-49a9-a87a-064b17897230" }, { "cell_type": "markdown", @@ -1293,11 +1357,11 @@ "of stationary point we have encountered we now compute the *second\n", "derivative*, $$\n", "\\frac{\\text{d}^2E(m,c)}{\\text{d}c^2} = 2n.\n", - "$$ The second derivative is positive, which in turn implies that we have\n", - "found a minimum of the function. This means that setting $c$ in this way\n", + "$$ The second derivative is positive, which implies that we have found a\n", + "minimum of the function. This means that setting $c$ using this update\n", "will take us to the lowest point along that axes." ], - "id": "864cef28-265e-4d9d-a2fb-5cbb6f7fed5b" + "id": "932d39f6-842f-4a74-8ed0-e5c3684c9b5a" }, { "cell_type": "code", @@ -1309,7 +1373,7 @@ "c = (y - m*x).mean()\n", "print(c)" ], - "id": "e316435e-5a1e-455b-94e6-1b4860390512" + "id": "f646bc62-bd73-4367-9c02-993be5478545" }, { "cell_type": "markdown", @@ -1332,7 +1396,7 @@ "that, however, we will briefly review how to write mathematics in the\n", "notebook." ], - "id": "8bf6a777-3aea-444b-9f53-1d01125d03dc" + "id": "3dc4c41d-1f11-4ee6-ac47-00faaded3bba" }, { "cell_type": "markdown", @@ -1357,7 +1421,7 @@ "You can combine many of these operations together for composing\n", "expressions." ], - "id": "a31430bf-b8ca-49e0-a029-af62e4464f67" + "id": "79472a90-4159-4030-a96f-c1b6317a28e9" }, { "cell_type": "markdown", @@ -1377,7 +1441,7 @@ "\n", "`(c) g = (x*w).sum()`" ], - "id": "160b7620-f32e-4300-a082-4703be46451d" + "id": "96abe91d-ca48-49ae-842f-7e6b3b3efa3b" }, { "cell_type": "markdown", @@ -1387,7 +1451,7 @@ "\n", "Write your answer to Exercise 1 here" ], - "id": "853baa0c-456d-4bc0-b0f7-b23e92f793a1" + "id": "c49a32dc-5fa9-4eb3-8b11-b1a09b112538" }, { "cell_type": "markdown", @@ -1408,7 +1472,7 @@ "\\end{aligned}\n", "$$" ], - "id": "29012a4b-b194-479b-8f15-f559179f4905" + "id": "2b08402d-fc2e-4e1e-8f64-15b454d1b5b7" }, { "cell_type": "markdown", @@ -1420,7 +1484,7 @@ "will be able to use it to answer questions. The next thing we are going\n", "to do is a little differentiation practice." ], - "id": "eb55b46a-bc86-4990-8d8c-75a8874c1649" + "id": "79aee7e8-4972-4da3-bcfd-2d3a9af6a044" }, { "cell_type": "markdown", @@ -1433,7 +1497,7 @@ "does find the stationary points of the objective function. By computing\n", "its derivative show that it’s a minimum." ], - "id": "ba5b74ba-0a5f-4414-9223-87d6fba80fd2" + "id": "88bc931c-4bac-4843-8c7a-3c53a8a6560a" }, { "cell_type": "markdown", @@ -1443,7 +1507,7 @@ "\n", "Write your answer to Exercise 2 here" ], - "id": "f42c35b9-4944-473c-bf4d-bc54f5508c3f" + "id": "94c1ad9f-faa9-4695-b225-a4b3069febac" }, { "cell_type": "code", @@ -1454,7 +1518,7 @@ "m = ((y - c)*x).sum()/(x**2).sum()\n", "print(m)" ], - "id": "87403175-3589-4db5-bb8d-af8b9ca97238" + "id": "6e6a92b2-2877-4c74-b92e-865be6d215f2" }, { "cell_type": "markdown", @@ -1463,7 +1527,7 @@ "We can have a look at how good our fit is by computing the prediction\n", "across the input space. First create a vector of ‘test points’," ], - "id": "24546d2f-95ab-41cb-84a3-8bebce82802a" + "id": "bffaf47f-0e71-4eb4-9a85-db036eaa52ab" }, { "cell_type": "code", @@ -1473,7 +1537,7 @@ "source": [ "import numpy as np" ], - "id": "abf3500e-1c81-49b1-8118-90580d47610f" + "id": "e24dc5da-a376-421e-b421-b034db0cf131" }, { "cell_type": "code", @@ -1483,7 +1547,7 @@ "source": [ "x_test = np.linspace(1890, 2020, 130)[:, None]" ], - "id": "ca18e309-4260-4453-af55-4b3423920d37" + "id": "286ad287-bbd7-4615-b080-2bc3c58e4c02" }, { "cell_type": "markdown", @@ -1491,7 +1555,7 @@ "source": [ "Now use this vector to compute some test predictions," ], - "id": "e86cd4a7-fef9-4691-a96a-4b734fbaf456" + "id": "c220a887-6aa2-4e70-9824-ce5683f20abf" }, { "cell_type": "code", @@ -1501,7 +1565,7 @@ "source": [ "f_test = m*x_test + c" ], - "id": "d4148abf-2591-4e49-890f-087e14d7f6b6" + "id": "51dc6795-128d-4d8b-819f-d266c454b9d2" }, { "cell_type": "markdown", @@ -1510,7 +1574,7 @@ "Now plot those test predictions with a blue line on the same plot as the\n", "data," ], - "id": "3a914168-bbb4-4c48-a9fb-bd1760c16fb2" + "id": "10fc23b9-1ff0-478c-969c-28b664327025" }, { "cell_type": "code", @@ -1520,7 +1584,7 @@ "source": [ "import matplotlib.pyplot as plt" ], - "id": "5812db76-077e-42b7-a17a-ae09c6c6b233" + "id": "e742abab-fc4c-4cfb-bc10-f0901e09724a" }, { "cell_type": "code", @@ -1531,7 +1595,7 @@ "plt.plot(x_test, f_test, 'b-')\n", "plt.plot(x, y, 'rx')" ], - "id": "f60a6aa5-ab15-4319-8274-319398d4151d" + "id": "a2b0f7b7-76d5-4ed8-b292-13a133810cab" }, { "cell_type": "markdown", @@ -1540,7 +1604,7 @@ "The fit isn’t very good, we need to iterate between these parameter\n", "updates in a loop to improve the fit, we have to do this several times," ], - "id": "176f7311-231c-4951-b68f-3362e27f5661" + "id": "aa57dc2e-70ff-447c-b48b-16a7b66b69b8" }, { "cell_type": "code", @@ -1554,7 +1618,7 @@ "print(m)\n", "print(c)" ], - "id": "9ebfdfbc-26e6-4445-8478-b345f71d1252" + "id": "302c5c0a-3700-4607-b585-fe90218bb6f0" }, { "cell_type": "markdown", @@ -1562,7 +1626,7 @@ "source": [ "And let’s try plotting the result again" ], - "id": "7605b8ac-530b-47d1-8887-f326d69fa272" + "id": "65ddaa78-a281-4128-b90e-4b5d4f57361b" }, { "cell_type": "code", @@ -1574,7 +1638,7 @@ "plt.plot(x_test, f_test, 'b-')\n", "plt.plot(x, y, 'rx')" ], - "id": "321d28e6-3aa1-4400-9d92-79090e423366" + "id": "58922bed-fb48-4d85-a96a-c56fd7095d36" }, { "cell_type": "markdown", @@ -1583,7 +1647,20 @@ "Clearly we need more iterations than 10! In the next question you will\n", "add more iterations and report on the error as optimisation proceeds." ], - "id": "b2fd37b2-e6b6-4993-86de-e818e11a41f1" + "id": "d9893007-28f0-4beb-b1a8-eece08480c0b" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Coordiate Descent Algorithm\n", + "\n", + "Let’s run the complete coordiante descent algorithm and visualise how\n", + "the parameters evolve over multiple iterations. The animation will show\n", + "the path taken through parameter space as the algorithm navigates toward\n", + "the minimum of the error surface." + ], + "id": "321cb08f-1048-47e0-a921-fd76316f5567" }, { "cell_type": "markdown", @@ -1598,15 +1675,14 @@ "\n", "1. Initialise with `m=-0.4` and `c=80`.\n", "2. Every 10 iterations compute the value of the objective function for\n", - " the training data and print it to the screen (you’ll find hints on\n", - " this in [the lab from last week](./week2.ipynb)).\n", - "3. Cause the code to stop running when the error change over less than\n", - " 10 iterations is smaller than $1\\times10^{-4}$. This is known as a\n", + " the training data and print it to the screen.\n", + "3. Cause the code to stop running when the error changes over less than\n", + " 10 iterations is smaller than $1\\times 10^{-4}$. This is known as a\n", " stopping criterion.\n", "\n", "Why do we need so many iterations to get to the solution?" ], - "id": "87b01d9c-9f53-45ff-9b81-159c1467d4e4" + "id": "a82157de-a9f9-46ae-8dd9-b69ad8b1f5df" }, { "cell_type": "code", @@ -1620,7 +1696,7 @@ "\n", "\n" ], - "id": "028b7664-71ee-4092-acde-d6d1905f254b" + "id": "b2099e31-227d-4baa-b2cd-2cc09c77f891" }, { "cell_type": "markdown", @@ -1634,7 +1710,7 @@ "- Effective heuristics such as momentum.\n", "- Local vs global solutions." ], - "id": "151a0467-1aea-4847-b7c0-8bf4a0668870" + "id": "562da036-f1bd-4443-b88d-3d0fc8736ff8" }, { "cell_type": "markdown", @@ -1644,9 +1720,39 @@ "\n", "\\[edit\\]" + "style=\"\">edit\\]\n", + "\n", + "In classification we’re predicting discrete values, we use the sign\n", + "function in the Perceptron algorithm to convert from a continuous value\n", + "to a discrete by interpreting the continuos values as defining a\n", + "(hyper)plane. In regression we’re trying to predict a continuous\n", + "real-valued output rather than a discrete class label. Our goal is to\n", + "find a function that maps input features to real-valued predictions.\n", + "\n", + "For linear regression, we assume a simple linear relationship between\n", + "input and output. For a single feature, our prediction function takes\n", + "the form $$\n", + "f(x_i) = mx_i + c\n", + "$$ where $m$ is the slope and $c$ is the intercept. How should we find\n", + "the best values for these parameters?\n", + "\n", + "This is where the objective function comes in. We need a way to measure\n", + "how well our current parameter values fit the data. The least squares\n", + "objective function provides a principled approach by measuring the sum\n", + "of squared differences between our predictions and the true values $$\n", + "E(m, c) = \\sum_{i=1}^n(y_i - f(x_i))^2\n", + "$$ The algorithm’s job is to find the values of $m$ and $c$ that\n", + "minimize this error function.\n", + "\n", + "To demonstrate how regression works, we’ll create an artificial dataset\n", + "where we know the true underlying relationship. This allows us to test\n", + "whether our algorithm can recover the parameters we used to generate the\n", + "data.\n", + "\n", + "We start by creating some random input values from a normal\n", + "distribution. These will serve as our feature values $x_i$." ], - "id": "4daf1de1-9026-49da-9f84-bc190b1c3f36" + "id": "830ee235-994b-4c47-a468-3492e078c2e7" }, { "cell_type": "code", @@ -1658,7 +1764,7 @@ "import matplotlib.pyplot as plt\n", "import mlai" ], - "id": "842a7010-1487-4d3d-a993-8c6cc0a5e6fa" + "id": "d8042962-a2bf-4a88-8128-0eda0baa093f" }, { "cell_type": "code", @@ -1668,7 +1774,19 @@ "source": [ "x = np.random.normal(size=(4, 1))" ], - "id": "1b2b6c9c-7b2d-422f-8362-3dde3d55cd9a" + "id": "649b12b9-881f-4057-aa5e-e2c59b29e82d" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now need to decide on a *true* value for $m$ and a *true* value for\n", + "$c$ to use for generating the data. In real-world scenarios, we don’t\n", + "know these values - that’s what we’re trying to learn. But for this\n", + "demonstration, we’ll choose specific values so we can verify our\n", + "algorithm works correctly." + ], + "id": "a1a6abd2-73e7-44bc-b446-0749214b9e74" }, { "cell_type": "code", @@ -1679,7 +1797,18 @@ "m_true = 1.4\n", "c_true = -3.1" ], - "id": "b3b2dcba-ecf3-4a92-8340-2553b1da4608" + "id": "61ef7a72-e7bd-4385-8f1a-9f40eacf21f2" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can generate our target values using the linear relationship. The\n", + "mathematical formula $y_i = mx_i + c$ translates directly into code.\n", + "This creates an exact linear relationship - each point will lie exactly\n", + "on the line defined by our chosen slope and intercept." + ], + "id": "03545f2b-e885-4c92-91f6-ebfbf88e38e2" }, { "cell_type": "code", @@ -1689,7 +1818,19 @@ "source": [ "y = m_true*x+c_true" ], - "id": "40f9b9de-627e-4792-a18b-04a016342a62" + "id": "c672666f-8350-4d62-afb6-32dc8e8fefb1" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Plotting our artificial data helps us visualise the linear relationship\n", + "we’ve created. Since we generated the data deterministically using our\n", + "linear function, all points should lie exactly on a straight line. This\n", + "gives us a baseline to understand how the algorithm should perform in\n", + "the ideal case." + ], + "id": "5f689767-a89b-4b73-91f9-5d8ab22b8361" }, { "cell_type": "code", @@ -1699,7 +1840,7 @@ "source": [ "import matplotlib.pyplot as plt" ], - "id": "6f1f7824-527f-48eb-8c30-c6d63bfcb779" + "id": "0e8137a3-c82f-4885-b660-f62ac4dd9d29" }, { "cell_type": "code", @@ -1711,25 +1852,41 @@ "plt.xlim([-3, 3])\n", "mlai.write_figure(filename='regression.svg', directory='./ml', transparent=True)" ], - "id": "7565993a-9dbd-46d6-97e1-89105c0c5789" + "id": "caa2e760-eeae-40b5-8b53-c840d71a4068" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", + "\n", + "Figure: A simple linear regression.\n", "\n", - "Figure: A simple linear regression." + "As expected, these points lie exactly on a straight line since we\n", + "generated them deterministically. However, real-world data is rarely\n", + "this perfect. There are always measurement errors, unknown factors, and\n", + "random variations that cause data to deviate from perfect relationships." ], - "id": "651300b4-bc7a-4106-aa67-e3bab6529167" + "id": "4a90e865-a785-4798-8e6a-8f19cfca822a" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Noise Corrupted Plot" + "## Noise Corrupted Plot\n", + "\n", + "To make our artificial dataset more realistic, we add Gaussian noise to\n", + "our target values. This simulates the random variations we’d encounter\n", + "in real data. The noise has zero mean, so it doesn’t systematically bias\n", + "our data, but it does add random scatter around the true linear\n", + "relationship.\n", + "\n", + "The standard deviation of the noise (0.5 in this case) controls how much\n", + "the data deviates from the perfect line. Larger noise values make the\n", + "regression problem more challenging, while smaller values keep the data\n", + "closer to the underlying linear trend." ], - "id": "acc6356a-2acb-46ab-aab6-ed03bf7c56c0" + "id": "80d255d5-a9d9-46ad-ba3e-4b62a82f74b2" }, { "cell_type": "code", @@ -1743,17 +1900,17 @@ "plt.xlim([-3, 3])\n", "mlai.write_figure(filename='regression_noise.svg', directory='./ml', transparent=True)" ], - "id": "41b3c910-ce36-4610-a723-50fc3c7070b4" + "id": "2f3194a7-8f02-4ad6-9157-4e22c11bffe8" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: A simple linear regression with noise." ], - "id": "0fd57712-bcf2-418f-a563-7bb5f58c046e" + "id": "e1ea6849-c39a-4b35-abd1-7856eac1a4a5" }, { "cell_type": "markdown", @@ -1761,9 +1918,28 @@ "source": [ "## Contour Plot of Error Function\n", "\n", - "- Visualise the error function surface, create vectors of values." + "\\[edit\\]\n", + "\n", + "To understand how the least squares algorithm works, it’s helpful to\n", + "visualize the error function as a surface in parameter space. Since we\n", + "have two parameters ($m$ and $c$), our error function $E(m, c)$ defines\n", + "a surface in 3D space where the height at any point represents the error\n", + "for that combination of parameters.\n", + "\n", + "The global minimum of this surface is given by the optimal parameter\n", + "values that best fit our data according to the least squares objective.\n", + "By visualising this surface through contour plots, we can gain intuition\n", + "about the optimization landscape and understand why gradient-based\n", + "methods work effectively for this problem.\n", + "\n", + "First, we create vectors of parameter values to explore around the true\n", + "values we used to generate the data. We sample points in a range around\n", + "the true parameters to see how the error function behaves in the local\n", + "neighborhood." ], - "id": "3c22516f-ce85-4b0e-b3bd-b58886cae0a2" + "id": "9a0674ed-c1fd-4efe-a668-f66e1dd2a6df" }, { "cell_type": "code", @@ -1773,18 +1949,21 @@ "source": [ "# create an array of linearly separated values around m_true\n", "m_vals = np.linspace(m_true-3, m_true+3, 100) \n", - "# create an array of linearly separated values ae\n", + "# create an array of linearly separated values around c_true\n", "c_vals = np.linspace(c_true-3, c_true+3, 100)" ], - "id": "6c0b702c-0b8d-4eb6-99a3-8f17f2eed6b9" + "id": "040beb81-7d22-4673-bb3c-5011d978f12c" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "- create a grid of values to evaluate the error function in 2D." + "Next, we create a 2D grid from these parameter vectors. This grid allows\n", + "us to evaluate the error function at every combination of $m$ and $c$\n", + "values, giving us a complete picture of the error surface over the\n", + "parameter space we’re exploring." ], - "id": "c1dac843-56fc-4161-87ac-96172a930f3c" + "id": "c4997ca6-8f19-4d31-944f-0810e7d9c2cb" }, { "cell_type": "code", @@ -1794,15 +1973,26 @@ "source": [ "m_grid, c_grid = np.meshgrid(m_vals, c_vals)" ], - "id": "b61e6b9b-4cac-421e-9357-584b973759f9" + "id": "1f55c384-cc2a-4340-bc06-d567f935486e" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "- compute the error function at each combination of $c$ and $m$." + "Now we compute the error function at each combination of parameters. For\n", + "each point in our grid, we:\n", + "\n", + "1. Use the parameter values to make predictions:\n", + " $\\hat{y}_i = m \\cdot x_i + c$ \n", + "2. Calculate the squared errors: $(y_i - \\hat{y}_i)^2$\n", + "3. Sum these squared errors to get the total error for that parameter\n", + " combination\n", + "\n", + "This gives us the complete error surface that we can then visualize. The\n", + "nested loop structure evaluates the sum of squared errors formula at\n", + "each $(m, c)$ coordinate in our grid." ], - "id": "6c7813e6-946b-44ea-9a75-0eb3ea58580f" + "id": "c13748ae-9707-4b03-8ed7-16d8806c63bc" }, { "cell_type": "code", @@ -1815,15 +2005,31 @@ " for j in range(100):\n", " E_grid[i, j] = ((y - m_grid[i, j]*x - c_grid[i, j])**2).sum()" ], - "id": "10f4c7bb-e789-4093-9ab8-74d3686a941a" + "id": "ba34ce09-4b61-4f6e-b53b-55305b8bdafe" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Contour Plot of Error" + "With our error surface computed, we can now create a contour plot to\n", + "visualize the optimization landscape. A contour plot shows lines of\n", + "equal error value, similar to elevation contours on a topographic map.\n", + "\n", + "Insights from this visualisation include:\n", + "\n", + "- *Bowl-shaped surface*: For linear regression with least squares, the\n", + " error surface is a smooth, convex bowl with a unique global minimum\n", + "- *Contour lines*: Each contour represents parameter combinations that\n", + " yield the same error value\n", + "- *Minimum location*: The centre of the concentric ellipses shows\n", + " where the error is minimized - this should be close to our true\n", + " parameter values\n", + "\n", + "This visualisation helps explain why least squares regression has nice\n", + "mathematical properties and why optimisation algorithms converge\n", + "reliably to the solution." ], - "id": "37e04a2b-0f32-4b65-b717-3640e3dd8e4d" + "id": "cbd6096b-71ee-479d-9a88-bdd92769e739" }, { "cell_type": "code", @@ -1834,7 +2040,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "ef1c123e-e7fd-4dc1-9a6b-d8c7b25638c1" + "id": "8fa7ec46-7e16-4da7-9874-0bdd268c05f5" }, { "cell_type": "code", @@ -1846,26 +2052,70 @@ "plot.regression_contour(f, ax, m_vals, c_vals, E_grid)\n", "mlai.write_figure(filename='regression_contour.svg', directory='./ml')" ], - "id": "b1f0a4b6-fa65-4f87-89d0-b93ece656393" + "id": "0775f8cd-25fc-4c82-baea-d39b62191e2a" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Contours of the objective function for linear regression by\n", - "minimizing least squares." + "minimizing least squares.\n", + "\n", + "The contour plot reveals the characteristic elliptical shape of the\n", + "least squares error surface. The concentric ellipses represent\n", + "increasing levels of error as we move away from the optimal parameters.\n", + "\n", + "Key observations from this visualization:\n", + "\n", + "- *Convex optimisation*: The smooth, bowl-shaped surface guarantees\n", + " that any local minimum is also the global minimum\n", + "- *Parameter sensitivity*: The shape of the ellipses tells us how\n", + " sensitive the error is to changes in each parameter\n", + "- *Optimization efficiency*: The regular, predictable shape means we\n", + " can develop optimisation methods that will converge quickly and\n", + " reliably\n", + "- *True parameter location*: The minimum should occur very close to\n", + " our known true values ($m_{true} = 1.4$, $c_{true} = -3.1$)\n", + "\n", + "*Warning:* This visualisation is great for giving some intuition, but\n", + "can be quite misleading about how these objective funcitons look in very\n", + "high dimensions. Unfortunately high dimensions are much harder to\n", + "visualise." ], - "id": "bb3580af-5c75-4232-a357-3671113a5da2" + "id": "03ea6553-82b2-4888-a7e9-4689663469a3" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Steepest Descent" + "## Steepest Descent\n", + "\n", + "\\[edit\\]\n", + "\n", + "Now that we understand the shape of the error surface from our contour\n", + "plot, we need an algorithm to find the minimum. Gradient descent is one\n", + "of the most fundamental optimisation algorithms in machine learning.\n", + "\n", + "The intuition behind gradient descent is simple: imagine you’re standing\n", + "on a hillside in fog and want to reach the bottom. Even though you can’t\n", + "see the whole landscape, you can feel the steepest downward slope at\n", + "your current position. By repeatedly taking steps in the steepest\n", + "downward direction, you’ll eventually reach the bottom.\n", + "\n", + "Mathematically, the gradient points in the direction of steepest ascent,\n", + "so we move in the negative gradient direction to descend toward the\n", + "minimum. The algorithm works by:\n", + "\n", + "1. Starting with an initial guess for our parameters\n", + "2. Computing the gradient (slope) of the error function at that point\n", + "3. Taking a small step in the opposite direction of the gradient\n", + "4. Repeating until we converge to the minimum" ], - "id": "1d3512d3-9248-4519-93b3-ac084aedcca9" + "id": "52581eb2-1273-491b-92f1-758259036c9c" }, { "cell_type": "markdown", @@ -1873,9 +2123,19 @@ "source": [ "## Algorithm\n", "\n", - "- We start with a guess for $m$ and $c$." + "The first step in gradient descent is to initialise our parameters with\n", + "some starting values. The choice of initial values can affect how\n", + "quickly the algorithm converges, although because of the simple shape of\n", + "the linear regression objective (known as a convex optimisation), any\n", + "starting point should eventually reach the global minimum.\n", + "\n", + "Here we’re starting with $m = 0$ (no slope) and $c = -5$ (a negative\n", + "intercept). These values are deliberately chosen to be different from\n", + "our true values ($m_{true} = 1.4$, $c_{true} = -3.1$) so we can see the\n", + "algorithm as it navigates toward the correct solution. Normally you\n", + "would initialise with smaller values closer to the origin." ], - "id": "5635d547-4bd8-4584-a65c-191f6f5f4bee" + "id": "afe02ec5-3d76-4672-aac6-4eca1d247416" }, { "cell_type": "code", @@ -1886,7 +2146,7 @@ "m_star = 0.0\n", "c_star = -5.0" ], - "id": "167f4b51-448c-4865-9c17-f4fbd48897a4" + "id": "48df7f5d-c66c-434e-9d06-f7ac437662a0" }, { "cell_type": "markdown", @@ -1894,14 +2154,22 @@ "source": [ "## Offset Gradient\n", "\n", - "- Now we need to compute the gradient of the error function, firstly\n", - " with respect to $c$, $$\n", - " \\frac{\\text{d}E(m, c)}{\\text{d} c} = -2\\sum_{i=1}^n(y_i - mx_i - c)\n", - " $$\n", + "To implement gradient descent, we need to compute the partial\n", + "derivatives of our error function with respect to each parameter. We’ll\n", + "start with the gradient with respect to the intercept parameter $c$.\n", "\n", - "- This is computed in python as follows" + "Our error function is $$E(m, c) = \\sum_{i=1}^n(y_i - mx_i - c)^2.$$\n", + "\n", + "To find how the error changes with respect to $c$, we use the chain\n", + "rule. Each squared term contributes to the gradient, and since $c$\n", + "appears in every term with a coefficient of $-1$, the partial derivative\n", + "becomes:\n", + "$$\\frac{\\text{d}E(m, c)}{\\text{d} c} = -2\\sum_{i=1}^n(y_i - mx_i - c).$$\n", + "The negative sign comes from differentiating the $-c$ term inside the\n", + "squared expression, and the factor of 2 comes from differentiating the\n", + "square." ], - "id": "d2a8ca05-4930-4b86-b2c0-1697f3977359" + "id": "f3fbed99-b242-4a12-82ac-5f9ef7d4b4f0" }, { "cell_type": "code", @@ -1912,7 +2180,7 @@ "c_grad = -2*(y-m_star*x - c_star).sum()\n", "print(\"Gradient with respect to c is \", c_grad)" ], - "id": "cfb3790b-1647-47fe-85c9-cdc28ff689e4" + "id": "ae0ba2bd-d3f1-48f6-b79c-73e3b28dfe7d" }, { "cell_type": "markdown", @@ -1920,17 +2188,27 @@ "source": [ "## Deriving the Gradient\n", "\n", - "To see how the gradient was derived, first note that the $c$ appears in\n", - "every term in the sum. So we are just differentiating\n", - "$(y_i - mx_i - c)^2$ for each term in the sum. The gradient of this term\n", - "with respect to $c$ is simply the gradient of the outer quadratic,\n", - "multiplied by the gradient with respect to $c$ of the part inside the\n", - "quadratic. The gradient of a quadratic is two times the argument of the\n", - "quadratic, and the gradient of the inside linear term is just minus one.\n", - "This is true for all terms in the sum, so we are left with the sum in\n", - "the gradient." + "To understand how we derived the gradient formula, let’s work through\n", + "the calculus step by step.\n", + "\n", + "The error function has the form:\n", + "$$E(m, c) = \\sum_{i=1}^n(y_i - mx_i - c)^2$$\n", + "\n", + "For the derivative with respect to $c$, note that $c$ appears in every\n", + "term of the sum. For each term $(y_i - mx_i - c)^2$, we apply the chain\n", + "rule:\n", + "\n", + "1. *Outer function*: The derivative of $u^2$ is $2u$\n", + "2. *Inner function*: The derivative of $(y_i - mx_i - c)$ with respect\n", + " to $c$ is $-1$\n", + "\n", + "Combining these:\n", + "$\\frac{d}{dc}[(y_i - mx_i - c)^2] = 2(y_i - mx_i - c) \\cdot (-1)$\n", + "\n", + "Since this applies to all terms in the sum, we get:\n", + "$$\\frac{\\text{d}E(m, c)}{\\text{d} c} = -2\\sum_{i=1}^n(y_i - mx_i - c)$$" ], - "id": "4941e6ae-e478-4319-a7bf-16d7a748204b" + "id": "51807e50-7b9a-441a-af4f-cbd4cc9f4c69" }, { "cell_type": "markdown", @@ -1938,16 +2216,23 @@ "source": [ "## Slope Gradient\n", "\n", - "The gradient with respect tom $m$ is similar, but now the gradient of\n", - "the quadratic’s argument is $-x_i$ so the gradient with respect to $m$\n", - "is\n", + "The gradient with respect to the slope parameter $m$ follows the same\n", + "chain rule approach, but now $m$ appears multiplied by $x_i$ in each\n", + "term.\n", "\n", - "$$\\frac{\\text{d}E(m, c)}{\\text{d} m} = -2\\sum_{i=1}^nx_i(y_i - mx_i -\n", - "c)$$\n", + "For the derivative with respect to $m$, each term $(y_i - mx_i - c)^2$\n", + "contributes: 1. *Outer function*: Still $2u$ where\n", + "$u = (y_i - mx_i - c)$ 2. *Inner function*: The derivative of\n", + "$(y_i - mx_i - c)$ with respect to $m$ is $-x_i$\n", "\n", - "which can be implemented in python (numpy) as" + "This gives us:\n", + "$$\\frac{\\text{d}E(m, c)}{\\text{d} m} = -2\\sum_{i=1}^nx_i(y_i - mx_i - c)$$\n", + "\n", + "Notice that each term is weighted by $x_i$, which makes intuitive sense:\n", + "data points with larger input values have more influence on the slope\n", + "parameter." ], - "id": "ff55d71f-a102-45fc-bcd7-1cf2e45768c5" + "id": "9aa18343-1c39-4219-811a-69827c9c643d" }, { "cell_type": "code", @@ -1958,7 +2243,7 @@ "m_grad = -2*(x*(y-m_star*x - c_star)).sum()\n", "print(\"Gradient with respect to m is \", m_grad)" ], - "id": "afad5b23-7464-4c91-ba0e-bad0599588e6" + "id": "50d8fd9b-d09a-49f1-b354-85c1c2fd8f4a" }, { "cell_type": "markdown", @@ -1966,23 +2251,52 @@ "source": [ "## Update Equations\n", "\n", - "- Now we have gradients with respect to $m$ and $c$.\n", - "- Can update our inital guesses for $m$ and $c$ using the gradient.\n", - "- We don’t want to just subtract the gradient from $m$ and $c$,\n", - "- We need to take a *small* step in the gradient direction.\n", - "- Otherwise we might overshoot the minimum.\n", - "- We want to follow the gradient to get to the minimum, the gradient\n", - " changes all the time." + "Now that we have computed both gradients, we can update our parameter\n", + "estimates. The key insight is that we don’t want to simply subtract the\n", + "full gradient from our current parameter values - this would likely\n", + "cause us to overshoot the minimum.\n", + "\n", + "Instead, we take small steps in the negative gradient direction. This is\n", + "crucial because:\n", + "\n", + "1. *Gradient changes*: As we move through parameter space, the gradient\n", + " itself changes, so we need to recompute it frequently\n", + "2. *Overshooting*: Large steps might cause us to jump over the minimum\n", + " and potentially diverge\n", + "3. *Local information*: The gradient only gives us local information\n", + " about the slope, not global information about the entire surface\n", + "\n", + "The size of the step we take is controlled by the learning rate, which\n", + "we’ll introduce shortly." ], - "id": "eb6b8173-b537-49f1-9552-cb80852285a7" + "id": "04f7668c-e933-43cb-989b-3e4e6c0ee653" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Move in Direction of Gradient" + "## Move in Direction of Gradient\n", + "\n", + "Let’s visualise what a single gradient descent step looks like on our\n", + "error surface. The plot shows our current parameter position as a green\n", + "star and the direction we should move (negative gradient direction) as\n", + "an arrow.\n", + "\n", + "The arrow points toward lower error values, following the steepest\n", + "descent path from our current location. Notice how the arrow is\n", + "perpendicular to the contour lines - this is always true for gradients,\n", + "which by definition point in the direction of steepest increase (and\n", + "thus their negative points in the direction of steepest decrease).\n", + "\n", + "The length and direction of this arrow tell us:\n", + "\n", + "- *Direction*: Where to move in parameter space\n", + "- *Magnitude*: How steep the slope is (longer arrows mean steeper\n", + " slopes)\n", + "- *Step size*: We scale the arrow by the learning rate to determine\n", + " our actual step size" ], - "id": "c59da336-5f37-438b-8e68-e0cb7506db74" + "id": "97d13616-7310-4166-9b1a-a64160b3cb4d" }, { "cell_type": "code", @@ -1992,7 +2306,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "bfd55511-4e4b-4531-a336-93f73c899c81" + "id": "6f4fcd93-697c-46cb-b2c0-f8f9c4a7d615" }, { "cell_type": "code", @@ -2006,18 +2320,18 @@ "ax.arrow(m_star, c_star, -m_grad*0.1, -c_grad*0.1, head_width=0.2)\n", "mlai.write_figure(filename='regression_contour_step001.svg', directory='./ml/', transparent=True)" ], - "id": "bc459547-5560-4a6f-8acb-00f7e42e7a84" + "id": "e4dd5339-b68b-473e-8ef2-a5ebc8efc1e1" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Single update descending the contours of the error surface\n", "for regression." ], - "id": "2416ae16-e9e2-4993-824c-4feaeabafcb1" + "id": "d0810303-ff49-4265-af5d-335eb34235d3" }, { "cell_type": "markdown", @@ -2025,20 +2339,20 @@ "source": [ "## Update Equations\n", "\n", - "- The step size has already been introduced, it’s again known as the\n", - " learning rate and is denoted by $\\eta$. $$\n", - " c_\\text{new}\\leftarrow c_{\\text{old}} - \\eta\\frac{\\text{d}E(m, c)}{\\text{d}c}\n", - " $$\n", + "The crucial hyperparameter in gradient descent is the learning rate\n", + "$\\eta$, which controls how big steps we take in the direction of the\n", + "negative gradient. The learning rate is a balancing act:\n", "\n", - "- gives us an update for our estimate of $c$ (which in the code we’ve\n", - " been calling `c_star` to represent a common way of writing a\n", - " parameter estimate, $c^*$) and $$\n", - " m_\\text{new} \\leftarrow m_{\\text{old}} - \\eta\\frac{\\text{d}E(m, c)}{\\text{d}m}\n", - " $$\n", + "- *Too large*: We might overshoot the minimum, potentially causing the\n", + " algorithm to diverge or oscillate wildly\n", + "- *Too small*: The algorithm will converge very slowly, requiring many\n", + " iterations to reach the minimum\n", + "- *Just right*: We make steady progress toward the minimum without\n", + " overshooting\n", "\n", - "- Giving us an update for $m$." + "The update equations formally incorporate the learning rate:" ], - "id": "51850c93-44fe-41d7-80ee-539c79e4cc07" + "id": "8212e55c-3eb8-4fa7-ae7e-1d5db168bbf0" }, { "cell_type": "markdown", @@ -2046,9 +2360,16 @@ "source": [ "## Update Code\n", "\n", - "- These updates can be coded as" + "Let’s implement the parameter updates in code. We choose a learning rate\n", + "of 0.01, which is small enough to ensure stable convergence but large\n", + "enough to make reasonable progress.\n", + "\n", + "The code shows the before and after parameter values, demonstrating how\n", + "a single gradient descent step moves us closer to the optimal solution.\n", + "After this update, we would recompute the gradients at our new position\n", + "and repeat the process." ], - "id": "c935e7bc-8cec-4ee6-8f1b-19c32f421f08" + "id": "cfb8c16c-4de3-4e33-b656-36983eb79dca" }, { "cell_type": "code", @@ -2062,25 +2383,54 @@ "m_star = m_star - learn_rate*m_grad\n", "print(\"New m is\", m_star, \"and new c is\", c_star)" ], - "id": "008ea2ce-90d9-4a54-9ab8-acf5c9900a6b" + "id": "c9a1fe85-4045-4a64-b14b-9bc0c53f5bf9" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Iterating Updates\n", + "## Iterating Updates\n", + "\n", + "One gradient step only takes us partway to the minimum. The full\n", + "gradient descent algorithm requires iterating these updates until\n", + "convergence. The process is:\n", "\n", - "- Fit model by descending gradient." + "1. *Initialise* parameters with random or reasonable starting values\n", + "2. *Compute gradients* at the current position\n", + "3. *Update parameters* using the gradient and learning rate\n", + "4. *Repeat* steps 2-3 until convergence (gradients become very small or\n", + " error stops decreasing)\n", + "\n", + "The beauty of this algorithm is its simplicity and general\n", + "applicability - the same basic approach works for many different types\n", + "of models and error functions." ], - "id": "cc427622-ec97-4fce-95ed-b7654c742e57" + "id": "e47fdccd-4654-480b-9b41-12b0812fb6db" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Gradient Descent Algorithm" + "## Gradient Descent Algorithm\n", + "\n", + "Let’s run the complete gradient descent algorithm and visualise how the\n", + "parameters evolve over multiple iterations. The animation will show the\n", + "path taken through parameter space as the algorithm navigates toward the\n", + "minimum of the error surface.\n", + "\n", + "Each frame shows:\n", + "\n", + "- *Current position*: The green star indicating our current parameter\n", + " estimates\n", + "- *Error contours*: The background showing the error landscape\n", + "- *Path*: The trajectory we’ve taken from the starting point to the\n", + " current position\n", + "\n", + "Watch how the algorithm follows a curved path that eventually spiral\n", + "into the minimum, demonstrating the iterative nature of gradient-based\n", + "optimisation." ], - "id": "b94e007d-6eea-4e04-819a-1fd25efc9b67" + "id": "d5140d1b-6af8-4087-ab39-f67aa064d2f5" }, { "cell_type": "code", @@ -2088,9 +2438,9 @@ "metadata": {}, "outputs": [], "source": [ - "num_plots = plot.regression_contour_fit(x, y, diagrams='./ml')" + "import mlai.plot as plot" ], - "id": "b49f5b07-05ef-4896-b82a-b0cf7d6c4aae" + "id": "8ec313f9-c810-4fb5-a106-5b7f94cf23c7" }, { "cell_type": "code", @@ -2098,10 +2448,9 @@ "metadata": {}, "outputs": [], "source": [ - "import notutils as nu\n", - "from ipywidgets import IntSlider" + "num_plots = plot.regression_contour_fit(x, y, diagrams='./ml')" ], - "id": "ce75c03b-d1d9-46e1-8400-177a373dab13" + "id": "fd535a7f-fbef-4e52-9072-5d7b4ae29e19" }, { "cell_type": "code", @@ -2109,9 +2458,10 @@ "metadata": {}, "outputs": [], "source": [ - "import notutils as nu" + "import notutils as nu\n", + "from ipywidgets import IntSlider" ], - "id": "e1ee3e3f-2814-4c83-9978-f3c7f0e75608" + "id": "2bb48341-606c-4017-939d-ae9ab870cf5f" }, { "cell_type": "code", @@ -2121,17 +2471,35 @@ "source": [ "nu.display_plots('regression_contour_fit{num:0>3}.svg', directory='./ml', num=IntSlider(0, 0, num_plots, 1))" ], - "id": "3a6d6715-47ad-457b-975b-1c322352b3c4" + "id": "fae307c4-be59-42e0-8b95-9aded2fd82d4" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", + "\n", + "Figure: Batch gradient descent for linear regression showing the\n", + "final converged solution.\n", "\n", - "Figure: Batch gradient descent for linear regression" + "The final frame shows the algorithm has converged to the minimum of the\n", + "error surface. Notice how:\n", + "\n", + "- *Path shape*: The trajectory follows the natural gradient flow,\n", + " starting with larger steps when gradients are large and taking\n", + " smaller steps as we approach the minimum\n", + "- *Convergence*: The final position should be very close to our true\n", + " parameter values ($m_{true} = 1.4$, $c_{true} = -3.1$)\n", + "- *Efficiency*: The algorithm finds the optimal solution automatically\n", + " without us needing to specify the answer ahead of time\n", + "\n", + "But note the limitations of gradient-based optimisation: by following\n", + "local information (the gradient), we can sometimes approach the minimum\n", + "only very slowly. To improve convergence we can look to more advanced\n", + "methods that consider curvature or, in neural networks, methods that use\n", + "a stochastic approximation to the gradient." ], - "id": "7991437f-5043-4b55-9fc9-9d3e0da4a098" + "id": "e1fd653e-3064-48ed-af90-47090e013b67" }, { "cell_type": "markdown", @@ -2139,14 +2507,34 @@ "source": [ "## Stochastic Gradient Descent\n", "\n", - "- If $n$ is small, gradient descent is fine.\n", - "- But sometimes (e.g. on the internet $n$ could be a billion.\n", - "- Stochastic gradient descent is more similar to perceptron.\n", - "- Look at gradient of one data point at a time rather than summing\n", - " across *all* data points)\n", - "- This gives a stochastic estimate of gradient." + "\\[edit\\]\n", + "\n", + "While batch gradient descent is possible for small datasets, it becomes\n", + "computationally prohibitive when dealing with large-scale data. Consider\n", + "modern internet applications where you might have millions or billions\n", + "of data points - computing the full gradient by summing over all data\n", + "points in each iteration would be extremely slow.\n", + "\n", + "Stochastic Gradient Descent (SGD) offers an elegant solution by\n", + "approximating the full gradient using just one (or a few) randomly\n", + "selected data points at each iteration. This approach:\n", + "\n", + "1. *Scales to large datasets*: Each update requires only O(1)\n", + " computation regardless of dataset size\n", + "2. *Enables online learning*: We can process data as it arrives,\n", + " updating our model incrementally\n", + "3. *Provides regularization*: The noise in stochastic updates can help\n", + " escape local minima (though less relevant for convex problems like\n", + " linear regression)\n", + "4. *Mirrors biological learning*: Similar to how the perceptron\n", + " processes one example at a time\n", + "\n", + "The key insight is that while each individual gradient estimate is\n", + "noisy, on average it points in the correct direction toward the minimum." ], - "id": "1b150e08-3032-4663-9825-7595a03ce3f4" + "id": "ade183f3-de4b-4548-9811-fbdf72a8129d" }, { "cell_type": "markdown", @@ -2154,29 +2542,24 @@ "source": [ "## Stochastic Gradient Descent\n", "\n", - "- The real gradient with respect to $m$ is given by\n", + "To understand how stochastic gradient descent works mathematically,\n", + "let’s examine how the full batch gradient can be decomposed into\n", + "individual per-example contributions. This decomposition reveals why we\n", + "can update parameters using just one data point at a time.\n", "\n", - " $$\\frac{\\text{d}E(m, c)}{\\text{d} m} = -2\\sum_{i=1}^nx_i(y_i -\n", - " mx_i - c)$$\n", + "The batch gradient is a sum over all data points:\n", + "$$\\frac{\\text{d}E(m, c)}{\\text{d} m} = -2\\sum_{i=1}^nx_i(y_i - mx_i - c)$$\n", "\n", - " but it has $n$ terms in the sum. Substituting in the gradient we can\n", - " see that the full update is of the form\n", + "When we substitute this into our parameter update equation, we get:\n", + "$$m_\\text{new} \\leftarrow m_\\text{old} + 2\\eta\\left[\\sum_{i=1}^nx_i (y_i - m_\\text{old}x_i - c_\\text{old})\\right]$$\n", "\n", - " $$m_\\text{new} \\leftarrow\n", - " m_\\text{old} + 2\\eta\\left[x_1 (y_1 - m_\\text{old}x_1 - c_\\text{old}) + (x_2 (y_2 - m_\\text{old}x_2 - c_\\text{old}) + \\dots + (x_n (y_n - m_\\text{old}x_n - c_\\text{old})\\right]$$\n", - "\n", - " This could be split up into lots of individual updates\n", - " $$m_1 \\leftarrow m_\\text{old} + 2\\eta\\left[x_1 (y_1 - m_\\text{old}x_1 -\n", - " c_\\text{old})\\right]$$ $$m_2 \\leftarrow m_1 + 2\\eta\\left[x_2 (y_2 -\n", - " m_\\text{old}x_2 - c_\\text{old})\\right]$$\n", - " $$m_3 \\leftarrow m_2 + 2\\eta\n", - " \\left[\\dots\\right]$$\n", - " $$m_n \\leftarrow m_{n-1} + 2\\eta\\left[x_n (y_n -\n", - " m_\\text{old}x_n - c_\\text{old})\\right]$$\n", - "\n", - "which would lead to the same final update." + "The key insight is that this single large update is mathematically\n", + "equivalent to a sequence of smaller updates, each using just one data\n", + "point. However, there’s an important difference in practice - in SGD, we\n", + "update the parameters after each individual contribution rather than\n", + "keeping them fixed throughout the sum." ], - "id": "fb69b004-3248-4e2c-8580-2b2f580349cc" + "id": "b88401d1-e47c-409e-86c6-2dead788cd89" }, { "cell_type": "markdown", @@ -2184,17 +2567,28 @@ "source": [ "## Updating $c$ and $m$\n", "\n", - "- In the sum we don’t $m$ and $c$ we use for computing the gradient\n", - " term at each update.\n", - "- In stochastic gradient descent we *do* change them.\n", - "- This means it’s not quite the same as steepest desceint.\n", - "- But we can present each data point in a random order, like we did\n", - " for the perceptron.\n", - "- This makes the algorithm suitable for large scale web use (recently\n", - " this domain is know as ‘Big Data’) and algorithms like this are\n", - " widely used by Google, Microsoft, Amazon, Twitter and Facebook." + "Here’s where stochastic gradient descent fundamentally differs from\n", + "batch gradient descent. In batch gradient descent, we compute the full\n", + "gradient using fixed parameter values $m_{\\text{old}}$ and\n", + "$c_{\\text{old}}$ for all data points, then make one large update.\n", + "\n", + "In stochastic gradient descent, we update the parameters after\n", + "processing each individual data point. This means:\n", + "\n", + "1. *Dynamic parameters*: The values of $m$ and $c$ change as we process\n", + " each example, so later examples in the sequence see updated\n", + " parameter values\n", + "2. *Approximate gradient*: Since parameters change during the “sum,”\n", + " we’re not computing the true batch gradient - we’re using a\n", + " stochastic approximation\n", + "3. *Online processing*: We can process data points as they arrive,\n", + " making the algorithm suitable for streaming data\n", + "\n", + "This dynamic updating is what makes SGD both powerful and noisy. The\n", + "noise can actually be beneficial - it provides a form of regularization\n", + "and can help escape poor local minima in more complex problems." ], - "id": "67bd814d-d0cb-4969-9679-35b43a251cb1" + "id": "b5ba39d2-c654-4408-a697-4b9e316f0561" }, { "cell_type": "markdown", @@ -2202,12 +2596,25 @@ "source": [ "## Stochastic Gradient Descent\n", "\n", - "- Or more accurate, since the data is normally presented in a random\n", - " order we just can write $$\n", - " m_\\text{new} = m_\\text{old} + 2\\eta\\left[x_i (y_i - m_\\text{old}x_i - c_\\text{old})\\right]\n", - " $$" + "In practice, stochastic gradient descent simplifies to a very clean\n", + "algorithm. Since we process data points in random order, each SGD update\n", + "uses the gradient contribution from just one randomly selected example.\n", + "The update equations become:\n", + "\n", + "For the slope parameter $$\n", + "m_\\text{new} = m_\\text{old} + 2\\eta\\left[x_i (y_i - m_\\text{old}x_i - c_\\text{old})\\right].\n", + "$$ For the intercept parameter\n", + "$$c_\\text{new} = c_\\text{old} + 2\\eta\\left[(y_i - m_\\text{old}x_i - c_\\text{old})\\right].\n", + "$$ Notice how these look exactly like the per-example terms from our\n", + "batch gradient, but now we apply them immediately rather than\n", + "accumulating them in a sum.\n", + "\n", + "The implementation is straightforward: randomly select a data point,\n", + "compute its contribution to the gradient, and immediately update the\n", + "parameters. This process repeats for many iterations, with each\n", + "iteration using a different random example." ], - "id": "3037210e-d064-4939-9929-4308cfe124ed" + "id": "4ef7e85e-7a69-4d4a-ba0e-fc354b3faf02" }, { "cell_type": "code", @@ -2222,7 +2629,7 @@ "# update c\n", "c_star = c_star + 2*learn_rate*(y[i]-m_star*x[i] - c_star)" ], - "id": "34cc9f1d-2ead-49f1-8f39-7a9ca933bebe" + "id": "2e777e62-d778-471a-b3c6-f3cfa817fffa" }, { "cell_type": "markdown", @@ -2230,10 +2637,23 @@ "source": [ "## SGD for Linear Regression\n", "\n", - "Putting it all together in an algorithm, we can do stochastic gradient\n", - "descent for our regression data." + "Let’s now run the complete stochastic gradient descent algorithm and\n", + "visualize how it differs from batch gradient descent. The key\n", + "differences you’ll observe:\n", + "\n", + "1. *Noisier path*: SGD takes a much more erratic path through parameter\n", + " space because each update is based on just one noisy example\n", + "2. *More iterations needed*: While each iteration is faster ($O(1)$ vs\n", + " $O(n)$), SGD typically needs more iterations to converge\n", + "3. *Never fully converges*: SGD will continue to “bounce around” near\n", + " the minimum due to the noise in gradient estimates\n", + "4. *Practical efficiency*: Despite the noise, SGD often reaches good\n", + " solutions faster in wall-clock time for large datasets\n", + "\n", + "The animation shows how the stochastic approximation creates a much more\n", + "chaotic but ultimately effective optimization trajectory." ], - "id": "59809f1e-5ea2-4f08-8040-20064f612e10" + "id": "7cdfd457-e9b9-4a49-bd45-a0ab1c457d2a" }, { "cell_type": "code", @@ -2243,7 +2663,7 @@ "source": [ "num_plots = plot.regression_contour_sgd(x, y, diagrams='./ml')" ], - "id": "c36c86a5-7bd8-467a-a8eb-afe83aeba57f" + "id": "fdbf54a3-b2c4-4717-b364-aa88425d31db" }, { "cell_type": "code", @@ -2254,7 +2674,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "b91c78cd-c733-4fc0-95b5-dacd8411ea52" + "id": "68b544f9-2a65-4000-8f3c-b4f02614f77c" }, { "cell_type": "code", @@ -2264,7 +2684,7 @@ "source": [ "import notutils as nu" ], - "id": "ae52a723-5be5-417a-b170-c594ba35f697" + "id": "9ed3c875-af6f-44a0-86b3-f3e45c62e7ab" }, { "cell_type": "code", @@ -2275,17 +2695,91 @@ "nu.display_plots('regression_sgd_contour_fit{num:0>3}.svg', \n", " directory='./ml', num=IntSlider(0, 0, num_plots, 1))" ], - "id": "fc516dd5-f3c9-4633-a9ff-1ebd90f21250" + "id": "9c7d62a8-64d9-4bb6-a732-c4fb454b16e4" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Stochastic gradient descent for linear regression showing the\n", + "noisy but effective optimization path.\n", + "\n", + "The final visualisation demonstrates the behaviour of SGD compared to\n", + "batch gradient descent:\n", + "\n", + "**Optimization Path Characteristics:**\n", + "\n", + "- *Erratic trajectory*: The path zigzags chaotically through parameter\n", + " space rather than following smooth contours\n", + "- *Local exploration*: The algorithm continues to explore around the\n", + " minimum even after finding a good solution\n", + "- *Effective convergence*: Despite the noise, SGD successfully\n", + " navigates to the optimal region\n", + "\n", + "**Practical Implications:**\n", + "\n", + "- *Computational efficiency*: Each SGD iteration processes just one\n", + " example, making it ideal for large datasets where computing the full\n", + " gradient is prohibitive\n", + "- *Online learning capability*: The algorithm can adapt to new data\n", + " points as they arrive, enabling real-time model updates\n", + "- *Regularization effect*: The noise in SGD can prevent overfitting\n", + " and help escape poor local minima in more complex optimization\n", + " landscapes\n", + "\n", + "**Trade-offs:**\n", + "\n", + "- *Convergence vs. efficiency*: SGD trades smooth convergence for\n", + " computational speed - it may never fully settle but reaches good\n", + " solutions quickly\n", + "- *Hyperparameter sensitivity*: Learning rate tuning becomes more\n", + " critical as too large values can cause divergence, while too small\n", + " values slow convergence excessively\n", + "\n", + "This stochastic approach forms the foundation for training modern\n", + "machine learning models, particularly deep neural networks where batch\n", + "gradient computation is computationally intractable." + ], + "id": "141c8b0f-8cb3-47f9-b641-f82b5aa47550" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "## Mini-Batch\n", + "\n", + "In practice we normally use mini-batches for gradient descent. Rather\n", + "than computing the gradient with respect to one point, you compute\n", + "gradient with respect to a small batch of points." + ], + "id": "0fc7faf2-f1d4-42db-a141-630fbb1d6970" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Exercise 4\n", + "\n", + "Can you write your own implementation of mini-batch gradient ascent for\n", + "the regression problem? Allow the user to choose the mini-batch size." + ], + "id": "70702cfa-c320-43d6-a1a7-e93f741f420e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Write your answer to Exercise 4 here\n", + "\n", "\n", - "Figure: Stochastic gradient descent for linear regression." + "\n", + "\n" ], - "id": "9386d20a-ca3d-4417-8c8d-e8818a157639" + "id": "2f2eefa3-02a5-429a-9772-fb3e4fdb239a" }, { "cell_type": "markdown", @@ -2293,18 +2787,24 @@ "source": [ "## Reflection on Linear Regression and Supervised Learning\n", "\n", + "\\[edit\\]\n", + "\n", "Think about:\n", "\n", - "1. What effect does the learning rate have in the optimization? What’s\n", - " the effect of making it too small, what’s the effect of making it\n", - " too big? Do you get the same result for both stochastic and steepest\n", - " gradient descent?\n", + "1. What effect does the learning rate have in the optimization?\n", + "\n", + "- What’s the effect of making it too small?\n", + "- What’s the effect of making it too big?\n", "\n", - "2. The stochastic gradient descent doesn’t help very much for such a\n", - " small data set. It’s real advantage comes when there are many,\n", - " you’ll see this in the lab." + "1. Do you get the same result for both stochastic and steepest gradient\n", + " descent?\n", + "\n", + "2. Are there any advantages for stochastic gradient descent for small\n", + " data?" ], - "id": "6dec43bc-bae6-4ca0-8d6f-0afc6c9889ef" + "id": "3b576ba1-7009-43b9-bf9e-463e5899f238" }, { "cell_type": "markdown", @@ -2316,7 +2816,7 @@ "style=\"\">\\[edit\\]" ], - "id": "0f342488-550a-4797-a931-939381c3b5cb" + "id": "504ec5b6-690e-4844-9319-4847220de011" }, { "cell_type": "markdown", @@ -2339,7 +2839,7 @@ "$$ To compute the gradient of the objective, we first expand the\n", "brackets." ], - "id": "e525ba39-953e-485a-b0a4-c5b0ec2edf23" + "id": "e29f0854-cf93-44b3-8ca0-f6c3e092b149" }, { "cell_type": "markdown", @@ -2349,21 +2849,18 @@ "\n", "$$\n", "\\begin{align*}\n", - " E(\\mathbf{ w},\\sigma^2) = &\n", - "\\frac{n}{2}\\log \\sigma^2 + \\frac{1}{2\\sigma^2}\\sum\n", - "_{i=1}^{n}y_i^{2}-\\frac{1}{\\sigma^2}\\sum\n", - "_{i=1}^{n}y_i\\mathbf{ w}^{\\top}\\mathbf{ x}_i\\\\&+\\frac{1}{2\\sigma^2}\\sum\n", - "_{i=1}^{n}\\mathbf{ w}^{\\top}\\mathbf{ x}_i\\mathbf{ x}_i^{\\top}\\mathbf{ w}\n", - "+\\text{const}.\\\\\n", - " = & \\frac{n}{2}\\log \\sigma^2 + \\frac{1}{2\\sigma^2}\\sum\n", - "_{i=1}^{n}y_i^{2}-\\frac{1}{\\sigma^2}\n", - "\\mathbf{ w}^\\top\\sum_{i=1}^{n}\\mathbf{ x}_iy_i\\\\&+\\frac{1}{2\\sigma^2}\n", + " E(\\mathbf{ w},\\sigma^2) = & \\sum\n", + "_{i=1}^{n}y_i^{2}- 2\\sum\n", + "_{i=1}^{n}y_i\\mathbf{ w}^{\\top}\\mathbf{ x}_i\\\\&+\\sum\n", + "_{i=1}^{n}\\mathbf{ w}^{\\top}\\mathbf{ x}_i\\mathbf{ x}_i^{\\top}\\mathbf{ w}\\\\\n", + " = & \\sum_{i=1}^{n}y_i^{2}-\n", + "2 \\mathbf{ w}^\\top\\sum_{i=1}^{n}\\mathbf{ x}_iy_i\\\\&+\n", "\\mathbf{ w}^{\\top}\\left[\\sum\n", - "_{i=1}^{n}\\mathbf{ x}_i\\mathbf{ x}_i^{\\top}\\right]\\mathbf{ w}+\\text{const}.\n", + "_{i=1}^{n}\\mathbf{ x}_i\\mathbf{ x}_i^{\\top}\\right]\\mathbf{ w}.\n", "\\end{align*}\n", "$$" ], - "id": "f81dfc96-79e9-40d2-b08c-e4e1665d399d" + "id": "8cb9ce2e-705f-4259-a2d7-091ff02e8f22" }, { "cell_type": "markdown", @@ -2416,7 +2913,7 @@ "\\mathbf{ x}\\cdot\\mathbf{ w}= 1 \\times c + x \\times m = mx + c\n", "$$ In `numpy` we can define this vector as follows" ], - "id": "6758858e-1c9a-479d-8aad-5548cc199e90" + "id": "b619d8c9-e132-429a-b206-7b71e8196976" }, { "cell_type": "code", @@ -2426,7 +2923,7 @@ "source": [ "import numpy as np" ], - "id": "fe3d3ae8-3875-438a-918c-f73c1d534892" + "id": "17ae9c09-4be0-458e-aa26-43d7ac2850cc" }, { "cell_type": "code", @@ -2439,7 +2936,7 @@ "w[0] = m\n", "w[1] = c" ], - "id": "a97eb5a3-2ca2-4225-b962-5be5971807db" + "id": "1ab8f181-e7f4-4eea-9c92-19fde29d1579" }, { "cell_type": "markdown", @@ -2462,7 +2959,7 @@ "\\end{bmatrix}\n", "$$" ], - "id": "00200ff6-64cd-460c-9361-83935ddceb62" + "id": "d8cbafd8-ebb6-4608-954c-3ec49b50f91c" }, { "cell_type": "markdown", @@ -2471,8 +2968,9 @@ "# Design Matrix\n", "\n", "We can do this for the entire data set to form a [*design\n", - "matrix*](http://en.wikipedia.org/wiki/Design_matrix) $\\mathbf{X}$, $$\n", - "\\mathbf{X}\n", + "matrix*](http://en.wikipedia.org/wiki/Design_matrix)\n", + "$\\boldsymbol{ \\Phi}$, $$\n", + "\\boldsymbol{ \\Phi}\n", "= \\begin{bmatrix} \n", "\\mathbf{ x}_1^\\top \\\\\\ \n", "\\mathbf{ x}_2^\\top \\\\\\ \n", @@ -2487,7 +2985,7 @@ "\\end{bmatrix},\n", "$$ which in `numpy` can be done with the following commands:" ], - "id": "d0118501-efa1-4ffa-8bf1-8b669ca6c814" + "id": "ea4fdb23-0714-4ab4-afa8-9514e3343972" }, { "cell_type": "code", @@ -2497,7 +2995,7 @@ "source": [ "import numpy as np" ], - "id": "dace37f7-ea67-4771-84b7-c48298ce9c03" + "id": "96ce1eae-20f9-48b2-9b4f-a52a4569a3c6" }, { "cell_type": "code", @@ -2505,10 +3003,10 @@ "metadata": {}, "outputs": [], "source": [ - "X = np.hstack((np.ones_like(x), x))\n", - "print(X)" + "Phi = np.hstack((np.ones_like(x), x))\n", + "print(Phi)" ], - "id": "dae561ad-1bcd-4f5c-8151-7fea0af60f3f" + "id": "85c7ea32-92b7-4427-9268-45ae52d051f5" }, { "cell_type": "markdown", @@ -2546,15 +3044,16 @@ "idea is so straightforward, that we are going to ignore it for the\n", "moment, and barely use it in the derivation. But it will reappear later\n", "when we introduce *basis functions*. So, we will for the moment ignore\n", - "the dependence of $\\mathbf{ f}$ on $\\mathbf{ w}$ and $\\mathbf{X}$ and\n", - "simply summarise it by a vector of numbers $$\n", + "the dependence of $\\mathbf{ f}$ on $\\mathbf{ w}$ and\n", + "$\\boldsymbol{ \\Phi}$ and simply summarise it by a vector of numbers $$\n", "\\mathbf{ f}= \\begin{bmatrix}f_1\\\\f_2\\\\\n", "\\vdots \\\\ f_n\\end{bmatrix}.\n", "$$ This allows us to write our objective in the folowing, linear\n", "algebraic form, $$\n", "E(\\mathbf{ w}) = (\\mathbf{ y}- \\mathbf{ f})^\\top(\\mathbf{ y}- \\mathbf{ f})\n", - "$$ from the rules of inner products. But what of our matrix $\\mathbf{X}$\n", - "of input data? At this point, we need to dust off [*matrix-vector\n", + "$$ from the rules of inner products. But what of our matrix\n", + "$\\boldsymbol{ \\Phi}$ of input data? At this point, we need to dust off\n", + "[*matrix-vector\n", "multiplication*](http://en.wikipedia.org/wiki/Matrix_multiplication).\n", "Matrix multiplication is simply a convenient way of performing many\n", "inner products together, and it’s exactly what we need to summarize the\n", @@ -2591,10 +3090,10 @@ "matrix multiplication,\n", "\n", "$$\n", - "\\mathbf{ f}= \\mathbf{X}\\mathbf{ w}.\n", + "\\mathbf{ f}= \\boldsymbol{ \\Phi}\\mathbf{ w}.\n", "$$" ], - "id": "2051aab0-620e-475d-bf9f-f0873b3e9898" + "id": "c7f75ea5-da47-4036-9697-47b9b9d074aa" }, { "cell_type": "code", @@ -2604,7 +3103,7 @@ "source": [ "import numpy as np" ], - "id": "fa692743-25be-4004-aa6d-66c1af27b030" + "id": "17ff5f9e-0b42-4797-b71b-7710e48f0799" }, { "cell_type": "code", @@ -2612,9 +3111,9 @@ "metadata": {}, "outputs": [], "source": [ - "f = X@w # The @ sign performs matrix multiplication" + "f = Phi@w # The @ sign performs matrix multiplication" ], - "id": "5ccb4921-f7df-4471-8495-6304316dbe78" + "id": "17bb2e90-cd4b-4ca6-8914-983a147dcb5f" }, { "cell_type": "markdown", @@ -2626,7 +3125,7 @@ "tells us the form of our predictive function and how it depends on its\n", "parameters, the other tells us the form of our objective function." ], - "id": "9054495b-2b2e-4b52-8bb7-e4480f739667" + "id": "029c46e9-cc86-4709-a17e-a44f10ea9899" }, { "cell_type": "code", @@ -2638,13 +3137,13 @@ "E = np.dot(resid.T, resid) # matrix multiplication on a single vector is equivalent to a dot product.\n", "print(\"Error function is:\", E)" ], - "id": "d6a9d61c-38cc-43bd-886a-a8dee870baab" + "id": "579ca14f-f4fc-4f6c-8401-7a3d9c3f6e68" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 4\n", + "### Exercise 5\n", "\n", "The prediction for our movie recommender system had the form $$\n", "f_{i,j} = \\mathbf{u}_i^\\top \\mathbf{v}_j\n", @@ -2656,17 +3155,17 @@ "the terms as you can. Use $\\LaTeX$ to give your answers and give the\n", "*dimensions* of any matrices you create." ], - "id": "f2f26e56-7c5d-462b-9650-95f13955a2f9" + "id": "ba61e7d1-b518-4fcb-b89a-38f238e7071a" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 4 Answer\n", + "### Exercise 5 Answer\n", "\n", - "Write your answer to Exercise 4 here" + "Write your answer to Exercise 5 here" ], - "id": "9cb349f2-fd68-4cbe-87fe-7f968d8ebd6e" + "id": "40759ed6-8674-4af6-a9a0-33cb7fc9b25c" }, { "cell_type": "markdown", @@ -2717,7 +3216,7 @@ "addition, then its rules of differentiation are quite straightforward\n", "too, but slightly more complex than regular derivatives." ], - "id": "acc3ef90-fe1e-4d52-8be4-d12e25f66c4a" + "id": "cd539dab-4947-4809-ab1d-686f16501a95" }, { "cell_type": "markdown", @@ -2748,7 +3247,7 @@ "written as $\\mathbf{z}^\\top \\mathbf{C}\\mathbf{z}$, which is itself a\n", "*scalar* quantity, but it is a function of a *vector*." ], - "id": "6c760206-1fd5-4e08-8af3-1b08c3d002d9" + "id": "88fd9f10-a748-4c33-9358-13b5803a37f9" }, { "cell_type": "markdown", @@ -2810,7 +3309,7 @@ "2\\mathbf{C}\\mathbf{z}.\n", "$$" ], - "id": "a774162b-2007-48fb-a7c0-075a863c8542" + "id": "fa2e3aaa-f4f4-403e-8abc-5b34d804be18" }, { "cell_type": "markdown", @@ -2821,7 +3320,7 @@ "First, we need to compute the full objective by substituting our\n", "prediction function into the objective function to obtain the objective\n", "in terms of $\\mathbf{ w}$. Doing this we obtain $$\n", - "E(\\mathbf{ w})= (\\mathbf{ y}- \\mathbf{X}\\mathbf{ w})^\\top (\\mathbf{ y}- \\mathbf{X}\\mathbf{ w}).\n", + "E(\\mathbf{ w})= (\\mathbf{ y}- \\boldsymbol{ \\Phi}\\mathbf{ w})^\\top (\\mathbf{ y}- \\boldsymbol{ \\Phi}\\mathbf{ w}).\n", "$$ We now need to differentiate this *quadratic form* to find the\n", "minimum. We differentiate with respect to the *vector* $\\mathbf{ w}$.\n", "But before we do that, we’ll expand the brackets in the quadratic form\n", @@ -2831,27 +3330,28 @@ "(\\mathbf{c} - \\mathbf{d}) = \\mathbf{a}^\\top \\mathbf{c} - \\mathbf{a}^\\top\n", "\\mathbf{d} - \\mathbf{b}^\\top \\mathbf{c} + \\mathbf{b}^\\top \\mathbf{d}\n", "$$ which substituting for $\\mathbf{a} = \\mathbf{c} = \\mathbf{ y}$ and\n", - "$\\mathbf{b}=\\mathbf{d} = \\mathbf{X}\\mathbf{ w}$ gives $$\n", + "$\\mathbf{b}=\\mathbf{d} = \\boldsymbol{ \\Phi}\\mathbf{ w}$ gives $$\n", "E(\\mathbf{ w})=\n", - "\\mathbf{ y}^\\top\\mathbf{ y}- 2\\mathbf{ y}^\\top\\mathbf{X}\\mathbf{ w}+\n", - "\\mathbf{ w}^\\top\\mathbf{X}^\\top\\mathbf{X}\\mathbf{ w}\n", + "\\mathbf{ y}^\\top\\mathbf{ y}- 2\\mathbf{ y}^\\top\\boldsymbol{ \\Phi}\\mathbf{ w}+\n", + "\\mathbf{ w}^\\top\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}\\mathbf{ w}\n", "$$ where we used the fact that\n", - "$\\mathbf{ y}^\\top\\mathbf{X}\\mathbf{ w}=\\mathbf{ w}^\\top\\mathbf{X}^\\top\\mathbf{ y}$.\n", + "$\\mathbf{ y}^\\top\\boldsymbol{ \\Phi}\\mathbf{ w}=\\mathbf{ w}^\\top\\boldsymbol{ \\Phi}^\\top\\mathbf{ y}$.\n", "\n", "Now we can use our rules of differentiation to compute the derivative of\n", "this form, which is, $$\n", - "\\frac{\\text{d}}{\\text{d}\\mathbf{ w}}E(\\mathbf{ w})=- 2\\mathbf{X}^\\top \\mathbf{ y}+\n", - "2\\mathbf{X}^\\top\\mathbf{X}\\mathbf{ w},\n", - "$$ where we have exploited the fact that $\\mathbf{X}^\\top\\mathbf{X}$ is\n", - "symmetric to obtain this result." + "\\frac{\\text{d}}{\\text{d}\\mathbf{ w}}E(\\mathbf{ w})=- 2\\boldsymbol{ \\Phi}^\\top \\mathbf{ y}+\n", + "2\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}\\mathbf{ w},\n", + "$$ where we have exploited the fact that\n", + "$\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}$ is symmetric to obtain this\n", + "result." ], - "id": "9e053f58-3fa5-4fe6-a960-f7373642c892" + "id": "44426fbc-a16b-40b9-b5ea-62f358fa19dc" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 5\n", + "### Exercise 6\n", "\n", "Use the equivalence between our vector and our matrix formulations of\n", "linear regression, alongside our definition of vector derivates, to\n", @@ -2860,17 +3360,17 @@ "$\\frac{\\text{d}E(c, m)}{\\text{d}m}$ to those for\n", "$\\frac{\\text{d}E(\\mathbf{ w})}{\\text{d}\\mathbf{ w}}$." ], - "id": "00ea4958-48ee-4dc1-9a9f-f8d61fec69af" + "id": "911f70c6-eb40-48df-ad51-6189a656a93b" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 5 Answer\n", + "### Exercise 6 Answer\n", "\n", - "Write your answer to Exercise 5 here" + "Write your answer to Exercise 6 here" ], - "id": "8e472351-0cd0-44ff-8a5b-ebd7f47d3084" + "id": "a0feb74c-4ece-458f-809a-39e4a2f91781" }, { "cell_type": "markdown", @@ -2882,16 +3382,16 @@ "objective function, we can minimize for our parameter vector\n", "$\\mathbf{ w}$. Firstly, we seek stationary points by find parameter\n", "vectors that solve for when the gradients are zero, $$\n", - "\\mathbf{0}=- 2\\mathbf{X}^\\top\n", - "\\mathbf{ y}+ 2\\mathbf{X}^\\top\\mathbf{X}\\mathbf{ w},\n", + "\\mathbf{0}=- 2\\boldsymbol{ \\Phi}^\\top\n", + "\\mathbf{ y}+ 2\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}\\mathbf{ w},\n", "$$ where $\\mathbf{0}$ is a *vector* of zeros. Rearranging this equation,\n", "we find the solution to be $$\n", - "\\mathbf{X}^\\top \\mathbf{X}\\mathbf{ w}= \\mathbf{X}^\\top\n", + "\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}\\mathbf{ w}= \\boldsymbol{ \\Phi}^\\top\n", "\\mathbf{ y}\n", "$$ which is a matrix equation of the familiar form\n", "$\\mathbf{A}\\mathbf{x} = \\mathbf{b}$." ], - "id": "9815cdfe-3433-4c5b-8f44-fdb5e02c22bf" + "id": "c3002295-1db8-4090-9c05-891d97f37d67" }, { "cell_type": "markdown", @@ -2900,16 +3400,16 @@ "## Solving the Multivariate System\n", "\n", "The solution for $\\mathbf{ w}$ can be written mathematically in terms of\n", - "a matrix inverse of $\\mathbf{X}^\\top\\mathbf{X}$, but computation of a\n", - "matrix inverse requires an algorithm to resolve it. You’ll know this if\n", - "you had to invert, by hand, a $3\\times 3$ matrix in high school. From a\n", - "numerical stability perspective, it is also best not to compute the\n", - "matrix inverse directly, but rather to ask the computer to *solve* the\n", - "system of linear equations given by $$\n", - "\\mathbf{X}^\\top\\mathbf{X}\\mathbf{ w}= \\mathbf{X}^\\top\\mathbf{ y}\n", + "a matrix inverse of $\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}$, but\n", + "computation of a matrix inverse requires an algorithm to resolve it.\n", + "You’ll know this if you had to invert, by hand, a $3\\times 3$ matrix in\n", + "high school. From a numerical stability perspective, it is also best not\n", + "to compute the matrix inverse directly, but rather to ask the computer\n", + "to *solve* the system of linear equations given by $$\n", + "\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}\\mathbf{ w}= \\boldsymbol{ \\Phi}^\\top\\mathbf{ y}\n", "$$ for $\\mathbf{ w}$." ], - "id": "8f4b0ffc-8876-4ce9-a7cb-434a87360c25" + "id": "ab2b4dff-a711-4634-9429-6fb4198be87f" }, { "cell_type": "markdown", @@ -2922,13 +3422,13 @@ "what the length of the vector $\\mathbf{ x}$ should be, or equivalently\n", "the size of the design matrix." ], - "id": "0bc78576-bb73-4dec-8799-0b0741277fc6" + "id": "61320b44-1799-4317-aff0-2fca99fa9229" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Movie Body Count Data\n", + "## Movie Violence Data\n", "\n", "\\[\\[\n", + "\n", "\n", "Figure: Residual values for the ratings from the prediction of the\n", "movie rating given the data from the film.\n", @@ -3107,7 +3619,7 @@ "because the spread of values is large. We can check what the rating is\n", "dominated by in terms of regression coefficients." ], - "id": "f184f3ce-01fd-42bc-bfbb-b78e9c69022a" + "id": "6d8c9a39-e368-466d-b3d8-41c134e868cc" }, { "cell_type": "code", @@ -3117,7 +3629,7 @@ "source": [ "w" ], - "id": "d9061182-b1da-41cf-b488-29f2fc4842bc" + "id": "cb7a08c1-fd3b-47c8-85b7-2e0f30d8a5d1" }, { "cell_type": "markdown", @@ -3131,7 +3643,7 @@ "earlier films generally do better, but the residuals are so high that we\n", "probably haven’t modelled the system very well." ], - "id": "2a2b3aa0-c7c4-4fed-a449-92f5e3fd224a" + "id": "fe618427-df1f-4835-a1de-55a52dc8242a" }, { "cell_type": "code", @@ -3142,7 +3654,7 @@ "from IPython.lib.display import YouTubeVideo\n", "YouTubeVideo('ui-uNlFHoms')" ], - "id": "487171fd-1d72-4f8b-b5c9-6fa9edc5b926" + "id": "73184a68-8b54-47b6-b7b4-b33139b53c59" }, { "cell_type": "markdown", @@ -3150,7 +3662,7 @@ "source": [ "Figure: MLAI Lecture 15 from 2014 on Multivariate Regression." ], - "id": "47849d88-1271-4df8-88a4-110de5a2663b" + "id": "c4bee4c1-eabe-46b4-946d-4f69d14069ae" }, { "cell_type": "code", @@ -3161,7 +3673,7 @@ "from IPython.lib.display import YouTubeVideo\n", "YouTubeVideo('78YNphT90-k')" ], - "id": "81da2858-1b6a-4f62-afc9-1013eacadfc1" + "id": "56cc8144-b052-472a-898d-0afd9ff9b6ef" }, { "cell_type": "markdown", @@ -3169,7 +3681,7 @@ "source": [ "Figure: MLAI Lecture 3 from 2012 on Maximum Likelihood" ], - "id": "a1c7fd5a-a161-4e43-801d-aa673fa52bc4" + "id": "a704210a-f64c-4197-911d-42befb71d002" }, { "cell_type": "markdown", @@ -3182,14 +3694,21 @@ "style=\"\">edit\\]\n", "\n", "Performing a solve instead of a matrix inverse is the more numerically\n", - "stable approach, but we can do even better. A\n", - "[QR-decomposition](http://en.wikipedia.org/wiki/QR_decomposition) of a\n", + "stable approach, but we can do even better.\n", + "\n", + "The problems can arise with very large data sets, where computing\n", + "$\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}$ can lead to a very poorly\n", + "conditioned matrix. Instead, we can compute the solution without having\n", + "to compute the matrix. The trick is to use a QR decomposition.\n", + "\n", + "A [QR-decomposition](http://en.wikipedia.org/wiki/QR_decomposition) of a\n", "matrix factorizes it into a matrix which is an orthogonal matrix\n", "$\\mathbf{Q}$, so that $\\mathbf{Q}^\\top \\mathbf{Q} = \\mathbf{I}$. And a\n", "matrix which is upper triangular, $\\mathbf{R}$. $$\n", - "\\mathbf{X}^\\top \\mathbf{X}\\boldsymbol{\\beta} =\n", - "\\mathbf{X}^\\top \\mathbf{ y}\n", - "$$ and we substitute $\\mathbf{X}= \\mathbf{Q}{\\mathbf{R}$ so we have $$\n", + "\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}\\boldsymbol{\\beta} =\n", + "\\boldsymbol{ \\Phi}^\\top \\mathbf{ y}\n", + "$$ and we substitute $\\boldsymbol{ \\Phi}= \\mathbf{Q}\\mathbf{R}$ so we\n", + "have $$\n", "(\\mathbf{Q}\\mathbf{R})^\\top\n", "(\\mathbf{Q}\\mathbf{R})\\boldsymbol{\\beta} = (\\mathbf{Q}\\mathbf{R})^\\top\n", "\\mathbf{ y}\n", @@ -3206,17 +3725,19 @@ "$$ which leaves us with a lower triangular system to solve.\n", "\n", "This is a more numerically stable solution because it removes the need\n", - "to compute $\\mathbf{X}^\\top\\mathbf{X}$ as an intermediate. Computing\n", - "$\\mathbf{X}^\\top\\mathbf{X}$ is a bad idea because it involves squaring\n", - "all the elements of $\\mathbf{X}$ and thereby potentially reducing the\n", - "numerical precision with which we can represent the solution. Operating\n", - "on $\\mathbf{X}$ directly preserves the numerical precision of the model.\n", + "to compute $\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}$ as an\n", + "intermediate. Computing $\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}$ is a\n", + "bad idea because it involves squaring all the elements of\n", + "$\\boldsymbol{ \\Phi}$ and thereby potentially reducing the numerical\n", + "precision with which we can represent the solution. Operating on\n", + "$\\boldsymbol{ \\Phi}$ directly preserves the numerical precision of the\n", + "model.\n", "\n", "This can be more particularly seen when we begin to work with *basis\n", "functions* in the next session. Some systems that can be resolved with\n", "the QR decomposition cannot be resolved by using solve directly." ], - "id": "7047f9f9-cebe-411e-a5b1-57ea8e7d61c6" + "id": "baec7d99-378b-42eb-81e7-0a7f6f72e4ff" }, { "cell_type": "code", @@ -3226,7 +3747,7 @@ "source": [ "import scipy as sp" ], - "id": "f1545e4f-e5c5-43b6-adfc-a69c536f6dc8" + "id": "e0cbcc0a-3c14-4951-bf7c-cb7a3288b025" }, { "cell_type": "code", @@ -3234,12 +3755,97 @@ "metadata": {}, "outputs": [], "source": [ - "Q, R = np.linalg.qr(X)\n", + "Q, R = np.linalg.qr(Phi)\n", "w = sp.linalg.solve_triangular(R, Q.T@y) \n", - "w = pd.DataFrame(w, index=X.columns)\n", + "w = pd.DataFrame(w, index=Phi.columns)\n", "w" ], - "id": "ad6168c2-f236-4ea9-90fa-938fd72dca85" + "id": "d6166292-6cdf-4a55-bda9-92a2e2e11522" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Quadratic Loss and Linear System\n", + "\n", + "We will consider a simplified system, to remind us of some of the linear\n", + "algebra involved, and introduce some of the fundamental issues." + ], + "id": "0913d306-9f81-4951-b800-169e5b0166e1" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hessian Matrix\n", + "\n", + "\\[edit\\]\n", + "\n", + "We can also compute the [Hessian\n", + "matrix](https://en.wikipedia.org/wiki/Hessian_matrix), the curvature of\n", + "the loss function. To do this we take the second derivative of the loss\n", + "function with respect to the parameter vector, $$\n", + "\\frac{\\text{d}^2}{\\text{d}\\mathbf{ w}\\text{d}\\mathbf{ w}^\\top} E(\\mathbf{ w}) = 2\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}.\n", + "$$ So, we see that the curvature is only dependent on the design matrix.\n", + "\n", + "Note that for this linear model the curvature is *not* dependent on the\n", + "values of the parameter vector, $\\mathbf{ w}$, or indeed on the\n", + "*response* variables, $\\mathbf{ y}$. This is unusual, in general the\n", + "curvature will depend on the parameters and the response variables. The\n", + "linear model with quadratic loss is a special case because the overall\n", + "loss function has a *quadratic form* which is the unique form with\n", + "constant curvature across the whole space.\n", + "\n", + "This is one reason why linear models are so easy to work with.\n", + "\n", + "Because the curvature is constant everywhere, we know that the curvature\n", + "at the minimum is given by $2\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}$.\n", + "\n", + "From univariate calculus you might recall that the optimum is a maximum\n", + "if the curvature is negative, and a minimum if the curvature is\n", + "positive. A similar theorem holds for multivariate calculus, but now the\n", + "curvature must be *positive definite* for the point to be a minimum. The\n", + "constant curvature also shows us also that the minimum is *unique*.\n", + "\n", + "Positive definite means that for any two vectors, $\\mathbf{u}$ of unit\n", + "length $\\mathbf{u}^\\top\\mathbf{u}$ we have that, $$\n", + "\\mathbf{u}^\\top\\mathbf{A} \\mathbf{u} > 0 \\quad \\forall \\quad \\mathbf{u} \\quad \\text{with}\\quad \\mathbf{u}^\\top\\mathbf{u}=1\n", + "$$ The matrix $\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}$ (where we’ve\n", + "dropped the 2) will satisfy this condition as long as the columns of\n", + "$\\boldsymbol{ \\Phi}$ are *linearly independent* and the number of basis\n", + "functions is less or equal to the number of data points." + ], + "id": "7ff5a36b-b20d-4046-9dbb-18d9f35b337d" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Eigendecomposition of Hessian\n", + "\n", + "Applying a vector $\\mathbf{u}$ to the Hessian matrix gives us the\n", + "curvature in a particular direction. So we can use this to look at the\n", + "shape of the minimum by projecting onto the different directions,\n", + "$\\mathbf{u}$.\n", + "\n", + "Recall the eigendecomposition of a matrix, $$\n", + "\\mathbf{A}\\mathbf{u} = \\lambda\\mathbf{u}\n", + "$$ If we allow $\\mathbf{u}_i$ to be an *eigenvector* of\n", + "$\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}\\mathbf{ w}$ then the\n", + "curvature in that direction is given by the corresponding eigenvalue,\n", + "$\\lambda_i$. $$\n", + "\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}\\mathbf{u}_i = \\lambda_i \\mathbf{u}_i.\n", + "$$\n", + "\n", + "The eigendecomposition of the Hessian is a convenient representation of\n", + "the nature of these minima. The principal eigenvector (the one\n", + "associated with the largest eigenvalue), $\\mathbf{u}_1$ is associated\n", + "with the direction of *highest curvature*. While the minor eigenvector\n", + "shows us the flattest direction, where the curvature is smallest." + ], + "id": "06c4bcd2-4718-40a2-bc69-266458196907" }, { "cell_type": "markdown", @@ -3250,6 +3856,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -3257,7 +3866,7 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "1c5d7579-072e-4253-ad56-f8a2dc53dec8" + "id": "9df250c3-25c2-4509-b2f7-ba7ea2086ef9" }, { "cell_type": "markdown", @@ -3272,20 +3881,14 @@ "\n", "- Section 1.3 for Matrix & Vector Review of Rogers and Girolami (2011)" ], - "id": "12a0c277-740d-4c05-b009-35905e9821ce" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "808261f1-eb7e-4aa2-96fa-fa33988156f9" + "id": "c6e79590-a23f-4138-888f-5673b58d847f" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Bishop, C.M., 2006. Pattern recognition and machine learning. springer.\n", "\n", "Laplace, P.S., 1814. Essai philosophique sur les probabilités, 2nd ed.\n", @@ -3297,7 +3900,7 @@ "Rogers, S., Girolami, M., 2011. A first course in machine learning. CRC\n", "Press." ], - "id": "8f38c99a-11b2-4c7e-937c-855235d78159" + "id": "a16a8946-e537-4c66-b891-98f3ccff1830" } ], "nbformat": 4, diff --git a/_notebooks/06-basis-functions.ipynb b/_notebooks/06-basis-functions.ipynb index 4702c20d..74f3606d 100644 --- a/_notebooks/06-basis-functions.ipynb +++ b/_notebooks/06-basis-functions.ipynb @@ -6,7 +6,7 @@ "source": [ "# Basis Functions" ], - "id": "b060c35f-d53f-487f-914f-fb8eab07fda5" + "id": "93240ed8-b6d2-4080-90a1-01d0106f28aa" }, { "cell_type": "markdown", @@ -19,7 +19,7 @@ "In this session we will introduce *basis functions* which allow us to\n", "implement *non-linear regression models*." ], - "id": "5d54a144-7c49-441d-9050-44731f272ee8" + "id": "4656ef50-0bf4-4a7d-88b4-ec0f814666f5" }, { "cell_type": "markdown", @@ -28,17 +28,20 @@ "$$\n", "$$" ], - "id": "c3be330c-2a1a-43f6-b308-ef13a3b19e40" + "id": "ed80e014-18fb-43bb-bba6-7bfb0dc42473" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -46,7 +49,7 @@ "\n", "-->" ], - "id": "a588a55c-9d3f-42c1-8b28-170b2040de3e" + "id": "b3519b3a-1fe5-4924-968a-11c84c08bd93" }, { "cell_type": "markdown", @@ -58,7 +61,7 @@ "style=\"\">\\[edit\\]" ], - "id": "9e2095e3-604c-4ba2-82d2-9d64ce287c39" + "id": "69e57f01-4d76-4774-95f5-1e4fbd6a5840" }, { "cell_type": "code", @@ -67,9 +70,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "eab89c1a-5534-43d8-b0d2-aa1f465b7f77" + "id": "52612341-9f55-4da0-8060-3e1de159e31d" }, { "cell_type": "markdown", @@ -80,7 +93,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "8cc298f0-984f-4d2a-b100-45ddf5dd1fd6" + "id": "48bd0f4f-db87-4923-9040-1a27bd8d9a84" }, { "cell_type": "markdown", @@ -97,7 +110,17 @@ "\n", "The software can be installed using" ], - "id": "f057fa3b-47c1-42ba-9c70-165fe291fb3a" + "id": "3ce342ca-527f-4324-a253-a51506827e6d" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "b70bc64d-3fdc-4f56-a24e-012492a521ae" }, { "cell_type": "code", @@ -105,9 +128,48 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" ], - "id": "1012c2b9-4d83-4e68-9972-23c438a55890" + "id": "b87a3889-cce7-4441-8ec8-49df287213a6" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "b7f0a7b5-b510-4b44-ab22-333c8058b84e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "5caae121-f117-4cff-8318-589f71ae1341" }, { "cell_type": "markdown", @@ -120,7 +182,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "76f79bc2-7367-425f-8131-8c5005c8f4d0" + "id": "6cbb0fef-cd12-4fbe-959d-545052234406" }, { "cell_type": "code", @@ -130,7 +192,7 @@ "source": [ "import notutils" ], - "id": "3b97b271-05b5-4206-9c6c-d81b132f88f3" + "id": "252f0e2f-6290-444a-98c6-840ca51cdf58" }, { "cell_type": "markdown", @@ -152,7 +214,17 @@ "\n", "The software can be installed using" ], - "id": "58df8ac0-f722-4b79-b3f2-f231a2b84549" + "id": "dbd96865-13ab-4eae-93bc-ec0ccfe542d9" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "e536141a-28f0-4a00-8c40-09e3e3fa43af" }, { "cell_type": "code", @@ -160,9 +232,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "%system {cmd}" ], - "id": "a6dcc5e2-ea04-4acd-9c39-0950bf39e7d3" + "id": "17d822f8-025f-4238-b2f7-33ff3ada7123" }, { "cell_type": "markdown", @@ -174,7 +246,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "6193a67f-3fb4-453e-b724-fe8aa7976441" + "id": "c30fe0ce-a74f-4ffa-887e-640062695732" }, { "cell_type": "code", @@ -184,7 +256,7 @@ "source": [ "import pods" ], - "id": "ecd1e89b-3880-4f6d-9153-0ebbc0e50765" + "id": "e505c54b-63b9-4920-8d89-8a6468a7e792" }, { "cell_type": "markdown", @@ -202,7 +274,7 @@ "\n", "The software can be installed using" ], - "id": "bc80cef0-b333-4696-8d34-7a11b8444ee2" + "id": "2bd06b34-3277-452e-bbcf-ef08971cfc5a" }, { "cell_type": "code", @@ -210,9 +282,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "cmd = install_command('mlai')" ], - "id": "9c81ee58-7144-45e3-a8bf-f89303947085" + "id": "fce9f69b-65a2-4eb6-9c44-165ddae51841" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "e7ea86bc-210d-4613-881b-00b7331d8a89" }, { "cell_type": "markdown", @@ -224,7 +306,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "592702f2-e810-4864-a349-f3eaf1796483" + "id": "366ed778-2128-4d9c-b2f6-ff519d893cf3" }, { "cell_type": "code", @@ -232,9 +314,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "1741f9dd-9c68-4f78-b243-5ebaf7ea584c" + "id": "317c82ed-70a0-4f14-834c-e136932df10c" }, { "cell_type": "markdown", @@ -259,7 +342,7 @@ "only have one input value? We can actually artificially generate more\n", "input values with basis functions." ], - "id": "88fabcc8-46fd-44e5-8ff2-72e466fa9bbf" + "id": "b4db5e41-8b5f-4be6-a163-12b4ce822c28" }, { "cell_type": "markdown", @@ -284,15 +367,19 @@ "functions. When we are introducing non-linear regression these functions\n", "are known as *basis functions*." ], - "id": "ff1f6631-8981-4179-90b4-f99e21b17911" + "id": "c5b168de-0786-4f1e-aa9d-047fb74901ec" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Basis Functions" + "# Basis Functions\n", + "\n", + "\\[edit\\]" ], - "id": "31e4347c-2f7c-430d-852e-93fbed9dac4b" + "id": "587625e2-a8bb-488e-9193-74f20b45b5c7" }, { "cell_type": "markdown", @@ -309,7 +396,7 @@ "$\\boldsymbol{ \\phi}(\\mathbf{ x})$ where $\\boldsymbol{ \\phi}(\\cdot)$ is a\n", "*vector-valued* function that is defined on the space $\\mathbf{ x}$." ], - "id": "271ea6d4-96fb-457d-81a7-fc87eb9895ca" + "id": "72527a18-5067-4bac-ae32-861937a1edb0" }, { "cell_type": "markdown", @@ -334,7 +421,7 @@ "easier to deal with in our mathematics.\n", "\n", "When we consider the vector-valued function for each data point, then we\n", - "place all the data into a matrix. The result is a matrix valued\n", + "place all the data into a design matrix. The result is a matrix valued\n", "function, $$\n", "\\boldsymbol{ \\Phi}(\\mathbf{ x}) = \n", "\\begin{bmatrix} 1 & x_1 &\n", @@ -343,13 +430,14 @@ "\\vdots & \\vdots & \\vdots \\\\\n", "1 & x_n& x_n^2\n", "\\end{bmatrix}\n", - "$$ where we are still in the one dimensional input setting so\n", - "$\\mathbf{ x}$ here represents a vector of our inputs with $n$ elements.\n", + "$$ where here we are still considering the one dimensional input setting\n", + "so $\\mathbf{ x}$ here represents a vector of our inputs with $n$\n", + "elements.\n", "\n", "Let’s try constructing such a matrix for a set of inputs. First of all,\n", "we create a function that returns the matrix valued function." ], - "id": "464cf44f-f27a-49e2-ac4c-327269c8b87a" + "id": "f606d18f-025b-4ad0-917c-a37e95eebb5b" }, { "cell_type": "code", @@ -359,7 +447,7 @@ "source": [ "import numpy as np" ], - "id": "a352a5cc-7ee2-48fe-ae67-3cfecb6be019" + "id": "cbaef4a3-2029-4787-95a3-9c60236b70ea" }, { "cell_type": "code", @@ -372,7 +460,7 @@ " with the basis functions.\"\"\"\n", " return np.hstack([np.ones((x.shape[0], 1)), x, x**2])" ], - "id": "336ae97c-1fb8-4413-8577-03d153e601dc" + "id": "1602c558-a280-49f8-8d3c-cdde47aae825" }, { "cell_type": "markdown", @@ -388,7 +476,7 @@ "f(x) = {\\color{red}{w_0}} + {\\color{magenta}{w_1 x}} + {\\color{blue}{w_2 x^2}}\n", "$$" ], - "id": "3f88a4e2-7a68-40c7-8459-d06419de2efb" + "id": "0530215e-48e9-456a-aaef-ecdd4f087ee0" }, { "cell_type": "code", @@ -399,7 +487,7 @@ "import matplotlib.pyplot as plt\n", "import mlai.plot as plot" ], - "id": "fb3c3264-de6f-4f09-bd5f-1d9f3a5d99a7" + "id": "87191243-375a-4cea-8b59-79d95c71d7dc" }, { "cell_type": "code", @@ -411,26 +499,26 @@ "loc =[[0, 1.4,],\n", " [0, -0.7],\n", " [0.75, -0.2]]\n", - "text =['$\\phi(x) = 1$',\n", - " '$\\phi(x) = x$',\n", - " '$\\phi(x) = x^2$']\n", + "text =[r'$\\phi(x) = 1$',\n", + " r'$\\phi(x) = x$',\n", + " r'$\\phi(x) = x^2$']\n", "\n", "plot.basis(quadratic, x_min=-1.3, x_max=1.3, \n", " fig=f, ax=ax, loc=loc, text=text,\n", " diagrams='./ml')\n" ], - "id": "68821b36-c2bb-4e90-b7a8-b382cdc6b70c" + "id": "3e1975b0-33bd-4cac-95fb-8087a8e3a029" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The set of functions which are combined to form a *quadratic*\n", "basis." ], - "id": "bba5bfa4-be4e-4eeb-b1e4-8cf5faec59a1" + "id": "a4d920e3-ba44-4ddc-8343-fd0b59a318f0" }, { "cell_type": "code", @@ -441,7 +529,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "636a6178-ea2e-4374-8887-9a3abb72c99e" + "id": "d64e7911-790f-4f15-b1bf-c1d4d6f3d5c3" }, { "cell_type": "code", @@ -451,7 +539,7 @@ "source": [ "import notutils as nu" ], - "id": "9afdb235-e3df-4e31-aed3-7b4f85272310" + "id": "c0f9edb0-6ce0-46a1-af8f-3e037e8bb371" }, { "cell_type": "code", @@ -463,7 +551,7 @@ " directory='./ml', \n", " num_basis=IntSlider(0,0,2,1))" ], - "id": "4d579520-d718-4952-8bdd-0821865acb65" + "id": "12e00513-3f59-4093-b011-3503309e8d26" }, { "cell_type": "markdown", @@ -473,7 +561,7 @@ "$n\\times 3$ dimensional *design matrix* containing the basis functions.\n", "We can plot those basis functions against there input as follows." ], - "id": "4820b356-afad-4362-b109-717ce5a44408" + "id": "e5c59262-3662-4dcd-bc21-fe610fff6f6f" }, { "cell_type": "code", @@ -490,13 +578,13 @@ "\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "ax.set_ylim([-1.2, 1.2]) # set y limits to ensure basis functions show.\n", - "ax.plot(x[:,0], Phi[:, 0], 'r-', label = '$\\phi=1$', linewidth=3)\n", - "ax.plot(x[:,0], Phi[:, 1], 'g-', label = '$\\phi=x$', linewidth=3)\n", - "ax.plot(x[:,0], Phi[:, 2], 'b-', label = '$\\phi=x^2$', linewidth=3)\n", + "ax.plot(x[:,0], Phi[:, 0], 'r-', label = r'$\\phi=1$', linewidth=3)\n", + "ax.plot(x[:,0], Phi[:, 1], 'g-', label = r'$\\phi=x$', linewidth=3)\n", + "ax.plot(x[:,0], Phi[:, 2], 'b-', label = r'$\\phi=x^2$', linewidth=3)\n", "ax.legend(loc='lower right')\n", "_ = ax.set_title('Quadratic Basis Functions')" ], - "id": "6edc6ea4-bf01-4f9a-920f-c035d22e1d0e" + "id": "91824a0e-0162-4294-a8ff-9a3ee9ca51fb" }, { "cell_type": "markdown", @@ -509,7 +597,7 @@ "prediction function. Our prediction function can only be composed of a\n", "weighted linear sum of our basis functions." ], - "id": "e4a79f7f-585f-44ec-af22-02c41aa894bf" + "id": "cb84237b-1782-4b52-9fa7-07196f21117d" }, { "cell_type": "markdown", @@ -517,12 +605,12 @@ "source": [ "## Quadratic Functions\n", "\n", - "\n", + "\n", "\n", "Figure: Functions constructed by weighted sum of the components of a\n", "quadratic basis." ], - "id": "789e48ed-4f68-4135-80f5-d0a946930b14" + "id": "544279b4-0ebf-4a51-b78a-ac8f09aef4e3" }, { "cell_type": "code", @@ -533,7 +621,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "063980d1-d9b7-4944-879e-a7ea725297f7" + "id": "925f11b4-745d-48ac-8b49-84d8db0a2c7d" }, { "cell_type": "code", @@ -543,7 +631,7 @@ "source": [ "import notutils as nu" ], - "id": "ad0fff89-e472-43bb-9285-0593c1dd5b30" + "id": "9f1c75ef-acd7-4b4d-b38b-37f7f964f160" }, { "cell_type": "code", @@ -555,17 +643,17 @@ " directory='./ml', \n", " num_function=IntSlider(0,0,2,1))" ], - "id": "99b3c4cd-3245-4f8c-9daf-7d03c41bd3fb" + "id": "790e1dff-6414-4a14-b4f4-965269a1fc92" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Different Bases\n", + "## Choice of Basis\n", "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", "Our choice of basis can be made based on what our beliefs about what is\n", "appropriate for the data. For example, the polynomial basis extends the\n", @@ -574,7 +662,7 @@ "\\phi_j(x_i) = x_i^j\n", "$$ which is known as the *polynomial basis*." ], - "id": "62a5f06d-11e5-48ac-b249-d9da75c21d4d" + "id": "8681317e-aa49-4996-8714-7bd58b2b2b39" }, { "cell_type": "markdown", @@ -592,7 +680,7 @@ "\\phi_j(x) = x^j\n", "$$" ], - "id": "c8699f33-e28f-4e5d-97e9-3250952fd0e1" + "id": "70b4afcb-4117-4387-84c6-8c3073434d78" }, { "cell_type": "code", @@ -602,7 +690,7 @@ "source": [ "import numpy as np" ], - "id": "f92decea-c7d2-496d-ac78-d6a1fc9741c1" + "id": "310db3a1-4bc3-4cd3-83e1-59685248d052" }, { "cell_type": "code", @@ -610,9 +698,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import polynomial\n", + "import inspect\n", + "file_path = inspect.getfile(polynomial)" ], - "id": "eafd4808-7027-4f8b-ac25-1f5ea51569a5" + "id": "d31aee10-57ec-45d8-aa27-1d0d4ee8fa45" }, { "cell_type": "code", @@ -620,9 +710,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.polynomial" + "%load -s polynomial {file_path}" ], - "id": "99de214c-37df-4e3e-99e8-2c49418851de" + "id": "fcc801bf-1877-44a5-8010-dea5e995b4d9" }, { "cell_type": "code", @@ -634,7 +724,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "4c3f6327-1bcd-443f-9a83-6ed3a8ea0e2e" + "id": "3553b992-5c0e-41ac-af4e-7ab304ce0299" }, { "cell_type": "code", @@ -649,28 +739,28 @@ " [0.75, -0.2],\n", " [-0.75, -0.2],\n", " [-0.75, 2]]\n", - "text =['$\\phi(x) = 1$',\n", - " '$\\phi(x) = x$',\n", - " '$\\phi(x) = x^2$',\n", - " '$\\phi(x) = x^3$',\n", - " '$\\phi(x) = x^4$']\n", + "text =[r'$\\phi(x) = 1$',\n", + " r'$\\phi(x) = x$',\n", + " r'$\\phi(x) = x^2$',\n", + " r'$\\phi(x) = x^3$',\n", + " r'$\\phi(x) = x^4$']\n", "\n", "plot.basis(mlai.polynomial, x_min=-1.3, x_max=1.3, \n", " fig=f, ax=ax, loc=loc, text=text, num_basis=5,\n", " diagrams='./ml')" ], - "id": "e6dd82a2-9fa4-4d21-bcf7-13031b5cc6ea" + "id": "bc80f773-af36-4cbd-95e1-a7ae9ad20fc7" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The set of functions which are combined to form a\n", "*polynomial* basis." ], - "id": "b3ca687a-623c-441a-9edf-cb1f6390cbbf" + "id": "de67afe4-a599-4069-9650-1109f4573e4a" }, { "cell_type": "code", @@ -682,7 +772,7 @@ "from ipywidgets import IntSlider\n", "import notutils as nu" ], - "id": "a4808e57-17fa-4703-bce0-34f6735facfd" + "id": "6bc1cba8-662e-428b-89fc-9d90e1342fec" }, { "cell_type": "code", @@ -694,7 +784,7 @@ " directory='./ml', \n", " num_basis=IntSlider(0,0,4,1))" ], - "id": "f76e84e5-59ae-495b-99c6-f97f4eb47253" + "id": "35d55a33-e202-4b8d-8736-8d41553ad54f" }, { "cell_type": "code", @@ -704,7 +794,7 @@ "source": [ "nu.display_prediction(basis=mlai.polynomial, num_basis=5)" ], - "id": "ec9964cd-0622-4ecd-a788-7fef6b6f3321" + "id": "c867aa22-15b2-4c0f-a85e-a675065e700d" }, { "cell_type": "markdown", @@ -716,12 +806,12 @@ "f(x) = {\\color{red}{w_0}} + {\\color{magenta}{w_1 x}} + {\\color{blue}{w_2 x^2}} + {\\color{green}{w_3 x^3}} + {\\color{cyan}{w_4 x^4}}\n", "$$\n", "\n", - "\n", + "\n", "\n", "Figure: A random combination of functions from the polynomial\n", "basis." ], - "id": "f8a28ec1-5e79-438d-98c3-fadd427445d2" + "id": "acc89930-5733-48e7-acef-5dc298dce641" }, { "cell_type": "code", @@ -733,7 +823,7 @@ "from ipywidgets import IntSlider\n", "import notutils as nu" ], - "id": "69bfcf6a-7bd0-4b28-b19c-bf5543ab304e" + "id": "882b6f36-4844-41ca-9b55-87719cf312d5" }, { "cell_type": "code", @@ -745,7 +835,7 @@ " directory='./ml', \n", " func_num=IntSlider(0,0,2,1))" ], - "id": "62684af4-947b-43f5-9bb2-2e837ae5bc85" + "id": "49fbd3c9-b9fb-4b55-8d66-b2a45c1d14c7" }, { "cell_type": "markdown", @@ -755,7 +845,7 @@ "small interactive tool for exploring this polynomial basis. The tool can\n", "be summoned with the following command." ], - "id": "af50f3b9-13da-4efd-b894-c2cc3779a4ae" + "id": "1ab33ad4-d4e3-4547-b94c-430ac77aafc4" }, { "cell_type": "code", @@ -765,7 +855,7 @@ "source": [ "import notutils as nu" ], - "id": "ed63d33e-b9f5-43c6-843b-2b5b655858b6" + "id": "df9d3c74-4a84-4f1b-ab04-9c159429dc24" }, { "cell_type": "code", @@ -775,7 +865,7 @@ "source": [ "import notutils as nu" ], - "id": "472b175d-6052-47cf-ae2c-d1425040f9c4" + "id": "c2f27c70-6bc4-4b7b-be68-4837181256f2" }, { "cell_type": "code", @@ -785,7 +875,7 @@ "source": [ "nu.display_prediction(basis=mlai.polynomial, num_basis=5)" ], - "id": "ea75a1e1-a356-480b-8b7a-6b07b09ffcc7" + "id": "882000a5-5d21-43aa-8c1a-d8f4bc3690cb" }, { "cell_type": "markdown", @@ -801,7 +891,7 @@ "parameter is always associated with the corresponding polynomial’s\n", "degree." ], - "id": "3874c907-6aad-4c66-ac98-2ffd6023e7e1" + "id": "cabac591-910a-4d9c-8271-655b38181a9c" }, { "cell_type": "markdown", @@ -814,7 +904,7 @@ "increase number of basis up to 10. Is it easy to change the function in\n", "intiutive ways?" ], - "id": "a8baa4df-0418-447e-9c83-13d1c011fff8" + "id": "04ca7f5e-225a-498f-b83c-c8bfbfdf974b" }, { "cell_type": "markdown", @@ -824,7 +914,7 @@ "\n", "Write your answer to Exercise 1 here" ], - "id": "6075f26e-d415-4215-8e04-57c8f3a332ae" + "id": "4fda69a0-8612-4fe4-a8e0-d39b5309d51b" }, { "cell_type": "code", @@ -835,7 +925,7 @@ "# Use this box for any code you need\n", "\n" ], - "id": "46f2448e-dda2-40ae-923d-4aad67a047cd" + "id": "2d585dc6-25c5-4799-9426-6d39c50cdbfd" }, { "cell_type": "markdown", @@ -850,7 +940,7 @@ "Now we look at basis functions that have been used as the *activation*\n", "functions in neural network model." ], - "id": "5c7fa5f2-52df-4822-8a84-bfe821866fc5" + "id": "67ea649c-8529-47db-b04b-f46307516613" }, { "cell_type": "markdown", @@ -871,7 +961,7 @@ "\\phi_j(x) = \\exp\\left(-\\frac{(x-\\mu_j)^2}{\\ell^2}\\right)\n", "$$" ], - "id": "54c2d964-5cde-4f07-be3d-198e52d42ed3" + "id": "97b5c50d-029b-49b6-8bfb-7036a00345c0" }, { "cell_type": "code", @@ -879,9 +969,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import radial\n", + "import inspect\n", + "file_path = inspect.getfile(radial)" ], - "id": "9172652d-428e-4d0b-a16e-8eede9114f5a" + "id": "420f0c6f-c94d-4abd-846b-e1b55eff3a90" }, { "cell_type": "code", @@ -889,9 +981,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.radial" + "%load -s radial {file_path}" ], - "id": "cf23f0c8-01a2-4cc4-9997-a71eb112a813" + "id": "11310303-befc-464e-837b-4b28cfc075e0" }, { "cell_type": "code", @@ -903,7 +995,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "5d909ba3-e458-4243-a5fa-33a159b714da" + "id": "37f9c917-6374-4806-aa0c-7c0faead358e" }, { "cell_type": "code", @@ -916,25 +1008,25 @@ "loc = [[-1.25, -0.4],\n", " [0., 1.25],\n", " [1.25, -0.4]]\n", - "text = ['$\\phi_1(x) = e^{-(x + 1)^2}$',\n", - " '$\\phi_2(x) = e^{-2x^2}$', \n", - " '$\\phi_3(x) = e^{-2(x-1)^2}$']\n", + "text = [r'$\\phi_1(x) = e^{-(x + 1)^2}$',\n", + " r'$\\phi_2(x) = e^{-2x^2}$', \n", + " r'$\\phi_3(x) = e^{-2(x-1)^2}$']\n", "plot.basis(mlai.radial, x_min=-2, x_max=2, \n", " fig=f, ax=ax, loc=loc, text=text,\n", " diagrams='./ml')" ], - "id": "dcbb82be-0397-4a38-954b-afa42074d247" + "id": "f8bd6c68-7ec7-48d3-aa3a-dcfe2f87a760" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The set of functions which are combined to form the radial\n", "basis." ], - "id": "753befb0-7e8d-4e24-bea2-fa6f04e11e20" + "id": "46266b79-c3ea-404b-a912-9068e8b4f274" }, { "cell_type": "code", @@ -945,7 +1037,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "e86687f4-a22f-49e6-83ef-4da0012a591e" + "id": "a1ff0f12-a2cd-43f5-b8e2-edc1450b13a7" }, { "cell_type": "code", @@ -957,7 +1049,7 @@ " directory='./ml', \n", " num_basis=IntSlider(0,0,2,1))" ], - "id": "0e0c8d45-bfe8-4e4f-b3a6-bfe2fc2d248c" + "id": "2f2aac6c-13c4-4238-84e1-bdd9ae5ea38c" }, { "cell_type": "code", @@ -967,7 +1059,7 @@ "source": [ "nu.display_prediction(basis=mlai.radial, num_basis=3)" ], - "id": "ebce6eeb-bbdc-48dc-a3bc-7896905b6d49" + "id": "4ab949e1-37db-4b54-91b0-0536772e7dda" }, { "cell_type": "markdown", @@ -979,12 +1071,12 @@ "f(x) = \\color{red}{w_1 e^{-2(x+1)^2}} + \\color{magenta}{w_2e^{-2x^2}} + \\color{blue}{w_3 e^{-2(x-1)^2}}\n", "$$\n", "\n", - "\n", + "\n", "\n", "Figure: A radial basis is made up of different locally effective\n", "functions centered at different points." ], - "id": "b5524386-d610-40b2-bfd3-22ce1ec130ae" + "id": "955bf20b-eb8b-4436-9095-f25d26af2c24" }, { "cell_type": "code", @@ -995,7 +1087,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "578a3fa3-2148-44c1-afae-d0d1f6d81282" + "id": "deffb8ef-2141-43eb-8f45-ab544c4b0de2" }, { "cell_type": "code", @@ -1007,7 +1099,7 @@ " directory='./ml', \n", " func_num=IntSlider(0,0,2,1))" ], - "id": "aa2bf34e-b921-4778-b73e-214fc4f6c213" + "id": "406bf9a8-dac6-442b-926f-3a127306b036" }, { "cell_type": "markdown", @@ -1024,10 +1116,10 @@ "current generation of multilayer perceptron models, or deep networks.\n", "These basis functions start flat, and then become linear functions at a\n", "certain threshold. $$\n", - "\\phi_j(x) = xH(v_j x+ v_0)\n", + "\\phi_j(x) = xH(x+ v_j)\n", "$$" ], - "id": "08c67c31-0f69-41d3-b1cc-234c93e429bf" + "id": "a2bf2fb5-3133-4125-b67a-c68e6806b40a" }, { "cell_type": "code", @@ -1037,7 +1129,7 @@ "source": [ "import numpy as np" ], - "id": "16456c49-9474-4149-8893-25da4fdb6047" + "id": "4d750aac-647b-41bc-b9c1-8552c65c308a" }, { "cell_type": "code", @@ -1045,9 +1137,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import relu\n", + "import inspect\n", + "file_path = inspect.getfile(relu)" ], - "id": "fcb3294c-66b6-45ce-a990-a0797cc56609" + "id": "27b0bfdc-84cf-47a3-80fd-cf47c60a10d6" }, { "cell_type": "code", @@ -1055,9 +1149,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.relu" + "%load -s relu {file_path}" ], - "id": "eb166eee-172f-4af2-90dc-20e08d62ffd2" + "id": "994b368a-dac6-43c7-865e-fd1dc56f9ed9" }, { "cell_type": "code", @@ -1069,7 +1163,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "9e36aae3-598e-4d26-b112-4d07cab7a522" + "id": "c7a409ce-acbd-4bcd-bbec-73f8ee9867bf" }, { "cell_type": "code", @@ -1083,28 +1177,28 @@ " [-0.33, 0.2],\n", " [0.33, -0.5],\n", " [1, 0.2]]\n", - "text =['$\\phi(x) = 1$',\n", - " '$\\phi(x) = xH(x+1.0)$',\n", - " '$\\phi(x) = xH(x+0.33)$',\n", - " '$\\phi(x) = xH(x-0.33)$',\n", - " '$\\phi(x) = xH(x-1.0)$']\n", + "text =[r'$\\phi(x) = 1$',\n", + " r'$\\phi(x) = xH(x+1.0)$',\n", + " r'$\\phi(x) = xH(x+0.33)$',\n", + " r'$\\phi(x) = xH(x-0.33)$',\n", + " r'$\\phi(x) = xH(x-1.0)$']\n", "plot.basis(mlai.relu, x_min=-2.0, x_max=2.0, \n", " fig=f, ax=ax, loc=loc, text=text,\n", " diagrams='./ml',\n", " num_basis=5)" ], - "id": "52dbac84-3b80-4fef-8406-21aa5b1a545e" + "id": "283c9a1d-6176-492e-9c39-dd9df1c8b1ed" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The set of functions which are combined to form a rectified\n", "linear unit basis." ], - "id": "6f7f8528-3775-4f82-ae95-0dd589ac7b1d" + "id": "031bdb04-538c-4fc4-b0a3-80c3b6a0bfde" }, { "cell_type": "code", @@ -1115,7 +1209,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "1c709305-4bbb-4e00-8325-ec33a21cc8f6" + "id": "16d28c6f-91f5-4a02-9333-d89bf3e810cb" }, { "cell_type": "code", @@ -1125,7 +1219,7 @@ "source": [ "import notutils as nu" ], - "id": "63752c22-0c95-4bd7-84ff-1efc1ab16b40" + "id": "a4eb3734-8cd9-437e-8609-a14099f116ae" }, { "cell_type": "code", @@ -1137,7 +1231,7 @@ " directory='./ml', \n", " num_basis=IntSlider(0,0,4,1))" ], - "id": "74d8ecd7-e883-49cb-9f52-662afbd96e03" + "id": "49033d20-61b1-4086-be3d-1ccdc6b27545" }, { "cell_type": "code", @@ -1147,7 +1241,7 @@ "source": [ "import notutils as nu" ], - "id": "e8c546db-a481-4863-b193-741835ff0820" + "id": "47d1de1f-9ddc-4a77-b4ee-b89f8dde5785" }, { "cell_type": "code", @@ -1157,7 +1251,7 @@ "source": [ "nu.display_prediction(basis=mlai.relu, num_basis=5)" ], - "id": "180b4768-fe24-41f8-8f99-89b602990472" + "id": "69e4a019-33eb-41eb-b4a8-546f34e2559c" }, { "cell_type": "markdown", @@ -1169,12 +1263,12 @@ "f(x) = \\color{red}{w_0} + \\color{magenta}{w_1 xH(x+1.0) } + \\color{blue}{w_2 xH(x+0.33) } + \\color{green}{w_3 xH(x-0.33)} + \\color{cyan}{w_4 xH(x-1.0)}\n", "$$\n", "\n", - "\n", + "\n", "\n", "Figure: A rectified linear unit basis is made up of different\n", "rectified linear unit functions centered at different points." ], - "id": "146581ed-35ee-474e-b79f-6f1c0e4e5958" + "id": "e0244700-534e-49d4-9060-088692cb3269" }, { "cell_type": "code", @@ -1185,7 +1279,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "08550ebd-55fa-48c5-8699-f4bcd5642bc0" + "id": "532614e8-7679-481f-90f2-2b75f42daca4" }, { "cell_type": "code", @@ -1195,7 +1289,7 @@ "source": [ "import notutils as nu" ], - "id": "3c031000-3b5a-4a80-898f-655cd74c1460" + "id": "47399869-af6d-4c59-ad1d-30c8f29ff4c8" }, { "cell_type": "code", @@ -1207,7 +1301,7 @@ " directory='./ml', \n", " func_num=IntSlider(0,0,2,1))" ], - "id": "f970d759-8163-4619-8a01-fee5f4151125" + "id": "ac32782a-200f-4252-9fd4-cd75b648e82f" }, { "cell_type": "markdown", @@ -1225,7 +1319,7 @@ "\\phi_j(x) = \\tanh(v_j x+ v_0)\n", "$$" ], - "id": "f5d2a1cc-266c-45f0-82d9-2f2c567f61ce" + "id": "3937e9ec-9557-4c26-bec6-a4ccc5682aad" }, { "cell_type": "code", @@ -1235,7 +1329,7 @@ "source": [ "import numpy as np" ], - "id": "01fb637e-8e9a-4aae-98db-5dd2ec39af93" + "id": "8d7d7478-29d7-4894-9c9a-85915c6edb97" }, { "cell_type": "code", @@ -1243,9 +1337,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import hyperbolic_tangent\n", + "import inspect\n", + "file_path = inspect.getfile(hyperbolic_tangent)" ], - "id": "96975161-dabc-472f-8dbe-1d457210ff15" + "id": "4ace15d4-5993-4355-b9b2-c3613bc84504" }, { "cell_type": "code", @@ -1253,9 +1349,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.hyperbolic_tangent" + "%load -s hyperbolic_tangent {file_path}" ], - "id": "1ae7ac4a-c487-47d5-9438-e4b9deeea0ab" + "id": "274fe65e-19e9-40d3-a9bb-ff24165de5d8" }, { "cell_type": "markdown", @@ -1265,7 +1361,7 @@ "generation of multilayer perceptron models, or deep networks. These\n", "basis functions start flat, rise and then saturate." ], - "id": "ace677ea-6ef0-4561-a456-2a17ed6b9804" + "id": "fb09d441-85a4-414d-b779-c8aa8e94f655" }, { "cell_type": "code", @@ -1277,7 +1373,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "8c034b24-1316-4f16-8376-496d4d4ed798" + "id": "5c920ceb-d827-47e7-927c-93c60d5c075a" }, { "cell_type": "code", @@ -1291,28 +1387,28 @@ " [-0.33, 0],\n", " [0.33, -0.7],\n", " [1, 0]]\n", - "text =['$\\phi(x) = 1$',\n", - " '$\\phi(x) = \\\\tanh(x+1.0)$',\n", - " '$\\phi(x) = \\\\tanh(x+0.33)$',\n", - " '$\\phi(x) = \\\\tanh(x-0.33)$',\n", - " '$\\phi(x) = \\\\tanh(x-1.0)$']\n", + "text =[r'$\\phi(x) = 1$',\n", + " r'$\\phi(x) = \\tanh(x+1.0)$',\n", + " r'$\\phi(x) = \\tanh(x+0.33)$',\n", + " r'$\\phi(x) = \\tanh(x-0.33)$',\n", + " r'$\\phi(x) = \\tanh(x-1.0)$']\n", "plot.basis(mlai.hyperbolic_tangent, x_min=-2.0, x_max=2.0,\n", " fig=f, ax=ax, loc=loc, text=text,\n", " diagrams='./ml',\n", " num_basis=5)" ], - "id": "fefca6c0-2eb2-4d95-879b-b7d63d66c6cb" + "id": "9a6f6cbe-c5e1-43b2-ac3b-14ecf7d68dd0" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The set of functions which are combined to form a *hyberbolic\n", "tangent* basis." ], - "id": "a8afe2a8-1160-4d3d-a545-014f958b1daf" + "id": "7a54edf3-e3bd-41cc-beb8-80155875d80d" }, { "cell_type": "code", @@ -1323,7 +1419,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "2e3eef14-ef8c-4f61-b56f-69284e1ae0f2" + "id": "3e4e1ac1-5dc3-4748-a70d-9e05b218035f" }, { "cell_type": "code", @@ -1333,7 +1429,7 @@ "source": [ "import notutils as nu" ], - "id": "21be7c27-812f-43ee-9b17-4fa6c1d0dd4e" + "id": "8b220a9d-72c1-4087-8acb-6944664fe2f2" }, { "cell_type": "code", @@ -1345,7 +1441,7 @@ " directory='./ml', \n", " num_basis=IntSlider(0,0,4,1))" ], - "id": "2fb0cecf-3023-4b5c-8d63-b812fc4d6b8e" + "id": "0ab511d4-38d0-4936-b970-87c318b4ecee" }, { "cell_type": "markdown", @@ -1357,12 +1453,12 @@ "f(x) = {\\color{red}{w_0}} + {\\color{magenta}{w_1 \\text{tanh}\\left(x+1\\right)}} + {\\color{blue}{w_2 \\text{tanh}\\left(x+0.33\\right)}} + {\\color{green}{w_3 \\text{tanh}\\left(x-0.33\\right)}} + {\\color{cyan}{w_4 \\text{tanh}\\left(x-1\\right)}}\n", "$$\n", "\n", - "\n", + "\n", "\n", "Figure: A hyperbolic tangent basis is made up of s-shaped basis\n", "functions centered at different points." ], - "id": "775a8e6f-1f11-46bc-8756-7cc4ce1ad406" + "id": "0ef79484-be3b-424f-a898-07c9e5396ea1" }, { "cell_type": "code", @@ -1373,7 +1469,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "3e12285f-fe81-4c6a-84db-01d3f9b402ac" + "id": "c30d079d-2031-49dc-81a3-63dc7f3000af" }, { "cell_type": "code", @@ -1383,7 +1479,7 @@ "source": [ "import notutils as nu" ], - "id": "09bff91f-646e-4022-a25e-53410ce7ec55" + "id": "b716d901-b875-4a4a-9931-8942c49175cb" }, { "cell_type": "code", @@ -1395,7 +1491,7 @@ " directory='./ml', \n", " func_num=IntSlider(0,0,2,1))" ], - "id": "376acdeb-13ea-4f16-bb89-f2e4111f1683" + "id": "9eb698d9-49ec-4544-81d1-87486597327f" }, { "cell_type": "markdown", @@ -1413,7 +1509,7 @@ "f(x) = w_0 + w_1 \\sin(x) + w_2 \\cos(x) + w_3 \\sin(2x) + w_4 \\cos(2x)\n", "$$" ], - "id": "325dccaa-5762-4c49-a8d5-37c26fde0106" + "id": "a44341ca-85ba-4aaf-ab55-e7bf4e0d32c4" }, { "cell_type": "code", @@ -1423,7 +1519,7 @@ "source": [ "import numpy as np" ], - "id": "d0c8ff5a-19d9-43a8-bf6a-d9bb8c17572a" + "id": "35256999-0fd2-4f32-b1fa-d5766c64863b" }, { "cell_type": "code", @@ -1431,9 +1527,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import fourier\n", + "import inspect\n", + "file_path = inspect.getfile(fourier)" ], - "id": "05f13c5b-2b0e-4457-aab4-804d45e76df2" + "id": "771a6898-a7fd-4a9b-a30e-f54c36beda66" }, { "cell_type": "code", @@ -1441,9 +1539,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.fourier" + "%load -s fourier {file_path}" ], - "id": "f69da1ff-9788-4217-9cde-ff9fbd896c99" + "id": "968cc597-b1fb-4d2b-b90d-0fab3bc8ae59" }, { "cell_type": "code", @@ -1455,7 +1553,7 @@ "import mlai\n", "import mlai.plot as plot" ], - "id": "cae41e95-7410-4728-8a93-a7f363014053" + "id": "fcfb6316-84b2-4c76-9b7a-bed08483da12" }, { "cell_type": "code", @@ -1469,28 +1567,28 @@ " [1, -0.4],\n", " [1.25, 0.4],\n", " [1.5, -0.4]]\n", - "text =['$\\phi(x) = 1$',\n", - " '$\\phi(x) = \\sin(x)$',\n", - " '$\\phi(x) = \\cos(x)$',\n", - " '$\\phi(x) = \\sin(2x)$',\n", - " '$\\phi(x) = \\cos(2x)$']\n", + "text =[r'$\\phi(x) = 1$',\n", + " r'$\\phi(x) = \\sin(x)$',\n", + " r'$\\phi(x) = \\cos(x)$',\n", + " r'$\\phi(x) = \\sin(2x)$',\n", + " r'$\\phi(x) = \\cos(2x)$']\n", "plot.basis(mlai.fourier, x_min=0, x_max=2, \n", " fig=f, ax=ax, loc=loc, text=text,\n", " diagrams='./ml',\n", " num_basis=5)" ], - "id": "6adb78cd-e729-406a-a0c3-efe05efc1a34" + "id": "7c8a752e-d949-4f51-b1de-22933e650ff8" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The set of functions which are combined to form a *Fourier*\n", "basis." ], - "id": "a906236f-950a-4924-9f99-a3e2320c9252" + "id": "3541449f-a858-4ec1-b0ff-62c2d68a38ab" }, { "cell_type": "code", @@ -1501,7 +1599,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "8a924fa6-f6a6-4fd2-8474-6804b61049ca" + "id": "53a75632-277e-44ac-a3da-b35aa270e648" }, { "cell_type": "code", @@ -1511,7 +1609,7 @@ "source": [ "import notutils as nu" ], - "id": "a38344d1-d284-4bdb-8099-1341cf1ba061" + "id": "6ec26f28-d870-45ec-85f9-093cec184f72" }, { "cell_type": "code", @@ -1523,7 +1621,7 @@ " directory='./ml', \n", " num_basis=IntSlider(0,0,4,1))" ], - "id": "0669deaf-ac10-4b96-a3c2-12413a832944" + "id": "4d686040-5af4-4e50-8127-6b5cfcac4c0e" }, { "cell_type": "markdown", @@ -1534,7 +1632,7 @@ "(index 0, so cosine) has a frequency of 0 and then frequencies increase\n", "every time a sine and cosine are included." ], - "id": "3bb72e37-251b-4fa0-8f18-a65695c0c2c5" + "id": "d1b733b2-17f9-457a-99fa-b9859e1406cf" }, { "cell_type": "code", @@ -1544,7 +1642,7 @@ "source": [ "import notutils as nu" ], - "id": "2da9eeb2-225d-4e8b-8bf0-3f4dd4fb1ab5" + "id": "d2467790-cd1f-41cd-9afc-099f44eff34a" }, { "cell_type": "code", @@ -1554,7 +1652,7 @@ "source": [ "nu.display_prediction(basis=mlai.fourier, num_basis=5)" ], - "id": "0f72674e-d1f4-42eb-ab21-bb6b24104e0c" + "id": "e5ece4b7-ef35-4c18-a5d7-d7ec928713e4" }, { "cell_type": "markdown", @@ -1566,13 +1664,13 @@ "f(x) = {\\color{red}{w_0}} + {\\color{magenta}{w_1 \\sin(x)}} + {\\color{blue}{w_2 \\cos(x)}} + {\\color{green}{w_3 \\sin(2x)}} + {\\color{cyan}{w_4 \\cos(2x)}}\n", "$$\n", "\n", - "\n", + "\n", "\n", "Figure: A random combination of functions from the Fourier basis.\n", "Fourier basis is made up of sine and cosine functions with different\n", "frequencies." ], - "id": "1adf7d56-357f-4cf0-a124-81224ac1e254" + "id": "1b4f18a1-4cd1-4c6e-83bb-09004bb96137" }, { "cell_type": "code", @@ -1583,7 +1681,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "d49fdcca-ef07-4909-b076-ff20e7fe43d5" + "id": "d8bba5bb-a588-4b17-9989-4cd318d1554f" }, { "cell_type": "code", @@ -1593,7 +1691,7 @@ "source": [ "import notutils as nu" ], - "id": "8448e0a9-3b40-40b3-93e2-74693a4d7c9a" + "id": "fbfdb828-c754-4903-bf85-d2a3247b19f0" }, { "cell_type": "code", @@ -1605,7 +1703,7 @@ " directory='./ml', \n", " func_num=IntSlider(0,0,2,1))" ], - "id": "125916bd-f50c-4f8d-a40a-41c03ec02b9b" + "id": "187543d2-a6dc-4197-b808-37522a1e6036" }, { "cell_type": "markdown", @@ -1615,14 +1713,14 @@ "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", "Now we are going to consider how these basis functions can be adjusted\n", "to fit to a particular data set. We will return to the olympic marathon\n", "data from last time. First we will scale the output of the data to be\n", "zero mean and variance 1." ], - "id": "0c15d216-b5de-4353-8232-f6fe3cc3a542" + "id": "66d17d5d-c000-493c-a698-a8d24161b161" }, { "cell_type": "markdown", @@ -1634,31 +1732,41 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", "- Gold medal times for Olympic Marathon since 1896.\n", "- Marathons before 1924 didn’t have a standardized distance.\n", "- Present results using pace per km.\n", "- In 1904 Marathon was badly organized leading to very slow times.\n", "\n", - "\n", + "\n", + "Image from [Wikimedia\n", + "Commons](https://commons.wikimedia.org/wiki/File:Eliud_Kipchoge_in_Berlin_-_2015_(cropped).jpg)\n", "\n", - "\n", - "Image from Wikimedia Commons \n", - "\n", - "
\n", - "\n", - "The first thing we will do is load a standard data set for regression\n", + "The Olympic Marathon data is a standard dataset for regression\n", "modelling. The data consists of the pace of Olympic Gold Medal Marathon\n", "winners for the Olympics from 1896 to present. Let’s load in the data\n", "and plot." ], - "id": "af9adbe3-ba9f-4437-a859-487ca975283f" + "id": "a94873a9-6c70-453d-ae17-befc931f6c6d" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "d1c05a38-bfbb-480e-a0a7-08d96e9ff8d6" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "f99e6d50-5669-4e1c-96f5-69c4994252e8" }, { "cell_type": "code", @@ -1669,7 +1777,7 @@ "import numpy as np\n", "import pods" ], - "id": "168b800d-c1a9-42fb-829c-ef50ab37bac6" + "id": "54b5e9fa-d7e9-4520-acb3-4dfd32dbc071" }, { "cell_type": "code", @@ -1685,7 +1793,7 @@ "scale = np.sqrt(y.var())\n", "yhat = (y - offset)/scale" ], - "id": "68051dd2-d385-4744-9dd9-18771c4238a3" + "id": "40288721-b0f9-4de3-991c-9bdb4255aba0" }, { "cell_type": "code", @@ -1697,7 +1805,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "f7b68f3c-ad73-4b78-b80b-5a932ebd1282" + "id": "fe88ac65-bb76-4c6c-a3c5-f7c297d1a076" }, { "cell_type": "code", @@ -1706,7 +1814,7 @@ "outputs": [], "source": [ "\n", - "xlim = (1875,2030)\n", + "xlim = (1876,2044)\n", "ylim = (2.5, 6.5)\n", "\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", @@ -1719,13 +1827,13 @@ "mlai.write_figure(filename='olympic-marathon.svg', \n", " directory='./datasets')" ], - "id": "9931f152-ba7a-403a-b530-270a3adc7564" + "id": "f39a3ab9-2ada-44ce-9c72-22da2550c4da" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Olympic marathon pace times since 1896.\n", "\n", @@ -1735,7 +1843,7 @@ "participants got lost, and only very few participants completed. More\n", "recent years see more consistently quick marathons." ], - "id": "8d2d2d91-3503-4989-b924-34ea546d7ba1" + "id": "f7dcd4ee-b848-405b-899e-b709874f86f6" }, { "cell_type": "code", @@ -1746,7 +1854,7 @@ "import matplotlib.pyplot as plt\n", "import notutils as nu" ], - "id": "5c6903e9-137a-461f-8cc4-ee872bd449ac" + "id": "f685ad86-30cc-4194-8c6b-092c27969918" }, { "cell_type": "code", @@ -1763,13 +1871,13 @@ " tanh=mlai.hyperbolic_tangent, \n", " fourier=mlai.fourier, \n", " relu=mlai.relu), \n", - " data_limits=(1888, 2020),\n", + " data_limits=xlim,\n", " fig=fig, ax=ax,\n", " offset=0.,\n", " wlim = (-4., 4.),\n", " num_basis=4)" ], - "id": "324d2cf0-da2b-4147-9d7b-256b28add509" + "id": "dbaab648-b01e-46b7-8fc7-0052828fe516" }, { "cell_type": "markdown", @@ -1779,7 +1887,7 @@ "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", "Use the tool provided above to try and find the best fit you can to the\n", "data. Explore the parameter space and give the weight values you used\n", @@ -1792,7 +1900,7 @@ "Write your answers in the code box below creating a new vector of\n", "parameters (in the correct order!) for each basis." ], - "id": "614a482e-94be-4b5a-8b30-8b582c076d06" + "id": "65527459-67b9-42ae-b52b-5b9ada4e5d5e" }, { "cell_type": "code", @@ -1832,7 +1940,7 @@ "# w_fourier = np.asarray([[w_0], [w_1], [w_2], [w_3]])\n", "\n" ], - "id": "ff959063-f5d9-4331-9cd2-0e502856f20d" + "id": "e94041a8-77b0-46a4-b613-1ad6dfb31d36" }, { "cell_type": "code", @@ -1842,7 +1950,7 @@ "source": [ "np.asarray([[1, 2, 3, 4]]).shape" ], - "id": "ca1fc8fa-4538-48d7-86d5-22c4007b6685" + "id": "7ebaa926-5de8-40cc-ade2-d2ac847c417b" }, { "cell_type": "markdown", @@ -1858,7 +1966,7 @@ " f(\\mathbf{ x}_i) = \\mathbf{ w}^\\top \\boldsymbol{ \\phi}_i\n", " $$" ], - "id": "4eec6c61-1447-49ce-bc2c-7373c3d8c88b" + "id": "78daa3d3-5ff4-424b-931e-a42c0f46f7d4" }, { "cell_type": "markdown", @@ -1866,17 +1974,19 @@ "source": [ "## Log Likelihood for Basis Function Model\n", "\n", + "\\[edit\\]\n", + "\n", "The likelihood of a single data point given the model parameters is\n", "given by $$\n", " p\\left(y_i|x_i\\right)=\\frac{1}{\\sqrt{2\\pi\\sigma^2}}\\exp\\left(-\\frac{\\left(y_i-\\mathbf{ w}^{\\top}\\boldsymbol{ \\phi}_i\\right)^{2}}{2\\sigma^2}\\right).\n", " $$ and making an assumption of *conditional independence* given the\n", "parameters we can write\n", "\n", - "$$\n", - " L(\\mathbf{ w},\\sigma^2)= -\\frac{n}{2}\\log \\sigma^2-\\frac{n}{2}\\log 2\\pi -\\frac{\\sum_{i=1}^{n}\\left(y_i-\\mathbf{ w}^{\\top}\\boldsymbol{ \\phi}_i\\right)^{2}}{2\\sigma^2}.\n", - " $$ to give us the likelihood for the whole data set." + "to give us the likelihood for the whole data set." ], - "id": "87e4a9e3-0903-49ee-b568-4f447dd5acfb" + "id": "8725bc2a-eb3c-4735-bd80-161458918d66" }, { "cell_type": "markdown", @@ -1893,17 +2003,12 @@ "E(\\mathbf{ w},\\sigma^2)= \\frac{n}{2}\\log\\sigma^2 + \\frac{\\sum_{i=1}^{n}\\left(y_i-\\mathbf{ w}^{\\top}\\boldsymbol{ \\phi}_i\\right)^{2}}{2\\sigma^2}.\n", "$$\n", "\n", - "To minimize this objective, we first expand the brackets as follows, $$\n", - "\\begin{align}\n", - " E(\\mathbf{ w},\\sigma^2) = &\\frac{n}{2}\\log \\sigma^2 + \\frac{1}{2\\sigma^2}\\sum_{i=1}^{n}y_i^{2}-\\frac{1}{\\sigma^2}\\sum_{i=1}^{n}y_i\\mathbf{ w}^{\\top}\\boldsymbol{ \\phi}_i\\\\ &+\\frac{1}{2\\sigma^2}\\sum_{i=1}^{n}\\mathbf{ w}^{\\top}\\boldsymbol{ \\phi}_i\\boldsymbol{ \\phi}_i^{\\top}\\mathbf{ w}+\\text{const}.\n", - "\\end{align}\n", - "$$\n", + "To minimize this objective, we first expand the brackets as follows,\n", "\n", "Now we pull out the vectors, $\\mathbf{ w}$, to highlight that what we\n", - "have is a multivariate quadratic form in $\\mathbf{ w}$.\n", - "$$\\begin{align} E(\\mathbf{ w}, \\sigma^2) = & \\frac{n}{2}\\log \\sigma^2 + \\frac{1}{2\\sigma^2}\\sum_{i=1}^{n}y_i^{2}-\\frac{1}{\\sigma^2} \\mathbf{ w}^\\top\\sum_{i=1}^{n}\\boldsymbol{ \\phi}_i y_i\\\\ & +\\frac{1}{2\\sigma^2}\\mathbf{ w}^{\\top}\\left[\\sum_{i=1}^{n}\\boldsymbol{ \\phi}_i\\boldsymbol{ \\phi}_i^{\\top}\\right]\\mathbf{ w}+\\text{const}.\\end{align}$$" + "have is a multivariate quadratic form in $\\mathbf{ w}$." ], - "id": "69d1979c-e74b-4797-be91-4b20f79e1a13" + "id": "36a56143-d191-4775-8e8f-e23f9317ea17" }, { "cell_type": "markdown", @@ -1911,6 +2016,10 @@ "source": [ "## Design Matrices\n", "\n", + "\\[edit\\]\n", + "\n", "We like to make use of *design* matrices for our data. Design matrices\n", "involve placing the data points into rows of the matrix and data\n", "features into the columns of the matrix. By convention, we are\n", @@ -1918,10 +2027,10 @@ "bold upper case letter. The design matrix is therefore given by $$\n", " \\boldsymbol{ \\Phi}= \\begin{bmatrix} \\mathbf{1} & \\mathbf{ x}& \\mathbf{ x}^2\\end{bmatrix}\n", " $$ so that $$\n", - " \\boldsymbol{ \\Phi}\\in \\Re^{n\\times p}.\n", + " \\boldsymbol{ \\Phi}\\in \\Re^{n\\times d}.\n", " $$" ], - "id": "cc897515-a683-4666-b7a0-fb41a3618e8b" + "id": "cb02a76f-48ee-40b6-84b9-df1ac49e7f47" }, { "cell_type": "markdown", @@ -1932,14 +2041,13 @@ "To find the minimum of the objective function, we need to make use of\n", "multivariate calculus. The two results we need from multivariate\n", "calculus have the following form.\n", - "\n", "$$\\frac{\\text{d}\\mathbf{a}^{\\top}\\mathbf{ w}}{\\text{d}\\mathbf{ w}}=\\mathbf{a}$$\n", "and\n", "$$\\frac{\\text{d}\\mathbf{ w}^{\\top}\\mathbf{A}\\mathbf{ w}}{\\text{d}\\mathbf{ w}}=\\left(\\mathbf{A}+\\mathbf{A}^{\\top}\\right)\\mathbf{ w}$$\n", "or if $\\mathbf{A}$ is symmetric (*i.e.* $\\mathbf{A}=\\mathbf{A}^{\\top}$)\n", "$$\\frac{\\text{d}\\mathbf{ w}^{\\top}\\mathbf{A}\\mathbf{ w}}{\\text{d}\\mathbf{ w}}=2\\mathbf{A}\\mathbf{ w}.$$" ], - "id": "51ea333a-0107-4353-99c4-ccdbe91edd33" + "id": "abd3787e-2b12-4c18-9409-fc62a86894d8" }, { "cell_type": "markdown", @@ -1950,7 +2058,7 @@ "Differentiating with respect to the vector $\\mathbf{ w}$ we obtain\n", "$$\\frac{\\text{d} E\\left(\\mathbf{ w},\\sigma^2 \\right)}{\\text{d}\\mathbf{ w}}=-\\frac{1}{\\sigma^2} \\sum_{i=1}^{n}\\boldsymbol{ \\phi}_iy_i+\\frac{1}{\\sigma^2} \\left[\\sum_{i=1}^{n}\\boldsymbol{ \\phi}_i\\boldsymbol{ \\phi}_i^{\\top}\\right]\\mathbf{ w}$$\n", "Leading to\n", - "$$\\mathbf{ w}^{*}=\\left[\\sum_{i=1}^{n}\\boldsymbol{ \\phi}_i\\boldsymbol{ \\phi}_i^{\\top}\\right]^{-1}\\sum_{i=1}^{n}\\boldsymbol{ \\phi}_iy_i,$$\n", + "$$\\left[\\sum_{i=1}^{n}\\boldsymbol{ \\phi}_i\\boldsymbol{ \\phi}_i^{\\top}\\right]\\mathbf{ w}^{*}=\\sum_{i=1}^{n}\\boldsymbol{ \\phi}_iy_i.$$\n", "\n", "Rewriting this result in matrix notation we obtain: $$\n", "\\sum_{i=1}^{n}\\boldsymbol{ \\phi}_i\\boldsymbol{ \\phi}_i^\\top = \\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}$$\n", @@ -1958,19 +2066,16 @@ "$$\n", "\n", "Setting the derivative to zero we obtain update equations for the\n", - "parameter vector and the noise variance.\n", - "\n", - "$$\n", - " \\mathbf{ w}^{*} = \\left(\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}\\right)^{-1} \\boldsymbol{ \\Phi}^\\top \\mathbf{ y}\n", + "parameter vector and the noise variance. First to find $\\mathbf{ w}^{*}$\n", + "we solve $$\n", + " \\left(\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}\\right) \\mathbf{ w}^{*} = \\boldsymbol{ \\Phi}^\\top \\mathbf{ y}\n", + " $$ and the update for $\\left.\\sigma^2\\right.^*$ is $$\n", + " \\left.\\sigma^2\\right.^{*}=\\frac{\\sum_{i=1}^{n}\\left(y_i-\\left.\\mathbf{ w}^{*}\\right.^{\\top}\\boldsymbol{ \\phi}_i\\right)^{2}}{n}.\n", " $$\n", "\n", - "$$\n", - " \\left.\\sigma^2\\right.^{{*}}=\\frac{\\sum_{i=1}^{n}\\left(y_i-\\left.\\mathbf{ w}^{*}\\right.^{\\top}\\boldsymbol{ \\phi}_i\\right)^{2}}{n}.\n", - " $$\n", - "\n", - "In practice we should avoid solving these equations through direct use\n", - "of the inverse. Instead we solve for $\\mathbf{ w}$ in the following\n", - "linear system.\n", + "Wee should avoid solving these equations through direct use of the\n", + "inverse. Instead we solve for $\\mathbf{ w}$ in the following linear\n", + "system.\n", "\n", "$$\n", " \\left(\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}\\right)\\mathbf{ w}= \\boldsymbol{ \\Phi}^\\top \\mathbf{ y}\n", @@ -1979,9 +2084,35 @@ "$$ For example see the `numpy.linalg.solve` or `torch.linalg.solve`.\n", "\n", "But the correct and more stable approach is to make use of the QR\n", - "decomposition." + "decomposition.\n", + "\n", + "We can now set up the system to solve with QR decomposition." ], - "id": "92c41b6e-98a8-432f-ab8e-ff774a6a70aa" + "id": "f966215d-b32c-4c5d-8b5b-b5cf9c3ce178" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import mlai" + ], + "id": "d519bd08-e0c5-4254-97ab-c27a6b6f9a83" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "poly_args = {'num_basis':4, # four basis: 1, x, x^2, x^3\n", + " 'data_limits':xlim}\n", + "Phi = pd.DataFrame(data = mlai.polynomial(x, **poly_args), \n", + " columns = [f'$x^{n}$' for n in range(poly_args['num_basis'])])" + ], + "id": "988df967-03cc-49c9-b0ce-e0b1ecf97a85" }, { "cell_type": "markdown", @@ -1994,15 +2125,21 @@ "style=\"\">edit\\]\n", "\n", "Performing a solve instead of a matrix inverse is the more numerically\n", - "stable approach, but we can do even better. A\n", - "[QR-decomposition](http://en.wikipedia.org/wiki/QR_decomposition) of a\n", + "stable approach, but we can do even better.\n", + "\n", + "The problems can arise with very large data sets, where computing\n", + "$\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}$ can lead to a very poorly\n", + "conditioned matrix. Instead, we can compute the solution without having\n", + "to compute the matrix. The trick is to use a QR decomposition.\n", + "\n", + "A [QR-decomposition](http://en.wikipedia.org/wiki/QR_decomposition) of a\n", "matrix factorizes it into a matrix which is an orthogonal matrix\n", "$\\mathbf{Q}$, so that $\\mathbf{Q}^\\top \\mathbf{Q} = \\mathbf{I}$. And a\n", "matrix which is upper triangular, $\\mathbf{R}$. $$\n", - "\\designMatrix^\\top \\designMatrix \\boldsymbol{\\beta} =\n", - "\\designMatrix^\\top \\mathbf{ y}\n", - "$$ and we substitute $\\designMatrix = \\mathbf{Q}{\\mathbf{R}$ so we have\n", - "$$\n", + "\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}\\boldsymbol{\\beta} =\n", + "\\boldsymbol{ \\Phi}^\\top \\mathbf{ y}\n", + "$$ and we substitute $\\boldsymbol{ \\Phi}= \\mathbf{Q}\\mathbf{R}$ so we\n", + "have $$\n", "(\\mathbf{Q}\\mathbf{R})^\\top\n", "(\\mathbf{Q}\\mathbf{R})\\boldsymbol{\\beta} = (\\mathbf{Q}\\mathbf{R})^\\top\n", "\\mathbf{ y}\n", @@ -2019,18 +2156,19 @@ "$$ which leaves us with a lower triangular system to solve.\n", "\n", "This is a more numerically stable solution because it removes the need\n", - "to compute $\\designMatrix^\\top\\designMatrix$ as an intermediate.\n", - "Computing $\\designMatrix^\\top\\designMatrix$ is a bad idea because it\n", - "involves squaring all the elements of $\\designMatrix$ and thereby\n", - "potentially reducing the numerical precision with which we can represent\n", - "the solution. Operating on $\\designMatrix$ directly preserves the\n", - "numerical precision of the model.\n", + "to compute $\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}$ as an\n", + "intermediate. Computing $\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}$ is a\n", + "bad idea because it involves squaring all the elements of\n", + "$\\boldsymbol{ \\Phi}$ and thereby potentially reducing the numerical\n", + "precision with which we can represent the solution. Operating on\n", + "$\\boldsymbol{ \\Phi}$ directly preserves the numerical precision of the\n", + "model.\n", "\n", "This can be more particularly seen when we begin to work with *basis\n", "functions* in the next session. Some systems that can be resolved with\n", "the QR decomposition cannot be resolved by using solve directly." ], - "id": "6bec2104-d4e3-431c-9cd2-491f15cc1dd7" + "id": "1a57f257-8b25-46d6-8f98-1c4c2c17c4a3" }, { "cell_type": "code", @@ -2040,7 +2178,7 @@ "source": [ "import scipy as sp" ], - "id": "38ad0159-2fe3-4575-a051-a4b4b01e50c5" + "id": "54d647dc-0147-4c6c-9aec-356d3ace4214" }, { "cell_type": "code", @@ -2048,24 +2186,12 @@ "metadata": {}, "outputs": [], "source": [ - "Q, R = np.linalg.qr(\\designVariable)\n", + "Q, R = np.linalg.qr(Phi)\n", "w = sp.linalg.solve_triangular(R, Q.T@y) \n", - "w = pd.DataFrame(w, index=\\designVariable.columns)\n", + "w = pd.DataFrame(w, index=Phi.columns)\n", "w" ], - "id": "b3863ff2-57f7-44a0-99cf-f670eb6c9058" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Polynomial Fits to Olympic Data\n", - "\n", - "\\[edit\\]" - ], - "id": "fd1e3789-3b32-4dcb-b4ce-13d308921a93" + "id": "94645a0b-74a8-4f2e-bb86-2bb2fbe798e4" }, { "cell_type": "code", @@ -2076,7 +2202,7 @@ "import numpy as np\n", "import pods" ], - "id": "139c08dc-f331-4c3e-b996-0d66e7072919" + "id": "47497c44-fd43-4e05-96f1-dbe4315242fb" }, { "cell_type": "code", @@ -2084,18 +2210,26 @@ "metadata": {}, "outputs": [], "source": [ - "basis = mlai.polynomial\n", - "\n", "data = pods.datasets.olympic_marathon_men()\n", - "\n", "x = data['X']\n", "y = data['Y']\n", + "offset = y.mean()\n", + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale" + ], + "id": "fe17ef64-6e71-470c-add3-fb063c8c82d1" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Polynomial Fits to Olympic Data\n", "\n", - "xlim = [1892, 2020]\n", - "\n", - "basis=mlai.Basis(mlai.polynomial, number=1, data_limits=xlim)" + "\\[edit\\]" ], - "id": "8f8efce9-5127-4de4-9182-63d2cc29281b" + "id": "633a68fb-1f69-450f-8000-63ce003785f2" }, { "cell_type": "code", @@ -2103,11 +2237,9 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "import mlai.plot as plot\n", "import mlai" ], - "id": "778e5b70-c9ce-44ca-934a-e9f567382258" + "id": "fdc51097-e2fd-40f5-ad79-4ea0ee9ddd02" }, { "cell_type": "code", @@ -2115,13 +2247,10 @@ "metadata": {}, "outputs": [], "source": [ - "plot.rmse_fit(x, y, param_name='number', param_range=(1, 27), \n", - " model=mlai.LM, \n", - " basis=basis,\n", - " xlim=xlim, objective_ylim=[0, 0.8],\n", - " diagrams='./ml')" + "data_limits=xlim\n", + "basis=mlai.Basis(mlai.polynomial, number=1, data_limits=xlim)" ], - "id": "326ad87f-571f-4ace-be55-8d34a1e0dc3b" + "id": "0a1473e2-f0fb-409c-8549-99c572fea57d" }, { "cell_type": "code", @@ -2129,9 +2258,11 @@ "metadata": {}, "outputs": [], "source": [ - "from ipywidgets import IntSlider" + "import matplotlib.pyplot as plt\n", + "import mlai.plot as plot\n", + "import mlai" ], - "id": "d7e46293-a951-4f50-ba7d-8968a9003660" + "id": "1936f5c6-f4df-44ba-af89-76b8c78f7162" }, { "cell_type": "code", @@ -2139,9 +2270,13 @@ "metadata": {}, "outputs": [], "source": [ - "import notutils as nu" + "plot.rmse_fit(x, y, param_name='number', param_range=(1, 30), \n", + " model=mlai.LM, \n", + " basis=basis,\n", + " xlim=xlim, objective_ylim=[0, 0.8],\n", + " diagrams='./ml')" ], - "id": "864d2727-782e-4be9-97e8-5d64422b7008" + "id": "ae7134f4-bb4b-4241-bd3a-6ff3798c0e1e" }, { "cell_type": "code", @@ -2149,11 +2284,9 @@ "metadata": {}, "outputs": [], "source": [ - "nu.display_plots('olympic_LM_polynomial_number{num_basis:0>3}.svg',\n", - " directory='./ml', \n", - " num_basis=IntSlider(1,1,27,1))" + "from ipywidgets import IntSlider" ], - "id": "91a1c679-f031-486b-80d5-c38311fbe66e" + "id": "ae113ac4-3213-40fa-89c0-28407b0f6881" }, { "cell_type": "code", @@ -2161,9 +2294,9 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import notutils as nu" ], - "id": "dbb9eb80-2bb7-43ef-b0f0-43144bbf9d81" + "id": "65a31080-6604-403e-b83b-bde481fbb868" }, { "cell_type": "code", @@ -2171,33 +2304,27 @@ "metadata": {}, "outputs": [], "source": [ - "x = data['X']\n", - "y = data['Y']\n", - "\n", - "xlim = [1892, 2020]\n", - "max_basis = 27\n", - "\n", - "ll = np.array([np.nan]*(max_basis))\n", - "sum_squares = np.array([np.nan]*(max_basis))\n", - "basis=mlai.Basis(mlai.polynomial, number=1, data_limits=xlim)" + "nu.display_plots('olympic_LM_polynomial_number{num_basis:0>3}.svg',\n", + " directory='./ml', \n", + " num_basis=IntSlider(1,1,29,1))" ], - "id": "1e771447-3fcc-41f2-ab7d-2873aeac7a54" + "id": "e05bd212-f129-473f-8ae5-7809c97caa4f" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Fit of a 1 degree polynomial to the olympic marathon\n", "data.\n", "\n", - "\n", + "\n", "\n", "Figure: Fit of a 2 degree polynomial to the olympic marathon\n", "data." ], - "id": "48a7d031-5657-4d73-a28e-980d534008a5" + "id": "163abeb4-23a3-4aa4-a0d4-c20e2121843e" }, { "cell_type": "markdown", @@ -2205,6 +2332,10 @@ "source": [ "## Non-linear but Linear in the Parameters\n", "\n", + "\\[edit\\]\n", + "\n", "One rather nice aspect of our model is that whilst it is non-linear in\n", "the inputs, it is still linear in the parameters $\\mathbf{ w}$. This\n", "means that our derivations from before continue to operate to allow us\n", @@ -2220,12 +2351,12 @@ "*linear* in the parameters. In practice, our basis function itself may\n", "contain its own set of parameters, $$\n", "f(\\mathbf{ x}) = \\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x};\n", - "\\boldsymbol{\\theta}),\n", - "$$ that we’ve denoted here as $\\boldsymbol{\\theta}$. If these parameters\n", - "appear inside the basis function then our model is *non-linear* in these\n", + "\\mathbf{ v}),\n", + "$$ that we’ve denoted here as $\\mathbf{ v}$. If these parameters appear\n", + "inside the basis function then our model is *non-linear* in these\n", "parameters." ], - "id": "bad9fac3-e59c-4335-952d-871019a20f44" + "id": "235eb80c-fb26-4613-8e5e-877b1ad8b0ba" }, { "cell_type": "markdown", @@ -2246,7 +2377,7 @@ "\n", "5. $f(x) = \\exp(-\\mathbf{ w}^\\top \\mathbf{ x})$" ], - "id": "98b56dd5-f36b-4e21-b517-1a28771c9cb0" + "id": "6de48f07-1313-4cdf-bf58-b9d6a10bb5c3" }, { "cell_type": "markdown", @@ -2256,7 +2387,7 @@ "\n", "Write your answer to Exercise 3 here" ], - "id": "579aa008-9957-43fa-9166-da74a8b95cf8" + "id": "ce20ab27-8380-4765-9f00-94cde423939d" }, { "cell_type": "markdown", @@ -2264,10 +2395,14 @@ "source": [ "## Fitting the Model Yourself\n", "\n", + "\\[edit\\]\n", + "\n", "You now have everything you need to fit a non- linear (in the inputs)\n", "basis function model to the marathon data." ], - "id": "fd43a6a2-f5f6-4e86-9365-ac7a3983b895" + "id": "fa238a49-7a82-452f-bfd2-ab4aa8889e47" }, { "cell_type": "markdown", @@ -2279,14 +2414,14 @@ "design matrix on the covariates (or input data), `x`. Use the design\n", "matrix and the response variable `y` to solve the following linear\n", "system for the model parameters `w`. $$\n", - "\\boldsymbol{ \\phi}^\\top\\boldsymbol{ \\phi}\\mathbf{ w}= \\boldsymbol{ \\phi}^\\top \\mathbf{ y}\n", + "\\boldsymbol{ \\Phi}^\\top\\boldsymbol{ \\Phi}\\mathbf{ w}= \\boldsymbol{ \\Phi}^\\top \\mathbf{ y}\n", "$$ Compute the corresponding error on the training data. How does it\n", "compare to the error you were able to achieve fitting the basis above?\n", "Plot the form of your prediction function from the least squares\n", "estimate alongside the form of you prediction function you fitted by\n", "hand." ], - "id": "833bc129-28fb-4799-aecd-1a9c65e3ec01" + "id": "a3f97514-8f02-4cd0-8e63-3ff0b692d24c" }, { "cell_type": "code", @@ -2300,7 +2435,7 @@ "\n", "\n" ], - "id": "9ebdf0f6-1eff-40e0-bc4c-89de07fca2e6" + "id": "b9580e80-2786-4a47-84b2-199e1432643b" }, { "cell_type": "markdown", @@ -2308,7 +2443,7 @@ "source": [ "## Lecture on Basis Functions from GPRS Uganda" ], - "id": "63b2e300-2d24-42bc-841a-837345adf018" + "id": "8462a681-7089-4fcc-a475-14f64a65695c" }, { "cell_type": "code", @@ -2319,7 +2454,7 @@ "from IPython.lib.display import YouTubeVideo\n", "YouTubeVideo('PoNbOnUnOao')" ], - "id": "3531c239-ffb4-4bb6-87e3-88a02954de5f" + "id": "12b35c88-6563-46e8-b0a1-5019baad4ee2" }, { "cell_type": "markdown", @@ -2327,7 +2462,7 @@ "source": [ "Figure: Lecture on Basis functions from GPRS in Uganda in 2013." ], - "id": "5a1fbb02-ebd4-4f17-837d-d9a0a648e3a2" + "id": "13ffa065-2280-4fef-bbd0-6652e676e766" }, { "cell_type": "markdown", @@ -2344,7 +2479,7 @@ "\n", "*TODO* example with polynomials." ], - "id": "fef43663-4f98-49c5-ba2f-6c0baa6dd440" + "id": "aadf742e-9aad-4182-9acc-462934580646" }, { "cell_type": "code", @@ -2354,7 +2489,7 @@ "source": [ "import numpy as np" ], - "id": "75d8beb5-e892-4edf-8445-641ef0797b43" + "id": "d10ff962-9a74-4fc0-8bbd-5a44bcc6e127" }, { "cell_type": "code", @@ -2364,7 +2499,7 @@ "source": [ "x = np.random.normal(size=(10, 1))" ], - "id": "1de0629d-b8c4-480d-8275-f93932a91167" + "id": "5679690a-ca69-4033-b875-cac47c5b3c0b" }, { "cell_type": "code", @@ -2374,7 +2509,7 @@ "source": [ "Phi = mlai.fourier(x, 5)" ], - "id": "af910efa-7d30-4bab-9cc9-c41ffecc6778" + "id": "5e9f1559-6453-4f79-8aef-e0f6df2cb1e5" }, { "cell_type": "code", @@ -2384,7 +2519,7 @@ "source": [ "(np.dot(Phi.T,Phi))" ], - "id": "2cbf8e4e-e429-45e4-9c2a-83cfb62c61f6" + "id": "d8588721-ef59-49b9-b3b5-745e9d986ac6" }, { "cell_type": "code", @@ -2394,7 +2529,7 @@ "source": [ "Phi*Phi" ], - "id": "d97dc0c9-094c-4d80-8b2a-f2191631da07" + "id": "2cefa83f-3e4d-4420-be3c-593a73e0cb8d" }, { "cell_type": "markdown", @@ -2408,7 +2543,7 @@ "\n", "- Chapter 3, Section 3.1 up to pg 143 of Bishop (2006)" ], - "id": "3e04c82b-5a05-4780-ab6f-8605346c0d01" + "id": "f4293608-5952-41df-8053-cf2c028e7e0a" }, { "cell_type": "markdown", @@ -2419,6 +2554,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -2426,26 +2564,20 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "e96401b2-7ae5-470c-a4a8-e125752e71fe" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "5ad74c51-ed73-4ca3-8f57-5489eb3dce48" + "id": "6983981b-4278-4678-bf70-76757cc8b264" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Bishop, C.M., 2006. Pattern recognition and machine learning. springer.\n", "\n", "Rogers, S., Girolami, M., 2011. A first course in machine learning. CRC\n", "Press." ], - "id": "545d09d1-2753-4844-9ff1-b32e78163e71" + "id": "0018fa81-918a-49ea-bab7-ade020a9be2e" } ], "nbformat": 4, diff --git a/_notebooks/07-generalization.ipynb b/_notebooks/07-generalization.ipynb index 9f46e0ee..9693d43e 100644 --- a/_notebooks/07-generalization.ipynb +++ b/_notebooks/07-generalization.ipynb @@ -6,7 +6,7 @@ "source": [ "# Generalization: Model Validation" ], - "id": "b42193ce-b982-49a9-be36-7f7cec410863" + "id": "06304edc-71e2-473c-bd0f-5d636874c85c" }, { "cell_type": "markdown", @@ -18,7 +18,7 @@ "of *model validation*. In this lecture we introduce approaches to model\n", "validation such as hold out validation and cross validation." ], - "id": "a6ba5263-892c-47eb-80f8-3c996de6a6d8" + "id": "0784af85-fdf1-423c-bd1f-26fcd1d7d34e" }, { "cell_type": "markdown", @@ -27,17 +27,20 @@ "$$\n", "$$" ], - "id": "eeeab0e0-ecde-4d61-96a0-4aea91b45932" + "id": "144b6361-be73-4482-824f-5c56cab0a72f" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -45,7 +48,7 @@ "\n", "-->" ], - "id": "d5d2fc3f-c5f9-4e64-8fae-e2bccb3c527f" + "id": "8b29824e-09bf-4f3a-87b9-fe8d615bc995" }, { "cell_type": "markdown", @@ -57,7 +60,7 @@ "style=\"\">\\[edit\\]" ], - "id": "293ad93d-0131-42dc-9362-3b08d3e3b66e" + "id": "24e1c205-84da-48ab-b460-6a20ac636ffa" }, { "cell_type": "code", @@ -66,9 +69,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "65aa275e-3f83-4caf-a6c1-130b4a1a6020" + "id": "8e64b7c7-44fb-432d-9ab0-76740e20b8a0" }, { "cell_type": "markdown", @@ -79,7 +92,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "c435198e-b96d-46dd-9eb2-c89ae1ce7ea3" + "id": "dd6804dc-0dbb-4621-857c-83f308c5baab" }, { "cell_type": "markdown", @@ -96,7 +109,56 @@ "\n", "The software can be installed using" ], - "id": "4a55141a-7ad1-4e36-b779-9bbaac8d8457" + "id": "717dc721-6316-45e8-b7ba-0dab780dcd5f" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "879c5499-fd62-47b3-a06e-6fb232133f28" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" + ], + "id": "321cf182-b992-4fd0-af0d-64672c94da04" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "9380d73b-4fd9-4ca5-a310-830ee17654c7" }, { "cell_type": "code", @@ -104,9 +166,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "%system {cmd}" ], - "id": "c96296d0-0a54-4f4f-ad83-f3131446f0c3" + "id": "79da9827-7bc5-4f48-bfe5-eaed049d8dca" }, { "cell_type": "markdown", @@ -119,7 +181,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "603bf7d6-e749-4aba-acfb-143792ae0cbd" + "id": "04b01faa-2ec1-483f-82e8-4f457a25c298" }, { "cell_type": "code", @@ -129,7 +191,7 @@ "source": [ "import notutils" ], - "id": "01c5ceaf-e1e2-48ab-b5f9-149f399c8a20" + "id": "cc915aa8-7fc0-4744-a54b-2a9bde85eaf6" }, { "cell_type": "markdown", @@ -151,7 +213,7 @@ "\n", "The software can be installed using" ], - "id": "4f62b166-a337-4aba-815b-ef469afd80f6" + "id": "f9378f3a-50e4-4685-9a9a-579613687f7e" }, { "cell_type": "code", @@ -159,9 +221,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "cmd = install_command('pods')" ], - "id": "d4af5814-e0ef-4504-b619-a2702ccc1593" + "id": "96d9687a-da13-40f8-bc49-acbb9b6350fc" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "42ed0d57-a877-4220-b2fc-4d43beeaa605" }, { "cell_type": "markdown", @@ -173,7 +245,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "fa14adc1-0e4d-46cb-ab33-9fcc10779f73" + "id": "8ccbbad6-05d5-48ee-be48-2a2e3fa40462" }, { "cell_type": "code", @@ -183,7 +255,7 @@ "source": [ "import pods" ], - "id": "3e70a22d-8d7d-4f49-b633-81b65da98f9d" + "id": "76ef4011-d66a-460b-905d-5b6f7db3d551" }, { "cell_type": "markdown", @@ -201,7 +273,7 @@ "\n", "The software can be installed using" ], - "id": "a4fdc304-89f4-4405-b94c-8c2a439e3e1a" + "id": "25f278a4-0cf3-4de8-86bf-46b5ff78da39" }, { "cell_type": "code", @@ -209,9 +281,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "cmd = install_command('mlai')" ], - "id": "84d0a3c9-e4de-4f30-8a79-44f9b1430d01" + "id": "1955551e-8ceb-4005-98a5-8cc7fcbdc335" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "a3836dd0-1b0d-4bee-b49a-c4ffa3ea1e89" }, { "cell_type": "markdown", @@ -223,7 +305,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "2c4ec408-7755-44ae-afd1-56ee7c54c2fe" + "id": "6eaf5fae-8b7d-4354-9ea5-25f6489ae7fe" }, { "cell_type": "code", @@ -231,9 +313,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "be1894da-1202-45c4-8995-cb41dec1d8c4" + "id": "6edad1c1-6073-4e85-984b-2d45f5ed5d13" }, { "cell_type": "markdown", @@ -247,7 +330,7 @@ "- Explored the different characteristics of different basis function\n", " models" ], - "id": "b2e71c85-1e84-4e46-814a-cb38009b29be" + "id": "3967c166-c4c6-438b-bfcf-a354b10c65b0" }, { "cell_type": "markdown", @@ -259,20 +342,9 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", + "\n", "\n", "Figure: Alan Turing, in 1946 he was only 11 minutes slower than the\n", "winner of the 1948 games. Would he have won a hypothetical games held in\n", @@ -302,7 +374,7 @@ "the concept of generalization let’s introduce some formal representation\n", "of what it means to generalize in machine learning." ], - "id": "97f59ce8-8d83-4dc4-b156-f971e1bbbdc5" + "id": "14f8117b-747e-477e-9c79-7f3c728e71e9" }, { "cell_type": "markdown", @@ -335,10 +407,15 @@ "R(\\mathbf{ w}) = \\int L(y, x, \\mathbf{ w}) \\mathbb{P}(y, x) \\text{d}y\n", "\\text{d}x.\n", "$$ Of course, in practice, this value can’t be computed *but* it serves\n", - "as a reminder of what it is we are aiming to minimize and under certain\n", - "circumstances it can be approximated." + "as a reminder of what it is we are aiming to minimise and under certain\n", + "circumstances it can be approximated.\n", + "\n", + "Here we have used $L(\\cdot)$ as our notation for the *loss* function.\n", + "The cost we expect to pay for making errors. This is a different\n", + "interpretation of the objective function, $E(\\cdot)$, fromt he idea of\n", + "negative log likelihood we see in Gauss’s probabilistic approach." ], - "id": "e7b7ffd0-0783-440c-b5f6-9baf7f9ca87c" + "id": "de11f732-50cf-4763-8670-23fdd9deb354" }, { "cell_type": "markdown", @@ -371,7 +448,7 @@ "$$ which up to the constant $\\frac{1}{n}$ is identical to the objective\n", "function we have been using so far." ], - "id": "44cf602a-590b-4890-87ce-b862dced8595" + "id": "953f12c1-707c-405c-bcda-84d0ac800085" }, { "cell_type": "markdown", @@ -396,7 +473,7 @@ "our model. This means that it doesn’t exhibit the same bias that the\n", "empirical risk does when estimating the true risk." ], - "id": "70c1d3c6-4f60-44dc-ae92-612e14f31941" + "id": "984d6b45-a448-47ed-80cd-5a70465c2353" }, { "cell_type": "markdown", @@ -424,7 +501,7 @@ "We will review the different methods of model selection on the Olympics\n", "marathon data. Firstly we import the Olympic marathon data." ], - "id": "f3c15f57-28eb-42d9-888d-d9ac5ecf13a1" + "id": "d7d3b473-df89-448d-acfe-fac67877536f" }, { "cell_type": "markdown", @@ -436,31 +513,41 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", "- Gold medal times for Olympic Marathon since 1896.\n", "- Marathons before 1924 didn’t have a standardized distance.\n", "- Present results using pace per km.\n", "- In 1904 Marathon was badly organized leading to very slow times.\n", "\n", - "\n", - "\n", - "\n", - "Image from Wikimedia Commons \n", - "\n", - "
\n", + "\n", + "Image from [Wikimedia\n", + "Commons](https://commons.wikimedia.org/wiki/File:Eliud_Kipchoge_in_Berlin_-_2015_(cropped).jpg)\n", "\n", - "The first thing we will do is load a standard data set for regression\n", + "The Olympic Marathon data is a standard dataset for regression\n", "modelling. The data consists of the pace of Olympic Gold Medal Marathon\n", "winners for the Olympics from 1896 to present. Let’s load in the data\n", "and plot." ], - "id": "233334ec-0e7e-419f-9b22-b6d81402c1eb" + "id": "619dae5a-4464-49b9-814d-f06d7e414315" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "19083f1f-dc39-4996-b419-5740a95c76f7" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "0733c05a-1296-44bb-8a9f-6608d9feeba9" }, { "cell_type": "code", @@ -471,7 +558,7 @@ "import numpy as np\n", "import pods" ], - "id": "7d60eb49-bbfb-43aa-9de8-3a70e5d91089" + "id": "7948141a-428a-4055-8179-aa0b2993413d" }, { "cell_type": "code", @@ -487,7 +574,7 @@ "scale = np.sqrt(y.var())\n", "yhat = (y - offset)/scale" ], - "id": "d6caa842-e2c1-4a47-935d-a64bc66fee85" + "id": "3d3a2446-f995-46b7-86c9-88b07105bae8" }, { "cell_type": "code", @@ -499,7 +586,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "0102dcf7-cfa3-481b-a8fa-614f1e67c807" + "id": "770760bf-f14f-4a3a-aa83-10e64bbc0964" }, { "cell_type": "code", @@ -508,7 +595,7 @@ "outputs": [], "source": [ "\n", - "xlim = (1875,2030)\n", + "xlim = (1876,2044)\n", "ylim = (2.5, 6.5)\n", "\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", @@ -521,13 +608,13 @@ "mlai.write_figure(filename='olympic-marathon.svg', \n", " directory='./datasets')" ], - "id": "1caa2329-22cb-47ca-9c01-d81472c9e6a6" + "id": "e1aa2eb0-55e3-4ba1-92fc-490a96186535" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Olympic marathon pace times since 1896.\n", "\n", @@ -537,7 +624,33 @@ "participants got lost, and only very few participants completed. More\n", "recent years see more consistently quick marathons." ], - "id": "e600a9ae-5fc8-479a-9e96-963adfbfd63e" + "id": "e224d5b1-70b4-4aec-ac2e-2b0afcd57356" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pods" + ], + "id": "1a7ef314-8745-4299-ae75-93766b7c5a2e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.olympic_marathon_men()\n", + "x = data['X']\n", + "y = data['Y']\n", + "offset = y.mean()\n", + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale" + ], + "id": "bc1c6640-df5e-4f51-984d-547d17afb540" }, { "cell_type": "markdown", @@ -563,7 +676,7 @@ "E(\\mathbf{ w}) = (\\mathbf{ y}- \\mathbf{ f})^\\top(\\mathbf{ y}- \\mathbf{ f})\n", "$$" ], - "id": "8841412f-e579-4975-9997-f328eede38f7" + "id": "430c2ba6-3789-4fc5-b35b-f05bedc85039" }, { "cell_type": "markdown", @@ -614,7 +727,7 @@ "the resulting prediction between 1890 and 2020. Set the title of the\n", "plot to be the error of the fit on the *training data*." ], - "id": "e19726c5-50e4-4a6a-b285-5f2caaf9a0b1" + "id": "1d150d3e-15be-49c1-96df-3d674ed79b45" }, { "cell_type": "code", @@ -628,7 +741,7 @@ "\n", "\n" ], - "id": "74b68778-4557-4326-af77-9217f87358f0" + "id": "cd31c76a-b06c-4bb1-8a48-b246f238c5ae" }, { "cell_type": "markdown", @@ -636,7 +749,7 @@ "source": [ "## Polynomial Fit: Training Error" ], - "id": "2d7b412d-a406-462e-ab2f-1aef1a18ea90" + "id": "3548ca4b-e692-4b2c-91d8-13b1cb586eca" }, { "cell_type": "markdown", @@ -657,7 +770,7 @@ "Use this to compute the quadratic fit for the model, again plotting the\n", "result titled by the error." ], - "id": "c5c4773b-567b-493a-b22f-e0aa85b8eabf" + "id": "fec1f63e-21e4-4be8-88e4-a949b7192944" }, { "cell_type": "code", @@ -671,7 +784,7 @@ "\n", "\n" ], - "id": "be37d867-28e2-4270-8cf9-6a0001d274ab" + "id": "f01412e0-3320-48ba-b4ba-dd191e905d26" }, { "cell_type": "markdown", @@ -679,7 +792,7 @@ "source": [ "## Polynomial Fits to Olympics Data" ], - "id": "d7ab3942-b583-454a-ba67-fbd5d71f1b3f" + "id": "bfe3955c-df43-469a-b5c1-9182f1693ab9" }, { "cell_type": "code", @@ -690,7 +803,7 @@ "import pods\n", "import mlai" ], - "id": "1812dbb5-e4a5-4aa6-a596-f7c0bae42c47" + "id": "dafc8c0b-61e7-453f-a500-28d305c8e458" }, { "cell_type": "code", @@ -698,12 +811,12 @@ "metadata": {}, "outputs": [], "source": [ - "max_basis = 26\n", - "data_limits = (1888, 2020)\n", + "max_basis = 28\n", + "data_limits = xlim\n", "basis = mlai.Basis(mlai.polynomial, number=1, data_limits=data_limits)\n", "num_data = x.shape[0]" ], - "id": "c1c20f08-842c-4639-bf03-f8d572e04355" + "id": "78932b07-ce4f-4d53-9477-385235744789" }, { "cell_type": "code", @@ -713,7 +826,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "1fa0e599-f552-48ff-b7a8-d7185739f863" + "id": "d890e0e9-d041-4e5c-8b8f-678cb731c649" }, { "cell_type": "code", @@ -726,7 +839,7 @@ " xlim=data_limits, objective_ylim=[0, 0.8],\n", " diagrams='./ml')" ], - "id": "dbb10470-9737-4adc-acd4-77018ffcdc05" + "id": "2b1fce45-0683-4d3a-b584-6d28f3fa1eb3" }, { "cell_type": "code", @@ -736,7 +849,7 @@ "source": [ "from ipywidgets import IntSlider" ], - "id": "a2dfe050-c9b7-4024-a06c-76f0c40fa368" + "id": "45d3ba26-038a-4bd4-9961-5acaa479b89e" }, { "cell_type": "code", @@ -746,7 +859,7 @@ "source": [ "import notutils as nu" ], - "id": "c074b0de-2e78-4beb-ad0d-e40b257ed840" + "id": "be9fce47-31f6-46f0-b9e1-6f3db5c902d0" }, { "cell_type": "code", @@ -758,17 +871,43 @@ " directory='./ml', \n", " num_basis=IntSlider(1, 1, max_basis, 1))" ], - "id": "cb8e13b3-bc0e-4609-abc0-805ef0d45a5c" + "id": "f49079a2-7da9-4d44-a63e-ec6d6b79f98f" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", - "Figure: Polynomial fit to olympic data with 26 basis functions." + "Figure: Polynomial fit to olympic data with 28 basis functions." + ], + "id": "aac0ec7d-d720-4928-a96b-72c234005922" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pods" + ], + "id": "6a43d355-351f-4918-ac07-c3ea905cb8bc" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.olympic_marathon_men()\n", + "x = data['X']\n", + "y = data['Y']\n", + "offset = y.mean()\n", + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale" ], - "id": "f82c27f1-df64-4f1c-85a8-a68384621d9b" + "id": "2a93302f-569e-459d-8de0-66ef85585cfc" }, { "cell_type": "markdown", @@ -780,7 +919,7 @@ "style=\"\">\\[edit\\]" ], - "id": "936b83bd-291b-4839-9c6e-44610c35ca63" + "id": "b219d21e-f410-4c6b-986b-831c9c4e5fb6" }, { "cell_type": "code", @@ -788,10 +927,9 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai.plot as plot\n", "import mlai" ], - "id": "b960ef95-fcbc-4ea1-8abd-bfc4fe4c2012" + "id": "e990e211-5f91-44ce-a9e8-fe6a202194d9" }, { "cell_type": "code", @@ -803,7 +941,17 @@ "basis = mlai.Basis(mlai.polynomial, number=1, data_limits=data_limits)\n", "max_basis = 11" ], - "id": "ff491829-a0fa-41e5-a3d3-866d1910c33f" + "id": "306da8aa-a012-4b43-a79f-5a1620ca93fc" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import mlai.plot as plot" + ], + "id": "40a853f6-98f8-40f1-abd3-8ca9922c38df" }, { "cell_type": "code", @@ -818,7 +966,7 @@ " xlim=data_limits, prefix='olympic_val_extra', \n", " diagrams='./ml')" ], - "id": "129f4d8c-f3cb-4ba7-a92b-b8b49afc672e" + "id": "91ac5bda-4513-492d-9105-0055249389ae" }, { "cell_type": "code", @@ -829,7 +977,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "bc601bf1-6a9b-4809-a7cf-0e66b9e0f8f2" + "id": "ad2dd152-320f-418e-8e1d-fdd43e839c7a" }, { "cell_type": "code", @@ -839,7 +987,7 @@ "source": [ "import notutils as nu" ], - "id": "b8805cac-0ef1-474a-a8ed-2afdf9590413" + "id": "e302d3ee-7670-4049-823a-611b93e5f9dc" }, { "cell_type": "code", @@ -851,18 +999,18 @@ " directory='./ml', \n", " num_basis=IntSlider(1, 1, max_basis, 1))" ], - "id": "f43d7904-003a-4076-9371-7ebb7ec5e3f7" + "id": "0656081f-5cfb-4c2e-8438-1126d8323143" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Olympic marathon data with validation error for\n", "extrapolation." ], - "id": "a7a5d9e6-b684-4fd9-a457-ee1983a0504c" + "id": "0ddce23d-8c72-4970-8fd3-3229590a7591" }, { "cell_type": "markdown", @@ -870,7 +1018,7 @@ "source": [ "## Extrapolation" ], - "id": "f19ab945-9213-4b01-9126-7f766353e1f1" + "id": "cdfa841b-44ae-4f6b-93cf-42ab77e19924" }, { "cell_type": "markdown", @@ -878,7 +1026,7 @@ "source": [ "## Interpolation" ], - "id": "197fed9e-ec97-4d96-91f8-6ec3e35b433b" + "id": "eb8439e1-9db3-4208-8764-436769ba45a7" }, { "cell_type": "code", @@ -888,7 +1036,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "5175cdb6-f22d-49c6-a663-bf0927b7ddc1" + "id": "a4adffc7-14b9-467b-8e88-db7a638d0f9c" }, { "cell_type": "code", @@ -902,7 +1050,7 @@ " objective_ylim=[0.1, 0.6], permute=True,\n", " diagrams='./ml')" ], - "id": "13a53864-829b-456c-b802-52d05a1bfc3b" + "id": "4b58b449-c35c-4633-a690-4a7f3aeec554" }, { "cell_type": "code", @@ -913,7 +1061,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "9e385dc6-e765-4748-b494-0388eeb290e0" + "id": "31e79d75-de5a-4615-b6f4-0bd8afc72c11" }, { "cell_type": "code", @@ -923,7 +1071,7 @@ "source": [ "import notutils as nu" ], - "id": "a404dd26-9428-4943-9e4d-76fe7fe00e03" + "id": "d25976b9-c719-4f71-aee5-4457c4cb8b81" }, { "cell_type": "code", @@ -935,18 +1083,18 @@ " directory='./ml', \n", " num_basis=IntSlider(1, 1, max_basis, 1))" ], - "id": "2cc6528a-c09f-452a-9ea4-c829ebb6221c" + "id": "39eb1445-ea9e-40ea-be70-12d5c27e38d0" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Olympic marathon data with validation error for\n", "interpolation." ], - "id": "b89a1b9b-560d-483d-b0ad-2f100603f8cc" + "id": "02e12343-81db-4c10-8a4d-53462a484870" }, { "cell_type": "markdown", @@ -954,7 +1102,7 @@ "source": [ "## Choice of Validation Set" ], - "id": "cc733b40-0c9b-4fd2-956a-312a04bec2e8" + "id": "1cb2ace1-7268-4497-b647-483c516868ff" }, { "cell_type": "markdown", @@ -972,7 +1120,7 @@ "predictions. We will hold out data from after 1980 and fit the model to\n", "the data before 1980." ], - "id": "7f08dd57-0417-4bce-ab2a-36950233771e" + "id": "3eb873b4-e6fc-4d3e-8e7f-2adbb6b3d771" }, { "cell_type": "code", @@ -991,7 +1139,7 @@ "x_valid = np.take(x, indices_hold_out, axis=0)\n", "y_valid = np.take(y, indices_hold_out, axis=0)" ], - "id": "8de4325d-c108-4b9c-a54c-6c1c42272b93" + "id": "8e34e73a-523f-46f3-a534-a57407c2c540" }, { "cell_type": "markdown", @@ -1003,7 +1151,7 @@ "until 1980 and then compute the error on the held out data (from 1980\n", "onwards). Which model performs better on the validation data?" ], - "id": "a6ce0ffc-9c74-4c49-94b7-5a99169dc98c" + "id": "5dfe0662-87eb-40ee-8b87-95a7d47ddc40" }, { "cell_type": "code", @@ -1017,7 +1165,7 @@ "\n", "\n" ], - "id": "596ddccd-b07a-4045-af8c-433b5d368c47" + "id": "d8ae625e-15ac-42cd-9c6f-a2259b818307" }, { "cell_type": "markdown", @@ -1028,7 +1176,7 @@ "Now we have an approach for deciding which model to retain, we can\n", "consider the entire family of polynomial bases, with arbitrary degrees." ], - "id": "72c50c81-a210-47f3-bc47-015dfe0e2861" + "id": "5da20b94-d8e6-40c3-9ded-b68990de6533" }, { "cell_type": "markdown", @@ -1063,7 +1211,7 @@ "Which polynomial has the minimum training error? Which polynomial has\n", "the minimum validation error?" ], - "id": "4c3822ff-1779-46e5-9bd1-bba81f013b76" + "id": "39f1a54a-23d3-403e-a2eb-f332fd35ec32" }, { "cell_type": "code", @@ -1077,7 +1225,33 @@ "\n", "\n" ], - "id": "fc952e7d-f885-4f49-b22d-10d534a9a36c" + "id": "5702aee6-775e-4854-a776-904c16262ae5" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pods" + ], + "id": "c67cfa56-60db-4412-ae32-11f9a19e5fb9" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.olympic_marathon_men()\n", + "x = data['X']\n", + "y = data['Y']\n", + "offset = y.mean()\n", + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale" + ], + "id": "9c92026c-0e65-46db-9eba-d11c23b22883" }, { "cell_type": "markdown", @@ -1089,7 +1263,7 @@ "style=\"\">\\[edit\\]" ], - "id": "1b5fba54-bddf-4d97-a230-63b9b7298e13" + "id": "1cfa0d80-e6e7-45dc-9d72-cbdd3ef79480" }, { "cell_type": "code", @@ -1097,10 +1271,9 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai\n", - "import pods" + "import mlai" ], - "id": "1c48767a-6a92-42e7-8ba0-64d0fa60d01b" + "id": "53ff28bd-e097-4de8-9995-6fa36b2c40e7" }, { "cell_type": "code", @@ -1112,7 +1285,7 @@ "basis = mlai.Basis(mlai.polynomial, number=1, data_limits=data_limits)\n", "max_basis = 11" ], - "id": "1a937d5c-f0a8-4624-bcd4-4e749f22b450" + "id": "7ef60c99-cf5b-4504-9dab-0128b3d6b6f3" }, { "cell_type": "code", @@ -1122,7 +1295,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "ad7088cd-cde3-4deb-951a-1e5aeafd89b2" + "id": "11884105-926f-46a9-805d-f5f4b8970450" }, { "cell_type": "code", @@ -1136,7 +1309,7 @@ "# xlim=data_limits, objective_ylim=[0, 0.8], prefix='olympic_loo',\n", "# diagrams='./ml')" ], - "id": "adf4cedb-2d2b-4218-8097-7c5ec674ec20" + "id": "0c64591b-2a71-4c61-88c2-630170c54415" }, { "cell_type": "code", @@ -1147,7 +1320,7 @@ "from ipywidgets import IntSlider\n", "import pods" ], - "id": "670626d1-1be0-4d6d-8c8d-921011133111" + "id": "afcef7de-2d36-48c4-9b8e-7d83745521b2" }, { "cell_type": "code", @@ -1157,7 +1330,7 @@ "source": [ "import notutils as nu" ], - "id": "665d6727-05dd-466f-8cee-e1ebb13d7e28" + "id": "c7c3b80a-2708-4631-a4a4-aa7ac4f96868" }, { "cell_type": "code", @@ -1170,7 +1343,7 @@ " num_basis=IntSlider(1, 1, max_basis, 1), \n", " part=IntSlider(0, 0, x.shape[0], 1))" ], - "id": "73706f11-f6c4-4f82-a994-4a0850348e00" + "id": "d2ffb160-a4f6-4925-8f2b-bdcace5a3964" }, { "cell_type": "markdown", @@ -1198,7 +1371,7 @@ "out validation errors. The average of this estimate is a good estimate\n", "of performance of those models on the test data." ], - "id": "a5442fae-b75f-4af7-8ad9-7f4e348c0d31" + "id": "e0e7ccde-06aa-43b5-863d-84a03a1a6cd4" }, { "cell_type": "markdown", @@ -1212,7 +1385,7 @@ "*leave-one-out* cross validation error for basis functions containing a\n", "maximum degree from 0 to 17." ], - "id": "ec4a6988-5ff3-47eb-912b-e92317c02564" + "id": "c76693ac-74f2-4377-839d-3172fd5ef5c7" }, { "cell_type": "code", @@ -1226,7 +1399,33 @@ "\n", "\n" ], - "id": "1258b8c5-ad06-4644-a0e6-a22c820f2e88" + "id": "abe43306-94bc-4336-8ac8-c61e0a0f0670" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pods" + ], + "id": "8897468f-1dc8-4bb1-8bcf-ca3945042ca9" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.olympic_marathon_men()\n", + "x = data['X']\n", + "y = data['Y']\n", + "offset = y.mean()\n", + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale" + ], + "id": "5cf980f1-f75e-4580-9355-7d6095e2992c" }, { "cell_type": "markdown", @@ -1238,7 +1437,7 @@ "style=\"\">\\[edit\\]" ], - "id": "2a74e51f-f7f1-4573-b301-d2d03787cba0" + "id": "2442a13d-2c95-46d4-abcf-975c2eb9e4f8" }, { "cell_type": "code", @@ -1246,10 +1445,9 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai\n", - "import pods" + "import mlai" ], - "id": "0ed91587-ed21-4d05-b1ac-75e9ef8b920d" + "id": "512c8ec5-961e-433b-89f5-9c04f2251de5" }, { "cell_type": "code", @@ -1261,7 +1459,7 @@ "basis = mlai.Basis(mlai.polynomial, number=1, data_limits=data_limits)\n", "max_basis = 11" ], - "id": "15335ae5-58d3-436c-870a-91637a5494a7" + "id": "255af5bb-5930-46f5-8168-5bc28e121776" }, { "cell_type": "code", @@ -1271,7 +1469,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "905da1b1-d8c6-4650-aed5-71ca9536057b" + "id": "586e013e-95ea-4e2b-be41-c99be131850f" }, { "cell_type": "code", @@ -1289,7 +1487,7 @@ " num_parts=num_parts,\n", " diagrams='./ml')" ], - "id": "e1db189b-b943-4efe-8160-046b40bab71d" + "id": "fbcfa643-9a4a-41d8-a688-e4c4dc5181d7" }, { "cell_type": "code", @@ -1300,7 +1498,7 @@ "from ipywidgets import IntSlider\n", "import pods" ], - "id": "8a2a3744-d19a-4deb-97cc-1cbfdb75204d" + "id": "0722d679-4453-49a6-af84-9a52be6236c6" }, { "cell_type": "code", @@ -1310,7 +1508,7 @@ "source": [ "import notutils as nu" ], - "id": "6b92b106-d1d5-44c3-8f95-14a9af30702d" + "id": "db4b8a18-c794-4612-87f7-80749b770b6c" }, { "cell_type": "code", @@ -1323,7 +1521,7 @@ " part=IntSlider(0,0,5,1),\n", " number=IntSlider(1, 1, max_basis, 1))" ], - "id": "5625fe4b-e514-47d2-af52-8e34d521e836" + "id": "d0dc4ad1-9746-4243-81e8-8e6f29096d66" }, { "cell_type": "markdown", @@ -1355,7 +1553,7 @@ "over your validation portion back into the training data every time you\n", "perform a fold." ], - "id": "dfdffaa6-b12a-47fd-9b5d-aab8d4e41051" + "id": "c30024e9-bd26-4f73-b7da-7005205ac330" }, { "cell_type": "markdown", @@ -1373,7 +1571,7 @@ "of the partitions will have an extra data point. You might find\n", "`np.random.permutation?` useful." ], - "id": "fb37b11c-5c71-4c67-8cd3-b3f4bdbe75a0" + "id": "2f27cbfe-1edc-4802-9809-6623b837c2b9" }, { "cell_type": "code", @@ -1387,7 +1585,7 @@ "\n", "\n" ], - "id": "6b558619-9798-4a82-9f9e-afebed6d8425" + "id": "0283c139-3e80-4e1f-b070-da5beaa6704c" }, { "cell_type": "markdown", @@ -1400,9 +1598,9 @@ "style=\"\">edit\\]\n", "\n", "One of Breiman’s ideas for improving predictive performance is known as\n", - "bagging (**Breiman:bagging96?**). The idea is to train a number of\n", - "models on the data such that they overfit (high variance). Then average\n", - "the predictions of these models. The models are trained on different\n", + "bagging (Breiman, 1996). The idea is to train a number of models on the\n", + "data such that they overfit (high variance). Then average the\n", + "predictions of these models. The models are trained on different\n", "bootstrap samples (Efron, 1979) and their predictions are aggregated\n", "giving us the acronym, Bagging. By combining decision trees with\n", "bagging, we recover random forests (Breiman, 2001).\n", @@ -1422,8 +1620,8 @@ "and Nelder, 1989), this is the point at which the number of parameters\n", "is equal to the number of data[1].\n", "\n", - "The bias-variance decomposition (**Geman:biasvariance92?**) considers\n", - "the expected test error for different variations of the *training data*\n", + "The bias-variance decomposition (Geman et al., 1992) considers the\n", + "expected test error for different variations of the *training data*\n", "sampled from, $\\mathbb{P}(\\mathbf{ x}, y)$ $$\\begin{align*}\n", "R(\\mathbf{ w}) = & \\int \\left(y- f^*(\\mathbf{ x})\\right)^2 \\mathbb{P}(y, \\mathbf{ x}) \\text{d}y\\text{d}\\mathbf{ x}\\\\\n", "& \\triangleq \\mathbb{E}\\left[ \\left(y- f^*(\\mathbf{ x})\\right)^2 \\right].\n", @@ -1461,7 +1659,7 @@ "[1] Assuming we are ignoring parameters in the link function and the\n", "distribution function." ], - "id": "572905b2-8f98-45ed-80f3-5b64d2846de1" + "id": "cfda31c7-22e1-4438-9e50-dfb150942807" }, { "cell_type": "markdown", @@ -1475,7 +1673,7 @@ "\n", "Helper function for sampling data from two different classes." ], - "id": "56a68b6f-ed5e-4640-9910-e0f031ba4e83" + "id": "f6307019-f39e-485d-88db-e851df2953c8" }, { "cell_type": "code", @@ -1485,7 +1683,7 @@ "source": [ "import numpy as np" ], - "id": "7a84bd80-a2ec-49cc-b23c-3770f0cc5533" + "id": "07993bc7-5556-4c81-8ce7-357eb9b325b3" }, { "cell_type": "code", @@ -1507,14 +1705,14 @@ " neg_mean = [[0, -0.5],[0,-0.5],[0,-0.5]]\n", " neg_cov = [[prec, 0.], [0., prec]]\n", " for mean in pos_mean:\n", - " X.append(np.random.multivariate_normal(mean=mean, cov=pos_cov, size=per_class))\n", - " y.append(np.ones((per_class, 1)))\n", + " X.append(np.random.multivariate_normal(mean=mean, cov=pos_cov, size=per_cluster))\n", + " y.append(np.ones((per_cluster, 1)))\n", " for mean in neg_mean:\n", - " X.append(np.random.multivariate_normal(mean=mean, cov=neg_cov, size=per_class))\n", - " y.append(np.zeros((per_class, 1)))\n", + " X.append(np.random.multivariate_normal(mean=mean, cov=neg_cov, size=per_cluster))\n", + " y.append(np.zeros((per_cluster, 1)))\n", " return np.vstack(X), np.vstack(y).flatten()" ], - "id": "afce6a05-1eb6-4801-960c-8fbca9162689" + "id": "54320529-a91f-4e28-9aca-aa4942e7a9e0" }, { "cell_type": "markdown", @@ -1522,7 +1720,7 @@ "source": [ "Helper function for plotting the decision boundary of the SVM." ], - "id": "baad72c0-cd66-4b5a-8da4-0142602c6909" + "id": "a6e1b8db-365e-4d88-9731-39368eb7b089" }, { "cell_type": "code", @@ -1551,27 +1749,7 @@ " colors=[[0.5, 1.0, 0.5], [1.0, 0.5, 0.5]])\n", " return out" ], - "id": "83964bf3-2819-49d7-84e8-06a44081c108" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import urllib.request" - ], - "id": "5a1241d8-8c1b-445e-a425-485b04ffbc12" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "urllib.request.urlretrieve('https://raw.githubusercontent.com/lawrennd/talks/gh-pages/mlai.py','mlai.py')" - ], - "id": "fa076122-78b0-4d79-997c-6d3a06bdb7b7" + "id": "b0a2bba1-a950-4ae9-acbf-7e7fac4407fb" }, { "cell_type": "code", @@ -1582,7 +1760,7 @@ "import mlai\n", "import os" ], - "id": "2d6dc23e-6217-4477-b985-3e1a30371ca8" + "id": "dd63bf75-332b-4405-9407-646d8cd98e99" }, { "cell_type": "code", @@ -1623,7 +1801,7 @@ " transparent=True)\n", " return xlim, ylim" ], - "id": "91b28f5a-9728-4f4f-8b12-e709bdfb3165" + "id": "0d8d0213-5474-487e-80a0-af924a0e0d56" }, { "cell_type": "code", @@ -1639,7 +1817,7 @@ "matplotlib.rc('font', **font)\n", "import matplotlib.pyplot as plt" ], - "id": "ff6c8e79-964b-4647-a92c-f8d905266dee" + "id": "efe1aa23-2e97-4a98-9cee-d23f8fd49476" }, { "cell_type": "code", @@ -1649,7 +1827,7 @@ "source": [ "from sklearn import svm" ], - "id": "ff9fd33f-df8a-4fb5-a4db-c13dc5053fed" + "id": "a24d5e4e-9858-4e73-b1c2-8d41022c8a50" }, { "cell_type": "code", @@ -1679,12 +1857,12 @@ " xlim, ylim = decision_boundary_plot(models, X, y, \n", " axs=ax, \n", " filename='bias-variance{samp:0>3}.svg'.format(samp=samp), \n", - " directory='./ml'\n", + " directory='./ml',\n", " titles=titles,\n", - " xlim=xlim,\n", - " ylim=ylim)" + " xlim=xlim,\n", + " ylim=ylim)" ], - "id": "51bb6d8b-c69b-4073-b50e-75e84a13cb76" + "id": "2a81eb09-3042-4836-aef4-670c062e1398" }, { "cell_type": "code", @@ -1695,7 +1873,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "bcdb60a3-ddab-4aa4-a70b-4204f27d5c9e" + "id": "fab73ef8-2df2-40ff-8895-075f4e3a0ade" }, { "cell_type": "code", @@ -1705,7 +1883,7 @@ "source": [ "import notutils as nu" ], - "id": "20b64cee-9228-4c84-a610-c79b033cb5b4" + "id": "a27cfbcf-671b-4457-b2d7-3571f9f23f39" }, { "cell_type": "code", @@ -1717,7 +1895,7 @@ " directory='./ml', \n", " samp=IntSlider(0,0,10,1))" ], - "id": "907acc1a-f914-4922-b99e-038b5c2b73c1" + "id": "cef2e77c-7430-4ba0-8365-917940613a28" }, { "cell_type": "markdown", @@ -1725,7 +1903,7 @@ "source": [ "\n", "\n", - "\n", + "\n", "\n", "Figure: In each figure the simpler model is on the left, and the more\n", "complex model is on the right. Each fit is done to a different version\n", @@ -1733,7 +1911,7 @@ "(bias error), whereas the more complex model is varying in its errors\n", "(variance error)." ], - "id": "3fe9987d-7ae0-4ea4-868a-f0e63cd4b143" + "id": "ca68541c-98a8-4236-ba07-12cd92502330" }, { "cell_type": "markdown", @@ -1743,7 +1921,7 @@ "\n", "- Section 1.5 of Rogers and Girolami (2011)" ], - "id": "ec8466b5-bfd8-4de1-beb7-32a66291f323" + "id": "2174b278-ce05-4aba-b6ae-3aa9b8f26b36" }, { "cell_type": "markdown", @@ -1754,6 +1932,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -1761,25 +1942,26 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "3d15be9d-4798-4682-9625-fe392f43796c" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "16e42e0d-e561-434c-b7dc-d04aff9b2090" + "id": "8cb7e1bd-04d0-44a0-a3be-16e8a272ec2c" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Breiman, L., 2001. Random forests. Mach. Learn. 45, 5–32.\n", + "## References\n", + "\n", + "Breiman, L., 2001. Random forests. Machine Learning 45, 5–32.\n", "\n", "\n", - "Efron, B., 1979. Bootstrap methods: Another look at the jackkife. Annals\n", - "of Statistics 7, 1–26.\n", + "Breiman, L., 1996. Bagging predictors. Machine Learning 24, 123–140.\n", + "\n", + "\n", + "Efron, B., 1979. Bootstrap methods: Another look at the jackknife.\n", + "Annals of Statistics 7, 1–26.\n", + "\n", + "Geman, S., Bienenstock, E., Doursat, R., 1992. Neural networks and the\n", + "bias/variance dilemma. Neural Computation 4, 1–58.\n", + "\n", "\n", "McCullagh, P., Nelder, J.A., 1989. Generalized linear models, 2nd ed.\n", "Chapman; Hall.\n", @@ -1787,7 +1969,7 @@ "Rogers, S., Girolami, M., 2011. A first course in machine learning. CRC\n", "Press." ], - "id": "2e8e9cec-0734-4dd1-a39c-ad0a43f68232" + "id": "8196317a-d089-4723-a31e-ae3787686dbc" } ], "nbformat": 4, diff --git a/_notebooks/08-bayesian-regression.ipynb b/_notebooks/08-bayesian-regression.ipynb index ee701fe4..58892d68 100644 --- a/_notebooks/08-bayesian-regression.ipynb +++ b/_notebooks/08-bayesian-regression.ipynb @@ -6,7 +6,7 @@ "source": [ "# Bayesian Regression" ], - "id": "38206a98-caed-46a5-b967-d1424394bc71" + "id": "755a29d6-75b5-44f6-8a74-59cd5bdad020" }, { "cell_type": "markdown", @@ -14,7 +14,7 @@ "source": [ "**Abstract**: Bayesian formalisms deal with uncertainty in parameters," ], - "id": "66fe7848-cba1-4aae-81b1-c236db085494" + "id": "527334f6-e375-4cb2-b7a5-cb684a130b7b" }, { "cell_type": "markdown", @@ -23,17 +23,20 @@ "$$\n", "$$" ], - "id": "9d11c095-3479-4f78-896d-34a38e7f2c98" + "id": "eaadfc75-9386-418a-b425-c177b91fcf33" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -41,7 +44,7 @@ "\n", "-->" ], - "id": "b63e98fb-b66b-4a05-a44d-3ed7c6445d74" + "id": "2229320f-2a3f-4493-9a5d-f478f7704920" }, { "cell_type": "markdown", @@ -53,7 +56,7 @@ "style=\"\">\\[edit\\]" ], - "id": "b0f7dafa-a8f1-4788-9212-342ca191ba2d" + "id": "df7186b3-08cb-4682-8929-471fc6cc339e" }, { "cell_type": "code", @@ -62,9 +65,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "02db8887-8ece-4c7e-bcdb-249199488f84" + "id": "cc6a3054-4f93-429f-b9cd-62d67f59352f" }, { "cell_type": "markdown", @@ -75,7 +88,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "c8f644a4-c047-45d2-8257-b22fcc1e06d0" + "id": "a30da605-b873-4176-b38a-02f00daf2f28" }, { "cell_type": "markdown", @@ -92,7 +105,46 @@ "\n", "The software can be installed using" ], - "id": "c3b3beae-24e2-4f38-a31f-1093d2e363ee" + "id": "968eea21-3c3f-44cd-bf70-c39e67bef937" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "7d38729f-c1a9-4223-9266-126867faed8f" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" + ], + "id": "0af33c99-efb3-49f0-90d4-a8a6aa4026b5" }, { "cell_type": "code", @@ -100,9 +152,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "cmd = install_command('notutils')" ], - "id": "e9a1d2c6-c250-4d88-987b-12f60113a624" + "id": "4c91e2b1-0c88-49e2-93ab-815882319782" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "8341699d-c961-4810-a0d4-05fcea80771a" }, { "cell_type": "markdown", @@ -115,7 +177,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "94712155-9b6f-45f5-8bda-ffd2fada0355" + "id": "65a1f023-4764-4813-82db-47b3938a050d" }, { "cell_type": "code", @@ -125,7 +187,7 @@ "source": [ "import notutils" ], - "id": "3140cec6-bcea-41ed-a7a0-03bcbd5906e2" + "id": "5ecb2988-f782-43a9-b291-4ca681fa3245" }, { "cell_type": "markdown", @@ -147,7 +209,7 @@ "\n", "The software can be installed using" ], - "id": "fa9e0006-bf53-4cc6-8f57-09713552b5d2" + "id": "9fe0fd09-e35d-4dc8-b585-54f01c66d800" }, { "cell_type": "code", @@ -155,9 +217,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "cmd = install_command('pods')" ], - "id": "0cf7c482-1a6d-4824-81d0-e98e12258f10" + "id": "f30430f8-6f93-464b-8599-f3f26d930940" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "402eb57a-328b-4f61-adb8-df30ad70b3d3" }, { "cell_type": "markdown", @@ -169,7 +241,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "2171887a-1976-4f6c-80ed-bb5c561f1ea1" + "id": "66cd0588-6336-4be3-accb-c71d7121eb3c" }, { "cell_type": "code", @@ -179,7 +251,7 @@ "source": [ "import pods" ], - "id": "54a04d34-553a-4437-9dcb-7ebbf7ad5858" + "id": "15f03122-f761-432b-bf73-3f7d07e24467" }, { "cell_type": "markdown", @@ -197,7 +269,7 @@ "\n", "The software can be installed using" ], - "id": "9af48466-0146-4e6d-a356-61a4a7c21eb2" + "id": "58d7bdeb-eb38-46dc-b3c0-de22cdfa42a6" }, { "cell_type": "code", @@ -205,9 +277,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "cmd = install_command('mlai')" ], - "id": "5011fda8-e77c-48ff-9c76-71909c5cee03" + "id": "11fc8206-2096-43b1-98b1-e6fb5cf8efaf" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "a1cf18b9-9d48-4d74-917d-b949310cb51c" }, { "cell_type": "markdown", @@ -219,7 +301,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "11eb7945-2321-4b47-a9d6-bd2b9a18889a" + "id": "3ccf02dd-c6ec-4b33-88bd-2696a099ddfa" }, { "cell_type": "code", @@ -227,9 +309,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "fa13675d-e94f-4c85-ab40-04493efa91c2" + "id": "613fe3df-9e04-4b3c-a6d4-9a782942749e" }, { "cell_type": "markdown", @@ -251,7 +334,7 @@ "\n", "" ], - "id": "a4e51389-97d0-4aff-8ac5-5d996ce41ef8" + "id": "c85326ef-d71d-4b76-862c-0436d775d1f5" }, { "cell_type": "markdown", @@ -263,7 +346,7 @@ "style=\"\">\\[edit\\]" ], - "id": "1544e29e-8db5-4128-8d2b-07ce6578b7fe" + "id": "61413080-9387-4f34-9028-635d8f079c54" }, { "cell_type": "code", @@ -273,7 +356,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "8071d119-b96d-4b96-a1cd-6b85c1f4072f" + "id": "54235ade-e41a-40ab-b008-3dae0194e5bc" }, { "cell_type": "code", @@ -283,7 +366,7 @@ "source": [ "plot.under_determined_system(diagrams='./ml')" ], - "id": "af9d9ef6-e463-427e-8b74-e2f5a5e7dd95" + "id": "188dd411-2c2a-4047-8afa-5989615d8666" }, { "cell_type": "markdown", @@ -308,7 +391,7 @@ "assumption about the slack variables, we assumed that the slack\n", "variables were distributed according to a probability density. And for\n", "the moment we have been assuming that density was the Gaussian,\n", - "$$\\epsilon_i \\sim \\mathcal{N}\\left(0,\\sigma^2\\right),$$ with zero mean\n", + "$$\\epsilon_i \\sim \\mathscr{N}\\left(0,\\sigma^2\\right),$$ with zero mean\n", "and variance $\\sigma^2$.\n", "\n", "The follow up question is whether we can do the same thing with the\n", @@ -316,7 +399,7 @@ "a probability distribution over the parameters as we did with the slack\n", "variables? The answer is yes." ], - "id": "4c3c2336-da6e-42cf-b9a3-ace0c712e332" + "id": "899e2474-30e7-46ea-8e90-6bc7e1072e76" }, { "cell_type": "markdown", @@ -324,7 +407,7 @@ "source": [ "## Underdetermined System" ], - "id": "3ddc6707-3450-4d98-b6dd-67983ea7e8fa" + "id": "39824964-849e-41a4-9943-13233bbbe873" }, { "cell_type": "code", @@ -335,7 +418,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "d99de442-ec46-4a1f-9195-4282ea7c4044" + "id": "d05377d3-e4c0-4210-80c5-c9ed8465e28b" }, { "cell_type": "code", @@ -346,19 +429,19 @@ "nu.display_plots('under_determined_system{samp:0>3}.svg', \n", " directory='./ml', samp=IntSlider(0, 0, 9, 1))" ], - "id": "1f1c96fd-be2b-400f-95d0-a1c9fe27178f" + "id": "285c3d04-54f7-4229-bd9f-1b62cefa231a" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: An underdetermined system can be fit by considering\n", "uncertainty. Multiple solutions are consistent with one specified\n", "point." ], - "id": "049a36f4-c450-4026-a131-77385851a1cb" + "id": "5b048976-d411-41a7-abb0-0bda5b711821" }, { "cell_type": "markdown", @@ -386,10 +469,11 @@ "Unfortunately, this dispute led to a split in the modelling community\n", "that still has echoes today. It is known as the Bayesian vs Frequentist\n", "controversy. From my own perspective, I think that it is a false\n", - "dichotomy, and that the two approaches are actually complementary. My\n", - "own focus research focus is on *modelling* and in that context, the use\n", - "of probability is vital. For frequenstist statisticians, such as Fisher,\n", - "the emphasis was on the value of the evidence in the data for a\n", + "dichotomy, and that the two approaches are actually complementary.\n", + "\n", + "My own focus research focus is on *modelling* and in that context, the\n", + "use of probability is vital. For frequenstist statisticians, such as\n", + "Fisher, the emphasis was on the value of the evidence in the data for a\n", "particular hypothesis. This is known as hypothesis testing. The two\n", "approaches can be unified because one of the most important approaches\n", "to hypothesis testing is to [compute the ratio of the\n", @@ -397,7 +481,7 @@ "the result of applying a probability distribution to the parameters is\n", "merely to arrive at a different form of the likelihood." ], - "id": "df3e80e8-b05e-4a73-ae58-9e62a6fe628d" + "id": "0a4aad5b-9955-4318-b250-9757685cfe89" }, { "cell_type": "markdown", @@ -407,7 +491,7 @@ "\n", "A segment from the lecture in 2012 on philsophical underpinnings." ], - "id": "0be55101-bbfd-4832-be03-18f3704443d7" + "id": "255b684f-0900-4af7-8f4b-88a74b9a3c7e" }, { "cell_type": "code", @@ -418,16 +502,38 @@ "from IPython.lib.display import YouTubeVideo\n", "YouTubeVideo('AvlnFnvFw_0')" ], - "id": "493769ef-3f9d-4aa2-a1e5-584144b871e9" + "id": "9a7ad298-b6f3-4484-aaa7-48123bdce78b" }, { "cell_type": "markdown", "metadata": {}, "source": [ "Figure: The philosophical underpinnings of uncertainty, as discussed\n", - "in 2012 MLAI lecture." + "in 2012 MLAI lecture.
\n", + "\n", + "Our model doesn’t capture the entire system, we use the *noise* model to\n", + "capture aspects of the system we didn’t model explicitly. It gives the\n", + "mismatch between our model’s predictions and the data. The Gaussian\n", + "noise model can be justified by appealing to the central limit theorem,\n", + "but other noise models are also possible (e.g. the Student-$t$\n", + "distribution has heavy tails and deals better with outliers). Maximum\n", + "likelihood with the Gaussian leads to least squares.\n", + "\n", + "When we are being Bayesian we are assuming uncertainty in the\n", + "parameters. This uncertainty comes from e.g. not having seen enough\n", + "data.\n", + "\n", + "The parameter uncertainty can be thought of as *epistemic* uncertainty.\n", + "I.e. uncertainty that arises from lack of knowledge. But the noise model\n", + "uncertainty can be thought of as *aleatoric* uncertainty. It is\n", + "uncertainty that is intrinsic to the system as we’ve modelled it. No\n", + "amount of data will reduce it. Note, that this interpretation implies\n", + "that the split between aleatoric uncertainty and epistemic is a result\n", + "of our modelling choice. Of course, we can make our model more complex\n", + "and reduce the amount of aleatoric uncertainty. See for example the\n", + "bias-variance dilemma." ], - "id": "9af84217-48b9-405c-b9a8-53904de01762" + "id": "1b0487cf-51a5-467b-befb-cc1d4206becf" }, { "cell_type": "markdown", @@ -444,7 +550,7 @@ "- Section 1.2.6 (start from just past eq 1.64 pg 30-32) of Bishop\n", " (2006)" ], - "id": "16fd78ab-2b8e-4473-9d58-ac81835e480b" + "id": "51997183-78cd-406c-8270-be8299e91245" }, { "cell_type": "markdown", @@ -464,7 +570,7 @@ "first made this connection in his volume on *Theoria Motus Corprum\n", "Coelestium* (Gauss, 1809) (written in Latin)" ], - "id": "cca65a00-d1d4-43d7-a897-6545c2971468" + "id": "f09ede7f-e35d-48aa-ad97-83a9bde20f38" }, { "cell_type": "code", @@ -475,7 +581,7 @@ "import notutils as nu\n", "nu.display_google_book(id='ORUOAAAAQAAJ', page='PA213')" ], - "id": "1910c9fb-337a-484e-96d8-7ba4115ef5b8" + "id": "7069c0c6-4255-4bfa-9153-b74bac3971d9" }, { "cell_type": "markdown", @@ -506,7 +612,7 @@ "before 1900](http://www.hup.harvard.edu/catalog.php?isbn=9780674403413)\n", "(Stigler, 1999) has a nice chapter on this." ], - "id": "887e29ba-af26-4a68-9ff0-8dc06bd4416f" + "id": "65766cf9-f1e5-431b-a4e6-884bf5e64ba6" }, { "cell_type": "code", @@ -517,7 +623,7 @@ "import notutils as nu\n", "nu.display_google_book(id='ORUOAAAAQAAJ', page='PA217')" ], - "id": "5c79f5b0-d06b-40b2-9251-99b2199e4a4b" + "id": "b7791904-399d-4472-b8d6-7a4b42fb3e34" }, { "cell_type": "markdown", @@ -532,7 +638,7 @@ "make a claim for priority on the method on page 221 (towards the end of\n", "the first paragraph …)." ], - "id": "67c088f3-4d96-4976-8688-636539aaf180" + "id": "f2a5fc6f-e190-4052-b42e-c8b00ee775e1" }, { "cell_type": "code", @@ -543,7 +649,7 @@ "import notutils as nu\n", "nu.display_google_book(id='ORUOAAAAQAAJ', page='PA221')" ], - "id": "febeb14d-f5d4-44da-b49d-bfc7aeefb6ed" + "id": "06c24af6-fb3f-4325-8ae8-2b5695c8fe7b" }, { "cell_type": "markdown", @@ -553,97 +659,7 @@ "Legendre who published first. Gauss claims he used least squares for his\n", "prediction of the location of Ceres.
" ], - "id": "57da0ddf-6c0c-4b2e-bd5e-eb4181da0f65" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The Bayesian Approach\n", - "\n", - "\\[edit\\]\n", - "\n", - "Now we will study Bayesian approaches to regression. In the Bayesian\n", - "approach we define a *prior* density over our parameters, $m$ and $c$ or\n", - "more generally $\\mathbf{ w}$. This prior distribution gives us a range\n", - "of expected values for our parameter *before* we have seen the data. The\n", - "object in Bayesian inference is to then compute the*posterior* density\n", - "which is the effect on the density of having observed the data. In\n", - "standard probability notation we write the prior distribution as, $$\n", - "p(\\mathbf{ w}),\n", - "$$ so it is the *marginal* distribution for the parameters, i.e. the\n", - "distribution we have for the parameters without any knowledge about the\n", - "data. The posterior distribution is written as, $$\n", - "p(\\mathbf{ w}|\\mathbf{ y}, \\mathbf{X}).\n", - "$$ So the posterior distribution is the *conditional* distribution for\n", - "the parameters given the data (which in this case consists of pairs of\n", - "observations including response variables (or targets), $y_i$, and\n", - "covariates (or inputs) $\\mathbf{ x}_i$. Where here we are allowing the\n", - "inputs to be multivariate.\n", - "\n", - "The posterior is recovered from the prior using *Bayes’ rule*. Which is\n", - "simply a rewriting of the product rule. We can recover Bayes’ rule as\n", - "follows. The product rule of probability tells us that the joint\n", - "distribution is given as the product of the conditional and the\n", - "marginal. Dropping the inputs from our conditioning for the moment we\n", - "have, $$\n", - "p(\\mathbf{ w}, \\mathbf{ y})=p(\\mathbf{ y}|\\mathbf{ w})p(\\mathbf{ w}),\n", - "$$ where we see we have related the joint density to the prior density\n", - "and the *likelihood* from our previous investigation of regression, $$\n", - "p(\\mathbf{ y}|\\mathbf{ w}) = \\prod_{i=1}^n\\mathcal{N}\\left(y_i|\\mathbf{ w}^\\top \\mathbf{ x}_i, \\sigma^2\\right)\n", - "$$ which arises from the assumption that our observation is given by $$\n", - "y_i = \\mathbf{ w}^\\top \\mathbf{ x}_i + \\epsilon_i.\n", - "$$ In other words this is the Gaussian likelihood we have been fitting\n", - "by minimizing the sum of squares. Have a look at [the session on\n", - "multivariate regression](./03-linear-regression.html) as a reminder.\n", - "\n", - "We’ve introduce the likelihood, but we don’t have relationship with the\n", - "posterior, however, the product rule can also be written in the\n", - "following way $$\n", - "p(\\mathbf{ w}, \\mathbf{ y}) = p(\\mathbf{ w}|\\mathbf{ y})p(\\mathbf{ y}),\n", - "$$ where here we have simply used the opposite conditioning. We’ve\n", - "already introduced the *posterior* density above. This is the density\n", - "that represents our belief about the parameters *after* observing the\n", - "data. This is combined with the *marginal likelihood*, sometimes also\n", - "known as the evidence. It is the marginal likelihood, because it is the\n", - "original likelihood of the data with the parameters marginalised,\n", - "$p(\\mathbf{ y})$. Here it’s conditioned on nothing, but in practice you\n", - "should always remember that everything here is conditioned on things\n", - "like model choice: which set of basis functions. Because it’s a\n", - "regression problem, its also conditioned on the inputs. Using the\n", - "equalitybetween the two different forms of the joint density we recover\n", - "$$\n", - "p(\\mathbf{ w}|\\mathbf{ y}) = \\frac{p(\\mathbf{ y}|\\mathbf{ w})p(\\mathbf{ w})}{p(\\mathbf{ y})}\n", - "$$ where we divided both sides by $p(\\mathbf{ y})$ to recover this\n", - "result. Let’s re-introduce the conditioning on the input locations (or\n", - "covariates), $\\mathbf{X}$ to write the full form of Bayes’ rule for the\n", - "regression problem. $$\n", - "p(\\mathbf{ w}|\\mathbf{ y}, \\mathbf{X}) = \\frac{p(\\mathbf{ y}|\\mathbf{ w}, \\mathbf{X})p(\\mathbf{ w})}{p(\\mathbf{ y}|\\mathbf{X})}\n", - "$$ where the posterior density for the parameters given the data is\n", - "$p(\\mathbf{ w}|\\mathbf{ y}, \\mathbf{X})$, the marginal likelihood is\n", - "$p(\\mathbf{ y}|\\mathbf{X})$, the prior density is $p(\\mathbf{ w})$ and\n", - "our original regression likelihood is given by\n", - "$p(\\mathbf{ y}|\\mathbf{ w}, \\mathbf{X})$. It turns out that to compute\n", - "the posterior the only things we need to do are define the prior and the\n", - "likelihood. The other term on the right hand side can be computed by\n", - "*the sum rule*. It is one of the key equations of Bayesian inference,\n", - "the expectation of the likelihood under the prior, this process is known\n", - "as marginalisation, $$\n", - "p(\\mathbf{ y}|\\mathbf{X}) = \\int p(\\mathbf{ y}|\\mathbf{ w},\\mathbf{X})p(\\mathbf{ w}) \\text{d}\\mathbf{ w}\n", - "$$ I like the term marginalisation, and the description of the\n", - "probability as the *marginal likelihood*, because (for me) it somewhat\n", - "has the implication that the variable name has been removed, and\n", - "(perhaps) written in the margin. Marginalisation of a variable goes from\n", - "a likelihood where the variable is in place, to a new likelihood where\n", - "all possible values of that variable (under the prior) have been\n", - "considered and weighted in the integral. This implies that all we need\n", - "for specifying our model is to define the likelihood and the prior. We\n", - "already have our likelihood from our earlier discussion, so our focus\n", - "now turns to the prior density." - ], - "id": "5e093d74-1463-4000-8265-7d1c1120d5d5" + "id": "58b08859-25b7-4259-99d6-8e6bb8c61464" }, { "cell_type": "markdown", @@ -662,10 +678,12 @@ "the modelling treatment does not differentiate between epistemic and\n", "aleatoric uncertainty. For linear regression we could consider the\n", "following Gaussian prior on the intercept parameter,\n", - "$$c \\sim \\mathcal{N}\\left(0,\\alpha_1\\right)$$ where $\\alpha_1$ is the\n", - "variance of the prior distribution, its mean being zero." + "$$c \\sim \\mathscr{N}\\left(0,\\alpha_1\\right)$$ where $\\alpha_1$ is the\n", + "variance of the prior distribution, its mean being zero.\n", + "\n", + "The posterior distribution is found by *Bayes’ rule*." ], - "id": "93e83a98-53f1-4333-b2e4-18f91329d2e8" + "id": "697bc081-c142-4a3f-bd45-1376d44476d5" }, { "cell_type": "markdown", @@ -673,16 +691,25 @@ "source": [ "## Posterior Distribution\n", "\n", + "$$\n", + " p(c|y) = \\frac{p(y|c)p(c)}{p(y)}\n", + " $$ $$\n", + " \\text{posterior} = \\frac{\\text{likelihood}\\times \\text{prior}}{\\text{marginal likelihood}}\n", + " $$\n", + "\n", "The prior distribution is combined with the likelihood of the data given\n", "the parameters $p(y|c)$ to give the posterior via *Bayes’ rule*, $$\n", " p(c|y) = \\frac{p(y|c)p(c)}{p(y)}\n", - " $$ where $p(y)$ is the marginal probability of the data, obtained\n", - "through integration over the joint density, $p(y, c)=p(y|c)p(c)$.\n", - "Overall the equation can be summarized as, $$\n", + " $$\n", + "\n", + "where $p(y)$ is the marginal probability of the data, obtained through\n", + "integration over the joint density, $p(y, c)=p(y|c)p(c)$. Overall the\n", + "equation can be summarized as, \n", + "$$\n", " \\text{posterior} = \\frac{\\text{likelihood}\\times \\text{prior}}{\\text{marginal likelihood}}.\n", " $$" ], - "id": "dfa65d9d-6c6d-43ff-b788-4abd95064b4f" + "id": "1cf9c485-6ab4-4a90-ad40-4d9698c68b00" }, { "cell_type": "code", @@ -692,7 +719,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "508b1fb6-3afd-40e2-9879-48987288c740" + "id": "cd8fa9fd-9178-4b83-81f4-52da1912e166" }, { "cell_type": "code", @@ -702,7 +729,7 @@ "source": [ "plot.bayes_update(diagrams='./ml')" ], - "id": "a1e5b65a-6b64-4321-8b92-f23dbf0d50ce" + "id": "c85599fd-1f01-4fbe-8e2e-1c621e70eb39" }, { "cell_type": "code", @@ -713,7 +740,7 @@ "from ipywidgets import IntSlider\n", "import pods" ], - "id": "d691dbbf-f693-4c15-aa91-2c16ef1b40ab" + "id": "3734e051-6bf6-498b-9846-6ef8a17cfa21" }, { "cell_type": "code", @@ -723,7 +750,7 @@ "source": [ "import notutils as nu" ], - "id": "6e4bcc9f-6770-4c5f-9321-465bb6221a30" + "id": "8e05e1a4-06eb-4523-ad3f-04029f9c241e" }, { "cell_type": "code", @@ -731,17 +758,17 @@ "metadata": {}, "outputs": [], "source": [ - "nu.display_plots('dem_gaussian{stage:0>2}.svg', \n", - " diagrams='./ml', \n", + "nu.display_plots('dem_gaussian{stage:0>3}.svg', \n", + " directory='./ml', \n", " stage=IntSlider(1, 1, 3, 1))" ], - "id": "6c34ba70-b5c3-4bfa-8016-d6e22187214d" + "id": "2f5c5c86-d1d4-4e4b-9c31-2a3b518ce150" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Combining a Gaussian likelihood with a Gaussian prior to form\n", "a Gaussian posterior\n", @@ -766,31 +793,79 @@ "implies that the posterior is also Gaussian, because a normalized\n", "exponentiated quadratic is a Gaussian distribution.[1]\n", "\n", - "$$p(c) = \\frac{1}{\\sqrt{2\\pi\\alpha_1}} \\exp\\left(-\\frac{1}{2\\alpha_1}c^2\\right)$$\n", - "$$p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2) = \\frac{1}{\\left(2\\pi\\sigma^2\\right)^{\\frac{n}{2}}} \\exp\\left(-\\frac{1}{2\\sigma^2}\\sum_{i=1}^n(y_i - mx_i - c)^2\\right)$$\n", + "::: {.cell .markdown}\n", + "\n", + "## Main Trick\n", + "\n", + "\\[edit\\]\n", + "\n", + "$$\n", + "p(c) = \\frac{1}{\\sqrt{2\\pi\\alpha_1}} \\exp\\left(-\\frac{1}{2\\alpha_1}c^2\\right)\n", + "$$\n", "\n", - "$$p(c| \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) = \\frac{p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2)p(c)}{p(\\mathbf{ y}|\\mathbf{ x}, m, \\sigma^2)}$$\n", + "$$\n", + "p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2) = \n", + "\\frac{1}{\\left(2\\pi\\sigma^2\\right)^{\\frac{n}{2}}} \\exp\\left(-\\frac{1}{2\\sigma^2}\\sum_{i=1}^n(y_i - mx_i - c)^2\\right)\n", + "$$\n", "\n", - "$$p(c| \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) = \\frac{p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2)p(c)}{\\int p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2)p(c) \\text{d} c}$$\n", + "$$\n", + "p(c| \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) = \\frac{p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2)p(c)}{p(\\mathbf{ y}|\\mathbf{ x}, m, \\sigma^2)}\n", + "$$\n", "\n", - "$$p(c| \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) \\propto p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2)p(c)$$\n", + "$$\n", + "p(c| \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) = \\frac{p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2)p(c)}{\\int p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2)p(c) \\text{d} c}\n", + "$$\n", "\n", - "$$\\begin{aligned}\n", - " \\log p(c | \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) =&-\\frac{1}{2\\sigma^2} \\sum_{i=1}^n(y_i-c - mx_i)^2-\\frac{1}{2\\alpha_1} c^2 + \\text{const}\\\\\n", - " = &-\\frac{1}{2\\sigma^2}\\sum_{i=1}^n(y_i-mx_i)^2 -\\left(\\frac{n}{2\\sigma^2} + \\frac{1}{2\\alpha_1}\\right)c^2\\\\\n", - " & + c\\frac{\\sum_{i=1}^n(y_i-mx_i)}{\\sigma^2},\n", - " \\end{aligned}$$\n", + "$$\n", + "p(c| \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) \\propto p(\\mathbf{ y}|\\mathbf{ x}, c, m, \\sigma^2)p(c)\n", + "$$\n", "\n", - "complete the square of the quadratic form to obtain\n", - "$$\\log p(c | \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) = -\\frac{1}{2\\tau^2}(c - \\mu)^2 +\\text{const},$$\n", - "where $\\tau^2 = \\left(n\\sigma^{-2} +\\alpha_1^{-1}\\right)^{-1}$ and\n", + "complete the square of the quadratic form to obtain $$\n", + "\\log p(c | \\mathbf{ y}, \\mathbf{ x}, m, \\sigma^2) = -\\frac{1}{2\\tau^2}(c - \\mu)^2 +\\text{const},\n", + "$$ where $\\tau^2 = \\left(n\\sigma^{-2} +\\alpha_1^{-1}\\right)^{-1}$ and\n", "$\\mu = \\frac{\\tau^2}{\\sigma^2} \\sum_{i=1}^n(y_i-mx_i)$.\n", "\n", + "{:::\n", + "\n", "[1] Note not all exponentiated quadratics can be normalized, to do so,\n", "the coefficient associated with the variable squared, $y^2$, must be\n", "strictly positive." ], - "id": "71951601-9689-4217-8c52-cfe14a1d9a55" + "id": "b717c60d-e0dd-40f2-8203-7c801510bfae" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Bayesian Inference in the Univariate Case\n", + "\n", + "This video talks about Bayesian inference across the single parameter,\n", + "the offset $c$, illustrating how the prior and the likelihood combine in\n", + "one dimension to form a posterior." + ], + "id": "f74cf6c8-3574-4e73-83a8-31821ee8a11f" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.lib.display import YouTubeVideo\n", + "YouTubeVideo('AvlnFnvFw_0')" + ], + "id": "29b1a548-b272-4f35-b338-3bda1b7d6015" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Figure: Univariate Bayesian inference. Lecture 10 from 2012 MLAI\n", + "Course." + ], + "id": "e1e6be7d-8254-4cd8-b461-e2f7c85a6b0e" }, { "cell_type": "markdown", @@ -798,16 +873,102 @@ "source": [ "## The Joint Density\n", "\n", + "This gives us the distribution over $c$, but in reality we want to know\n", + "the *joint* posterior density over the parmeters $c$ and $m$. We could\n", + "find this by now considering $m$, but it’s easier to consider the\n", + "multivariate case." + ], + "id": "3fe2d72e-5470-48d9-acab-cf44e671cd8f" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## The Bayesian Approach\n", + "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", - "- Really want to know the *joint* posterior density over the\n", - " parameters $c$ *and* $m$.\n", - "- Could now integrate out over $m$, but it’s easier to consider the\n", - " multivariate case." + "Now we will study Bayesian approaches to regression. In the Bayesian\n", + "approach we define a *prior* density over our parameters, $m$ and $c$ or\n", + "more generally $\\mathbf{ w}$. This prior distribution gives us a range\n", + "of expected values for our parameter *before* we have seen the data. The\n", + "object in Bayesian inference is to then compute the*posterior* density\n", + "which is the effect on the density of having observed the data. In\n", + "standard probability notation we write the prior distribution as, $$\n", + "p(\\mathbf{ w}),\n", + "$$ so it is the *marginal* distribution for the parameters, i.e. the\n", + "distribution we have for the parameters without any knowledge about the\n", + "data. The posterior distribution is written as, $$\n", + "p(\\mathbf{ w}|\\mathbf{ y}, \\mathbf{X}).\n", + "$$ So the posterior distribution is the *conditional* distribution for\n", + "the parameters given the data (which in this case consists of pairs of\n", + "observations including response variables (or targets), $y_i$, and\n", + "covariates (or inputs) $\\mathbf{ x}_i$. Where here we are allowing the\n", + "inputs to be multivariate.\n", + "\n", + "The posterior is recovered from the prior using *Bayes’ rule*. Which is\n", + "simply a rewriting of the product rule. We can recover Bayes’ rule as\n", + "follows. The product rule of probability tells us that the joint\n", + "distribution is given as the product of the conditional and the\n", + "marginal. Dropping the inputs from our conditioning for the moment we\n", + "have, $$\n", + "p(\\mathbf{ w}, \\mathbf{ y})=p(\\mathbf{ y}|\\mathbf{ w})p(\\mathbf{ w}),\n", + "$$ where we see we have related the joint density to the prior density\n", + "and the *likelihood* from our previous investigation of regression, $$\n", + "p(\\mathbf{ y}|\\mathbf{ w}) = \\prod_{i=1}^n\\mathscr{N}\\left(y_i|\\mathbf{ w}^\\top \\mathbf{ x}_i, \\sigma^2\\right)\n", + "$$ which arises from the assumption that our observation is given by $$\n", + "y_i = \\mathbf{ w}^\\top \\mathbf{ x}_i + \\epsilon_i.\n", + "$$ In other words this is the Gaussian likelihood we have been fitting\n", + "by minimizing the sum of squares. Have a look at [the session on\n", + "multivariate regression](./03-linear-regression.html) as a reminder.\n", + "\n", + "We’ve introduce the likelihood, but we don’t have relationship with the\n", + "posterior, however, the product rule can also be written in the\n", + "following way $$\n", + "p(\\mathbf{ w}, \\mathbf{ y}) = p(\\mathbf{ w}|\\mathbf{ y})p(\\mathbf{ y}),\n", + "$$ where here we have simply used the opposite conditioning. We’ve\n", + "already introduced the *posterior* density above. This is the density\n", + "that represents our belief about the parameters *after* observing the\n", + "data. This is combined with the *marginal likelihood*, sometimes also\n", + "known as the evidence. It is the marginal likelihood, because it is the\n", + "original likelihood of the data with the parameters marginalised,\n", + "$p(\\mathbf{ y})$. Here it’s conditioned on nothing, but in practice you\n", + "should always remember that everything here is conditioned on things\n", + "like model choice: which set of basis functions. Because it’s a\n", + "regression problem, its also conditioned on the inputs. Using the\n", + "equalitybetween the two different forms of the joint density we recover\n", + "$$\n", + "p(\\mathbf{ w}|\\mathbf{ y}) = \\frac{p(\\mathbf{ y}|\\mathbf{ w})p(\\mathbf{ w})}{p(\\mathbf{ y})}\n", + "$$ where we divided both sides by $p(\\mathbf{ y})$ to recover this\n", + "result. Let’s re-introduce the conditioning on the input locations (or\n", + "covariates), $\\mathbf{X}$ to write the full form of Bayes’ rule for the\n", + "regression problem. $$\n", + "p(\\mathbf{ w}|\\mathbf{ y}, \\mathbf{X}) = \\frac{p(\\mathbf{ y}|\\mathbf{ w}, \\mathbf{X})p(\\mathbf{ w})}{p(\\mathbf{ y}|\\mathbf{X})}\n", + "$$ where the posterior density for the parameters given the data is\n", + "$p(\\mathbf{ w}|\\mathbf{ y}, \\mathbf{X})$, the marginal likelihood is\n", + "$p(\\mathbf{ y}|\\mathbf{X})$, the prior density is $p(\\mathbf{ w})$ and\n", + "our original regression likelihood is given by\n", + "$p(\\mathbf{ y}|\\mathbf{ w}, \\mathbf{X})$. It turns out that to compute\n", + "the posterior the only things we need to do are define the prior and the\n", + "likelihood. The other term on the right hand side can be computed by\n", + "*the sum rule*. It is one of the key equations of Bayesian inference,\n", + "the expectation of the likelihood under the prior, this process is known\n", + "as marginalisation, $$\n", + "p(\\mathbf{ y}|\\mathbf{X}) = \\int p(\\mathbf{ y}|\\mathbf{ w},\\mathbf{X})p(\\mathbf{ w}) \\text{d}\\mathbf{ w}\n", + "$$ I like the term marginalisation, and the description of the\n", + "probability as the *marginal likelihood*, because (for me) it somewhat\n", + "has the implication that the variable name has been removed, and\n", + "(perhaps) written in the margin. Marginalisation of a variable goes from\n", + "a likelihood where the variable is in place, to a new likelihood where\n", + "all possible values of that variable (under the prior) have been\n", + "considered and weighted in the integral. This implies that all we need\n", + "for specifying our model is to define the likelihood and the prior. We\n", + "already have our likelihood from our earlier discussion, so our focus\n", + "now turns to the prior density." ], - "id": "d9638b08-5df7-4b0b-b788-daf155e7aeb7" + "id": "01f4f51a-a787-4128-b1a5-485135b3e71e" }, { "cell_type": "markdown", @@ -824,14 +985,14 @@ "Gaussian density with mean given by $1.7\\text{m}$ and a standard\n", "deviation of $0.15\\text{m}$, implying a variance of $\\sigma^2=0.0225$,\n", "$$\n", - " p(h) \\sim \\mathcal{N}\\left(1.7,0.0225\\right).\n", + " p(h) \\sim \\mathscr{N}\\left(1.7,0.0225\\right).\n", " $$ Similarly, we assume that weights of the population are distributed\n", "a Gaussian density with a mean of $75 \\text{kg}$ and a standard\n", "deviation of $6 kg$ (implying a variance of 36), $$\n", - " p(w) \\sim \\mathcal{N}\\left(75,36\\right).\n", + " p(w) \\sim \\mathscr{N}\\left(75,36\\right).\n", " $$" ], - "id": "74cbcc68-9585-479b-8cde-3a1c936358e6" + "id": "a2d2f25f-507f-4676-862d-d037108a7dd4" }, { "cell_type": "code", @@ -841,7 +1002,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "f1413578-00ae-4b0a-a08b-a8a06db2178f" + "id": "a0adf441-c09b-4436-859c-ec658d63517a" }, { "cell_type": "code", @@ -851,17 +1012,17 @@ "source": [ "plot.height_weight(diagrams='./ml')" ], - "id": "c8db79b2-099f-4f54-8d41-853b696f16f4" + "id": "4713204e-c017-4ba4-8d25-b10affa11ead" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Gaussian distributions for height and weight." ], - "id": "12a375b4-b661-4ec0-a0cf-b4709ff1c6e1" + "id": "d850a81a-52d5-4f29-8b3c-48906093a281" }, { "cell_type": "markdown", @@ -877,7 +1038,7 @@ " $$ Given this assumption we can sample from the joint distribution by\n", "independently sampling weights and heights." ], - "id": "2a32e8a8-57c7-485f-966a-5423a048ed12" + "id": "a4d0e460-6a1e-45b7-97a4-66fdd769e9f3" }, { "cell_type": "code", @@ -887,7 +1048,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "ca781ad2-4df8-457d-ab1a-c73c0886bb2d" + "id": "fe75cd73-4460-4ae2-b0a3-c196092f065c" }, { "cell_type": "code", @@ -895,10 +1056,14 @@ "metadata": {}, "outputs": [], "source": [ - "plot.independent_height_weight(num_samps=8, \n", + "plot.independent_gaussians_sample(\n", + " mu_x=1.7, var_x=0.0225,\n", + " mu_y=75, var_y=36, num_samps=8,\n", + " xlabel='$h/m$', ylabel='$w/kg$',\n", + " filestub=\"independent_height_weight\",\n", " diagrams='./ml')" ], - "id": "b5e4f6c1-f6ef-4694-8e69-6657fac3f2ad" + "id": "8aa9d758-4ef5-40db-ad1b-5cf7e79cd794" }, { "cell_type": "code", @@ -909,35 +1074,17 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "2c37c4d5-d240-4172-a1d4-e584cfebd69c" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import notutils as nu" - ], - "id": "030ee90b-d4f4-4adb-9e20-5f0f36d92df7" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nu.display_plots('independent_height_weight{fig:0>3}.svg', \n", - " directory='./ml', \n", - " fig=IntSlider(0, 0, 7, 1))" - ], - "id": "45fb7702-a28e-449f-bde5-c7dbd8ab4da3" + "id": "3213e578-af73-47df-b4c6-ede77a8afc30" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + " \n", + "displaycode{nu.display_plots(‘independent_height_weight{fig:0\\>3}.svg’,\n", + "directory=‘./ml’, fig=IntSlider(0, 0, 7, 1))}\n", + "\n", + "\n", "\n", "Figure: Samples from independent Gaussian variables that might\n", "represent heights and weights.\n", @@ -952,7 +1099,7 @@ "$$To deal with this dependence we now introduce the notion of\n", "*correlation* to the multivariate Gaussian density." ], - "id": "de08ae21-893f-4dd8-8bbc-80ea55511104" + "id": "f4d00a1c-f4d6-4b6c-b35f-59db06941d0e" }, { "cell_type": "markdown", @@ -964,7 +1111,7 @@ "style=\"\">\\[edit\\]" ], - "id": "9850acd9-1972-4bcf-bf2c-55e610d4b3ee" + "id": "acbffb76-2c4b-4172-b860-ecdb62aed598" }, { "cell_type": "code", @@ -974,7 +1121,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "2042d4ca-b9e6-4cef-84b5-76a1a88eff5c" + "id": "8ddded48-09b6-4132-ac69-bcbfe2225b03" }, { "cell_type": "code", @@ -982,10 +1129,14 @@ "metadata": {}, "outputs": [], "source": [ - "plot.correlated_height_weight(num_samps=8, \n", + "plot.correlated_gaussians_sample(\n", + " mu_x=1.7, var_x=0.0225,\n", + " mu_y=75, var_y=36, num_samps=8,\n", + " xlabel='$h/m$', ylabel='$w/kg$',\n", + " filestub='correlated_height_weight',\n", " diagrams='./ml')" ], - "id": "91ed563b-1314-4a99-9aea-1d7fe9a8cd0d" + "id": "348fe19f-44fa-4ee6-bb6c-2a8fd7b5907c" }, { "cell_type": "code", @@ -996,7 +1147,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "00172458-88b1-4916-8467-7ff7ceb57f9d" + "id": "8ba12af5-69d5-404e-9a3c-152789aa7b5f" }, { "cell_type": "code", @@ -1008,18 +1159,18 @@ " directory='./ml', \n", " fig=IntSlider(0, 0, 7, 1))" ], - "id": "1725f2a2-fdf4-44be-9001-7c7c069b7e3b" + "id": "5c785687-fbce-4bd4-b523-22944bbd8e70" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Samples from *correlated* Gaussian variables that might\n", "represent heights and weights." ], - "id": "4f1342d2-0778-4aba-b8ff-d7adb2fd755e" + "id": "59d583f3-7e48-431b-b1f9-f0c64680e697" }, { "cell_type": "markdown", @@ -1047,7 +1198,7 @@ "p(\\mathbf{ y}) = \\frac{1}{\\det{2\\pi \\mathbf{D}}^{\\frac{1}{2}}} \\exp\\left(-\\frac{1}{2}(\\mathbf{ y}- \\boldsymbol{ \\mu})^\\top\\mathbf{D}^{-1}(\\mathbf{ y}- \\boldsymbol{ \\mu})\\right)\n", "$$" ], - "id": "e18305a9-55bd-4f54-8f89-1658b27a9956" + "id": "9e80d00e-e37b-424a-9c54-89de8473234c" }, { "cell_type": "markdown", @@ -1068,17 +1219,17 @@ "\n", "$$\n", "p(\\mathbf{ y}) = \\frac{1}{\\det{2\\pi\\mathbf{D}}^{\\frac{1}{2}}} \\exp\\left(-\\frac{1}{2}(\\mathbf{ y}- \\boldsymbol{ \\mu})^\\top\\mathbf{R}\\mathbf{D}^{-1}\\mathbf{R}^\\top(\\mathbf{ y}- \\boldsymbol{ \\mu})\\right)\n", - "$$ this gives a covariance matrix: $$\n", + "$$ this gives an inverse covariance matrix: $$\n", "\\mathbf{C}^{-1} = \\mathbf{R}\\mathbf{D}^{-1} \\mathbf{R}^\\top\n", "$$\n", "\n", "$$\n", "p(\\mathbf{ y}) = \\frac{1}{\\det{2\\pi\\mathbf{C}}^{\\frac{1}{2}}} \\exp\\left(-\\frac{1}{2}(\\mathbf{ y}- \\boldsymbol{ \\mu})^\\top\\mathbf{C}^{-1} (\\mathbf{ y}- \\boldsymbol{ \\mu})\\right)\n", - "$$ this gives a covariance matrix: $$\n", + "$$ $$\n", "\\mathbf{C}= \\mathbf{R}\\mathbf{D} \\mathbf{R}^\\top\n", "$$" ], - "id": "f6a656ce-ff9e-438f-aac7-82996fa41b54" + "id": "adf274f3-79ad-4ea7-8b92-d169e81fcc99" }, { "cell_type": "markdown", @@ -1088,15 +1239,15 @@ "\n", "Let’s assume that the prior density is given by a zero mean Gaussian,\n", "which is independent across each of the parameters, $$\n", - "\\mathbf{ w}\\sim \\mathcal{N}\\left(\\mathbf{0},\\alpha \\mathbf{I}\\right)\n", + "\\mathbf{ w}\\sim \\mathscr{N}\\left(\\mathbf{0},\\alpha \\mathbf{I}\\right)\n", "$$ In other words, we are assuming, for the prior, that each element of\n", "the parameters vector, $w_i$, was drawn from a Gaussian density as\n", "follows $$\n", - "w_i \\sim \\mathcal{N}\\left(0,\\alpha\\right)\n", + "w_i \\sim \\mathscr{N}\\left(0,\\alpha\\right)\n", "$$ Let’s start by assigning the parameter of the prior distribution,\n", "which is the variance of the prior distribution, $\\alpha$." ], - "id": "598e444d-3c33-4e74-8d20-9988e923aa47" + "id": "3326d014-85cd-4d78-a9e9-4285b7c6a86c" }, { "cell_type": "code", @@ -1111,7 +1262,7 @@ "# set the noise variance\n", "sigma2 = 0.01" ], - "id": "2db7c015-a653-4c8e-ae50-f2f7176ac487" + "id": "a767402d-24e6-4495-8a74-dd420dfada8a" }, { "cell_type": "markdown", @@ -1124,7 +1275,7 @@ "\n", "- Section 3.3 up to 159 (pg 152–159) of Bishop (2006)" ], - "id": "e98ecd15-a9e5-477e-931e-b35fce2c2681" + "id": "a134f47c-9c7f-40dc-b864-8b02e289c8eb" }, { "cell_type": "markdown", @@ -1153,7 +1304,19 @@ "for our training data, and for a range of prediction locations\n", "(`x_pred`)." ], - "id": "66fa850a-2774-4c18-bc6b-fcf24e0d81b3" + "id": "8092ef84-c5b9-4e2d-80d9-6b66b7b7f074" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Olympic Marathon Polynomial Sample\n", + "\n", + "\\[edit\\]" + ], + "id": "2cfb28cf-3ab1-4aa5-a381-26a1983e6a62" }, { "cell_type": "code", @@ -1164,7 +1327,7 @@ "import numpy as np\n", "import pods" ], - "id": "c9535a53-156b-486d-9cb9-8679e7fbad6b" + "id": "12bb4fec-5c68-4c03-bcda-d14e8219ce42" }, { "cell_type": "code", @@ -1175,20 +1338,36 @@ "data = pods.datasets.olympic_marathon_men()\n", "x = data['X']\n", "y = data['Y']\n", - "num_data = x.shape[0]\n", - "num_pred_data = 100 # how many points to use for plotting predictions\n", - "x_pred = np.linspace(1890, 2016, num_pred_data)[:, None] # input locations for predictions" + "\n", + "offset = y.mean()\n", + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale\n", + "xlim = (1876,2044)\n", + "\n", + "num_data = x.shape[0]" ], - "id": "2531a5bb-54f2-4f2f-8315-db0c03d8a2b7" + "id": "1f2af2b6-0c70-47b5-9261-971a4f03219e" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "now let’s build the basis matrices. We define the polynomial basis as\n", + "Now let’s build the basis matrices. We define the polynomial basis as\n", "follows." ], - "id": "161d928b-5fdf-4865-bf81-ce23bbbfceaa" + "id": "6a57196a-130c-462c-8a16-550f9767c947" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import polynomial\n", + "import inspect\n", + "file_path = inspect.getfile(polynomial)" + ], + "id": "1f1f5f8b-b39c-4c50-a43d-b5e99a1b605c" }, { "cell_type": "code", @@ -1196,12 +1375,9 @@ "metadata": {}, "outputs": [], "source": [ - "def polynomial(x, num_basis=2, loc=0., scale=1.):\n", - " degree=num_basis-1\n", - " degrees = np.arange(degree+1)\n", - " return ((x-loc)/scale)**degrees" + "%load -s polynomial {file_path}" ], - "id": "8e9ebb01-7b24-40d2-9450-babccbcefa09" + "id": "f0d0e5d7-6ca5-46aa-b30f-0d193d5b46c4" }, { "cell_type": "code", @@ -1211,7 +1387,7 @@ "source": [ "import mlai" ], - "id": "bf6d0511-0276-45dc-9f61-0b2034de9e35" + "id": "0220f2df-0dc9-4b7c-a12c-643967bcf58f" }, { "cell_type": "code", @@ -1219,14 +1395,17 @@ "metadata": {}, "outputs": [], "source": [ - "loc=1950\n", - "scale=1\n", "degree=4\n", - "basis = mlai.Basis(polynomial, number=degree+1, loc=loc, scale=scale)\n", + "alpha = 4\n", + "sigma2 = 0.1\n", + "num_pred_data = 100 # how many points to use for plotting predictions\n", + "x_pred = np.linspace(xlim[0], xlim[1], num_pred_data)[:, None] # input locations for predictions\n", + "data_limits=xlim\n", + "basis = mlai.Basis(polynomial, number=degree+1, data_limits=data_limits)\n", "Phi_pred = basis.Phi(x_pred)\n", "Phi = basis.Phi(x)" ], - "id": "dcaf3e3f-016f-4e1d-8e60-ef0977d8cb20" + "id": "7b196a13-6ee5-45f4-8f17-cafe4436472c" }, { "cell_type": "markdown", @@ -1238,68 +1417,15 @@ "use it to plot a function which is representative of our belief *before*\n", "we fit the data. To do this we are going to use the properties of the\n", "Gaussian density and a sample from a *standard normal* using the\n", - "function `np.random.normal`." - ], - "id": "1fa2a553-4697-454e-a352-a2728666dae2" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Scaling Gaussian-distributed Variables\n", + "function `np.random.normal`.\n", "\n", - "First, let’s consider the case where we have one data point and one\n", - "feature in our basis set. In otherwords $\\mathbf{ f}$ would be a scalar,\n", - "$\\mathbf{ w}$ would be a scalar and $\\boldsymbol{ \\Phi}$ would be a\n", - "scalar. In this case we have $$\n", - "f= \\phi w\n", - "$$ If $w$ is drawn from a normal density, $$\n", - "w\\sim \\mathcal{N}\\left(\\mu_w,c_w\\right)\n", - "$$ and $\\phi$ is a scalar value which we are given, then properties of\n", - "the Gaussian density tell us that $$\n", - "\\phi w\\sim \\mathcal{N}\\left(\\phi\\mu_w,\\phi^2c_w\\right)\n", - "$$ Let’s test this out numerically. First we will draw 200 samples from\n", - "a standard normal," - ], - "id": "115cba6d-c237-4d40-8d41-a72e4f6f5dee" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "w_vec = np.random.normal(size=200)" - ], - "id": "24c9dc1c-d07c-465a-97d6-1a02603a831e" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can compute the mean of these samples and their variance" - ], - "id": "823a636f-992e-41e6-ba83-86dc4b63dc32" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print('w sample mean is ', w_vec.mean())\n", - "print('w sample variance is ', w_vec.var())" - ], - "id": "6743c7ab-42f2-4bc5-bd19-abda8bc05426" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "These are close to zero (the mean) and one (the variance) as you’d\n", - "expect. Now compute the mean and variance of the scaled version," + "Let’s use this way of constructing samples from a Gaussian to check what\n", + "functions look like *a priori*. The process will be as follows. First,\n", + "we sample a random vector $K$ dimensional from `np.random.normal`. Then\n", + "we scale it by $\\sqrt{\\alpha}$ to obtain a prior sample of\n", + "$\\mathbf{ w}$." ], - "id": "96bc6a5f-e478-48e3-a2fc-ba33e4e92253" + "id": "80637ca0-30a5-490d-809e-8c2dabc0a3a5" }, { "cell_type": "code", @@ -1307,30 +1433,21 @@ "metadata": {}, "outputs": [], "source": [ - "phi = 7\n", - "f_vec = phi*w_vec\n", - "print('True mean should be phi*0 = 0.')\n", - "print('True variance should be phi*phi*1 = ', phi*phi)\n", - "print('f sample mean is ', f_vec.mean())\n", - "print('f sample variance is ', f_vec.var())" + "K = degree + 1\n", + "z_vec = np.random.normal(size=K)\n", + "w_sample = z_vec*np.sqrt(alpha)\n", + "print(w_sample)" ], - "id": "5718e05a-fb12-4832-9594-02fb2c27f31b" + "id": "a1818d32-9c52-4112-8045-f03671ddab7b" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "If you increase the number of samples then you will see that the sample\n", - "mean and the sample variance begin to converge towards the true mean and\n", - "the true variance. Obviously adding an offset to a sample from\n", - "`np.random.normal` will change the mean. So if you want to sample from a\n", - "Gaussian with mean `mu` and standard deviation `sigma` one way of doing\n", - "it is to sample from the standard normal and scale and shift the result,\n", - "so to sample a set of $w$ from a Gaussian with mean $\\mu$ and variance\n", - "$\\alpha$, $$w\\sim \\mathcal{N}\\left(\\mu,\\alpha\\right)$$ We can simply\n", - "scale and offset samples from the *standard normal*." + "Now we can combine our sample from the prior with the basis functions to\n", + "create a function," ], - "id": "6c25d6f1-8872-4358-9bca-4fa11cb53d34" + "id": "3c471dda-132c-4185-898c-03292e144260" }, { "cell_type": "code", @@ -1338,28 +1455,9 @@ "metadata": {}, "outputs": [], "source": [ - "mu = 4 # mean of the distribution\n", - "alpha = 2 # variance of the distribution\n", - "w_vec = np.random.normal(size=200)*np.sqrt(alpha) + mu\n", - "print('w sample mean is ', w_vec.mean())\n", - "print('w sample variance is ', w_vec.var())" - ], - "id": "7084cba7-1031-4700-af55-b794a6b62ef5" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here the `np.sqrt` is necesssary because we need to multiply by the\n", - "standard deviation and we specified the variance as `alpha`. So scaling\n", - "and offsetting a Gaussian distributed variable keeps the variable\n", - "Gaussian, but it effects the mean and variance of the resulting\n", - "variable.\n", - "\n", - "To get an idea of the overall shape of the resulting distribution, let’s\n", - "do the same thing with a histogram of the results." + "import numpy as np" ], - "id": "c0f3c883-ddf0-4d73-8c6a-88b02831e305" + "id": "2f1c19c2-15fb-41cf-8290-ae8e977653a9" }, { "cell_type": "code", @@ -1367,9 +1465,9 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline" + "f_sample = Phi_pred@w_sample" ], - "id": "6e3d598a-c806-4e9f-9433-1e515c311fa4" + "id": "ccf7b502-9a7a-4808-a7b4-e1ed6bc1b246" }, { "cell_type": "code", @@ -1378,9 +1476,9 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", - "import mlai.plot as plot" + "import mlai.plot" ], - "id": "391fd182-ac44-4cdb-8d6d-c26f298d1754" + "id": "7689badf-ce8d-4f52-a2df-d4cbb1325419" }, { "cell_type": "code", @@ -1388,39 +1486,46 @@ "metadata": {}, "outputs": [], "source": [ - "# First the standard normal\n", - "z_vec = np.random.normal(size=1000) # by convention, in statistics, z is often used to denote samples from the standard normal\n", - "w_vec = z_vec*np.sqrt(alpha) + mu\n", - "# plot normalized histogram of w, and then normalized histogram of z on top\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", - "ax.hist(w_vec, bins=30, density=True)\n", - "ax.hist(z_vec, bins=30, density=True)\n", - "_ = ax.legend(('$w$', '$z$'))" + "_ = ax.plot(x_pred.flatten(), f_sample.flatten(), 'r-', linewidth=3)\n", + "\n", + "mlai.write_figure(filename='polynomial-prior-sample.svg', directory='./ml')" ], - "id": "82a80101-fd94-44f4-bcb1-3c2fc18d3374" + "id": "18f49994-248e-4872-a993-c97cf0adbcc4" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now re-run this histogram with 100,000 samples and check that the both\n", - "histograms look qualitatively Gaussian." + "\n", + "\n", + "Figure: Prior sample of $\\boldsymbol{ \\Phi}\\mathbf{ w}$ with\n", + "$\\mathbf{ w}$ taken from the prior.\n", + "\n", + "Note that we have carefully scaled our data using `xlim` to ensure that\n", + "the polynomials don’t go too large. These samples should be well\n", + "behaved.\n", + "\n", + "Now we need to recompute the basis functions from above," ], - "id": "e1cc765b-22c7-4a50-bfe4-c723d7ec0e53" + "id": "91ae2be3-1d28-4a05-9f9d-082be5114005" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Sampling from the Prior\n", + "## Mean Output\n", "\n", - "Let’s use this way of constructing samples from a Gaussian to check what\n", - "functions look like *a priori*. The process will be as follows. First,\n", - "we sample a random vector $K$ dimensional from `np.random.normal`. Then\n", - "we scale it by $\\sqrt{\\alpha}$ to obtain a prior sample of\n", - "$\\mathbf{ w}$." + "The prediction function can now be computed. In matrix form, the\n", + "predictions can be computed as $$\n", + "\\mathbf{ f}_s = \\boldsymbol{ \\Phi}\\mathbf{ w}_s.\n", + "$$ This involves a matrix multiplication between a fixed matrix\n", + "$\\boldsymbol{ \\Phi}$ and a vector drawn from a distribution\n", + "$p(\\mathbf{ w})$. Because $\\mathbf{ w}_s$ is drawn from a distribution,\n", + "this imples that $\\mathbf{ f}_s$ should also be drawn from a\n", + "distribution." ], - "id": "b9acb128-8ab6-463b-a8e3-bdf3e2fb2e12" + "id": "fd68f681-d81c-4399-bf9c-377b661cc401" }, { "cell_type": "code", @@ -1428,21 +1533,9 @@ "metadata": {}, "outputs": [], "source": [ - "K = degree + 1\n", - "z_vec = np.random.normal(size=K)\n", - "w_sample = z_vec*np.sqrt(alpha)\n", - "print(w_sample)" - ], - "id": "6e0b529c-97b3-42fa-a863-7cb74bc76ab7" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can combine our sample from the prior with the basis functions to\n", - "create a function," + "import numpy as np" ], - "id": "5e5dc91c-ec7c-48e1-be7f-3b7a938d66e8" + "id": "ea1e4e08-52bc-427a-b30a-25249f8ec95a" }, { "cell_type": "code", @@ -1450,26 +1543,9 @@ "metadata": {}, "outputs": [], "source": [ - "f_sample = np.dot(Phi_pred,w_sample)\n", - "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", - "_ = ax.plot(x_pred.flatten(), f_sample.flatten(), 'r-', linewidth=3)" - ], - "id": "37ad25ba-545d-40ba-b646-d7b546fa050b" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This shows the recurring problem with the polynomial basis (note the\n", - "scale on the left hand side!). Our prior allows relatively large\n", - "coefficients for the basis associated with high polynomial degrees.\n", - "Because we are operating with input values of around 2000, this leads to\n", - "output functions of very high values. The fix we have used for this\n", - "before is to rescale our data before we apply the polynomial basis to\n", - "it. Above, we set the scale of the basis to 1. Here let’s set it to 100\n", - "and try again." + "f_sample = Phi_pred@w_sample" ], - "id": "46008740-bdd8-447f-b362-ccd9de9bc43e" + "id": "bdb8a076-3da7-49d1-878e-072040168657" }, { "cell_type": "code", @@ -1477,20 +1553,10 @@ "metadata": {}, "outputs": [], "source": [ - "scale = 100.\n", - "basis = mlai.Basis(polynomial, number=degree+1, loc=loc, scale=scale)\n", - "Phi_pred = basis.Phi(x_pred)\n", - "Phi = basis.Phi(x)" - ], - "id": "ae7bd5c4-cfb0-4df3-9bf1-ef779989ed83" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we need to recompute the basis functions from above," + "import matplotlib.pyplot as plt\n", + "import mlai.plot" ], - "id": "f54a0d88-aabb-473e-8618-97a4ce350cc3" + "id": "c3b30405-fec1-4d65-bad3-911b46537c5e" }, { "cell_type": "code", @@ -1498,11 +1564,10 @@ "metadata": {}, "outputs": [], "source": [ - "f_sample = np.dot(Phi_pred, w_sample)\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "_ = ax.plot(x_pred.flatten(), f_sample.flatten(), 'r-', linewidth=3)" ], - "id": "d7e7acef-74ef-4774-9df8-7e7cfb487cc1" + "id": "56edecc3-86ed-4361-a2c8-f60238487d58" }, { "cell_type": "markdown", @@ -1511,7 +1576,7 @@ "Now let’s loop through some samples and plot various functions as\n", "samples from this system," ], - "id": "ea7ba934-68ee-4d5f-ab4d-ecfeadab1cde" + "id": "7b6f20dd-678c-460f-b2cf-e03faa975a0c" }, { "cell_type": "code", @@ -1526,29 +1591,28 @@ " z_vec = np.random.normal(size=K)\n", " w_sample = z_vec*np.sqrt(alpha)\n", " f_sample = np.dot(Phi_pred,w_sample)\n", - " _ = ax.plot(x_pred.flatten(), f_sample.flatten(), linewidth=2)" + " _ = ax.plot(x_pred.flatten(), f_sample.flatten(), linewidth=2)\n", + " \n", + "mlai.write_figure(filename='polynomial-prior-samples.svg', directory='./ml')" ], - "id": "ddaf088a-e64a-40db-9a25-9184522edf78" + "id": "6764f27d-2c3b-4991-8572-3acdb2e3b816" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The predictions for the mean output can now be computed. We want the\n", - "expected value of the predictions under the posterior distribution. In\n", - "matrix form, the predictions can be computed as $$\n", - "\\mathbf{ f}= \\boldsymbol{ \\Phi}\\mathbf{ w}.\n", - "$$ This involves a matrix multiplication between a fixed matrix\n", - "$\\boldsymbol{ \\Phi}$ and a vector that is drawn from a distribution\n", - "$\\mathbf{ w}$. Because $\\mathbf{ w}$ is drawn from a distribution, this\n", - "imples that $\\mathbf{ f}$ should also be drawn from a distribution.\n", + "\n", + "\n", + "Figure: Samples of $\\boldsymbol{ \\Phi}\\mathbf{ w}$ with $\\mathbf{ w}$\n", + "taken from the prior.\n", + "\n", "There are two distributions we are interested in though. We have just\n", "been sampling from the *prior* distribution to see what sort of\n", "functions we get *before* looking at the data. In Bayesian inference, we\n", - "need to computer the *posterior* distribution and sample from that\n", + "also need to compute the *posterior* distribution and sample from that\n", "density." ], - "id": "8ea7362c-7099-4bd1-b967-ff6a0801b9a4" + "id": "8cd02c9c-23ba-4a38-987f-852c6a91e63c" }, { "cell_type": "markdown", @@ -1564,7 +1628,7 @@ "lecture we went through the maths that allows us to compute the\n", "posterior distribution for $\\mathbf{ w}$. This distribution is also\n", "Gaussian, $$\n", - "p(\\mathbf{ w}| \\mathbf{ y}, \\mathbf{ x}, \\sigma^2) = \\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu}_w,\\mathbf{C}_w\\right)\n", + "p(\\mathbf{ w}| \\mathbf{ y}, \\mathbf{ x}, \\sigma^2) = \\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu}_w,\\mathbf{C}_w\\right)\n", "$$ with covariance, $\\mathbf{C}_w$, given by $$\n", "\\mathbf{C}_w= \\left(\\sigma^{-2}\\boldsymbol{ \\Phi}^\\top \\boldsymbol{ \\Phi}+ \\alpha^{-1}\\mathbf{I}\\right)^{-1}\n", "$$ whilst the mean is given by $$\n", @@ -1587,39 +1651,7 @@ "updated distribution includes information from the data set. First we\n", "need to compute the posterior mean and *covariance*." ], - "id": "ea1e590e-95f9-42ee-ade5-dab42722deb1" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Bayesian Inference in the Univariate Case\n", - "\n", - "This video talks about Bayesian inference across the single parameter,\n", - "the offset $c$, illustrating how the prior and the likelihood combine in\n", - "one dimension to form a posterior." - ], - "id": "5b771b7b-ed0b-4839-92d4-61549a8acdd6" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from IPython.lib.display import YouTubeVideo\n", - "YouTubeVideo('AvlnFnvFw_0')" - ], - "id": "82cdce7a-946b-447c-8bd4-ba1985121084" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Figure: Univariate Bayesian inference. Lecture 10 from 2012 MLAI\n", - "Course." - ], - "id": "5a2d4856-cacd-49f8-becb-c9ac6f3a8320" + "id": "80ed0b8d-904c-4326-9425-dc1266fbe215" }, { "cell_type": "markdown", @@ -1632,7 +1664,7 @@ "multivariate Gaussian and how to complete the square in the linear\n", "algebra as we managed below." ], - "id": "be279523-4d9e-4578-b20b-5048076b82f1" + "id": "959130f4-8f34-4fc0-9d71-600ec186bb71" }, { "cell_type": "code", @@ -1643,7 +1675,7 @@ "from IPython.lib.display import YouTubeVideo\n", "YouTubeVideo('Os1iqgpelPw')" ], - "id": "aa175b06-22f8-4493-96d9-0d662330ae43" + "id": "aafbb2dd-8e48-4aa9-99d4-fdb136898edd" }, { "cell_type": "markdown", @@ -1659,7 +1691,7 @@ "\\boldsymbol{ \\mu}_w = \\mathbf{C}_w\\sigma^{-2}\\boldsymbol{ \\Phi}^\\top \\mathbf{ y}.\n", "$$" ], - "id": "ad927fd4-6528-409a-a1b2-7b1c5b830b76" + "id": "389ba327-94dc-4a0a-911e-88d777df5cd5" }, { "cell_type": "markdown", @@ -1672,7 +1704,7 @@ "the training data. Call the resulting variable `w_mean`. Assume that\n", "$\\sigma^2 = 0.01$" ], - "id": "924ca6d9-c934-4b9a-b083-6a9cf9415e41" + "id": "ee03f75f-2f73-4983-aad3-a65e9cd72fda" }, { "cell_type": "code", @@ -1689,7 +1721,7 @@ "\n", "\n" ], - "id": "3486bf30-b83b-41f7-a44f-0c1385dc2da5" + "id": "f2aec038-de6b-489f-ab27-1714de70a4db" }, { "cell_type": "markdown", @@ -1701,31 +1733,41 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", "- Gold medal times for Olympic Marathon since 1896.\n", "- Marathons before 1924 didn’t have a standardized distance.\n", "- Present results using pace per km.\n", "- In 1904 Marathon was badly organized leading to very slow times.\n", "\n", - "\n", - "\n", - "\n", - "Image from Wikimedia Commons \n", - "\n", - "
\n", + "\n", + "Image from [Wikimedia\n", + "Commons](https://commons.wikimedia.org/wiki/File:Eliud_Kipchoge_in_Berlin_-_2015_(cropped).jpg)\n", "\n", - "The first thing we will do is load a standard data set for regression\n", + "The Olympic Marathon data is a standard dataset for regression\n", "modelling. The data consists of the pace of Olympic Gold Medal Marathon\n", "winners for the Olympics from 1896 to present. Let’s load in the data\n", "and plot." ], - "id": "f09b10a5-211a-4c78-9c56-18c7ff13ca95" + "id": "0262c52f-d1be-4b6e-b1a0-32ef76f7c429" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "7d3fc7be-41f1-41b5-8ffc-af7fffe4ac91" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "f48c5181-bb8e-448a-8976-0be4e4b37680" }, { "cell_type": "code", @@ -1736,7 +1778,7 @@ "import numpy as np\n", "import pods" ], - "id": "52c48e7c-5fc9-4a84-a680-34140df61868" + "id": "0dd0ccdc-e706-4fc6-a86a-86e663f954e6" }, { "cell_type": "code", @@ -1752,7 +1794,7 @@ "scale = np.sqrt(y.var())\n", "yhat = (y - offset)/scale" ], - "id": "88ab1265-a147-4ca1-ba6d-aabc370a2844" + "id": "2ccc9947-611f-49ad-8998-d5527538783d" }, { "cell_type": "code", @@ -1764,7 +1806,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "235cbf07-9d0f-410b-9555-7ecb51bc5063" + "id": "cd3c89bc-6e17-4e5d-84b9-8caf3a7d6245" }, { "cell_type": "code", @@ -1773,7 +1815,7 @@ "outputs": [], "source": [ "\n", - "xlim = (1875,2030)\n", + "xlim = (1876,2044)\n", "ylim = (2.5, 6.5)\n", "\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", @@ -1786,13 +1828,13 @@ "mlai.write_figure(filename='olympic-marathon.svg', \n", " directory='./datasets')" ], - "id": "231e0e6e-0ae4-40ab-8d3d-cdd666185b16" + "id": "41927040-d9f9-4749-b860-4a3e00e7740e" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Olympic marathon pace times since 1896.\n", "\n", @@ -1802,7 +1844,7 @@ "participants got lost, and only very few participants completed. More\n", "recent years see more consistently quick marathons." ], - "id": "8bfa18cc-84e3-41c3-bc84-72cf959825f6" + "id": "d2a5c5ba-b5b5-4095-8a44-14bbc29c430e" }, { "cell_type": "markdown", @@ -1817,7 +1859,7 @@ "Five fold cross validation tests the ability of the model to\n", "*interpolate*." ], - "id": "65d85f1f-2d07-4fa8-850e-dc745165cd24" + "id": "0bb579a0-1b9c-474f-9275-b1711c315b40" }, { "cell_type": "code", @@ -1828,7 +1870,7 @@ "import mlai\n", "import pods" ], - "id": "d571e637-8d7d-4956-ad12-ae4b87721ca7" + "id": "97c9a8c6-59a2-4155-a562-3bab43228a17" }, { "cell_type": "code", @@ -1836,12 +1878,10 @@ "metadata": {}, "outputs": [], "source": [ - "data_limits = (1888, 2020)\n", "basis = mlai.Basis(mlai.polynomial, number=1, data_limits=data_limits)\n", - "\n", "max_basis = y.shape[0]" ], - "id": "82f56e1a-3284-40b0-a9d7-3dd1af91ba21" + "id": "c8e6357b-bf59-4166-b249-c4551429ede4" }, { "cell_type": "code", @@ -1851,7 +1891,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "6eba21e7-450c-4158-af2c-7ce5e50f8836" + "id": "9ae4528a-4693-40f8-b852-cfaae6db6d01" }, { "cell_type": "code", @@ -1864,12 +1904,11 @@ " basis=basis, \n", " alpha=1, \n", " sigma2=0.04, \n", - " data_limits=data_limits,\n", - " xlim=data_limits, \n", - " objective_ylim=[0.5,1.6]\n", + " xlim=xlim, \n", + " objective_ylim=[0.1,0.8],\n", " diagrams='./ml')" ], - "id": "e7e2cffe-d30b-4f29-b0b7-00455e3a8b51" + "id": "0453b9bf-8f5f-4ccf-92a6-7bbdbcb95170" }, { "cell_type": "code", @@ -1880,7 +1919,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "05921002-b66a-48b9-bc87-4f19c821cf41" + "id": "65678f70-8307-4bee-bd79-cd7fccea8bd4" }, { "cell_type": "code", @@ -1890,7 +1929,7 @@ "source": [ "import notutils as nu" ], - "id": "28c975c2-04c9-42d6-aa7b-188694008c04" + "id": "963c0f72-28c3-4354-92a8-6f6b459fafe1" }, { "cell_type": "code", @@ -1900,20 +1939,20 @@ "source": [ "nu.display_plots('olympic_BLM_polynomial_number{num_basis:0>3}.svg', \n", " directory='./ml/', \n", - " num_basis=IntSlider(1, 1, 27, 1))" + " num_basis=IntSlider(1, 1, max_basis, 1))" ], - "id": "0ac994c6-c517-477c-b3fc-078fb0bfadac" + "id": "32c62f2e-77c6-4fbe-b176-d8fab4f0636d" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Bayesian fit with 26th degree polynomial and negative\n", "marginal log likelihood." ], - "id": "855f17a7-bbe5-4f71-b87c-d0bae8301488" + "id": "91390419-4789-416f-91f5-bf462574ac61" }, { "cell_type": "markdown", @@ -1925,7 +1964,7 @@ "we are holding out some of the most recent points. This tests the abilit\n", "of our model to *extrapolate*." ], - "id": "a8a2540c-c5b7-44cd-b07c-acf9055bd25a" + "id": "7a2bc295-20a8-4398-aa18-dbfec0679bb3" }, { "cell_type": "code", @@ -1935,7 +1974,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "7d844ed4-5319-4187-adf2-70b14bdb98e4" + "id": "c1200370-4338-491d-8b0d-4ad9b50f49b2" }, { "cell_type": "code", @@ -1943,7 +1982,7 @@ "metadata": {}, "outputs": [], "source": [ - "plot.holdout_fit(x, y, param_name='number', param_range=(1, 27),\n", + "plot.holdout_fit(x, y, param_name='number', param_range=(1, max_basis+1),\n", " diagrams='./ml',\n", " model=mlai.BLM, \n", " basis=basis, \n", @@ -1953,7 +1992,7 @@ " objective_ylim=[0.1,0.6], \n", " permute=False)" ], - "id": "99ea9585-dc3e-468a-b0f4-c856cd4c0abf" + "id": "2a4055e7-c1da-409d-aa9e-2b00fc5aed25" }, { "cell_type": "code", @@ -1964,7 +2003,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "d22d0f63-6699-4ad3-bd5f-6f8f01521a63" + "id": "4a36ea36-442d-4e8b-bfe0-19835854b20c" }, { "cell_type": "code", @@ -1974,7 +2013,7 @@ "source": [ "import notutils as nu" ], - "id": "9346d116-8be1-486f-bfed-2e58a4c5bf9d" + "id": "96d58ef1-b924-4ccb-859f-b44267a1dc51" }, { "cell_type": "code", @@ -1984,20 +2023,20 @@ "source": [ "nu.display_plots('olympic_val_BLM_polynomial_number{num_basis:0>3}.svg', \n", " directory='./ml', \n", - " num_basis=IntSlider(1, 1, 27, 1))" + " num_basis=IntSlider(1, 1, max_basis, 1))" ], - "id": "eb561326-5056-446f-951e-0bf3d617c669" + "id": "72aae0ad-24f6-4255-a5c9-d4d358cf2e9b" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", - "Figure: Bayesian fit with 26th degree polynomial and hold out\n", + "Figure: Bayesian fit with 29th degree polynomial and hold out\n", "validation scores." ], - "id": "e9392113-55da-460a-8e1a-6f808ab0a974" + "id": "fd4a1901-5a67-45b5-af79-f0175e2ebbdc" }, { "cell_type": "markdown", @@ -2008,7 +2047,7 @@ "Five fold cross validation tests the ability of the model to\n", "*interpolate*." ], - "id": "2f4de074-aecf-42c4-a93e-f4d05788aabc" + "id": "f7adda4d-c57f-494a-92b8-8431903b9317" }, { "cell_type": "code", @@ -2017,7 +2056,7 @@ "outputs": [], "source": [ "num_parts=5\n", - "plot.cv_fit(x, y, param_name='number', param_range=(1, 27), \n", + "plot.cv_fit(x, y, param_name='number', param_range=(1, max_basis+1), \n", " diagrams='./ml',\n", " model=mlai.BLM, \n", " basis=basis, \n", @@ -2027,7 +2066,7 @@ " objective_ylim=[0.2,0.6], \n", " num_parts=num_parts)" ], - "id": "71322a8d-3ae2-4264-8472-239d228e8adf" + "id": "18bb25d7-4013-48a6-a8c3-6c26ad97fd1d" }, { "cell_type": "code", @@ -2038,7 +2077,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "3dee3b91-5bca-427f-a8dc-bc769c6d6cec" + "id": "a607d89c-7592-4772-8f94-fdf4b9412de8" }, { "cell_type": "code", @@ -2048,7 +2087,7 @@ "source": [ "import notutils as nu" ], - "id": "d1a79492-d8be-47ae-a9ab-ca2a2903f8d4" + "id": "4427c221-f139-4f5f-a2df-ebadabea055d" }, { "cell_type": "code", @@ -2059,20 +2098,20 @@ "nu.display_plots('olympic_5cv{part:0>2}_BLM_polynomial_number{num_basis:0>3}.svg', \n", " directory='./ml', \n", " part=(0, 5), \n", - " num_basis=IntSlider(1, 1, 27, 1))" + " num_basis=IntSlider(1, 1, max_basis, 1))" ], - "id": "0855d0a2-6377-4c30-b21f-54909dc97887" + "id": "21071d59-233f-4d67-a16e-ea0ee86d5570" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", - "Figure: Bayesian fit with 26th degree polynomial and five fold cross\n", + "Figure: Bayesian fit with 29th degree polynomial and five fold cross\n", "validation scores." ], - "id": "d57bbe2d-96b9-4f5f-af42-6022ab4128cd" + "id": "e66e611b-edea-4dba-9d6f-faa189297e66" }, { "cell_type": "markdown", @@ -2084,15 +2123,25 @@ "style=\"\">\\[edit\\]\n", "\n", - "Before we were able to sample the prior values for the mean\n", - "*independently* from a Gaussian using `np.random.normal` and scaling the\n", - "result. However, observing the data *correlates* the parameters. Recall\n", - "this from the first lab where we had a correlation between the offset,\n", - "$c$ and the slope $m$ which caused such problems with the coordinate\n", - "ascent algorithm. We need to sample from a *correlated* Gaussian. For\n", - "this we can use `np.random.multivariate_normal`." + "Sample the prior values for the mean *independently* from a Gaussian\n", + "using `np.random.normal` and scaling the result. However, observing the\n", + "data *correlates* the parameters. Recall this from the first lab where\n", + "we had a correlation between the offset, $c$ and the slope $m$ which\n", + "caused such problems with the coordinate ascent algorithm. We need to\n", + "sample from a *correlated* Gaussian. For this we can use\n", + "`np.random.multivariate_normal`." + ], + "id": "0838c4fd-8593-4f55-990b-53d1bff9dc8b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" ], - "id": "91903261-c143-4e4d-a537-7f928d0e7a90" + "id": "afc688ba-ca7a-4d75-9f8c-da0fc219fd5c" }, { "cell_type": "code", @@ -2101,12 +2150,33 @@ "outputs": [], "source": [ "w_sample = np.random.multivariate_normal(w_mean.flatten(), w_cov)\n", - "f_sample = np.dot(Phi_pred,w_sample)\n", + "f_sample = np.dot(Phi_pred,w_sample)" + ], + "id": "12264d32-b6c1-4d19-92ad-8845f65d543e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import mlai.plot" + ], + "id": "3724f56b-6a80-4119-9fce-b25b5267cb8e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "ax.plot(x_pred.flatten(), f_sample.flatten(), 'r-', linewidth=3)\n", "ax.plot(x, y, 'r.', markersize=10) # plot data to show fit." ], - "id": "93d529f1-94d4-4b83-ba34-739446e53996" + "id": "5c46736a-4f0c-49bc-9cd6-0c1f95df2d31" }, { "cell_type": "markdown", @@ -2115,7 +2185,25 @@ "Now let’s sample several functions and plot them all to see how the\n", "predictions fluctuate." ], - "id": "374088cd-779c-40a9-9da9-1479b2c1cdae" + "id": "b73ce2dd-16fb-4bb7-8267-26b731aca9e2" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt" + ], + "id": "950ea837-09c0-4b70-9549-d527b7ad3987" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "import mlai.plot import mlai" + ], + "id": "fab081bb-7bf2-4999-a47a-3ac7878d7587" }, { "cell_type": "code", @@ -2128,14 +2216,20 @@ " w_sample = np.random.multivariate_normal(w_mean.flatten(), w_cov)\n", " f_sample = np.dot(Phi_pred,w_sample)\n", " ax.plot(x_pred.flatten(), f_sample.flatten(), linewidth=2)\n", - "ax.plot(x, y, 'r.', markersize=10) # plot data to show fit." + "ax.plot(x, y, 'r.', markersize=10) # plot data to show fit.\n", + "mlai.write_figure(filename='polynomial-function-samples.svg', \n", + " directory='./ml')" ], - "id": "76577036-1b43-4493-a3e7-589632493c97" + "id": "6662eea1-8d61-4191-ae24-d5db51c082ce" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "\n", + "\n", + "Figure: Samples from a prior distribution over polynomials.\n", + "\n", "This gives us an idea of what our predictions are. These are the\n", "predictions that are consistent with data and our prior. Try plotting\n", "different numbers of predictions. You can also try plotting beyond the\n", @@ -2149,7 +2243,7 @@ "Fortunately, in the Gaussian case, we can use properties of multivariate\n", "Gaussians to compute both the mean and the variance of these samples." ], - "id": "b205812f-275a-464c-82f1-8a974b34b111" + "id": "24cd2a82-eb0d-47f3-ac12-ec57f76d5660" }, { "cell_type": "markdown", @@ -2162,7 +2256,7 @@ "of any Gaussian distributed set of random variables also turns out to be\n", "Gaussian distributed. This property is much rarer than you might expect." ], - "id": "535aee8c-a045-4c98-aaa7-de9709b3e364" + "id": "032c639a-4e85-4012-84d9-9b90a5e68a91" }, { "cell_type": "markdown", @@ -2173,17 +2267,17 @@ "The sum of Gaussian random variables is also Gaussian, so if we have a\n", "random variable $y_i$ drawn from a Gaussian density with mean $\\mu_i$\n", "and variance $\\sigma^2_i$, $$\n", - "y_i \\sim \\mathcal{N}\\left(\\mu_i,\\sigma^2_i\\right)\n", + "y_i \\sim \\mathscr{N}\\left(\\mu_i,\\sigma^2_i\\right)\n", "$$ Then the sum of $K$ independently sampled values of $y_i$ will be\n", "drawn from a Gaussian with mean $\\sum_{i=1}^K \\mu_i$ and variance\n", "$\\sum_{i=1}^K \\sigma_i^2$, $$\n", - "\\sum_{i=1}^K y_i \\sim \\mathcal{N}\\left(\\sum_{i=1}^K \\mu_i,\\sum_{i=1}^K \\sigma_i^2\\right).\n", + "\\sum_{i=1}^K y_i \\sim \\mathscr{N}\\left(\\sum_{i=1}^K \\mu_i,\\sum_{i=1}^K \\sigma_i^2\\right).\n", "$$ Let’s try that experimentally. First let’s generate a vector of\n", "samples from a standard normal distribution,\n", - "$z \\sim \\mathcal{N}\\left(0,1\\right)$, then we will scale and offset\n", + "$z \\sim \\mathscr{N}\\left(0,1\\right)$, then we will scale and offset\n", "them, then keep adding them into a vector `y_vec`." ], - "id": "9dd9e32b-12f8-4706-9598-73ca50087a4c" + "id": "46839944-7562-4de5-b0d8-0658b324f04e" }, { "cell_type": "markdown", @@ -2191,7 +2285,7 @@ "source": [ "## Sampling from Gaussians and Summing Up" ], - "id": "2da79577-67de-40b0-adad-6be45b8d0bd4" + "id": "f1a52a6d-deb3-4fa1-8bc6-681b8daad41d" }, { "cell_type": "code", @@ -2213,7 +2307,7 @@ "print('True mean should be ', mus.sum())\n", "print('True variance should be ', (sigmas**2).sum(), ' standard deviation ', np.sqrt((sigmas**2).sum())) " ], - "id": "fe30c50e-6e48-4570-a334-0d08e8dd4399" + "id": "e01fda95-4851-423b-9e43-01e1a1f40673" }, { "cell_type": "markdown", @@ -2221,7 +2315,7 @@ "source": [ "Of course, we can histogram `y_vec` as well." ], - "id": "ea54c225-cf3e-470a-8b98-0c834dbf9b17" + "id": "6d8db758-a6d2-4b17-be94-f4c03832fec7" }, { "cell_type": "code", @@ -2230,10 +2324,10 @@ "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", - "ax.hist(y_vec, bins=30, normed=True)\n", + "ax.hist(y_vec, bins=30, density=True)\n", "ax.legend('$y$')" ], - "id": "c04e1ebd-997e-4acd-acf4-aaec0ef95743" + "id": "353e146e-c91f-4c5d-b70e-cbffbfd94d55" }, { "cell_type": "markdown", @@ -2257,51 +2351,50 @@ "$$ and if we now say that $w_i$ is Gaussian distributed, then because a\n", "scaled Gaussian is also Gaussian, and because a sum of Gaussians is also\n", "Gaussian, we know that $f_i$ is also Gaussian distributed. It merely\n", - "remains to work out its mean and covariance. We can do this by looking\n", - "at the expectation under a Gaussian distribution. The expectation of the\n", - "mean vector is given by $$\n", - "\\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\int \\mathbf{ f}\n", - "\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)\n", + "remains to work out its mean and covariance.\n", + "\n", + "We can do this by looking at the expectation under a Gaussian\n", + "distribution. The expectation of the mean vector is given by $$\n", + "\\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\int \\mathbf{ f}\n", + "\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)\n", "\\text{d}\\mathbf{ w}= \\int \\boldsymbol{ \\Phi}\\mathbf{ w}\n", - "\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)\n", + "\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)\n", "\\text{d}\\mathbf{ w}= \\boldsymbol{ \\Phi}\\int \\mathbf{ w}\n", - "\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)\n", + "\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)\n", "\\text{d}\\mathbf{ w}= \\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}\n", - "$$\n", - "\n", - "Which is straightforward. The expectation of\n", + "$$ Which is straightforward. The expectation of\n", "$\\mathbf{ f}=\\boldsymbol{ \\Phi}\\mathbf{ w}$ under the Gaussian\n", "distribution for $\\mathbf{ f}$ is simply\n", "$\\mathbf{ f}=\\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}$, where\n", "$\\boldsymbol{ \\mu}$ is the *mean* of the Gaussian density for\n", "$\\mathbf{ w}$. Because our prior distribution was Gaussian with zero\n", "mean, the expectation under the prior is given by $$\n", - "\\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\mathbf{0},\\alpha\\mathbf{I}\\right)} = \\mathbf{0}\n", + "\\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\mathbf{0},\\alpha\\mathbf{I}\\right)} = \\mathbf{0}\n", "$$\n", "\n", "The covariance is a little more complicated. A covariance matrix is\n", "defined as $$\n", - "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}\n", - "= \\left\\langle\\mathbf{ f}\\mathbf{ f}^\\top\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}\n", - "- \\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}\\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}^\\top\n", + "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}\n", + "= \\left\\langle\\mathbf{ f}\\mathbf{ f}^\\top\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}\n", + "- \\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}\\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}^\\top\n", "$$ we’ve already computed\n", - "$\\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}=\\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}$\n", + "$\\left\\langle\\mathbf{ f}\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)}=\\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}$\n", "so we can substitute that in to recover $$\n", - "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\left\\langle\\mathbf{ f}\\mathbf{ f}^\\top\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} - \\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}\\boldsymbol{ \\mu}^\\top \\boldsymbol{ \\Phi}^\\top\n", + "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\left\\langle\\mathbf{ f}\\mathbf{ f}^\\top\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} - \\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}\\boldsymbol{ \\mu}^\\top \\boldsymbol{ \\Phi}^\\top\n", "$$\n", "\n", "So we need the expectation of $\\mathbf{ f}\\mathbf{ f}^\\top$.\n", "Substituting in $\\mathbf{ f}= \\boldsymbol{ \\Phi}\\mathbf{ w}$ we have $$\n", - "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\left\\langle\\boldsymbol{ \\Phi}\\mathbf{ w}\\mathbf{ w}^\\top \\boldsymbol{ \\Phi}^\\top\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} - \\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}\\boldsymbol{ \\mu}^\\top \\boldsymbol{ \\Phi}^\\top\n", + "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\left\\langle\\boldsymbol{ \\Phi}\\mathbf{ w}\\mathbf{ w}^\\top \\boldsymbol{ \\Phi}^\\top\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} - \\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}\\boldsymbol{ \\mu}^\\top \\boldsymbol{ \\Phi}^\\top\n", "$$ $$\n", - "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\boldsymbol{ \\Phi}\\left\\langle\\mathbf{ w}\\mathbf{ w}^\\top\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} \\boldsymbol{ \\Phi}^\\top - \\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}\\boldsymbol{ \\mu}^\\top\\boldsymbol{ \\Phi}^\\top\n", + "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\boldsymbol{ \\Phi}\\left\\langle\\mathbf{ w}\\mathbf{ w}^\\top\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} \\boldsymbol{ \\Phi}^\\top - \\boldsymbol{ \\Phi}\\boldsymbol{ \\mu}\\boldsymbol{ \\mu}^\\top\\boldsymbol{ \\Phi}^\\top\n", "$$ Which is dependent on the second moment of the Gaussian, $$\n", - "\\left\\langle\\mathbf{ w}\\mathbf{ w}^\\top\\right\\rangle_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\mathbf{C}+ \\boldsymbol{ \\mu}\\boldsymbol{ \\mu}^\\top\n", + "\\left\\langle\\mathbf{ w}\\mathbf{ w}^\\top\\right\\rangle_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\mathbf{C}+ \\boldsymbol{ \\mu}\\boldsymbol{ \\mu}^\\top\n", "$$ that can be substituted in to recover, $$\n", - "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\boldsymbol{ \\Phi}\\mathbf{C}\\boldsymbol{ \\Phi}^\\top\n", + "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\boldsymbol{ \\Phi}\\mathbf{C}\\boldsymbol{ \\Phi}^\\top\n", "$$ so in the case of the prior distribution, where we have\n", "$\\mathbf{C}= \\alpha \\mathbf{I}$ we can write $$\n", - "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathcal{N}\\left(\\mathbf{ w}|\\mathbf{0},\\alpha \\mathbf{I}\\right)} = \\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top\n", + "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathscr{N}\\left(\\mathbf{ w}|\\mathbf{0},\\alpha \\mathbf{I}\\right)} = \\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top\n", "$$\n", "\n", "This implies that the prior we have suggested for $\\mathbf{ w}$, which\n", @@ -2314,22 +2407,22 @@ "\\mathbf{ y}= \\mathbf{ f}+ \\boldsymbol{ \\epsilon}\n", "$$ where the noise, $\\boldsymbol{ \\epsilon}$, is sampled from a Gaussian\n", "density:\n", - "$\\boldsymbol{ \\epsilon}\\sim \\mathcal{N}\\left(\\mathbf{0},\\sigma^2\\mathbf{I}\\right)$.\n", + "$\\boldsymbol{ \\epsilon}\\sim \\mathscr{N}\\left(\\mathbf{0},\\sigma^2\\mathbf{I}\\right)$.\n", "So, in other words, we are taking a Gaussian distributed random value\n", "$\\mathbf{ f}$, $$\n", - "\\mathbf{ f}\\sim \\mathcal{N}\\left(\\mathbf{0},\\alpha\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top\\right)\n", + "\\mathbf{ f}\\sim \\mathscr{N}\\left(\\mathbf{0},\\alpha\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top\\right)\n", "$$ and adding to it another Gaussian distributed value,\n", - "$\\boldsymbol{ \\epsilon}\\sim \\mathcal{N}\\left(\\mathbf{0},\\sigma^2\\mathbf{I}\\right)$,\n", + "$\\boldsymbol{ \\epsilon}\\sim \\mathscr{N}\\left(\\mathbf{0},\\sigma^2\\mathbf{I}\\right)$,\n", "to form our data observations, $\\mathbf{ y}$. Once again the sum of two\n", "(multivariate) Gaussian distributed variables is also Gaussian, with a\n", "mean given by the sum of the means (both zero in this case) and the\n", "covariance given by the sum of the covariances. So we now have that the\n", "marginal likelihood for the data, $p(\\mathbf{ y})$ is given by $$\n", - "p(\\mathbf{ y}) = \\mathcal{N}\\left(\\mathbf{ y}|\\mathbf{0},\\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top + \\sigma^2\\mathbf{I}\\right)\n", + "p(\\mathbf{ y}) = \\mathscr{N}\\left(\\mathbf{ y}|\\mathbf{0},\\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top + \\sigma^2\\mathbf{I}\\right)\n", "$$ This is our *implicit* assumption for $\\mathbf{ y}$ given our prior\n", "assumption for $\\mathbf{ w}$." ], - "id": "202956b6-c51e-4d86-8e8a-53c4453ca6aa" + "id": "bf6fd447-ebe6-41e9-9516-17de8db58f9b" }, { "cell_type": "markdown", @@ -2341,15 +2434,14 @@ "style=\"\">\\[edit\\]\n", "\n", - "- The marginal likelihood can also be computed, it has the form: $$\n", - " p(\\mathbf{ y}|\\mathbf{X}, \\sigma^2, \\alpha) = \\frac{1}{(2\\pi)^\\frac{n}{2}\\left|\\mathbf{K}\\right|^\\frac{1}{2}} \\exp\\left(-\\frac{1}{2} \\mathbf{ y}^\\top \\mathbf{K}^{-1} \\mathbf{ y}\\right)\n", - " $$ where\n", - " $\\mathbf{K}= \\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top + \\sigma^2 \\mathbf{I}$.\n", - "\n", - "- So it is a zero mean $n$-dimensional Gaussian with covariance matrix\n", - " $\\mathbf{K}$." + "The marginal likelihood can also be computed, it has the form $$\n", + " p(\\mathbf{ y}|\\mathbf{X}, \\sigma^2, \\alpha) = \\frac{1}{(2\\pi)^\\frac{n}{2}\\left|\\mathbf{K}\\right|^\\frac{1}{2}} \\exp\\left(-\\frac{1}{2} \\mathbf{ y}^\\top \\mathbf{K}^{-1} \\mathbf{ y}\\right)\n", + " $$ where\n", + "$\\mathbf{K}= \\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top + \\sigma^2 \\mathbf{I}$.\n", + "So it is a zero mean $n$-dimensional Gaussian with covariance matrix\n", + "$\\mathbf{K}$." ], - "id": "df7676c1-788a-4107-95aa-b71ab816ab67" + "id": "6a959247-6f35-4908-a54d-a281898d76b4" }, { "cell_type": "markdown", @@ -2368,7 +2460,7 @@ "$$ in matrix form. This gives you enough information to compute the\n", "predictive mean." ], - "id": "9b1b3018-36db-4566-9fe3-10f656192b20" + "id": "5a93a592-59a5-4acc-b51d-49670c172ea3" }, { "cell_type": "markdown", @@ -2378,15 +2470,15 @@ "\n", "Compute the predictive mean for the function at all the values of the\n", "basis function given by `Phi_pred`. Call the vector of predictions\n", - "`\\mappingFunction_pred_mean`. Plot the predictions alongside the data.\n", - "We can also compute what the training error was. Use the output from\n", - "your model to compute the predictive mean, and then compute the sum of\n", - "squares error of that predictive mean. $$\n", + "`f_pred_mean`. Plot the predictions alongside the data. We can also\n", + "compute what the training error was. Use the output from your model to\n", + "compute the predictive mean, and then compute the sum of squares error\n", + "of that predictive mean. $$\n", "E = \\sum_{i=1}^n(y_i - \\langle f_i\\rangle)^2\n", "$$ where $\\langle f_i\\rangle$ is the expected output of the model at\n", "point $x_i$." ], - "id": "c6aeae5a-627c-4e4e-968c-40b279823f60" + "id": "2c771e68-3da6-4660-8364-6a1520bf8d95" }, { "cell_type": "code", @@ -2408,7 +2500,7 @@ "print('The error is: ', sum_squares)\n", "\n" ], - "id": "f93ef742-4a70-4441-9cdc-95cc49b47a94" + "id": "f5dba350-51c9-4053-ae14-a1f22a63982e" }, { "cell_type": "markdown", @@ -2423,12 +2515,12 @@ "found from the variance of the prediction at each point. Those variances\n", "are the diagonal entries of the covariance matrix. We’ve already\n", "computed the form of the covariance under Gaussian expectations, $$\n", - "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\boldsymbol{ \\Phi}\\mathbf{C}\\boldsymbol{ \\Phi}^\\top\n", + "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu},\\mathbf{C}\\right)} = \\boldsymbol{ \\Phi}\\mathbf{C}\\boldsymbol{ \\Phi}^\\top\n", "$$ which under the posterior density is given by $$\n", - "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathcal{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu}_w,\\mathbf{C}_w\\right)} = \\boldsymbol{ \\Phi}\\mathbf{C}_w \\boldsymbol{ \\Phi}^\\top\n", + "\\text{cov}\\left(\\mathbf{ f}\\right)_{\\mathscr{N}\\left(\\mathbf{ w}|\\boldsymbol{ \\mu}_w,\\mathbf{C}_w\\right)} = \\boldsymbol{ \\Phi}\\mathbf{C}_w \\boldsymbol{ \\Phi}^\\top\n", "$$" ], - "id": "10999520-e177-4770-bd3f-d9f982ea8c26" + "id": "4927db2e-b03a-4760-84cc-4566750ef66e" }, { "cell_type": "markdown", @@ -2447,7 +2539,7 @@ "\n", "Plot the mean function and the error bars for your basis." ], - "id": "5e88db87-1417-4935-901f-e13f498535a3" + "id": "6089e946-7e46-49ca-8930-5350b0c82641" }, { "cell_type": "code", @@ -2465,7 +2557,7 @@ "# plot the mean and error bars at 2 standard deviations above and below the mean\n", "\n" ], - "id": "b2aa5b26-1e9c-4f7c-81bb-a54013445395" + "id": "d89de59c-73d5-44b3-8d06-ffc981b4747a" }, { "cell_type": "markdown", @@ -2477,7 +2569,7 @@ "are going to use hold out validation to attempt to see which model is\n", "best for extrapolating." ], - "id": "dda36339-da00-45bd-acdb-7c2e9ed79e0e" + "id": "4da17b10-a2c2-4b63-8009-71c85e4d7556" }, { "cell_type": "markdown", @@ -2491,7 +2583,7 @@ "according to *hold out* validation. Is it the same as the maximum\n", "likelihood result fom last week?" ], - "id": "322ba435-b68c-46c3-8197-26d193921ef8" + "id": "297d7003-3ac5-4666-8856-b7e11565ddc0" }, { "cell_type": "code", @@ -2505,7 +2597,7 @@ "\n", "\n" ], - "id": "2c9c8b94-5846-406e-ba20-5c997c5271e4" + "id": "5f1b395c-e67c-44d2-a1fa-3ab3c358cd19" }, { "cell_type": "markdown", @@ -2523,7 +2615,7 @@ "leave one out solution) and the training data from the hold out\n", "experiment. Discuss your answer." ], - "id": "f404db4c-fc86-4e20-9584-6dee10be0bc1" + "id": "a597a5e0-17f1-43d7-8479-c1a8ca509b35" }, { "cell_type": "code", @@ -2537,7 +2629,7 @@ "\n", "\n" ], - "id": "153e14de-340a-4ba3-9edd-986a3fb00cc2" + "id": "40950aa4-e530-4923-979c-5fdec7572702" }, { "cell_type": "markdown", @@ -2549,7 +2641,7 @@ "\n", "- Section 3.4 (pg 161–165) of Bishop (2006)" ], - "id": "4bca2fb3-d4ba-4eaf-90c0-f48ca38d93dd" + "id": "ca3622ae-3c4f-48eb-b798-9933344759c5" }, { "cell_type": "markdown", @@ -2560,6 +2652,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -2567,20 +2662,14 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "36793609-16fa-49e3-97b4-60c4a665aaa7" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "f494222e-0019-4777-9ae6-1ad08dedcf60" + "id": "4393b724-d9cd-4289-a218-3a04f393e460" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Bishop, C.M., 2006. Pattern recognition and machine learning. springer.\n", "\n", "Gauss, C.F., 1809. Theoria motus corporum coelestium in sectionibus\n", @@ -2592,7 +2681,7 @@ "Stigler, S.M., 1999. Statistics on the table: The history of statistical\n", "concepts and methods. harvard, Cambridge, MA." ], - "id": "14201e05-87c6-43fb-a9dd-074fc20a0d92" + "id": "c2393dad-d549-431e-890b-489ba8e8fc6e" } ], "nbformat": 4, diff --git a/_notebooks/09-dimensionality-reduction.ipynb b/_notebooks/09-dimensionality-reduction.ipynb index af586a8d..e1a64e27 100644 --- a/_notebooks/09-dimensionality-reduction.ipynb +++ b/_notebooks/09-dimensionality-reduction.ipynb @@ -6,7 +6,7 @@ "source": [ "# Dimensionality Reduction: Latent Variable Modelling" ], - "id": "9851622e-08a0-4bf2-bb76-ad9f809dea9f" + "id": "8ce0dc2d-3ee8-469b-bc45-98b86fb56647" }, { "cell_type": "markdown", @@ -17,7 +17,7 @@ "variable models are a probabilistic perspective on unsupervised learning\n", "which lead to dimensionality reduction algorithms." ], - "id": "c67d011d-e61f-4ff4-b82a-3a9bb0cfdc6a" + "id": "6c974bc8-b685-4a3d-a32e-f1599361a624" }, { "cell_type": "markdown", @@ -26,17 +26,20 @@ "$$\n", "$$" ], - "id": "f0efe756-9fa1-4539-a764-fbc76ee2c129" + "id": "8e4bcc1a-3597-4975-896e-a5600177785c" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -44,7 +47,7 @@ "\n", "-->" ], - "id": "725cbd33-171c-428b-b5aa-0fb440a8e787" + "id": "c928f14b-3f4e-4f82-b060-4e3664d0070d" }, { "cell_type": "markdown", @@ -56,7 +59,7 @@ "style=\"\">\\[edit\\]" ], - "id": "e17f2916-4763-403d-95e2-799cf930ee07" + "id": "c28c2018-fab0-40d2-b728-a1d0f08bd86f" }, { "cell_type": "code", @@ -65,9 +68,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "1b45899a-4397-4e6c-8276-a48b5da45d3d" + "id": "7be84bb4-22c4-4b86-aeea-1ddfca7082dc" }, { "cell_type": "markdown", @@ -78,7 +91,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "b7200233-d561-466b-a39d-7637112b0d41" + "id": "721bfe81-4a9b-427f-9622-371e75d5e837" }, { "cell_type": "markdown", @@ -95,7 +108,56 @@ "\n", "The software can be installed using" ], - "id": "13fd3576-e879-4ddf-91f1-7f35321b5d48" + "id": "2c9436f4-8223-488e-859e-b4c69acaa9c5" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "992b33d4-caad-43f2-afe1-5a6200f3bcbc" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" + ], + "id": "4a0d4e04-3661-4329-aad3-1adccfd2a3aa" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "1a405168-7ad7-404d-9af4-8d1459f88854" }, { "cell_type": "code", @@ -103,9 +165,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "%system {cmd}" ], - "id": "48d8364f-fec1-4dfe-b8cc-cec5aae31403" + "id": "f99c4cc6-6ffb-4e96-b05a-ba4a8ae14470" }, { "cell_type": "markdown", @@ -118,7 +180,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "2dc90b6f-46e8-4426-a077-9a46855c2a65" + "id": "c0750119-4437-49c3-960f-dcc6dc2f7c11" }, { "cell_type": "code", @@ -128,7 +190,7 @@ "source": [ "import notutils" ], - "id": "060d52df-c0e2-48a2-a896-a01d9b954383" + "id": "6c98fefa-16d3-4118-868f-a54319997e53" }, { "cell_type": "markdown", @@ -150,7 +212,17 @@ "\n", "The software can be installed using" ], - "id": "107611ec-b24c-461f-b69c-e4bd0f880270" + "id": "0bcf7bc9-a6e1-4959-a11e-a22c2b2814a2" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "1120f75b-50d4-4c4c-9649-79cd43625b43" }, { "cell_type": "code", @@ -158,9 +230,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "%system {cmd}" ], - "id": "eee10cef-97ab-466b-b279-8e15b0918ae6" + "id": "1f5302a3-9ddc-4148-885c-ceb9461ca580" }, { "cell_type": "markdown", @@ -172,7 +244,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "a6354baf-19e8-42a1-aa4a-f52d10bcc625" + "id": "bfd5babf-d077-42cd-9cf2-690cf5d0936b" }, { "cell_type": "code", @@ -182,7 +254,7 @@ "source": [ "import pods" ], - "id": "f0728663-7924-4f68-8a27-7ce9eca39fbe" + "id": "ecc5f96a-9bb3-4315-9e4e-cd9faf347849" }, { "cell_type": "markdown", @@ -200,7 +272,17 @@ "\n", "The software can be installed using" ], - "id": "0f0b7958-85df-4708-8c62-fa9627949ae2" + "id": "ce9132b3-1cff-4a95-8b7f-c8c27f3ebbf1" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('mlai')" + ], + "id": "2bb6c855-6d22-4038-93c6-3995b82e0eff" }, { "cell_type": "code", @@ -208,9 +290,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "%system {cmd}" ], - "id": "80e5734f-dda5-43aa-a3ad-903d3b792408" + "id": "a65328be-15d8-442f-813b-5537b1a85d3a" }, { "cell_type": "markdown", @@ -222,7 +304,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "0503202a-8835-4df6-802b-c8b9475f58cf" + "id": "bd2626ef-0c0c-40d2-bb75-f7c60f2a86f0" }, { "cell_type": "code", @@ -230,9 +312,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "1558b6c1-4bb2-4396-8fca-1f8402196115" + "id": "dfb8b5f3-fe8b-4138-a87d-4cbaedcdbe53" }, { "cell_type": "markdown", @@ -248,7 +331,7 @@ "session we saw how we can *marginalize* the parameters in a process\n", "known as Bayesian inference." ], - "id": "9bf259e4-689d-48dc-907d-51fead5a4f1d" + "id": "aa09cb55-bc55-434b-833e-aebd1f295a48" }, { "cell_type": "markdown", @@ -258,56 +341,250 @@ "\n", "\\[edit\\]\n", - "\n", - "- One common approach, not deeply covered in this course.\n", - "\n", - "- Associate each data point, $\\mathbf{ y}_{i, :}$ with one of $k$\n", - " different discrete groups.\n", - "\n", - "- For example:\n", - "\n", - " - Clustering animals into discrete groups. Are animals discrete or\n", - " continuous?\n", - " - Clustering into different different *political* affiliations.\n", - "\n", - "- Humans do seem to like clusters:\n", - "\n", - " - Very useful when interacting with biologists.\n", + "style=\"\">edit\\]\n", + "\n", + "Clustering is a common approach to data analysis, though we will not\n", + "cover it in great depth in this course. The fundamental idea is to\n", + "associate each data point $\\mathbf{ y}_{i, :}$ with one of $k$ different\n", + "discrete groups. This approach raises interesting questions - for\n", + "instance, when clustering animals into groups, we might ask whether\n", + "animal traits are truly discrete or continuous in nature. Similar\n", + "questions arise when clustering political affiliations.\n", + "\n", + "Humans seem to have a natural affinity for discrete clustering\n", + "approaches. This makes clustering particularly useful when collaborating\n", + "with biologists, who often think in terms of discrete categories.\n", + "However, we should be mindful that this preference for discrete\n", + "categories may sometimes oversimplify continuous variations in data.\n", + "\n", + "There is a subtle but important distinction between clustering and\n", + "vector quantisation. In true clustering, we typically expect to see\n", + "reductions in data density between natural groups - essentially, gaps in\n", + "the data that separate different clusters. This definition isn’t\n", + "universally applied though, and vector quantization may partition data\n", + "without requiring such density gaps. For our current discussion, we’ll\n", + "treat them similarly, focusing on the common challenges they share: how\n", + "to allocate points to groups and, more challengingly, how to determine\n", + "the optimal number of groups.\n", + "\n", + "Clustering methods associate data points with different labels that are\n", + "allocated by the computer rather than provided by human annotators. This\n", + "process is quite intuitive for humans - we naturally cluster our\n", + "observations of the real world. For example, we cluster animals into\n", + "groups like birds, mammals, and insects. While these labels can be\n", + "provided by humans, they were originally invented through a clustering\n", + "process. With computational clustering, we want to recreate that process\n", + "of label invention.\n", + "\n", + "When thinking about ideas, the Greek philosopher Plato considered the\n", + "concept of Platonic ideals - the most quintessential version of a thing,\n", + "like the most bird-like bird or chair-like chair. In clustering, we aim\n", + "to define different categories by finding their Platonic ideals (cluster\n", + "centers) and allocating each data point to its nearest center. This\n", + "allows computers to form categorizations of data at scales too large for\n", + "human processing." + ], + "id": "1af280e8-f41a-4a03-a31e-62a2882faf5d" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## $k$-means Clustering\n", "\n", - "- Subtle difference between clustering and *vector quantisation*\n", + "\\[edit\\]\n", "\n", - "- Little anecdote.\n", + "To implement clustering computationally, we need to mathematically\n", + "represent both our objects and cluster centres as vectors\n", + "($\\mathbf{ x}_i$ and $\\boldsymbol{ \\mu}_j$ respectively) and define a\n", + "notion of either similarity or distance between them. The distance\n", + "function $d_{ij} = f(\\mathbf{ x}_i, \\boldsymbol{ \\mu}_j)$ measures how\n", + "far each object is from potential cluster centres. For example, we might\n", + "cluster customers by representing them through their purchase history\n", + "and measuring their distance to different customer archetypes." + ], + "id": "29aac78d-38e9-4d29-946e-c10cb1a32c92" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Squared Distance\n", "\n", - "- To my mind difference is in clustering there should be a reduction\n", - " in data density between samples.\n", + "A commonly used distance metric is the squared distance:\n", + "$d_{ij} = (\\mathbf{ x}_i - \\boldsymbol{ \\mu}_j)^2$. This metric appears\n", + "frequently in machine learning - we saw it earlier measuring prediction\n", + "errors in regression, and here it measures dissimilarity between data\n", + "points and cluster centres.\n", "\n", - "- This definition is not universally applied.\n", + "Once we have decided on the distance or similarity function, we can\n", + "decide a number of cluster centres, $K$. We find their location by\n", + "allocating each center to a sub-set of the points and minimizing the sum\n", + "of the squared errors, $$\n", + "E(\\mathbf{M}) = \\sum_{i \\in \\mathbf{i}_j} (\\mathbf{ x}_i - \\boldsymbol{ \\mu}_j)^2\n", + "$$ where the notation $\\mathbf{i}_j$ represents all the indices of each\n", + "data point which has been allocated to the $j$th cluster represented by\n", + "the center $\\boldsymbol{ \\mu}_j$." + ], + "id": "99e72c99-2bfa-4d3b-abea-7b29b142332f" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## $k$-Means Clustering\n", "\n", - "- For today’s purposes we merge them:\n", + "One approach to minimizing this objective function is known as\n", + "*$k$-means clustering*. It is simple and relatively quick to implement,\n", + "but it is an initialization sensitive algorithm. Initialization is the\n", + "process of choosing an initial set of parameters before optimization.\n", + "For $k$-means clustering you need to choose an initial set of centres.\n", + "In $k$-means clustering your final set of clusters is very sensitive to\n", + "the initial choice of centres. For more technical details on $k$-means\n", + "clustering you can watch a video of Alex Ihler introducing the algorithm\n", + "here.\n", "\n", - " - Determine how to allocate each point to a group and *harder*\n", - " total number of groups.\n", + "The $k$-means algorithm provides a straightforward approach to\n", + "clustering data. It requires two key elements: a set of $k$ cluster\n", + "centres and a way to assign each data point to a cluster. The algorithm\n", + "follows a simple iterative process:\n", "\n", - "- Simple algorithm for allocating points to groups.\n", + "1. First, initialize cluster centres by randomly selecting $k$ data\n", + " points\n", + "2. Assign each data point to its nearest cluster centre\n", + "3. Update each cluster centre by computing the mean of all points\n", + " assigned to it\n", + "4. Repeat steps 2 and 3 until the cluster assignments stop changing\n", "\n", - "- *Require*: Set of $k$ cluster centres & assignment of each points to\n", - " a cluster.\n", + "This process is intuitive and relatively easy to implement, though it\n", + "comes with certain limitations.\n", "\n", - "1. Initialize cluster centres as randomly selected data points.\n", - " 1. Assign each data point to *nearest* cluster centre.\n", - " 2. Update each cluster centre by setting it to the mean of assigned\n", - " data points.\n", - " 3. Repeat 2 and 3 until cluster allocations do not change.\n", + "The $k$-means algorithm works by minimizing an objective function that\n", + "measures the sum of squared Euclidean distances between each point and\n", + "its assigned cluster center. This objective function can be written\n", + "mathematically as shown above, where $\\boldsymbol{ \\mu}_{j, :}$\n", + "represents the mean of cluster $j$.\n", "\n", - "- This minimizes the objective $$\n", - " E=\\sum_{j=1}^K \\sum_{i\\ \\text{allocated to}\\ j} \\left(\\mathbf{ y}_{i, :} - \\boldsymbol{ \\mu}_{j, :}\\right)^\\top\\left(\\mathbf{ y}_{i, :} - \\boldsymbol{ \\mu}_{j, :}\\right)\n", - " $$ *i.e.* it minimizes thesum of Euclidean squared distances betwen\n", - " points and their associated centres.\n", - "- The minimum is *not* guaranteed to be *global* or *unique*.\n", - "- This objective is a non-convex optimization problem." + "It’s important to understand that while this algorithm will always\n", + "converge to a minimum, this minimum is not guaranteed to be either\n", + "global or unique. The optimization problem is non-convex, meaning there\n", + "can be multiple local minima. Different initializations of the cluster\n", + "centres can lead to different final solutions, which is one of the key\n", + "challenges in applying $k$-means clustering in practice." ], - "id": "bf9155a5-b6ff-496f-94c0-126c8a580f12" + "id": "954cd6ef-976f-4638-9f49-639d307f9562" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import generate_cluster_data\n", + "import inspect\n", + "file_path = inspect.getfile(generate_cluster_data)" + ], + "id": "7c64b7eb-45d9-4ab8-b33c-dcac313c36e3" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s generate_cluster_data {file_path}" + ], + "id": "23674d1b-3c92-4804-8e72-851265a18e47" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import dist2\n", + "import inspect\n", + "file_path = inspect.getfile(dist2)" + ], + "id": "544fe8c5-1396-470a-b126-772d36a0338d" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s dist2 {file_path}" + ], + "id": "ea876f99-f748-43b8-b533-982dc8bb9fc2" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import kmeans_assignments\n", + "import inspect\n", + "file_path = inspect.getfile(kmeans_assignments)" + ], + "id": "1a99867b-a16d-4f00-9aec-56d230b4b12b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s kmeans_assignments {file_path}" + ], + "id": "a52ac383-d24e-4a09-8162-86d9bb1ebeac" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import kmeans_update\n", + "import inspect\n", + "file_path = inspect.getfile(kmeans_update)" + ], + "id": "e423636b-5243-4626-b6c8-d2c8d95b5d6b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s kmeans_update {file_path}" + ], + "id": "504ef92d-f56f-4c95-8020-56ba92415f74" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import kmeans_objective\n", + "import inspect\n", + "file_path = inspect.getfile(kmeans_objective)" + ], + "id": "0996a907-eb77-4191-8c98-8510af628ea9" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s kmeans_objective {file_path}" + ], + "id": "722d68e0-a6f6-475c-8331-e6e239fccfbd" }, { "cell_type": "code", @@ -315,10 +592,9 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai\n", "import numpy as np" ], - "id": "9b106291-82d6-42e6-abbd-f5209199da66" + "id": "75536ca4-0369-4416-b8b6-6612e748a945" }, { "cell_type": "code", @@ -326,15 +602,16 @@ "metadata": {}, "outputs": [], "source": [ - "def write_plot(counter, caption):\n", - " directory = \"./ml\"\n", - " filestub = f\"kmeans_clustering_{counter:0>3}\"\n", - " mlai.write_figure(filestub+\".svg\", directory=directory)\n", - " f = open(os.path.join(directory,filestub) + '.md', 'w')\n", - " f.write(caption)\n", - " f.close()" + "# Generate synthetic data with cluster structure\n", + "np.random.seed(24)\n", + "Y = generate_cluster_data(n_points_per_cluster=30)\n", + "\n", + "# Initialize cluster centres from first cluster\n", + "# Note: this is a *bad* initialisation so we can observe algorithm.\n", + "nclusters = 3\n", + "centres = Y[:nclusters, :].copy()" ], - "id": "22919c35-dc8f-4866-b7a8-4c2156f2907f" + "id": "848c03a6-179a-495e-a16a-323a6dd6be1d" }, { "cell_type": "code", @@ -342,52 +619,123 @@ "metadata": {}, "outputs": [], "source": [ - "fig, ax = plt.subplots(figsize=(5,5))\n", - "fontsize = 20\n", + "# Run k-means algorithm for several iterations\n", + "niters = 10\n", + "centres_store = [centres.copy()] # Store initial centres\n", + "objective_store = [kmeans_objective(Y, centres)] # Calculate initial objective\n", "\n", - "num_clust_points = 30\n", + "print(f\"Initial objective: {objective_store[0]:.2f}\")\n", + "print(\"Running k-means algorithm...\")\n", "\n", - "Y = np.vstack([np.random.normal(size=(num_clust_points, 2)) + 2.5,\n", - " np.random.normal(size=(num_clust_points, 2)) - 2.5,\n", - " np.random.normal(size=(num_clust_points, 2)) + np.array([2.5, -2.5])])\n", + "for iteration in range(niters):\n", + " centres, assignments = kmeans_update(Y, centres)\n", + " objective = kmeans_objective(Y, centres)\n", + " \n", + " print(f\" Iteration {iteration+1}: objective = {objective:.2f}\")\n", + " \n", + " # Store results for plotting\n", + " centres_store.append(centres.copy())\n", + " objective_store.append(objective)\n", "\n", - "centre_inds = np.random.permutation(Y.shape[0])[:3]\n", - "centres = Y[centre_inds, :]\n", + "print(f\"Final objective: {objective_store[-1]:.2f}\")\n", + "print(f\"Objective improvement: {objective_store[0] - objective_store[-1]:.2f}\")" + ], + "id": "be770d43-8c26-4f79-a5cd-ee00d29b4d61" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "import mlai\n", + "from mlai import plot" + ], + "id": "5178a6f9-78e5-4a80-8742-94807a35b179" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", + "colors = ['red', 'green', 'blue'] # Colors for each cluster\n", + "markers = ['x', 'o', '+'] # Different markers for each cluster\n", "\n", - "ax.cla()\n", + "centres = centres_store[0]\n", + "objective = objective_store[0]\n", "\n", - "ax.plot(Y[:, 0], Y[:, 1], '.', color=[0, 0, 0], markersize=10)\n", + "# Plot initial data\n", + "h_data = ax.plot(Y[:, 0], Y[:, 1], '.', color='black', markersize=10, alpha=0.6)[0]\n", "ax.set_xlabel('$y_1$')\n", "ax.set_ylabel('$y_2$')\n", - "ax.set_title('Data')\n", - "counter=0\n", - "write_plot(counter, 'Data set to be analyzed. Initialize cluster centres.')\n", - "ax.plot(centres[:, 0], centres[:, 1], 'o', color=[0,0,0], linewidth=3, markersize=12) \n", - "counter+=1\n", - "write_plot(counter, 'Allocate each point to the cluster with the nearest centre')\n", - "i = 0\n", - "\n", - "for i in range(6):\n", - " dist_mat = ((Y[:, :, None] - centres.T[None, :, :])**2).sum(1)\n", - " ind = dist_mat.argmin(1)\n", - " ax.cla()\n", - " ax.plot(Y[ind==0, 0], Y[ind==0, 1], 'x', color= [1, 0, 0], markersize=10)\n", - " ax.plot(Y[ind==1, 0], Y[ind==1, 1], 'o', color=[0, 1, 0], markersize=10)\n", - " ax.plot(Y[ind==2, 0], Y[ind==2, 1], '+', color=[0, 0, 1], markersize=10)\n", - " c = ax.plot(centres[:, 0], centres[:, 1], 'o', color=[0,0, 0], markersize=12, linewidth=3)\n", - " ax.set_xlabel('$y_1$',fontsize=fontsize)\n", - " ax.set_ylabel('$y_2$',fontsize=fontsize)\n", - " ax.set_title('Iteration ' + str(i))\n", - " counter+=1\n", - " write_plot(counter, 'Update each centre by setting to the mean of the allocated points.')\n", - " for j in range(centres.shape[0]):\n", - " centres[j, :] = np.mean(Y[ind==j, :], 0)\n", - " c[0].set_data(centres[:, 0], centres[:, 1])\n", - " counter+=1\n", - " mlai.write_figure(f\"kmeans_clustering_{counter:0>3}.svg\", directory=\"./ml\")\n", - " write_plot(counter, 'Allocate each data point to the nearest cluster centre.')" - ], - "id": "615b7c81-c5d6-4d66-abe5-9b76692e8717" + "ax.grid(True, alpha=0.3)\n", + "\n", + "# Save initial plot\n", + "counter = 0\n", + "mlai.write_figure_caption(counter, 'Data set to be analysed and initialise cluster centres.', filestub=\"kmeans_clustering\", ext=\"svg\", directory=\"./ml\")\n", + "\n", + "# Plot initial centres\n", + "h_centres = ax.plot(centres_store[0][:, 0], centres_store[0][:, 1], 'o', color='black', \n", + " linewidth=3, markersize=12)[0]\n", + "\n", + "counter += 1\n", + "mlai.write_figure_caption(counter, 'Allocate each point to the cluster with the nearest centre', filestub=\"kmeans_clustering\", ext=\"svg\", directory=\"./ml\")\n", + "\n", + "# Compute initial assignments\n", + "assignments = kmeans_assignments(Y, centres)\n", + "\n", + "# Initialize plot handles for each cluster\n", + "h_points = [None]*nclusters\n", + "h_data.set_data([], [])\n", + "for cluster_id in range(nclusters):\n", + " cluster_mask = assignments == cluster_id\n", + " if np.any(cluster_mask): # Only plot if cluster has points\n", + " h_points[cluster_id] = ax.plot(Y[cluster_mask, 0], Y[cluster_mask, 1], \n", + " markers[cluster_id], color=colors[cluster_id], \n", + " markersize=10, alpha=0.7)[0] # Get the Line2D object\n", + "\n", + "counter += 1\n", + "mlai.write_figure_caption(counter, 'Initial centre allocations', filestub=\"kmeans_clustering\", ext=\"svg\", directory=\"./ml\")\n", + "for iteration in range(len(centres_store)-1):\n", + " centres = centres_store[iteration+1]\n", + " assignments = kmeans_assignments(Y, centres)\n", + " objective = objective_store[iteration+1]\n", + " \n", + " # Update centres plot\n", + " h_centres.set_data(centres[:, 0], centres[:, 1])\n", + " \n", + " # Save plot for animation\n", + " counter += 1\n", + " print(f\"Plot {counter}\")\n", + " mlai.write_figure_caption(counter, f'Iteration {iteration + 1}: Update centres to mean of assigned points', filestub=\"kmeans_clustering\", ext=\"svg\", directory=\"./ml\")\n", + " \n", + " \n", + " # Update data points for each cluster\n", + " for cluster_id in range(nclusters):\n", + " cluster_mask = assignments == cluster_id\n", + " if np.any(cluster_mask): # Only update if cluster has points\n", + " if h_points[cluster_id] is None:\n", + " # Create new plot handle if it doesn't exist\n", + " h_points[cluster_id] = ax.plot(Y[cluster_mask, 0], Y[cluster_mask, 1], \n", + " markers[cluster_id], color=colors[cluster_id], \n", + " markersize=10, alpha=0.7)[0]\n", + " else:\n", + " # Update existing plot handle\n", + " h_points[cluster_id].set_data(Y[cluster_mask, 0], Y[cluster_mask, 1])\n", + " else:\n", + " # Hide cluster if it has no points\n", + " if h_points[cluster_id] is not None:\n", + " h_points[cluster_id].set_data([], [])\n", + " \n", + " # Save plot for animation\n", + " counter += 1\n", + " print(f\"Plot {counter}\")\n", + " mlai.write_figure_caption(counter, f'Iteration {iteration + 1}: Update assignments to centres', filestub=\"kmeans_clustering\", ext=\"svg\", directory=\"./ml\")" + ], + "id": "b1d7d90a-3d79-40fd-8d7a-3a586a4b1550" }, { "cell_type": "code", @@ -397,7 +745,7 @@ "source": [ "import notutils as nu" ], - "id": "37b34b9d-a045-4cc0-9376-bac3fece57ef" + "id": "72dfdd8d-d46a-448f-ba38-28040d625dd6" }, { "cell_type": "code", @@ -406,9 +754,19 @@ "outputs": [], "source": [ "nu.display_plots(\"kmeans_clustering_{counter:0>3}.svg\", directory=\"./ml\", \n", - " text_top='kmeans_clustering_{counter:0>3}.tex', counter=(0, 13))" + " text_top='kmeans_clustering_{counter:0>3}.tex', counter=(0, 22))" + ], + "id": "afd82a71-4e0e-42ad-94e7-6e4ec3902a51" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Clustering with the $k$-means clustering algorithm." ], - "id": "2121504c-b299-43b2-94c7-15a023d53026" + "id": "fd2f16dd-028b-4e61-aaaf-26d541e95c84" }, { "cell_type": "code", @@ -420,7 +778,7 @@ "import numpy as np\n", "import mlai" ], - "id": "cabf162a-cc77-449f-9357-e9ac88d13489" + "id": "27558e86-5ea6-4a38-b5d0-b52e58e33eed" }, { "cell_type": "code", @@ -454,7 +812,7 @@ " ax.plot(centres[i, 0] + el[:, 0], centres[i, 1] + el[:, 1], linewidth=2, color=[0,0,0])\n", "mlai.write_figure(\"cluster_data01.svg\", directory=\"./ml/\")" ], - "id": "807803d2-635b-4f74-b4a7-48c0dc38ff53" + "id": "f88eb847-cdfb-41dd-92d6-500fae3887f8" }, { "cell_type": "code", @@ -464,7 +822,7 @@ "source": [ "import notutils as nu" ], - "id": "024c6f36-596a-4d26-a6ad-f8106306a3c4" + "id": "23f1ee7a-634e-458f-badc-06dc9aec09d8" }, { "cell_type": "code", @@ -474,111 +832,146 @@ "source": [ "nu.display_plots('cluster_data{counter:0>2}.svg', directory='./ml', counter=(0, 1))" ], - "id": "09254ad8-2f6d-4ad4-b717-b9e58efd8343" + "id": "5fe90b11-859b-4157-bb96-78adc9d07d9c" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Clustering methods associate each data point with a different label.\n", - "Unlike in classification the label is not provided by a human annotator.\n", - "It is allocated by the computer. Clustering is quite intuitive for\n", - "humans, we do it naturally with our observations of the real world. For\n", - "example, we cluster animals into different groups. If we encounter a new\n", - "animal, we can immediately assign it to a group: bird, mammal, insect.\n", - "These are certainly labels that can be provided by humans, but they were\n", - "also originally invented by humans. With clustering we want the computer\n", - "to recreate that process of inventing the label.\n", + "## Hierarchical Clustering\n", "\n", - "Unsupervised learning enables computers to form similar categorizations\n", - "on data that is too large scale for us to process. When the Greek\n", - "philosopher, Plato, was thinking about ideas, he considered the concept\n", - "of the Platonic ideal. The Platonic ideal bird is the bird that is most\n", - "bird-like or the chair that is most chair-like. In some sense, the task\n", - "in clustering is to define different clusters, by finding their Platonic\n", - "ideal (known as the cluster center) and allocate each data point to the\n", - "relevant cluster center. So, allocate each animal to the class defined\n", - "by its nearest cluster center.\n", - "\n", - "To perform clustering on a computer we need to define a notion of either\n", - "similarity or distance between the objects and their Platonic ideal, the\n", - "cluster center. We normally assume that our objects are represented by\n", - "vectors of data, $\\mathbf{ x}_i$. Similarly, we represent our cluster\n", - "center for category $j$ by a vector $\\boldsymbol{ \\mu}_j$. This vector\n", - "contains the ideal features of a bird, a chair, or whatever category $j$\n", - "is. In clustering we can either think in terms of similarity of the\n", - "objects, or distances. We want objects that are similar to each other to\n", - "cluster together. We want objects that are distant from each other to\n", - "cluster apart.\n", + "\\[edit\\]\n", "\n", - "This requires us to formalize our notion of similarity or distance.\n", - "Let’s focus on distances. A definition of distance between an object,\n", - "$i$, and the cluster center of class $j$ is a function of two vectors,\n", - "the data point, $\\mathbf{ x}_i$ and the cluster center,\n", - "$\\boldsymbol{ \\mu}_j$, $$\n", - "d_{ij} = f(\\mathbf{ x}_i, \\boldsymbol{ \\mu}_j).\n", - "$$ Our objective is then to find cluster centers that are close to as\n", - "many data points as possible. For example, we might want to cluster\n", - "customers into their different tastes. We could represent each customer\n", - "by the products they’ve purchased in the past. This could be a binary\n", - "vector $\\mathbf{ x}_i$. We can then define a distance between the\n", - "cluster center and the customer." + "Hierarchical clustering builds a tree structure by iteratively merging\n", + "the closest clusters. Unlike $k$-means, we don’t need to specify the\n", + "number of clusters in advance. Instead, we start with each point as its\n", + "own cluster and merge the closest pairs until we have one cluster\n", + "containing all points. The result is a dendrogram - a tree that shows\n", + "the complete merge history and allows us to extract any number of\n", + "clusters by cutting the tree at different heights." ], - "id": "a058b045-193d-4f79-b672-652a308ade6c" + "id": "2b2af884-c427-4d4e-88f0-efa7c3c89bb5" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Squared Distance\n", - "\n", - "A commonly used distance is the squared distance, $$\n", - "d_{ij} = (\\mathbf{ x}_i - \\boldsymbol{ \\mu}_j)^2.\n", - "$$ The squared distance comes up a lot in machine learning. In\n", - "unsupervised learning it was used to measure dissimilarity between\n", - "predictions and observed data. Here its being used to measure the\n", - "dissimilarity between a cluster center and the data.\n", + "## Linkage Criteria\n", "\n", - "Once we have decided on the distance or similarity function, we can\n", - "decide a number of cluster centers, $K$. We find their location by\n", - "allocating each center to a sub-set of the points and minimizing the sum\n", - "of the squared errors, $$\n", - "E(\\mathbf{M}) = \\sum_{i \\in \\mathbf{i}_j} (\\mathbf{ x}_i - \\boldsymbol{ \\mu}_j)^2\n", - "$$ where the notation $\\mathbf{i}_j$ represents all the indices of each\n", - "data point which has been allocated to the $j$th cluster represented by\n", - "the center $\\boldsymbol{ \\mu}_j$." + "The choice of linkage criterion determines how we measure distance\n", + "between clusters. Single linkage uses the minimum distance between any\n", + "two points in different clusters, making it sensitive to outliers.\n", + "Complete linkage uses the maximum distance, creating more compact\n", + "clusters. Average linkage provides a balance, while Ward linkage\n", + "minimizes the increase in within-cluster variance, often producing more\n", + "balanced cluster sizes." ], - "id": "55de5936-7866-47c1-9586-efac70dd5621" + "id": "e3f3b773-c0a2-4b48-97e0-58f9a3ae8295" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### $k$-Means Clustering\n", + "## Ward’s Criterion\n", "\n", - "One approach to minimizing this objective function is known as\n", - "*$k$-means clustering*. It is simple and relatively quick to implement,\n", - "but it is an initialization sensitive algorithm. Initialization is the\n", - "process of choosing an initial set of parameters before optimization.\n", - "For $k$-means clustering you need to choose an initial set of centers.\n", - "In $k$-means clustering your final set of clusters is very sensitive to\n", - "the initial choice of centers. For more technical details on $k$-means\n", - "clustering you can watch a video of Alex Ihler introducing the algorithm\n", - "here." + "Ward’s criterion minimises the increase in within-cluster sum of squares\n", + "($E(\\mathbf{M})$) when merging two clusters. The selection criterion for\n", + "merging is $$\n", + "\\Delta_{i,j} = \\frac{n_i n_j}{n_i + n_j} \\|\\boldsymbol{ \\mu}_i - \\boldsymbol{ \\mu}_j\\|^2,\n", + "$$ where $\\boldsymbol{ \\mu}_i$ and $\\boldsymbol{ \\mu}_j$ are the\n", + "centroids of clusters $i$ and $j$, and $n_i, n_j$ are their sizes. This\n", + "formula comes from greedy minimisation of the within cluster variance.\n", + "The normalisation by $n_i$ and $n_j$ ensures that the distance between\n", + "cluster centroids with their sizes - larger clusters are penalized more\n", + "heavily for being far apart, encouraging the formation of compact,\n", + "well-separated groups.\n", + "\n", + "Ward’s method chooses the merge that result in the smallest possible\n", + "increase in total within-cluster variance. This means that at each step,\n", + "we’re making the locally optimal choice that preserves the most\n", + "information about our data structure. The method is particularly\n", + "effective when the underlying clusters are roughly spherical and\n", + "well-separated. It creates compact, convex clusters. The weighting by\n", + "cluster size ($\\frac{n_i n_j}{n_i + n_j}$) ensures that we don’t\n", + "prematurely merge large clusters with small outliers, leading to more\n", + "balanced and interpretable results." ], - "id": "d1e46118-5459-47cc-be08-847898433eae" + "id": "d9f10ed1-c503-41d2-ad01-6acfc89f0bac" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### $k$-Means Clustering\n", - "\n", - "\n", + "## Mathematical Derivation\n", + "\n", + "We can show that Ward’s method is optimal for spherical clusters. We can\n", + "derive the relationship between cluster merging and within-cluster\n", + "variance. The within-cluster sum of squares ($E(\\mathbf{M})$) measures\n", + "the total squared distance from each point to its cluster centroid, $$\n", + "E(\\mathbf{M}) = \\sum_{i=1}^k \\sum_{\\mathbf{ x}\\in C_i} \\|\\mathbf{ x}- \\boldsymbol{ \\mu}_i\\|^2.\n", + "$$ This is the same objective function that $k$-means clustering\n", + "minimises. When we merge two clusters, we want to minimise the increase\n", + "in this total variance.\n", + "\n", + "Consider two clusters $C_i$ and $C_j$ with $n_i$ and $n_j$ points\n", + "respectively, and centroids $\\boldsymbol{ \\mu}_i$ and\n", + "$\\boldsymbol{ \\mu}_j$. When we merge them, the new centroid becomes\n", + "$\\boldsymbol{ \\mu}_{ij} = \\frac{n_i \\boldsymbol{ \\mu}_i + n_j \\boldsymbol{ \\mu}_j}{n_i + n_j}$.\n", + "\n", + "The increase in $E(\\mathbf{M})$ is\n", + "$\\Delta_{i,j} = E(\\mathbf{M})_{\\text{new}} - E(\\mathbf{M})_{\\text{original}}$.\n", + "Let’s expand this step by step. For the new cluster, we have:\n", + "\n", + "Using the identity\n", + "$\\|\\mathbf{ x}- \\boldsymbol{ \\mu}_{ij}\\|^2 = \\|\\mathbf{ x}- \\boldsymbol{ \\mu}_i\\|^2 + \\|\\boldsymbol{ \\mu}_i - \\boldsymbol{ \\mu}_{ij}\\|^2 + 2(\\mathbf{ x}- \\boldsymbol{ \\mu}_i)^T(\\boldsymbol{ \\mu}_i - \\boldsymbol{ \\mu}_{ij})$,\n", + "we can expand the new $E(\\mathbf{M})$. The cross-term\n", + "$2(\\mathbf{ x}- \\boldsymbol{ \\mu}_i)^T(\\boldsymbol{ \\mu}_i - \\boldsymbol{ \\mu}_{ij})$\n", + "sums to zero because\n", + "$\\sum_{\\mathbf{ x}\\in C_i} (\\mathbf{ x}- \\boldsymbol{ \\mu}_i) = 0$ by\n", + "definition of centroid.\n", + "\n", + "We can manipulate to find that $$\n", + "\\Delta_{i,j} = n_i \\|\\boldsymbol{ \\mu}_i - \\boldsymbol{ \\mu}_{ij}\\|^2 + n_j \\|\\boldsymbol{ \\mu}_j - \\boldsymbol{ \\mu}_{ij}\\|^2$. Substituting $\\boldsymbol{ \\mu}_{ij} = \\frac{n_i \\boldsymbol{ \\mu}_i + n_j \\boldsymbol{ \\mu}_j}{n_i + n_j}\n", + "$$ and simplifying, we get the Ward distance formula $$\n", + "\\Delta_{i,j} = \\frac{n_i n_j}{n_i + n_j} \\|\\boldsymbol{ \\mu}_i - \\boldsymbol{ \\mu}_j\\|^2.\n", + "$$\n", "\n", - "Figure: Clustering with the $k$-means clustering algorithm." + "For spherical clusters, the $E(\\mathbf{M})$ is minimised when points are\n", + "as close as possible to their centroid. Ward’s method makes locally\n", + "optimal choices by always choosing the merge that results in the\n", + "smallest possible increase in total variance. However, this is *greedy\n", + "optimal*, it makes the best choice at each step without considering the\n", + "global consequences. The weighting factor we derived,\n", + "$\\frac{n_i n_j}{n_i + n_j}$, ensures that merging two large clusters is\n", + "penalised more heavily than merging small clusters, preventing the\n", + "algorithm from prematurely combining large, well-separated groups. While\n", + "Ward’s method is not guaranteed to find the globally optimal\n", + "hierarchical clustering, it often produces good results for spherical\n", + "cluster structures.\n", + "\n", + "The agglomerative hierarchical clustering algorithm follows a simple\n", + "iterative process. We start with each data point as its own cluster,\n", + "then repeatedly find the two closest clusters and merge them. After each\n", + "merge, we update our distance matrix to reflect the new cluster\n", + "structure. This process continues until all points belong to a single\n", + "cluster, creating a complete merge history that we can visualize as a\n", + "dendrogram." + ], + "id": "5153046f-ac7f-43eb-8634-3a6684f004e5" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import generate_cluster_data\n", + "import inspect\n", + "file_path = inspect.getfile(generate_cluster_data)" ], - "id": "c03c447b-7a1f-453a-9784-b6a9f001de74" + "id": "aeb5f346-8622-434c-a399-79371be31b5a" }, { "cell_type": "code", @@ -586,30 +979,737 @@ "metadata": {}, "outputs": [], "source": [ - "from IPython.lib.display import YouTubeVideo\n", - "YouTubeVideo('mfqmoUN-Cuw')" + "%load -s generate_cluster_data {file_path}" ], - "id": "304fc07a-ec13-4f9b-9a32-7523e34d9963" + "id": "ff17baf4-1bef-4cec-b580-e22e6404b4d8" }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "Figure: $k$-means clustering by Alex Ihler." + "from mlai import ClusterModel\n", + "import inspect\n", + "file_path = inspect.getfile(ClusterModel)" ], - "id": "fba653e6-0ee3-42a8-83ac-c9dc616cebf4" + "id": "e0019157-76f3-4437-8cda-d7254673d82f" }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s ClusterModel {file_path}" + ], + "id": "777ba570-9482-4a1b-8c92-bb37c96af5f0" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import WardsMethod\n", + "import inspect\n", + "file_path = inspect.getfile(WardsMethod)" + ], + "id": "64d5daf5-7a36-4a51-81b5-06bf05cfe247" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s WardsMethod {file_path}" + ], + "id": "d673ddf0-fd64-485c-83db-2df9de0e2934" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Generate synthetic data with cluster structure\n", + "np.random.seed(24)\n", + "Y = generate_cluster_data(n_points_per_cluster=30)\n", + "\n", + "ward = WardsMethod(Y)\n", + "ward.fit()\n", + "\n", + "linkage_matrix = ward.get_linkage_matrix()\n", + "print(f'Linkage matrix shape: {linkage_matrix.shape}')\n", + "\n", + "print(f\"\\nFinal clustering completed in {len(linkage_matrix)} steps\")\n", + "print(f\"Ward distances at each step: {[f'{d:.3f}' for d in linkage_matrix[:, 2]]}\")" + ], + "id": "83bc5230-1114-43a7-8870-da711393e9ce" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import mlai\n", + "from mlai import plot\n", + "import matplotlib.pyplot as plt" + ], + "id": "6f291c79-017c-441e-a77b-4c1304038c5c" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=plot.one_figsize)\n", + "\n", + "# Plot data\n", + "ax.plot(Y[:, 0], Y[:, 1], '.', color='black', markersize=10, alpha=0.6)\n", + "ax.set_xlabel('$y_1$')\n", + "ax.set_ylabel('$y_2$')\n", + "ax.grid(True, alpha=0.3)\n", + "\n", + "mlai.write_figure(filename=\"hierarchical_clustering_000.svg\", directory=\"./ml\")" + ], + "id": "85bf7b65-31ee-4bd4-a013-4bba03960b0f" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from scipy.cluster.hierarchy import dendrogram" + ], + "id": "49eb463d-2239-408d-a6f8-f7eaaeb1707e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Plot dendrogram\n", + "fig, ax = plt.subplots(figsize=plot.one_figsize)\n", + "dendrogram(linkage_matrix, ax=ax)\n", + "ax.set_xlabel('Sample Index')\n", + "ax.set_ylabel('Distance')\n", + "\n", + "plt.tight_layout()\n", + "\n", + "mlai.write_figure(filename=\"hierarchical_clustering_001.svg\", directory=\"./ml\")" + ], + "id": "a2f8f5f9-246d-489c-a6db-6314ba8931b0" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n", + "Figure: Hierarchical clustering of some artificial data. On the left\n", + "we have an artificially generated data set containing three clusters. On\n", + "the right we can see the dendogram formed by clustering using Ward’s\n", + "criterion." + ], + "id": "378816a0-b8ce-4f2f-9a05-57550bd8ff02" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import notutils as nu" + ], + "id": "b624e41e-2850-47f3-8b4c-38a3db956dcf" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.display_plots(\"hierarchical_clustering_{counter:0>3}.svg\", directory=\"./ml\", \n", + " text_top='hierarchical_clustering_{counter:0>3}.tex', counter=(0, 1))" + ], + "id": "1819da7c-a0c3-49b5-a32d-32f2473be33d" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that the hierarchical clustering here doesn’t always imply that the\n", + "data was hierarchical in origin. It’s a visualisation of distances\n", + "between points and clusters. The longer lenghts in the dendogram imply\n", + "larger distances. Next We’ll demonstrate agglomerative clustering on the\n", + "oil flow data set, which contains measurements from a multiphase flow\n", + "facility." + ], + "id": "ef4609ed-e09c-4e9a-a4f5-f89f78125232" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Oil Flow Data\n", + "\n", + "\\[edit\\]\n", + "\n", + "This data set is from a physics-based simulation of oil flow in a\n", + "pipeline. The data was generated as part of a project to determine the\n", + "fraction of oil, water and gas in North Sea oil pipes (Bishop and James,\n", + "1993)." + ], + "id": "40664f83-c6d4-4dab-ba85-87ed152cb2b4" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pods" + ], + "id": "1aad5be1-0e5e-4769-b00e-edc3b12c18e6" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.oil()" + ], + "id": "9468ec06-4fa2-44ec-bbf6-4578c9d79dc7" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data consists of 1000 12-dimensional observations of simulated oil\n", + "flow in a pipeline. Each observation is labelled according to the\n", + "multi-phase flow configuration (homogeneous, annular or laminar)." + ], + "id": "95e7d994-d0d0-4895-8068-52a87ec721f1" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Convert data[\"Y\"] from [1, -1, -1] in each row to rows of 0 or 1 or 2\n", + "Y = data[\"Y\"]\n", + "# Find rows with 1 in first column (class 0)\n", + "class0 = (Y[:, 0] == 1).astype(int) * 0\n", + "# Find rows with 1 in second column (class 1) \n", + "class1 = (Y[:, 1] == 1).astype(int) * 1\n", + "# Find rows with 1 in third column (class 2)\n", + "class2 = (Y[:, 2] == 1).astype(int) * 2\n", + "# Combine into single array of class labels 0,1,2\n", + "labels = class0 + class1 + class2" + ], + "id": "7d606502-13ac-48bb-928c-5fd77293958f" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data is returned as a dictionary containing training and test inputs\n", + "(‘X’, ‘Xtst’), training and test labels (‘Y’, ‘Ytst’), and the names of\n", + "the features." + ], + "id": "ba1186ab-3cc5-4265-85b1-f7d0a288c3e4" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import mlai.plot as plot\n", + "import mlai\n", + "import numpy as np" + ], + "id": "50187840-2714-4e26-a982-8770a1a43c84" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", + "# Plot first two dimensions of the data\n", + "class_names = [\"homogeneous\", \"annular\", \"stratified\"]\n", + "colors = ['r', 'g', 'b']\n", + "\n", + "for i, name in enumerate(class_names):\n", + " idx = labels == i\n", + " ax.plot(data['X'][idx, 0], data['X'][idx, 1], colors[i] + '.', \n", + " markersize=10, label=name)\n", + "ax.set_xlabel('1st dimension')\n", + "ax.set_ylabel('2nd dimension')\n", + "ax.legend()\n", + "\n", + "mlai.write_figure('oil-flow-data.svg', directory='./datasets')" + ], + "id": "30315c7b-a928-4af3-8e2f-3260b3ab804f" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Visualization of the first two dimensions of the oil flow\n", + "data from Bishop and James (1993)\n", + "\n", + "As normal we include the citation information for the data." + ], + "id": "09cc7201-59ba-4e3b-ba59-fc521012cf1d" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(data['citation'])" + ], + "id": "36205576-885e-415b-ab8c-11c580e6f3a7" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And extra information about the data is included, as standard, under the\n", + "keys `info` and `details`." + ], + "id": "292131ef-c301-4f01-b937-2cbf930abd18" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(data['details'])" + ], + "id": "03238660-42df-47c9-a7d9-b910e23e3175" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "X = data['X']\n", + "Y = data['Y']" + ], + "id": "d87f4690-55c0-43fb-9c40-e94e7645af33" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hierarchical Clustering of Oil Flow Data\n", + "\n", + "\\[edit\\]\n", + "\n", + "In this example, we’ll apply hierarchical clustering to the oil flow\n", + "data set. The data contains measurements from different flow regimes in\n", + "a multiphase flow facility. The dendrogram shows how measurements\n", + "naturally cluster into different flow types. Ward’s linkage method is\n", + "used as it tends to create compact, evenly-sized clusters." + ], + "id": "51c2b6ef-5ff5-40ff-889b-d55c74164812" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from scipy.cluster.hierarchy import dendrogram, linkage\n", + "import pods" + ], + "id": "3e044ea2-024a-4bfe-8772-1d358906a157" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Perform hierarchical clustering\n", + "linked = linkage(X, 'ward') # Ward's method for minimum variance" + ], + "id": "cd25a4cb-f64c-4407-bab6-359259a8c5a6" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "from mlai import plot" + ], + "id": "a9a51abd-25a4-47cd-8c08-19f5ecc88167" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", + "dendrogram(linked,\n", + " orientation='top',\n", + " distance_sort='descending',\n", + " show_leaf_counts=True,\n", + " leaf_rotation=45, # Rotate labels\n", + " leaf_font_size=8, # Reduce font size\n", + " ax=ax)\n", + "ax.set_xlabel('Sample Index')\n", + "ax.set_ylabel('Distance')\n", + "plt.xticks(rotation=45, ha='right') # Rotate x-axis labels\n", + "plt.tight_layout()\n", + "mlai.write_figure('hierarchical-clustering-oil.svg', directory='./dimred')" + ], + "id": "80477ffc-1315-4bd0-91a5-a21327112e9e" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Hierarchical clustering applied to oil flow data. The\n", + "dendrogram shows how different flow regimes are grouped based on their\n", + "measurement similarities. The three main flow regimes (homogeneous,\n", + "annular, and laminar) should form distinct clusters." + ], + "id": "b944f2fb-c93d-45e0-9f27-45a29940a2d7" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Phylogenetic Trees\n", + "\n", + "A powerful application of hierarchical clustering is in constructing\n", + "phylogenetic trees from genetic sequence data. By comparing DNA/RNA\n", + "sequences across species, we can reconstruct their evolutionary\n", + "relationships and estimate when species diverged from common\n", + "ancestors.[1] The resulting tree structure, called a phylogeny, maps out\n", + "the evolutionary history and relationships between organisms.\n", + "\n", + "These phylogenetic methods go beyond simple linkage methods based on\n", + "distance, they incorporate models of genetic mutation and molecular\n", + "evolution. These models can estimate not just the structure of\n", + "relationships, but also the timing of evolutionary divergence events\n", + "based on mutation rates. This has important applications in tracking the\n", + "origins and spread of rapidly evolving pathogens like Covid19, HIV and\n", + "influenza viruses. Understanding viral phylogenies helps epidemiologists\n", + "trace outbreak sources, track transmission patterns, and develop\n", + "targeted containment strategies.\n", + "\n", + "[1] Phylogenetic models incorporate molecular clock models that estimate\n", + "mutation rates over time. By calibrating these with known divergence\n", + "events from the fossil record, the timing of common ancestors can be\n", + "estimated.}" + ], + "id": "e025077d-a80e-4ae6-a9bb-4de70e173ef5" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Product Clustering\n", + "\n", + "An e-commerce company could apply hierarchical clustering to organize\n", + "their product catalog into a taxonomy tree. Products would be grouped\n", + "into increasingly specific categories - for example, Electronics might\n", + "split into Phones, Computers, etc., with Phones further dividing into\n", + "Smartphones, Feature Phones, and so on. This creates an intuitive\n", + "hierarchical organization. However, many products naturally belong in\n", + "multiple categories - for instance, running shoes could reasonably be\n", + "classified as both sporting equipment and footwear. The strict tree\n", + "structure of hierarchical clustering doesn’t allow for this kind of\n", + "multiple categorization, which is a key limitation for product\n", + "organization." + ], + "id": "b0ac7936-7e18-4c46-97b5-134b428f59ae" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Hierarchical Clustering Challenge\n", + "\n", + "Our psychological ability to form categories is far more sophisticated\n", + "than hierarchical trees. Research in cognitive science has revealed that\n", + "humans naturally form overlapping categories and learn abstract\n", + "principles that guide classification. Josh Tenenbaum’s work (see e.g.\n", + "Lake et al. (2018)) demonstrates how human concept learning combines\n", + "multiple forms of inference through hierarchical Bayesian models that\n", + "integrate similarity-based clustering with theory-based reasoning.\n", + "\n", + "In practice the simple methods we use for agglomerative clustering to\n", + "merge clusters care often used for visualisation of the cluster\n", + "structure even when we don’t expect a hieararchy to be present (like the\n", + "oil flow data and the artificial data above." + ], + "id": "49fd525b-444a-4163-84c9-02dfd3ff00c6" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## High Dimensional Data\n", + "\n", + "\\[edit\\]\n", + "\n", + "To introduce how to think about high dimensional data, we will first of\n", + "all introduce a hand written digit from the U.S. Postal Service\n", + "handwritten digit data set (originally collected from scanning\n", + "enveolopes) and used in the first convolutional neural network paper\n", + "(Le Cun et al., 1989).\n", + "\n", + "Le Cun et al. (1989) downscaled the images to $16 \\times 16$, here we\n", + "use an image at the original scale, containing 64 rows and 57 columns.\n", + "Since the pixels are binary, and the number of dimensions is 3,648, this\n", + "space contains $2^{3,648}$ possible images. So this space contains a lot\n", + "more than just one digit." + ], + "id": "d2008d33-653a-40e9-8afe-56d9064e560b" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## USPS Samples\n", + "\n", + "If we sample from this space, taking each pixel independently from a\n", + "probability which is given by the number of pixels which are ‘on’ in the\n", + "original image, over the total number of pixels, we see images that look\n", + "nothing like the original digit." + ], + "id": "9589a4da-d87a-4dd6-9239-cc0312be923e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import mlai\n", + "import pods" + ], + "id": "68dce313-4c90-48a6-9768-3df552bd7590" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=(5,5))\n", + "\n", + "pods.access.download_url(\"https://github.com/lawrennd/slides/raw/gh-pages/diagrams/ml/br1561_6.3.pgm\",\n", + " store_directory=\"./ml\")\n", + "six_image = mlai.load_pgm(\"br1561_6.3.pgm\", directory =\"./ml\")\n", + "rows = six_image.shape[0]\n", + "cols = six_image.shape[1]\n", + " \n", + "ax.imshow(six_image,interpolation='none').set_cmap('gray')\n", + "mlai.write_figure(\"dem_six000.png\", directory=\"./dimred/\")\n", + "for i in range(3):\n", + " rand_image = np.random.rand(rows, cols)<((six_image>0).sum()/float(rows*cols))\n", + " ax.imshow(rand_image,interpolation='none').set_cmap('gray')\n", + " mlai.write_figure('dem_six{i:0>3}.png'.format(i=i+1), directory=\"./dimred/\")" + ], + "id": "f3ec624d-df8b-4a7a-90b0-852347c4b43c" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipywidgets import IntSlider\n", + "import notutils as nu" + ], + "id": "3184a330-7717-45d8-af47-2e308b134dc4" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.display_plots('dem_six{counter:0>3}.png', directory='./dimred', counter=IntSlider(0, 0, 3, 1))" + ], + "id": "42d64db6-608c-450d-a4d0-b6a84604f8cb" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "Figure: Even if we sample every nano second from now until the end of\n", + "the universe we won’t see the original six again." + ], + "id": "b587243f-9ba6-44a5-9ee7-5bdcc6c53aa9" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Simple Model of Digit\n", + "\n", + "An independent pixel model for this digit doesn’t seem sensible. The\n", + "total space is enormous, and yet the space occupied by the type of data\n", + "we’re interested in is relatively small." + ], + "id": "031e2971-2669-4c63-876e-5e3e4bac7e23" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Probability of Random Image Generation\n", + "\n", + "Let’s consider a simple model for handwritten 6s. We model each pixel\n", + "independently with a binomial distribution. Each pixel is the result of\n", + "a single binomial trial, known as a Bernoulli distribution. $$\n", + "p(\\mathbf{ y}) = \\prod_{j=1}^d\\pi^{y_j}\n", + "(1-\\pi)^{(1-y_j)},\n", + "$$ where the probability across pixels are taken to be independent and\n", + "are governed by the same probability of being on, $\\pi$. The maximum\n", + "likelihood solution for this parameter is given by the number of pixels\n", + "that are on in the data point divided by the total number of pixels $$\n", + "\\pi= \\frac{849}{3,648}.\n", + "$$ The probability of recovering the original six in any given sample is\n", + "then given by $$\n", + "p(\\mathbf{ y}=\\text{given\\,image}) = \\pi^{849}(1-\\pi)^{3,648 - 849\n", + "}=2.67\\times10^{-860}.\n", + "$$\n", + "\n", + "This demonstrates why independent pixel models are inadequate for\n", + "high-dimensional data. The probability of generating the original image\n", + "by random sampling is so small ($2.67 \\times 10^{-860}$) that it’s\n", + "effectively impossible. This highlights the need for more sophisticated\n", + "models that capture the underlying structure and dependencies in the\n", + "data.\n", + "\n", + "Consider a different type of model. One where we take a prototype six\n", + "and we rotate it left and right to create new data." + ], + "id": "99c43688-6ff2-4163-8100-51d4fd63f503" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('scikit-image')" + ], + "id": "e7d1ccb9-a700-4eab-b81c-4a7f19060c48" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "334729a7-2f1c-418a-a8b0-52cec1cccc74" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from skimage.transform import rotate\n", + "import numpy as np\n", + "import mlai" + ], + "id": "da1237c8-e0e2-41f9-8711-2f02df4a4abb" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Load in the six\n", + "six_image = mlai.load_pgm('br1561_6.3.pgm', directory ='./ml')/255.0\n", + "\n", + "# Pad out to 64 columns\n", + "rows = six_image.shape[0]\n", + "six_image = np.hstack([np.ones((rows, 3)), six_image, np.ones((rows, 4))])\n", + "dim_one = np.asarray(six_image.shape)\n", + "angles = range(360)\n", + "Y = np.zeros((len(angles), np.prod(dim_one)))\n", + "for i, angle in enumerate(angles):\n", + " rot_image = rotate(six_image, angle, cval=1.0)\n", + " dim_two = np.asarray(rot_image.shape)\n", + " start = [int(round((dim_two[0] - dim_one[0])/2)), int(round((dim_two[1] - dim_one[1])/2))]\n", + " crop_image = rot_image[start[0]+np.array(range(dim_one[0])), :][:, start[1]+np.array(range(dim_one[1]))]\n", + " Y[i, :] = crop_image.flatten()" + ], + "id": "a4e73c81-29b1-481f-b6ba-bea365ef543b" + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "### Hierarchical Clustering\n", + "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "\n", - "Other approaches to clustering involve forming taxonomies of the cluster\n", - "centers, like humans apply to animals, to form trees. You can learn more\n", - "about agglomerative clustering in this video from Alex Ihler." + "show_angles = [31, 11, 350, 330, 310, 290]\n", + "for i, angle in enumerate(show_angles):\n", + " ax.imshow(np.reshape(Y[angle, :], dim_one),interpolation='none').set_cmap('gray')\n", + " ax.set_xticks(range(dim_one[1]), minor=True) # Every column\n", + " ax.set_yticks(range(dim_one[0]), minor=True) # Every row\n", + " ax.grid(True, which='minor', alpha=0.5, linewidth=0.1)\n", + " ax.tick_params(which='minor', size=0) \n", + " ax.tick_params(which='major', size=0) \n", + " ax.tick_params(labelbottom=False, labelleft=False) \n", + " \n", + " mlai.write_figure(f\"six_rotate{i:0>3}.svg\", directory=\"./dimred/\")" ], - "id": "1df7aeb1-93d8-49e5-81bc-2c7c47b90f8e" + "id": "1c8e3590-a5a5-4930-9657-e54a5c54eb53" }, { "cell_type": "code", @@ -617,126 +1717,77 @@ "metadata": {}, "outputs": [], "source": [ - "from IPython.lib.display import YouTubeVideo\n", - "YouTubeVideo('OcoE7JlbXvY')" + "import notutils as nu" ], - "id": "fb905c6f-31f4-4518-92fd-ed7de99d87bb" + "id": "1649480c-0303-41a8-8aae-5966b3363b74" }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "Figure: Hierarchical Clustering by Alex Ihler." + "nu.display_plots('six_rotate{counter:0>3}.svg', directory='./dimred', counter=(0, 5))" ], - "id": "ecc7b616-48ab-4ba3-8e47-1f8eabdedc2c" + "id": "53224f52-16bc-4472-ae78-a468c4ccd766" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Phylogenetic Trees\n", + "\n", "\n", - "Indeed, one application of machine learning techniques is performing a\n", - "hierarchical clustering based on genetic data, i.e. the actual contents\n", - "of the genome. If we do this across a number of species then we can\n", - "produce a *phylogeny*. The phylogeny aims to represent the actual\n", - "evolution of the species and some phylogenies even estimate the timing\n", - "of the common ancestor between two species[1]. Similar methods are used\n", - "to estimate the origin of viruses like AIDS or Bird flu which mutate\n", - "very quickly. Determining the origin of viruses can be important in\n", - "containing or treating outbreaks.\n", + "\n", "\n", - "[1] These models are quite a lot more complex than the simple clustering\n", - "we describe here. They represent a common ancestor through a cluster\n", - "center that is then allowed to evolve over time through a mutation rate.\n", - "The time of separation between different species is estimated via these\n", - "mutation rates." - ], - "id": "2c82e7fc-9f10-47e6-ba42-8871cdb3ba13" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Product Clustering\n", + "\n", "\n", - "An e-commerce company could apply hierarchical clustering to all its\n", - "products. That would give a phylogeny of products. Each cluster of\n", - "products would be split into sub-clusters of products until we got down\n", - "to individual products. For example, we might expect a high level split\n", - "to be Electronics/Clothing. Of course, a challenge with these tree-like\n", - "structures is that many products belong in more than one parent cluster:\n", - "for example running shoes should be in more than one group, they are\n", - "‘sporting goods’ and they are ‘apparel’. A tree structure doesn’t allow\n", - "this allocation." + "Figure: Rotate a prototype six to form a set of plausible sixes." ], - "id": "eebdbb85-829b-4f09-9314-9b493b0d96d9" + "id": "1ad95b9b-50ca-4833-ab3e-17e53b61c746" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Hierarchical Clustering Challenge\n", + "## PCA of Rotated Sixes\n", "\n", - "Our own psychological grouping capabilities are studied as a domain of\n", - "cognitive science. Researchers like Josh Tenenbaum have developed\n", - "algorithms that decompose data in more complex ways, but they can\n", - "normally only be applied to smaller data sets." + "Now let’s perform PCA on the rotated sixes to see how the data projects\n", + "into a lower dimensional space. The rotated data should lie\n", + "approximately on a one-dimensional manifold corresponding to the\n", + "rotation angle." ], - "id": "92cb55a2-234e-4567-bf99-573098263376" + "id": "ad3d8f97-4388-455a-ab1a-937631a25a46" }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "## Other Clustering Approaches\n", - "\n", - "- Spectral clustering (Shi and Malik (2000),Ng et al. (n.d.))\n", - " - Allows clusters which aren’t convex hulls.\n", - "- Dirichlet process\n", - " - A probabilistic formulation for a clustering algorithm that is\n", - " *non-parametric*.\n", - " - Loosely speaking it allows infinite clusters\n", - " - In practice useful for dealing with previously unknown species\n", - " (e.g. a “Black Swan Event”)." + "import numpy as np" ], - "id": "144f50b3-3373-4ea1-ac92-546579d1318b" + "id": "8fe91c48-4862-4129-8813-6ab2132c95f6" }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "## High Dimensional Data\n", - "\n", - "\\[edit\\]\n", + "# Center the data\n", + "Y_centered = Y - np.mean(Y, axis=0)\n", "\n", - "To introduce high dimensional data, we will first of all introduce a\n", - "hand written digit from the U.S. Postal Service handwritten digit data\n", - "set (originally collected from scanning enveolopes) and used in the\n", - "first convolutional neural network paper (Le Cun et al., 1989).\n", + "# SVD of the centered data matrix (360 x 3648)\n", + "U, s, Vt = np.linalg.svd(Y_centered, full_matrices=False)\n", "\n", - "Le Cun et al. (1989) downscaled the images to $16 \\times 16$, here we\n", - "use an image at the original scale, containing 64 rows and 57 columns.\n", - "Since the pixels are binary, and the number of dimensions is 3,648, this\n", - "space contains $2^{3,648}$ possible images. So this space contains a lot\n", - "more than just one digit." - ], - "id": "0b8fda1f-6221-4502-82c1-4a24af2becd0" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## USPS Samples\n", + "# Eigenvalues are s^2 / n \n", + "eigenvalues = s**2 / Y.shape[0]\n", "\n", - "If we sample from this space, taking each pixel independently from a\n", - "probability which is given by the number of pixels which are ‘on’ in the\n", - "original image, over the total number of pixels, we see images that look\n", - "nothing like the original digit." + "# Project data onto principal components\n", + "# Standard approach: X = U @ diag(s) gives the projections\n", + "X = U @ np.diag(s)" ], - "id": "b69081dc-66a7-4f0e-b2bb-f6d0d46f9b83" + "id": "4d7d6d17-6879-43b1-830e-4a660c682baf" }, { "cell_type": "code", @@ -744,12 +1795,57 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import mlai\n", - "import pods" - ], - "id": "fb037a70-dc0e-4e37-849b-09075bebf3c9" + "def plot_pca_manifold(X, Y, dims, filename, show_every=20, angle_ranges=None, colors=None):\n", + " \"\"\"Plot PCA manifold with optional angle range filtering\"\"\"\n", + " fig, ax = plt.subplots(figsize=plot.big_figsize)\n", + " h_points = ax.plot(X[:, 0], X[:, 1], 'rx', linewidth=2, alpha=0.7)[0]\n", + " show_indices = range(0, len(X), show_every) # Every 20 degrees\n", + " if angle_ranges is None:\n", + " # Plot all points\n", + " show_indices = range(0, len(X), show_every) # Every 20 degrees\n", + " else:\n", + " # Plot specific angle ranges\n", + " h_points.set_data([], [])\n", + " for i, (start, end, color) in enumerate(zip(angle_ranges['starts'], \n", + " angle_ranges['ends'], \n", + " colors or ['r', 'b'])):\n", + " mask = (np.arange(len(X)) >= start) & (np.arange(len(X)) < end)\n", + " ax.plot(X[mask, 0], X[mask, 1], f'{color}x', linewidth=2, alpha=0.7)\n", + " \n", + " # Show images only in specified ranges\n", + " orig_show = show_indices\n", + " show_indices = []\n", + " for start, end in zip(angle_ranges['starts'], angle_ranges['ends']):\n", + " for ind in orig_show:\n", + " if ind >= start and ind <= end:\n", + " show_indices.append(ind)\n", + " \n", + " # Add small image insets\n", + " for i in show_indices:\n", + " if i < len(X):\n", + " ax.scatter(X[i, 0], X[i, 1], s=50)\n", + " \n", + " # Calculate position for small image\n", + " x_pos, y_pos = X[i, 0], X[i, 1]\n", + " \n", + " # Get plot limits and convert to figure coordinates\n", + " x_min, x_max = ax.get_xlim()\n", + " y_min, y_max = ax.get_ylim()\n", + " \n", + " x_fig = 0.1 + 0.8 * (x_pos - x_min) / (x_max - x_min)\n", + " y_fig = 0.1 + 0.8 * (y_pos - y_min) / (y_max - y_min)\n", + " \n", + " inset_ax = fig.add_axes([x_fig, y_fig, 0.05, 0.05])\n", + " rotated_img = Y[i, :].reshape(dims)\n", + " inset_ax.imshow(rotated_img, cmap='gray')\n", + " inset_ax.axis('off')\n", + " \n", + " ax.grid(True, alpha=0.3)\n", + " \n", + " mlai.write_figure(filename, directory='./dimred/')\n", + " return fig, ax" + ], + "id": "afa49436-5bad-4289-ae1b-298773a3649b" }, { "cell_type": "code", @@ -757,33 +1853,22 @@ "metadata": {}, "outputs": [], "source": [ - "fig, ax = plt.subplots(figsize=(5,5))\n", - "\n", - "pods.access.download_url(\"https://github.com/lawrennd/slides/raw/gh-pages/diagrams/ml/br1561_6.3.pgm\",\n", - " store_directory=\"./ml\")\n", - "six_image = mlai.load_pgm(\"br1561_6.3.pgm\", directory =\"./ml\")\n", - "rows = six_image.shape[0]\n", - "col = six_image.shape[1]\n", - " \n", - "ax.imshow(six_image,interpolation='none').set_cmap('gray')\n", - "mlai.write_figure(\"dem_six000.png\", directory=\"./dimred/\")\n", - "for i in range(3):\n", - " rand_image = np.random.rand(rows, col)<((six_image>0).sum()/float(rows*col))\n", - " ax.imshow(rand_image,interpolation='none').set_cmap('gray')\n", - " mlai.write_figure('dem_six{i:0>3}.png'.format(i=i+1), directory=\"./dimred/\")" + "fig, ax = plot_pca_manifold(X, Y, dim_one, 'six_manifold_001.svg')" ], - "id": "3e35f03f-855c-4973-a937-9bf21b771b31" + "id": "dd4f0808-16ef-4a90-aaa9-bbf68a8fa00d" }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "from ipywidgets import IntSlider\n", - "import notutils as nu" + "\n", + "\n", + "Figure: The rotated sixes projected onto the first two principal\n", + "components. The data lives on a one-dimensional manifold in the\n", + "high-dimensional space, forming approximately a circle in the 2D\n", + "projection." ], - "id": "ee57664d-b57d-46e5-8e15-28135ca8d214" + "id": "804cb8d7-705e-4851-a2f2-411cec5aa76c" }, { "cell_type": "code", @@ -791,56 +1876,40 @@ "metadata": {}, "outputs": [], "source": [ - "nu.display_plots('dem_six{counter:0>3}.png', directory='./dimred', counter=IntSlider(0, 0, 3, 1))" + "fig, ax = plot_pca_manifold(X, Y, dim_one, 'six_manifold_002.svg',\n", + " angle_ranges={'starts': [0, 135, 315], 'ends': [45, 225, 360]},\n", + " colors=['r', 'b', 'r'])" ], - "id": "ea09b8f5-5d9c-42c3-8268-cc19581f06a4" + "id": "61c9367c-d5bd-4fac-9cae-5c36f01359ec" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "Figure: Even if we sample every nano second from now until the end of\n", - "the universe we won’t see the original six again.\n", - "\n", - "Even if we sample every nanosecond from now until the end of the\n", - "universe you won’t see the original six again." + "Figure: The rotated sixes projected onto the first two principal\n", + "components as sixes and nines. The six and nine data live on two\n", + "separate one-dimensional manifolds in the high-dimensional space,\n", + "forming two arcs from a circle in the 2D projection." ], - "id": "a9a22cb5-1147-4180-a8a2-4d9f74339887" + "id": "687e0e11-867b-4e79-8619-8a968a21f6d8" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Simple Model of Digit\n", - "\n", - "So, an independent pixel model for this digit doesn’t seem sensible. The\n", - "total space is enormous, and yet the space occupied by the type of data\n", - "we’re interested in is relatively small.\n", + "## Interpoint Distances for Rotated Sixes\n", "\n", - "Consider a different type of model. One where we take a prototype six\n", - "and we rotate it left and right to create new data." + "In this section we take all 360 examples of the rotated digits and\n", + "analyze their interpoint distances. The data set is inherently one\n", + "dimensional, with the dimension associated with the rotation\n", + "transformation. A pure rotation would lead to a pure circle in the\n", + "projected space. In practice, rotation of images requires some\n", + "interpolation and this leads to small corruptions of the latent\n", + "projections away from the circle." ], - "id": "dc02595a-7383-4e56-b963-f66778378042" + "id": "e3b6d285-a42d-4553-a394-cd7f3feaf444" }, { "cell_type": "code", @@ -848,9 +1917,13 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install scikit-image" + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from sklearn.decomposition import PCA\n", + "import mlai\n", + "import mlai.plot as plot" ], - "id": "77e5d7bf-af51-4229-aadf-8a7134d18d53" + "id": "8a6b8a9e-40e8-4ce0-93c2-044e00c7a0bc" }, { "cell_type": "code", @@ -858,9 +1931,26 @@ "metadata": {}, "outputs": [], "source": [ - "from skimage.transform import rotate" + "# Calculate variance of each dimension\n", + "varY = np.var(Y, axis=0)\n", + "# Sort by variance in descending order\n", + "sorted_indices = np.argsort(varY)[::-1]\n", + "# Take top q dimensions (e.g., q=50 for dimensionality reduction)\n", + "q = 50\n", + "order = sorted_indices[:q]\n", + "\n", + "# Calculate interpoint distances for selected dimensions\n", + "Y_selected = Y[:, order]\n", + "# Compute squared distances between all pairs\n", + "distMat = np.zeros((Y.shape[0], Y.shape[0]))\n", + "for i in range(Y.shape[0]):\n", + " for j in range(Y.shape[0]):\n", + " distMat[i, j] = np.sum((Y_selected[i, :] - Y_selected[j, :])**2) / q\n", + "\n", + "# Calculate error term\n", + "e = 2 * np.sum(varY[sorted_indices[q:]]) / Y.shape[1]" ], - "id": "796654bc-1bd6-4475-92be-6cbc790d3fdd" + "id": "a9063d0c-d059-40d8-acf9-a7598fbc5627" }, { "cell_type": "code", @@ -868,20 +1958,38 @@ "metadata": {}, "outputs": [], "source": [ - "six_image = mlai.load_pgm('br1561_6.3.pgm', directory ='./ml')\n", - "six_image = np.hstack([np.zeros((rows, 3)), six_image, np.zeros((rows, 4))])\n", - "dim_one = np.asarray(six_image.shape)\n", - "angles = range(360)\n", - "i = 0\n", - "Y = np.zeros((len(angles), np.prod(dim_one)))\n", - "for angle in angles:\n", - " rot_image = rotate(six_image, angle, mode='nearest')\n", - " dim_two = np.asarray(rot_image.shape)\n", - " start = [int(round((dim_two[0] - dim_one[0])/2)), int(round((dim_two[1] - dim_one[1])/2))]\n", - " crop_image = rot_image[start[0]+np.array(range(dim_one[0])), :][:, start[1]+np.array(range(dim_one[1]))]\n", - " Y[i, :] = crop_image.flatten()" + "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", + "\n", + "# Create heatmap of interpoint distances\n", + "im = ax.imshow(distMat, cmap='RdBu_r', aspect='equal', origin='upper', \n", + " extent=[0, 360, 360, 0]) # extent: [left, right, bottom, top]\n", + "ax.set_xticks([0, 90, 180, 270, 360])\n", + "ax.set_yticks([0, 90, 180, 270, 360])\n", + "ax.set_xlabel('Rotation Angle (degrees)')\n", + "ax.set_ylabel('Rotation Angle (degrees)')\n", + "\n", + "# Add colorbar\n", + "cbar = plt.colorbar(im, ax=ax)\n", + "cbar.set_label('Squared Distance')\n", + "\n", + "mlai.write_figure('dem-six-distances-360.svg', directory='./dimred/')" + ], + "id": "fa1387a9-ac64-4a6e-b96e-c1f0476345f2" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Inter-point squared distances for the rotated digits data.\n", + "Much of the data structure can be seen in the matrix. All points are\n", + "ordered by angle of rotation. We can see that the distance between two\n", + "points with similar angle of rotation is small (note in the upper right\n", + "and lower left corners the low distances associated with 6s rotated by\n", + "roughly 360 degrees and unrotated 6s)." ], - "id": "885607e8-924d-41f4-b3c3-889e9db90b5a" + "id": "69b53901-e7c2-4d7a-bc8c-8bfb2a1bc344" }, { "cell_type": "code", @@ -889,9 +1997,9 @@ "metadata": {}, "outputs": [], "source": [ - "import notutils as nu" + "import mlai.plot" ], - "id": "1c3e4ea4-7a28-4f7b-9c9b-ef0311377b12" + "id": "ecc0f14a-7b82-46fb-baf1-5fa651ae1241" }, { "cell_type": "code", @@ -899,56 +2007,20 @@ "metadata": {}, "outputs": [], "source": [ - "nu.display_plots('dem_six_rotate{counter:0>3}.png', directory='./dimred', counter=(0, 6))" + "plot.squared_distances(Y, 'rotated-sixes', 'Rotated sixes', directory='./dimred')" ], - "id": "56427ef7-9904-45b9-be74-a6aa6284ed85" + "id": "d0253a3e-aef1-41b5-9457-84ffa2318586" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "
\n", - "\n", - "Figure: Rotate a prototype six to form a set of plausible sixes.\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "Figure: The rotated sixes projected onto the first two principal\n", - "components of the ‘rotated data set’. The data lives on a one\n", - "dimensional manifold in the 3,648 dimensional space." + "Figure: Hisgogram of squared distances vs theory for the rotated\n", + "sixes." ], - "id": "faba79fc-c073-48e9-a8ac-cbcf74b5e8dd" + "id": "fb34f2ff-2c11-4c8d-bbcc-069dec1452b6" }, { "cell_type": "markdown", @@ -965,7 +2037,7 @@ "deal with high dimensional data by looking for a lower dimensional\n", "(non-linear) embedding." ], - "id": "73a30ee3-614f-4e05-bae2-058619f8c41f" + "id": "7d242340-e280-4e4e-bae2-926c79d54905" }, { "cell_type": "markdown", @@ -1019,8 +2091,8 @@ "also latent variables.\n", "\n", "In statistical analysis of experiments you try to control for each\n", - "aspect of the experiment, in particular by *randomization*. So if I’m\n", - "interested in the ability of a particular fertilizer to improve the\n", + "aspect of the experiment, in particular by *randomisation*. So if I’m\n", + "interested in the ability of a particular fertiliser to improve the\n", "yield of a particular plant I may design an experiment where I apply the\n", "fertilizer to some plants (the treatment group) and withold the\n", "fertilizer from others (the control group). I then test to see whether\n", @@ -1030,10 +2102,10 @@ "plants in a sunny part of the field, and all the control plants in a\n", "shady part of the field. That would also be a latent variable, in this\n", "case known as a *confounder*. In statistical experimental design\n", - "*randomization* is used to attempt to eliminate the correlated effects\n", + "*randomisation* is used to attempt to eliminate the correlated effects\n", "of these confounders: you aim to ensure that if these confounders *do*\n", "exist their effects are not correlated with treatment and contorl. This\n", - "is known as a [randomized control\n", + "is known as a [randomised control\n", "trial](http://en.wikipedia.org/wiki/Randomized_controlled_trial).\n", "\n", "Greek philosophers worried a great deal about what was knowable and what\n", @@ -1063,13 +2135,13 @@ "a simple linear system and see how it relates to *factor analysis* and\n", "*principal component analysis*." ], - "id": "c13a3d5b-68f3-474b-bea8-3b21b6c0a02f" + "id": "1467a768-95c3-4143-9ff5-7c7781e819f6" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Your Personality\n", + "## Your Personality\n", "\n", "At the beginning of the 20th century there was a great deal of interest\n", "amoungst psychologists in formalizing patterns of thought. The approach\n", @@ -1085,7 +2157,7 @@ "high dimensional characteristics of the individual into a few latent\n", "factors." ], - "id": "17d5bc17-3adf-4fd8-bdf4-311cb57045e2" + "id": "647771f7-b8a8-47c9-8603-1f7e9ceeb6ef" }, { "cell_type": "markdown", @@ -1113,7 +2185,7 @@ "\\mathbf{f}(\\mathbf{ z}) =\n", "\\begin{bmatrix} f_1(\\mathbf{ z}) \\\\ f_2(\\mathbf{ z}) \\\\ \\vdots \\\\\n", "f_p(\\mathbf{ z})\\end{bmatrix}\n", - "$$ where there are $p$ features associated with the individual. If we\n", + "$$ where there are $d$ features associated with the individual. If we\n", "consider any of these functions individually we have a prediction\n", "function that looks like a regression model, $$\n", "f_j(\\mathbf{ z}) =\n", @@ -1131,7 +2203,7 @@ "Just as in linear regression we can assume that this corruption is given\n", "by Gaussian noise, where the noise for the $j$th element of\n", "$\\mathbf{ y}$ is by, $$\n", - "\\epsilon_j \\sim \\mathcal{N}\\left(0,\\sigma^2_j\\right).\n", + "\\epsilon_j \\sim \\mathscr{N}\\left(0,\\sigma^2_j\\right).\n", "$$ Of course, just as in a regression problem we also need to make an\n", "assumption across the individual data points to form our full\n", "likelihood. Our data set now consists of many observations of\n", @@ -1141,9 +2213,7 @@ "$\\mathbf{ y}$ is a vector derived from a row of $\\mathbf{Y}$ we\n", "represent the observation of the features associated with the $i$th\n", "individual by $\\mathbf{ y}_{i, :}$, and place each individual in our\n", - "data matrix,\n", - "\n", - "$$\n", + "data matrix, $$\n", "\\mathbf{Y}\n", "= \\begin{bmatrix} \\mathbf{ y}_{1, :}^\\top \\\\ \\mathbf{ y}_{2, :}^\\top \\\\ \\vdots \\\\\n", "\\mathbf{ y}_{n, :}^\\top\\end{bmatrix},\n", @@ -1158,7 +2228,7 @@ "the function we have $\\mathbf{F}\\in \\Re^{n\\times p}$,\n", "$\\mathbf{W}\\in \\Re^{p \\times q}$ and $\\mathbf{Z}\\in \\Re^{n\\times q}$." ], - "id": "f23c2a9f-1a0c-4ea4-b61f-45aced956a4b" + "id": "77069fbb-4a18-4e6c-b3f6-e2db863dae29" }, { "cell_type": "markdown", @@ -1177,7 +2247,7 @@ "\\mathbf{ z}_{i, :}\n", "$$" ], - "id": "4b876711-0d66-494c-aa13-e6e852d7c031" + "id": "d4bcde0f-e223-4658-b6d6-a1c16c6b3f4b" }, { "cell_type": "markdown", @@ -1187,7 +2257,7 @@ "\n", "Write your answer to Exercise 1 here" ], - "id": "5be2ae8a-4060-4696-a1d5-7370370c8cc3" + "id": "8593748a-8102-4acb-8018-2d96ade07b2a" }, { "cell_type": "markdown", @@ -1205,20 +2275,20 @@ "data points, and across those associated with different data features,\n", "so we have, $$\n", "x_{i,j} \\sim\n", - "\\mathcal{N}\\left(0,1\\right),\n", + "\\mathscr{N}\\left(0,1\\right),\n", "$$ and we can write the density governing the latent variable associated\n", "with a single point as, $$\n", - "\\mathbf{ z}_{i, :} \\sim \\mathcal{N}\\left(\\mathbf{0},\\mathbf{I}\\right).\n", + "\\mathbf{ z}_{i, :} \\sim \\mathscr{N}\\left(\\mathbf{0},\\mathbf{I}\\right).\n", "$$ If we consider the values of the function for the $i$th data point as\n", "$$\n", "\\mathbf{f}_{i, :} =\n", "\\mathbf{f}(\\mathbf{ z}_{i, :}) = \\mathbf{W}\\mathbf{ z}_{i, :} \n", "$$ then we can use the rules for multivariate Gaussian relationships to\n", "write that $$\n", - "\\mathbf{f}_{i, :} \\sim \\mathcal{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top\\right)\n", + "\\mathbf{f}_{i, :} \\sim \\mathscr{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top\\right)\n", "$$ which implies that the distribution for $\\mathbf{ y}_{i, :}$ is given\n", "by $$\n", - "\\mathbf{ y}_{i, :} = \\sim \\mathcal{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\boldsymbol{\\Sigma}\\right)\n", + "\\mathbf{ y}_{i, :} = \\sim \\mathscr{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\boldsymbol{\\Sigma}\\right)\n", "$$ where $\\boldsymbol{\\Sigma}$ the covariance of the noise variable,\n", "$\\epsilon_{i, :}$ which for factor analysis is a diagonal matrix\n", "(because we have assumed that the noise was *independent* across the\n", @@ -1252,7 +2322,7 @@ "with the measurements of “the abilities of man” and is credited with the\n", "earliest version of factor analysis." ], - "id": "5916eee4-75b7-40c6-bdcd-1075a4596c63" + "id": "41ff41d7-2ba3-439e-921f-60de23966772" }, { "cell_type": "markdown", @@ -1270,7 +2340,7 @@ "other words he took $\\sigma^2_i \\rightarrow 0$ so that he had\n", "\n", "$$\n", - "\\mathbf{ y}_{i, :} \\sim \\lim_{\\sigma^2 \\rightarrow 0} \\mathcal{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}\\right).\n", + "\\mathbf{ y}_{i, :} \\sim \\lim_{\\sigma^2 \\rightarrow 0} \\mathscr{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}\\right).\n", "$$ The paper had two unfortunate effects. Firstly, the resulting model\n", "is no longer valid probablistically, because the covariance of this\n", "Gaussian is ‘degenerate’. Because $\\mathbf{W}\\mathbf{W}^\\top$ has rank\n", @@ -1309,7 +2379,7 @@ "are given by an *eigenvalue decomposition* of the empirical covariance\n", "matrix." ], - "id": "1f6c0a08-e003-4da3-ad9e-6ff6d09188f6" + "id": "f9b20081-a369-4e4a-9d9a-e4a61a572855" }, { "cell_type": "markdown", @@ -1318,26 +2388,26 @@ "## Computation of the Marginal Likelihood\n", "\n", "$$\n", - "\\mathbf{ y}_{i,:}=\\mathbf{W}\\mathbf{ z}_{i,:}+\\boldsymbol{ \\epsilon}_{i,:},\\quad \\mathbf{ z}_{i,:} \\sim \\mathcal{N}\\left(\\mathbf{0},\\mathbf{I}\\right), \\quad \\boldsymbol{ \\epsilon}_{i,:} \\sim \\mathcal{N}\\left(\\mathbf{0},\\sigma^{2}\\mathbf{I}\\right)\n", + "\\mathbf{ y}_{i,:}=\\mathbf{W}\\mathbf{ z}_{i,:}+\\boldsymbol{ \\epsilon}_{i,:},\\quad \\mathbf{ z}_{i,:} \\sim \\mathscr{N}\\left(\\mathbf{0},\\mathbf{I}\\right), \\quad \\boldsymbol{ \\epsilon}_{i,:} \\sim \\mathscr{N}\\left(\\mathbf{0},\\sigma^{2}\\mathbf{I}\\right)\n", "$$\n", "\n", "$$\n", - "\\mathbf{W}\\mathbf{ z}_{i,:} \\sim \\mathcal{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top\\right)\n", + "\\mathbf{W}\\mathbf{ z}_{i,:} \\sim \\mathscr{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top\\right)\n", "$$\n", "\n", "$$\n", - "\\mathbf{W}\\mathbf{ z}_{i, :} + \\boldsymbol{ \\epsilon}_{i, :} \\sim \\mathcal{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}\\right)\n", + "\\mathbf{W}\\mathbf{ z}_{i, :} + \\boldsymbol{ \\epsilon}_{i, :} \\sim \\mathscr{N}\\left(\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}\\right)\n", "$$\n", "\n", - "**Probabilistic PCA Max. Likelihood Soln** (Tipping and Bishop (1999a))\n", + "*Probabilistic PCA Max. Likelihood Soln* (Tipping and Bishop (1999a))\n", "\n", - "\n", + "\n", "\n", "Figure: Graphical model representing probabilistic PCA.\n", "\n", - "$$p\\left(\\mathbf{Y}|\\mathbf{W}\\right)=\\prod_{i=1}^{n}\\mathcal{N}\\left(\\mathbf{ y}_{i, :}|\\mathbf{0},\\mathbf{W}\\mathbf{W}^{\\top}+\\sigma^{2}\\mathbf{I}\\right)$$" + "$$p\\left(\\mathbf{Y}|\\mathbf{W}\\right)=\\prod_{i=1}^{n}\\mathscr{N}\\left(\\mathbf{ y}_{i, :}|\\mathbf{0},\\mathbf{W}\\mathbf{W}^{\\top}+\\sigma^{2}\\mathbf{I}\\right)$$" ], - "id": "269a7cd4-89ad-45a3-8b22-da96ec8e574e" + "id": "cbb11d4c-432d-4ec0-8ada-5e496541865c" }, { "cell_type": "markdown", @@ -1364,7 +2434,7 @@ "diagonalisation is very important because it returns the covariance to a\n", "form where there is no correlation between points." ], - "id": "95440578-18e0-4d34-9640-22731d0edf6f" + "id": "b4d64753-cbc2-4534-8566-a5e9435f4abf" }, { "cell_type": "markdown", @@ -1396,7 +2466,7 @@ "no vector $\\mathbf{ w}$, other than the zero vector, which causes the\n", "vector $\\mathbf{b}$ to be all zeros." ], - "id": "7d3faaa1-d605-4650-be81-cd15b467c41f" + "id": "8c2ba261-5a36-43e0-8fea-90e0b54ce3f4" }, { "cell_type": "markdown", @@ -1410,7 +2480,7 @@ "that (i) the matrix is symmetric and (ii) the matrix has positive\n", "elements along its diagonal." ], - "id": "658f758f-32bb-4d75-ae12-6fb486ee8023" + "id": "92d0057c-9912-47bc-ba9c-739090531cc2" }, { "cell_type": "markdown", @@ -1420,7 +2490,7 @@ "\n", "Write your answer to Exercise 2 here" ], - "id": "596cb79d-1cea-4cda-9a15-caa0f22409f8" + "id": "27894e38-2550-413f-8515-527dd41ff917" }, { "cell_type": "markdown", @@ -1435,7 +2505,7 @@ "$\\mathbf{u}_{:, i} ^\\top \\mathbf{u}_{:, j}$ is equal to 0 if $i\\neq j$\n", "and 1 if $i=j$." ], - "id": "0e8b39e6-14de-4402-b0ed-41562a68babe" + "id": "07f54b96-9379-4c98-88e6-0c5cf51c4251" }, { "cell_type": "markdown", @@ -1447,90 +2517,275 @@ "style=\"\">\\[edit\\]\n", "\n", - "- PCA (Hotelling (1933)) is a linear embedding.\n", - "\n", - "- Today its presented as:\n", - "\n", - " - Rotate to find ‘directions’ in data with maximal variance.\n", - " - How do we find these directions?\n", - "\n", - "- Algorithmically we do this by diagonalizing the sample covariance\n", - " matrix $$\n", - " \\mathbf{S}=\\frac{1}{n}\\sum_{i=1}^n\\left(\\mathbf{ y}_{i, :}-\\boldsymbol{ \\mu}\\right)\\left(\\mathbf{ y}_{i, :} - \\boldsymbol{ \\mu}\\right)^\\top\n", - " $$\n", - "\n", - "- Find directions in the data, $\\mathbf{ z}= \\mathbf{U}\\mathbf{ y}$,\n", - " for which variance is maximized.\n", - "\n", - "- Solution is found via constrained optimisation (which uses [Lagrange\n", - " multipliers](https://en.wikipedia.org/wiki/Lagrange_multiplier)): $$\n", - " L\\left(\\mathbf{u}_{1},\\lambda_{1}\\right)=\\mathbf{u}_{1}^{\\top}\\mathbf{S}\\mathbf{u}_{1}+\\lambda_{1}\\left(1-\\mathbf{u}_{1}^{\\top}\\mathbf{u}_{1}\\right)\n", - " $$\n", - "\n", - "- Gradient with respect to $\\mathbf{u}_{1}$\n", - " $$\\frac{\\text{d}L\\left(\\mathbf{u}_{1},\\lambda_{1}\\right)}{\\text{d}\\mathbf{u}_{1}}=2\\mathbf{S}\\mathbf{u}_{1}-2\\lambda_{1}\\mathbf{u}_{1}$$\n", - " rearrange to form\n", - " $$\\mathbf{S}\\mathbf{u}_{1}=\\lambda_{1}\\mathbf{u}_{1}.$$ Which is\n", - " known as an [*eigenvalue\n", - " problem*](https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors).\n", - "\n", - "- Further directions that are *orthogonal* to the first can also be\n", - " shown to be eigenvectors of the covariance.\n", - "\n", - "- Represent data, $\\mathbf{Y}$, with a lower dimensional set of latent\n", - " variables $\\mathbf{Z}$.\n", - "\n", - "- Assume a linear relationship of the form $$\n", - " \\mathbf{ y}_{i,:}=\\mathbf{W}\\mathbf{ z}_{i,:}+\\boldsymbol{ \\epsilon}_{i,:},\n", - " $$ where $$\n", - " \\boldsymbol{ \\epsilon}_{i,:} \\sim \\mathcal{N}\\left(\\mathbf{0},\\sigma^2\\mathbf{I}\\right)\n", - " $$\n", - "\n", - "**Probabilistic PCA**\n", - "\n", - "\n", - "\n", - "`\n", - " `{=html} \n", - "\n", - "
\n", - "\n", - "- Define *linear-Gaussian relationship* between latent variables and\n", - " data.\n", - "- **Standard** Latent variable approach:\n", - " - Define Gaussian prior over *latent space*, $\\mathbf{Z}$.\n", - "- Integrate out *latent variables*. `{=html} `\n", - "\n", - "\n", - "\n", - "Figure: Graphical model representing probabilistic PCA.\n", - "\n", - "$$\n", - "p\\left(\\mathbf{Y}|\\mathbf{Z},\\mathbf{W}\\right)=\\prod_{i=1}^{n}\\mathcal{N}\\left(\\mathbf{ y}_{i,:}|\\mathbf{W}\\mathbf{ z}_{i,:},\\sigma^2\\mathbf{I}\\right)\n", - "$$\n", + "Principal Component Analysis (PCA) is one of the most fundamental and\n", + "widely used dimensionality reduction techniques. While commonly credited\n", + "to Pearson (1901), who was interested in finding “lines and planes of\n", + "closest fit to systems of points in space”, the method as we will review\n", + "it today was introduced and *named* by Hotelling (Hotelling (1933)). The\n", + "approach is often used as a way of looking for correlations, but\n", + "Hotelling’s motivation was a principled alternative to Spearman’s factor\n", + "analysis (Spearman, 1904).\n", + "\n", + "Often PCA is introduced as a method for finding directions of maximum\n", + "variance in high-dimensional data, and this is the interpretation that\n", + "is due to Pearson (1901), but philosophically these approaches arre\n", + "different even though they turn out to be identical mathematically. In a\n", + "very real sense “many models lead to the PCA algorithm”. But these\n", + "equivalences are only true when a *linear* interpretation is sought.\n", + "Nonlinear extensions of these ideas (maximum variance directions,\n", + "eigenvalue problems, latent variable models) all lead to *different*\n", + "algorithms. However, since the linear algorithm has so many\n", + "interpretations it is a wise place to begin analysis.\n", + "\n", + "The mathematical foundation of PCA relies on analyzing the sample\n", + "covariance matrix of the data. For a dataset with $n$ points, this\n", + "matrix is given by:\n", "\n", "$$\n", - "p\\left(\\mathbf{Z}\\right)=\\prod_{i=1}^{n}\\mathcal{N}\\left(\\mathbf{ z}_{i,:}|\\mathbf{0},\\mathbf{I}\\right)\n", + "\\mathbf{S}=\\frac{1}{n}\\sum_{i=1}^n\\left(\\mathbf{ y}_{i, :}-\\boldsymbol{ \\mu}\\right)\\left(\\mathbf{ y}_{i, :} - \\boldsymbol{ \\mu}\\right)^\\top\n", "$$\n", "\n", - "$$\n", - "p\\left(\\mathbf{Y}|\\mathbf{W}\\right)=\\prod_{i=1}^{n}\\mathcal{N}\\left(\\mathbf{ y}_{i,:}|\\mathbf{0},\\mathbf{W}\\mathbf{W}^{\\top}+\\sigma^{2}\\mathbf{I}\\right)\n", - "$$\n", + "The modern interpretation focuses on finding a set of orthogonal\n", + "directions (principal components) along which the data varies the most,\n", + "but Hotelling’s original formulation was derived from the idea of latent\n", + "variables. The optimization problem we solve today, which maximizes the\n", + "variance along each direction while maintaining orthogonality\n", + "constraints, arises as the solution of the original latent variable\n", + "formulation (Tipping and Bishop, 1999a)." + ], + "id": "23bf0a25-f788-4c8f-aadb-7dffdae9c32e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ], + "id": "3c1d6719-e205-48d9-a4c4-f3939cddc554" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Generate correlated 2D Gaussian samples\n", + "num_points = 200\n", + "mean = np.array([0, 0])\n", + "cov = np.array([[2.0, 1.8], \n", + " [1.8, 2.0]])\n", + "X = np.random.multivariate_normal(mean, cov, num_points)" + ], + "id": "2811598e-7e75-4bf0-91f1-be972db58218" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import mlai.plot as plot" + ], + "id": "763e7ba4-542f-4e79-97e3-3e6e7c951a11" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def pca_plot(ax, X):\n", + " # Compute eigenvalues and eigenvectors of covariance matrix\n", + " eigvals, eigvecs = np.linalg.eigh(np.cov(X.T))\n", + " \n", + " # Plot data points\n", + " ax.scatter(X[:, 0], X[:, 1], alpha=0.5)\n", + " ax.set_xlabel('$x_1$')\n", + " ax.set_ylabel('$x_2$')\n", + " ax.axis('equal')\n", + "\n", + " mlai.write_figure(\"pca-directions-000.svg\", directory='./dimred')\n", + "\n", + " # Plot covariance ellipse\n", + " # Get eigenvalues and eigenvectors of covariance matrix\n", + " eigenvals, eigenvecs = np.linalg.eigh(cov)\n", + " \n", + " # Calculate angle of rotation from largest eigenvector\n", + " angle = np.arctan2(eigenvecs[1,1], eigenvecs[0,1])\n", + " \n", + " # Calculate width and height of ellipse from eigenvalues\n", + " width = 2 * np.sqrt(eigenvals[1]) # 2 std deviations\n", + " height = 2 * np.sqrt(eigenvals[0])\n", + " \n", + " # Create and add ellipse patch\n", + " ellipse = plt.matplotlib.patches.Ellipse(mean, width, height,\n", + " angle=angle * 180/np.pi,\n", + " facecolor='none',\n", + " edgecolor='r',\n", + " linestyle='--')\n", + " ax.add_patch(ellipse)\n", + " mlai.write_figure(\"pca-directions-001.svg\", directory='./dimred')\n", + " \n", + " # Plot principal components\n", + " counter = 1\n", + " for i in [0, 1]:\n", + " counter += 1\n", + " ax.arrow(mean[0], mean[1], \n", + " eigvecs[0, i]*np.sqrt(eigvals[i]), \n", + " eigvecs[1, i]*np.sqrt(eigvals[i]),\n", + " head_width=0.1, head_length=0.1, fc='k', ec='k')\n", + " mlai.write_figure(f\"pca-directions-{counter:03d}.svg\", directory='./dimred')" + ], + "id": "11adeeb3-4942-460c-852e-fd9d03c17402" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", + "pca_plot(ax, X)" + ], + "id": "ccfe88aa-2111-4da0-a709-152fb147cdad" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import notutils as nu" + ], + "id": "3cc192d6-5f23-4898-9fa8-9fd7c0111c8d" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.display_plots(\"pca-directions-{counter:0>3}.svg\", directory=\"./dimred\", counter=(0, 3))" + ], + "id": "3b1b973f-c735-45d1-9cdb-a751f219958b" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", "\n", - "
" + "Figure: Illustration of PCA on 2D correlated Gaussian data. The\n", + "arrows show the principal components (eigenvectors scaled by square root\n", + "of eigenvalues). The ellipse represents the covariance structure." ], - "id": "21394105-596f-4d5c-b918-4dd0c518bc2e" + "id": "af1c6343-fb24-49ee-85f3-37f19022c73e" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Probabilistic PCA\n", + "## Probabilistic PCA\n", "\n", "\\[edit\\]\n", "\n", + "This linear relationship between the observed data and the latent\n", + "variables is at the heart of Hotelling’s original formulation of PCA. It\n", + "explicitly models the idea that the observed high-dimensional data is\n", + "generated from a lower-dimensional set of latent variables, with some\n", + "added noise. This perspective aligns PCA more closely with factor\n", + "analysis and highlights its nature as a latent variable model.\n", + "\n", + "Probabilistic PCA (PPCA) provides a probabilistic interpretation of PCA\n", + "by explicitly modeling the generative process that creates the observed\n", + "data. The model assumes that each high-dimensional data point\n", + "$\\mathbf{ y}_{i,:}$ is generated from a lower-dimensional latent\n", + "variable $\\mathbf{ z}_{i,:}$ through a linear transformation with added\n", + "Gaussian noise: $$\n", + "\\mathbf{ y}_{i,:} = \\mathbf{W}\\mathbf{ z}_{i,:} + \\boldsymbol{ \\epsilon}_{i,:}\n", + "$$ where\n", + "$\\boldsymbol{ \\epsilon}_{i,:} \\sim \\mathscr{N}\\left(\\mathbf{0},\\sigma^2\\mathbf{I}\\right)$\n", + "represents isotropic Gaussian noise. The model places a standard\n", + "Gaussian prior over the latent variables: $$\n", + "p(\\mathbf{ z}_{i,:}) = \\mathscr{N}\\left(\\mathbf{ z}_{i,:}|\\mathbf{0},\\mathbf{I}\\right)\n", + "$$ Given these assumptions, the conditional probability of observing a\n", + "data point given its latent representation is: $$\n", + "p(\\mathbf{ y}_{i,:}|\\mathbf{ z}_{i,:},\\mathbf{W}) = \\mathscr{N}\\left(\\mathbf{ y}_{i,:}|\\mathbf{W}\\mathbf{ z}_{i,:},\\sigma^2\\mathbf{I}\\right)\n", + "$$ By integrating out the latent variables, we obtain the marginal\n", + "likelihood of the data: $$\n", + "p(\\mathbf{ y}_{i,:}|\\mathbf{W}) = \\mathscr{N}\\left(\\mathbf{ y}_{i,:}|\\mathbf{0},\\mathbf{W}\\mathbf{W}^{\\top}+\\sigma^{2}\\mathbf{I}\\right)\n", + "$$ This probabilistic formulation, developed by Tipping and Bishop\n", + "(Tipping and Bishop, 1999a), provides a principled framework that not\n", + "only recovers classical PCA as a special case when $\\sigma^2 \\to 0$, but\n", + "also enables extensions like handling missing data and mixture models." + ], + "id": "6a6dfc00-86f4-4554-b997-3ef16ce6eb0d" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('daft')" + ], + "id": "37b14020-69d6-4d01-aee4-b3c94530a700" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "fae22368-f88a-4bcd-bfd2-be655e1af487" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import plot\n", + "import mlai\n", + "from matplotlib import pyplot as plt" + ], + "id": "452554e9-7b03-4667-983f-519d93349827" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pgm = plot.ppca_graphical_model()\n", + "filename = mlai.filename_join('ppca_graphical_model.svg', directory='./dimred')\n", + "pgm.render().figure.savefig(filename, transparent=True)" + ], + "id": "bd2e160d-9b25-4538-aacd-a68156939514" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Graphical model representing probabilistic PCA." + ], + "id": "dcc14cab-2d6d-4ed4-b76e-e43d35f3a6d2" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Probabilistic PCA\n", + "\n", "In 1997 [Tipping and\n", "Bishop](http://research.microsoft.com/pubs/67218/bishop-ppca-jrss.pdf)\n", "(Tipping and Bishop, 1999b) and\n", @@ -1541,7 +2796,7 @@ "\\boldsymbol{\\Sigma} = \\sigma^2 \\mathbf{I}.\n", "$$ This leads to a marginal likelihood of the form $$\n", "p(\\mathbf{Y}|\\mathbf{W}, \\sigma^2)\n", - "= \\prod_{i=1}^n\\mathcal{N}\\left(\\mathbf{ y}_{i, :}|\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}\\right)\n", + "= \\prod_{i=1}^n\\mathscr{N}\\left(\\mathbf{ y}_{i, :}|\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}\\right)\n", "$$ where the limit of $\\sigma^2\\rightarrow 0$ is *not* taken. This\n", "defines a proper probabilistic model. Tippping and Bishop then went on\n", "to prove that the *maximum likelihood* solution of this model with\n", @@ -1551,10 +2806,9 @@ "\\mathbf{W}= \\mathbf{U}\\mathbf{L} \\mathbf{R}^\\top\n", "$$ where $\\mathbf{U}$ is the eigenvectors of the empirical covariance\n", "matrix $$\n", - "\\mathbf{S} = \\sum_{i=1}^n(\\mathbf{ y}_{i, :} - \\boldsymbol{ \\mu})(\\mathbf{ y}_{i,\n", - ":} - \\boldsymbol{ \\mu})^\\top,\n", + "\\mathbf{S}= \\sum_{i=1}^n(\\mathbf{ y}_{i, :} - \\boldsymbol{ \\mu})(\\mathbf{ y}_{i,:} - \\boldsymbol{ \\mu})^\\top,\n", "$$ which can be written\n", - "$\\mathbf{S} = \\frac{1}{n} \\mathbf{Y}^\\top\\mathbf{Y}$ if the data is zero\n", + "$\\mathbf{S}= \\frac{1}{n} \\mathbf{Y}^\\top\\mathbf{Y}$ if the data is zero\n", "mean. The matrix $\\mathbf{L}$ is diagonal and is dependent on the\n", "*eigenvalues* of $\\mathbf{S}$, $\\boldsymbol{\\Lambda}$. If the $i$th\n", "diagonal element of this matrix is given by $\\lambda_i$ then the\n", @@ -1564,7 +2818,29 @@ "larger than any particular eigenvalue, then that eigenvalue (along with\n", "its corresponding eigenvector) is *discarded* from the solution." ], - "id": "2521f2ce-a728-4fce-a65a-07459ebf2608" + "id": "49835950-fdae-4a9e-b29f-ebea9b0f0f1d" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## PPCA as Manifold Learning\n", + "\n", + "Probabilistic PCA can be viewed as a simple manifold learning algorithm.\n", + "It assumes that:\n", + "\n", + "1. The data lies near a linear manifold (subspace) of the\n", + " high-dimensional space\n", + "2. The deviation from this manifold is Gaussian noise\n", + "3. The intrinsic dimensionality is specified by the number of retained\n", + " components\n", + "\n", + "This view helps explain why PPCA works well when the manifold hypothesis\n", + "holds and the manifold is approximately linear. When the manifold is\n", + "nonlinear, we need more sophisticated methods like kernel PCA or neural\n", + "network-based approaches." + ], + "id": "29266029-15d5-4bc4-9656-aac5ef3ce7bf" }, { "cell_type": "markdown", @@ -1574,7 +2850,7 @@ "\n", "We will now implement this algorithm in python." ], - "id": "349e7644-6827-45c6-a4c8-a8b7cc1101fc" + "id": "3e3086e8-3939-4360-bc28-724a342f877b" }, { "cell_type": "code", @@ -1584,7 +2860,7 @@ "source": [ "import numpy as np" ], - "id": "e530ec6b-b669-48f4-9063-25dac88cd715" + "id": "02a6610d-e9fb-473a-8593-c22e649f1a7f" }, { "cell_type": "code", @@ -1592,22 +2868,31 @@ "metadata": {}, "outputs": [], "source": [ - "# probabilistic PCA algorithm\n", - "def ppca(Y, q):\n", - " # remove mean\n", - " Y_cent = Y - Y.mean(0)\n", - "\n", - " # Comute covariance\n", - " S = np.dot(Y_cent.T, Y_cent)/Y.shape[0]\n", - " lambd, U = np.linalg.eig(S)\n", - "\n", - " # Choose number of eigenvectors\n", - " sigma2 = np.sum(lambd[q:])/(Y.shape[1]-q)\n", - " l = np.sqrt(lambd[:q]-sigma2)\n", - " W = U[:, :q]*l[None, :]\n", - " return W, sigma2" + "from mlai import ppca_eig\n", + "import inspect\n", + "file_path = inspect.getfile(ppca_eig)" + ], + "id": "65cddeca-4c6b-4a1c-bb57-1a83e4a309f3" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s ppca_eig {file_path}" + ], + "id": "cb20fe1c-a373-4df3-aff5-fd6d5edf5e41" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ppca = ppca_eig" ], - "id": "32fff504-0a48-49b8-8816-8bd8626a17df" + "id": "5d6e7d8e-6c16-49b4-a73f-cb9172546f71" }, { "cell_type": "markdown", @@ -1625,13 +2910,13 @@ "computation of $\\mathbf{Z}^\\top\\mathbf{Z}$ (or in the case of\n", "$\\boldsymbol{\\Phi}^\\top\\boldsymbol{\\Phi}$)." ], - "id": "151770f6-1f84-4843-ac6d-b45bd72c8e3a" + "id": "e8f2f411-4873-4f40-99ed-52f7f0b054ba" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Posterior for Principal Component Analysis\n", + "## Posterior for Principal Component Analysis\n", "\n", "Under the latent variable model justification for principal component\n", "analysis, we are normally interested in inferring something about the\n", @@ -1657,7 +2942,7 @@ "\\mathbf{ z}_{i, :}^\\top \\mathbf{ z}_{i, :} + \\text{const}\n", "$$" ], - "id": "155620e5-4e4b-4e9f-a3e8-4b857131c2d3" + "id": "d2f18029-1616-452d-889a-db51ed1933e8" }, { "cell_type": "markdown", @@ -1667,7 +2952,7 @@ "\n", "Multiply out the terms in the brackets. Then collect the quadratic term\n", "and the linear terms together. Show that the posterior has the form $$\n", - "\\mathbf{ z}_{i, :} | \\mathbf{W}\\sim \\mathcal{N}\\left(\\boldsymbol{ \\mu}_x,\\mathbf{C}_x\\right)\n", + "\\mathbf{ z}_{i, :} | \\mathbf{W}\\sim \\mathscr{N}\\left(\\boldsymbol{ \\mu}_x,\\mathbf{C}_x\\right)\n", "$$ where $$\n", "\\mathbf{C}_x = \\left(\\sigma^{-2}\n", "\\mathbf{W}^\\top\\mathbf{W}+ \\mathbf{I}\\right)^{-1}\n", @@ -1677,7 +2962,7 @@ "$$ Compare this to the posterior for the Bayesian linear regression from\n", "last week, do they have similar forms? What matches and what differs?" ], - "id": "9cc2d4aa-76b8-4866-8add-7417df9336a4" + "id": "b0d4bab4-2c78-450b-9313-f37d331a56c2" }, { "cell_type": "markdown", @@ -1687,7 +2972,7 @@ "\n", "Write your answer to Exercise 3 here" ], - "id": "2d6adf8f-eb82-40d9-afd7-8d4679b02b25" + "id": "084972e2-1c98-43c9-98bd-34f0d53f474e" }, { "cell_type": "markdown", @@ -1697,7 +2982,7 @@ "\n", "Now let’s implement the system in code." ], - "id": "1dfca868-ad1f-4d6a-80f5-016b44570b7e" + "id": "f754fbee-f31a-4a56-839d-7d236d6b274a" }, { "cell_type": "markdown", @@ -1720,7 +3005,7 @@ "before computing the posterior: remember we assumed at the beginning of\n", "our analysis that the data had been centred (i.e. the mean was removed)." ], - "id": "50a3bf26-3538-4007-8107-4b0524bd9e11" + "id": "ab40f62a-01f1-43c3-abf6-9cc5824ee494" }, { "cell_type": "code", @@ -1745,7 +3030,7 @@ " return mu_x, C_x\n", "\n" ], - "id": "e5aa2a6e-5e01-4653-b99e-50fb6b144845" + "id": "0d9a4f74-bebb-464a-b755-0818fba8df6f" }, { "cell_type": "markdown", @@ -1774,7 +3059,7 @@ "by the singular values of the matrix $\\mathbf{Y}^\\top$ squared and the\n", "eigenvectors are given by $\\mathbf{U}$." ], - "id": "a9b66191-ba55-4686-9e98-32e449fbf94c" + "id": "05a24dd3-bb62-448a-87ec-9d9476d02a2b" }, { "cell_type": "markdown", @@ -1805,7 +3090,7 @@ "$$ where $\\mathbf{D}$ is a diagonal matrix with diagonal elements given\n", "by $d_{i} = \\frac{\\ell_i}{\\sigma^2 + \\ell_i^2}$." ], - "id": "b1be3b07-bde5-4b2c-9220-8c0bf66253cc" + "id": "95169d8c-061e-482a-82c0-df8e0c87dced" }, { "cell_type": "code", @@ -1816,7 +3101,7 @@ "import scipy as sp\n", "import numpy as np" ], - "id": "c69177a1-cb90-4adb-85b0-34a03e32cd77" + "id": "caa78dd3-a396-4dac-859b-3e28cd83bd5d" }, { "cell_type": "code", @@ -1824,35 +3109,192 @@ "metadata": {}, "outputs": [], "source": [ - "# probabilistic PCA algorithm using SVD\n", - "def ppca(Y, q, center=True):\n", - " \"\"\"Probabilistic PCA through singular value decomposition\"\"\"\n", - " # remove mean\n", - " if center:\n", - " Y_cent = Y - Y.mean(0)\n", - " else:\n", - " Y_cent = Y\n", - " \n", - " # Comute singluar values, discard 'R' as we will assume orthogonal\n", - " U, sqlambd, _ = sp.linalg.svd(Y_cent.T,full_matrices=False)\n", - " lambd = (sqlambd**2)/Y.shape[0]\n", - " # Compute residual and extract eigenvectors\n", - " sigma2 = np.sum(lambd[q:])/(Y.shape[1]-q)\n", - " ell = np.sqrt(lambd[:q]-sigma2)\n", - " return U[:, :q], ell, sigma2\n", - "\n", - "def posterior(Y, U, ell, sigma2, center=True):\n", - " \"\"\"Posterior computation for the latent variables given the eigendecomposition.\"\"\"\n", - " if center:\n", - " Y_cent = Y - Y.mean(0)\n", - " else:\n", - " Y_cent = Y\n", - " C_x = np.diag(sigma2/(sigma2+ell**2))\n", - " d = ell/(sigma2+ell**2)\n", - " mu_x = np.dot(Y_cent, U)*d[None, :]\n", - " return mu_x, C_x" + "from mlai import ppca_svd\n", + "import inspect\n", + "file_path = inspect.getfile(ppca_svd)" + ], + "id": "9bf65380-faf2-4cfa-9491-b53319591b18" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s ppca_svd {file_path}" + ], + "id": "5e237e6b-327f-4098-810f-97c23917add9" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import ppca_posterior\n", + "import inspect\n", + "file_path = inspect.getfile(ppca_posterior)" + ], + "id": "eb4c9c7f-780c-4ae0-831f-a4079278a274" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s ppca_posterior {file_path}" + ], + "id": "1d35db1e-cb9c-4132-87d9-f925d47843c2" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ppca = ppca_svd\n", + "posterior = ppca_posterior" + ], + "id": "a938f8b8-d08d-42c3-9acc-c710e49cfdf4" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Scikit-learn implementation PCA\n", + "\n", + "\\[edit\\]\n", + "\n", + "We’ve implemented PCA as part of supporting the learning process, but in\n", + "practice we can use the `scikit-learn` implementation. Let’s try it on\n", + "the oil flow data." + ], + "id": "e4277026-fa40-4b0e-87d8-229207d820d9" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pods" + ], + "id": "32ba12af-9edc-45e9-ad32-d3ccaa5f5527" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.oil()\n", + "X = data['X']\n", + "Y = data['Y']" + ], + "id": "4c89ad15-6fb6-4be9-b2e9-bd4e980a69a3" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('scikit-learn')" + ], + "id": "d79a93ed-dfc0-47ab-8802-65d207c60c85" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "c1c02698-c6f5-48a2-beae-d63fe3ab4b61" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.decomposition import PCA" + ], + "id": "26111edd-038b-4610-86f6-2581e4720f9b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pca = PCA(n_components=2)\n", + "pca.fit(X)\n", + "X_pca = pca.transform(X)" + ], + "id": "43d7ca21-38a7-448a-b885-5a515076362d" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Find rows with 1 in first column (class 0)\n", + "class0 = (Y[:, 0] == 1).astype(int) * 0\n", + "# Find rows with 1 in second column (class 1) \n", + "class1 = (Y[:, 1] == 1).astype(int) * 1\n", + "# Find rows with 1 in third column (class 2)\n", + "class2 = (Y[:, 2] == 1).astype(int) * 2\n", + "# Combine into single array of class labels 0,1,2\n", + "lbls = class0 + class1 + class2" ], - "id": "a0b6a44a-bc8a-4b3a-988e-8ee9e88cc6c5" + "id": "606c9312-3f44-4571-a71f-60ef12c85fb2" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "import mlai\n", + "from mlai import plot" + ], + "id": "3678711e-400d-45fb-8743-57affd36dd9b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", + "# Three labels stored in Y\n", + "labels = [\"homogeneous\", \"annular\", \"stratified\"]\n", + "for i in range(3):\n", + " ax.scatter(X_pca[lbls==i, 0], X_pca[lbls==i, 1], label=f'{labels[i]}')\n", + "ax.set_xlabel('First principal component')\n", + "ax.set_ylabel('Second principal component')\n", + "\n", + "mlai.write_figure(\"oil-flow-pca-sklearn.svg\", directory='./dimred')" + ], + "id": "5e2b3a4d-18aa-43ea-9333-0a19efd8431b" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: PCA of the oil flow data. The flow is homogeneous, annular\n", + "and stratified." + ], + "id": "c71fef6d-1a6c-4532-bc06-373cbbab112a" }, { "cell_type": "markdown", @@ -1862,17 +3304,23 @@ "\n", "\\[edit\\]\n", + "style=\"\">edit\\]" + ], + "id": "b97a4bf0-c247-438f-a6a3-c91374b9200d" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## OSU Motion Capture Data: Run 1\n", "\n", - "For our first example we’ll consider some motion capture data of a man\n", - "breaking into a run. [Motion capture\n", - "data](http://en.wikipedia.org/wiki/Motion_capture) involves capturing a\n", - "3-d point cloud to represent a character, often by an underlying\n", - "skeleton. For this data set, from Ohio State University, we have 54\n", - "frame of motion capture, each frame containing 102 values, which are the\n", - "3-d locations of 34 different points from the subject’s skeleton." + "Motion capture data the Open Motion Data Project by The Ohio State\n", + "University Advanced Computing Center for the Arts and Design.\n", + "Historically the data website was found here\n", + ", although it is now\n", + "missing. The centre website is here: ." ], - "id": "a647e93a-a0dd-4ce3-ae6f-10ded3051cd2" + "id": "8085f920-db05-4b54-ba00-ba86ce243ee1" }, { "cell_type": "code", @@ -1882,7 +3330,83 @@ "source": [ "import pods" ], - "id": "a4a64ac5-75df-4a65-aff1-e69db62f32e9" + "id": "7b7e9ba6-9253-4b76-ac97-210a79fff73d" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can download different data from the site, here we download the\n", + "‘run1’ motion." + ], + "id": "05e8f2fd-a6dc-47c8-9f50-0494c3a94f48" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data = pods.datasets.osu_run1()" + ], + "id": "73abfac5-f789-4425-8d32-40dfd2c0a7ca" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data dictionary contains the keys ‘Y’ and ‘connect’, which represent\n", + "the data and connections that can be used to create the skeleton." + ], + "id": "2896a7d9-4526-4d6f-b97a-7c1a55b38398" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data['Y'].shape" + ], + "id": "dd90fd4d-75e3-4e3d-9cf4-c812f7f4e11b" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The data has often been used in talks demonstrating GP-LVM models and\n", + "comparing variants such as back constrained and temporal models." + ], + "id": "071a6e9d-47be-4dd8-b255-9a15fbe82f97" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(data['citation'])" + ], + "id": "b6209818-2391-4913-97fe-02b7f06e10fa" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And extra information about the data is included, as standard, under the\n", + "keys under `details`." + ], + "id": "80285eb9-bac3-4c40-9015-0324708c0250" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(data['details'])" + ], + "id": "710ff131-b4d6-4fd5-b149-fdeaa49be45f" }, { "cell_type": "code", @@ -1890,10 +3414,9 @@ "metadata": {}, "outputs": [], "source": [ - "data = pods.datasets.osu_run1()\n", "Y = data['Y']" ], - "id": "d51767bd-2bea-4108-9758-a874058d4a2f" + "id": "79f683f4-3bc4-4b85-80d5-b6df81951db2" }, { "cell_type": "markdown", @@ -1902,7 +3425,7 @@ "Once the data is loaded in we can examine the first two principal\n", "components as follows," ], - "id": "596f92e2-ba9a-43ca-9db7-539597000d6b" + "id": "9adcdad9-e5e9-4809-94c9-51e21054d4ee" }, { "cell_type": "code", @@ -1914,7 +3437,7 @@ "U, ell, sigma2 = ppca(Y, q)\n", "mu_x, C_x = posterior(Y, U, ell, sigma2)" ], - "id": "8971750b-185e-42be-8801-f059d24f31dc" + "id": "52d77bb7-ab8d-408a-9773-95b15309ef5b" }, { "cell_type": "code", @@ -1925,7 +3448,7 @@ "import matplotlib.pyplot as plt\n", "import mlai" ], - "id": "a737cbb1-87f8-46ff-aead-d4896a9e9c47" + "id": "3aae64a4-417a-4b09-8666-2e9e0cdcd2fc" }, { "cell_type": "code", @@ -1934,17 +3457,17 @@ "outputs": [], "source": [ "plt.plot(mu_x[:, 0], mu_x[:, 1], 'rx-')\n", - "mlai.write_figure(\"dem_osu_run1.svg\", directory=\"./dimred/\")" + "mlai.write_figure(\"osu-run1-pca.svg\", directory=\"./dimred/\")" ], - "id": "8cbc63c8-e57e-4607-9c79-480bc2da5d01" + "id": "1e0a803c-8bc9-44d3-b0e3-1411edf1f7cd" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", - "Figure: First two principle components of motion capture data of an\n", + "Figure: First two principal components of motion capture data of an\n", "individual running.\n", "\n", "Here because the data is a time course, we have connected points that\n", @@ -1953,7 +3476,7 @@ "We can examin how much residual variance there is in the system by\n", "looking at `sigma2`." ], - "id": "451536c2-8425-48d1-8327-262751a22a98" + "id": "bcfcc683-086a-481b-bb09-c9fcf7fcbfac" }, { "cell_type": "code", @@ -1963,7 +3486,7 @@ "source": [ "print(sigma2)" ], - "id": "3ecad04a-3377-4899-91eb-2bed9c9bee59" + "id": "dbfb29d2-1c0d-4114-8f84-b60d500ba245" }, { "cell_type": "markdown", @@ -1982,7 +3505,7 @@ "all the wireless access points it can cache and stores their signal\n", "strength." ], - "id": "0ac5c1f7-cb8c-4849-9c83-23f1413911f3" + "id": "19d1bf09-1019-443c-9571-9781dacb71da" }, { "cell_type": "code", @@ -1992,7 +3515,7 @@ "source": [ "import pods" ], - "id": "9203f227-81f3-4585-bef1-1d0a85f8a816" + "id": "089b15e0-3fae-4817-afca-3ffaa66501ea" }, { "cell_type": "code", @@ -2004,7 +3527,7 @@ "Y = data['Y']\n", "Y.shape" ], - "id": "1a8f6f27-3427-4323-864c-443bed582930" + "id": "6d5a0a36-a688-4c8f-9659-4d9c258e359d" }, { "cell_type": "markdown", @@ -2017,7 +3540,7 @@ "position $x_{i, :}$ to be represented by\n", "$y_{i, j} = \\mathbf{ w}_{j, :}^\\top \\mathbf{ z}_{i, :} + \\epsilon_{i,j}$." ], - "id": "9dd4ad9a-f27b-4965-9923-dd30d58c2409" + "id": "4011557d-af4e-4ed3-8ff5-4988e9006708" }, { "cell_type": "code", @@ -2028,7 +3551,7 @@ "import matplotlib.pyplot as plt\n", "import mlai" ], - "id": "6a8d4865-b0cc-494c-888f-c6da28a2b9fa" + "id": "0814a0e6-23ef-48af-a533-d07ab2b6fd73" }, { "cell_type": "code", @@ -2047,31 +3570,27 @@ "W = U*ell[None, :]\n", "ax.plot(W[:, 0], W[:, 1], 'bo')\n", "ax.set_title('Access Point Inferred Locations')\n", - "mlai.write_figure(\"dem_robot_wireless_pca.svg\", directory=\"dimred\")" + "mlai.write_figure(\"robot_wireless_pca.svg\", directory=\"dimred\")" ], - "id": "8fb4a264-1a97-42df-8302-2cf746ccee21" + "id": "5e38de88-981a-4710-bf5f-786b8a8bccc5" }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "U, ell, sigma2 = ppca(Y.T, q)" + "\n", + "\n", + "Figure: PCA of the robot wireless data." ], - "id": "88d0ac1a-8e2f-4bea-a50a-e07514f6aef1" + "id": "a9fcb3ed-702f-4b0f-8809-2f36782ddfd5" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Interpretations of Principal Component Analysis\n", - "\n", - "\\[edit\\]" + "# Interpretations of Principal Component Analysis" ], - "id": "f55dd375-f564-4d10-ba9c-b74152c07d3c" + "id": "4b521bea-69a1-4f4a-9d03-b2b8aece0b79" }, { "cell_type": "markdown", @@ -2079,6 +3598,10 @@ "source": [ "## Relationship to Matrix Factorization\n", "\n", + "\\[edit\\]\n", + "\n", "We can use the robot naviation example to realise that PCA (and factor\n", "analysis) are very reminiscient of the that we used for introducing\n", "objective functions. In that system we used slightly different notation,\n", @@ -2118,13 +3641,17 @@ "f_{i, j} = \\mathbf{ z}_{i, :}^\\top \\mathbf{ w}_{j, :} \n", "$$ for probabilistic PCA and factor analysis are the same." ], - "id": "f7ebbc15-8e08-412a-b7c4-91fb79944c0f" + "id": "64396145-19a5-4028-a47b-b48779b56dee" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Other Interpretations of PCA: Separating Model and Algorithm\n", + "## PCA: Separating Model and Algorithm\n", + "\n", + "\\[edit\\]\n", "\n", "Since Hotelling first introduced his perspective on factor analysis as\n", "PCA there has been somewhat of a conflation of the idea of the model\n", @@ -2137,9 +3664,9 @@ "directions of *maximum variance* in a dataset. Pearson in 1901 arrived\n", "at the same algorithm driven by a desire to seek a *symmetric\n", "regression* between two covariate/response variables $x$ and $y$\n", - "(Pearson, 1901). He is, therefore, often credited with the invention of\n", - "principal component analysis, but to me this seems disengenous. His aim\n", - "was very different from Hotellings, it was just happened that the\n", + "(**Pearson:01?**). He is, therefore, often credited with the invention\n", + "of principal component analysis, but to me this seems disengenous. His\n", + "aim was very different from Hotellings, it was just happened that the\n", "optimal solution for his model was coincident with that of Hotelling.\n", "The approach is also known as the [Karhunen Loeve\n", "Transform](http://en.wikipedia.org/wiki/Karhunen%E2%80%93Lo%C3%A8ve_theorem)\n", @@ -2149,13 +3676,14 @@ "(see the section on Classical Multidimensional Scaling in [Mardia, Kent\n", "and\n", "Bibby](http://store.elsevier.com/Multivariate-Analysis/Kanti-%20Mardia/isbn-9780124712522/))\n", - "(Mardia et al., 1979). One of my own contributions to machine learning\n", - "was deriving yet another model whose linear variant was solved by\n", - "finding the principal subspace of the covariance matrix (an approach I\n", - "termed dual probabilistic PCA or probabilistic principal coordinate\n", - "analysis). Finally, the approach is sometimes referred to simply as\n", - "singular value decomposition (SVD). The singular value decomposition of\n", - "a data set has the following form, $$\n", + "(Mardia et al., 1979).\n", + "\n", + "One of my own contributions to machine learning was deriving yet another\n", + "model whose linear variant was solved by finding the principal subspace\n", + "of the covariance matrix (an approach I termed dual probabilistic PCA or\n", + "probabilistic principal coordinate analysis). Finally, the approach is\n", + "sometimes referred to simply as singular value decomposition (SVD). The\n", + "singular value decomposition of a data set has the following form, $$\n", "\\mathbf{Y}= \\mathbf{V} \\boldsymbol{\\Lambda} \\mathbf{U}^\\top\n", "$$ where $\\mathbf{V}\\in\\Re^{n\\times n}$ and\n", "$\\mathbf{U}^\\in \\Re^{p\\times p}$ are square orthogonal matrices and\n", @@ -2170,7 +3698,7 @@ "the SVD can simply be seen as another approach to determining the\n", "principal components." ], - "id": "09d503f2-94fc-454d-974b-40aafc0aa655" + "id": "7a59d9fe-74bc-4ed2-bccf-03c5ba89c4fa" }, { "cell_type": "markdown", @@ -2216,21 +3744,22 @@ "understanding it is necessary to maintain the separation and to maintain\n", "a handle on when and why we perform the conflation." ], - "id": "55d87212-3942-44d7-a607-861c0dbcfc9c" + "id": "fdc43415-1ac8-459f-b28e-4e46ffed8d66" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# PCA in Practice\n", + "# PCA Effectiveness\n", "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", "Principal component analysis is so effective in practice that there has\n", "almost developed a mini-industry in renaming the method itself (which is\n", - "ironic, given its origin). In particular [Latent Semantic\n", + "ironic, given its origin as a renaming of factor analysis). In\n", + "particular [Latent Semantic\n", "Indexing](http://en.wikipedia.org/wiki/Latent_semantic_indexing) in text\n", "processing is simply PCA on a particular representation of the term\n", "frequencies of the document. There is a particular fad to rename the\n", @@ -2239,22 +3768,36 @@ "Pentland’s](http://www.face-rec.org/algorithms/PCA/jcn.pdf) paper on\n", "eigenfaces, but also with\n", "[eigenvoices](https://wiki.inf.ed.ac.uk/twiki/pub/CSTR/ListenSemester1_2007_8/kuhn-%20junqua-eigenvoice-icslp1998.pdf)\n", - "and [eigengenes](http://www.biomedcentral.com/1752-0509/1/54). This\n", - "seems to be an instantiation of a wider, and hopefully subconcious,\n", + "and [eigengenes](http://www.biomedcentral.com/1752-0509/1/54).\n", + "\n", + "This seems to be an instantiation of a wider, and hopefully subconcious,\n", "tendency in academia to attempt to differentiate one idea from the same\n", "idea in related fields in order to emphasise the novelty. The\n", "unfortunate result is somewhat of a confusing literature for relatively\n", - "simple model. My recommendations would be as follows. If you have\n", - "multivariate data, applying some form of principal component would seem\n", - "to be a very good idea as a first step. Even if you intend to later\n", - "perform classification or regression on your data, it can give you\n", - "understanding of the structure of the underlying data and help you to\n", - "develop your intuitions about the nature of your data. Intelligent\n", - "plotting and interaction with your data is always a good think, and for\n", - "high dimensional data that means that you need some way of\n", - "visualisation, PCA is typically a good starting point." + "simple model.\n", + "\n", + "My recommendations would be as follows. If you have multivariate data,\n", + "applying some form of principal component would seem to be a very good\n", + "idea as a first step. Even if you intend to later perform classification\n", + "or regression on your data, it can give you understanding of the\n", + "structure of the underlying data and help you to develop your intuitions\n", + "about the nature of your data. Intelligent plotting and interaction with\n", + "your data is always a good think, and for high dimensional data that\n", + "means that you need some way of visualisation, PCA is typically a good\n", + "starting point.\n", + "\n", + "Indeed, sometimes I’ll say “have you done PCA yet?” is the data\n", + "scientists equivalent of “have you switched it off and on again?”." + ], + "id": "7abdb472-6fbf-49fb-8da3-d816b94bc3e3" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Derivation of PPCA" ], - "id": "0508e82e-d830-4285-9eb2-996b35b31a94" + "id": "03da1a48-2a58-433f-968d-b8d54ed6ed8b" }, { "cell_type": "markdown", @@ -2274,23 +3817,23 @@ "output regression we would find that the marginal likelihood of the data\n", "is independent across the features and correlated across the data, $$\n", "p(\\mathbf{Y}|\\mathbf{Z})\n", - "= \\prod_{j=1}^p \\mathcal{N}\\left(\\mathbf{ y}_{:, j}|\\mathbf{0},\n", + "= \\prod_{j=1}^d\\mathscr{N}\\left(\\mathbf{ y}_{:, j}|\\mathbf{0},\n", "\\alpha\\mathbf{Z}\\mathbf{Z}^\\top + \\sigma^2 \\mathbf{I}\\right)\n", "$$ where $\\mathbf{ y}_{:, j}$ is a column of the data matrix and the\n", "independence is across the *features*, in probabilistic PCA the marginal\n", "likelihood has the form, $$\n", - "p(\\mathbf{Y}|\\mathbf{W}) = \\prod_{i=1}^n\\mathcal{N}\\left(\\mathbf{ y}_{i,\n", + "p(\\mathbf{Y}|\\mathbf{W}) = \\prod_{i=1}^n\\mathscr{N}\\left(\\mathbf{ y}_{i,\n", ":}|\\mathbf{0},\\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}\\right)\n", "$$ where $\\mathbf{ y}_{i, :}$ is a row of the data matrix $\\mathbf{Y}$\n", "and the independence is across the data points." ], - "id": "653c54c2-7fdc-400f-b1ba-5ef9c6eda73c" + "id": "b546dc07-7005-468d-a537-250478da1c46" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# Computation of the Log Likelihood\n", + "## Computation of the Log Likelihood\n", "\n", "The quality of the model can be assessed using the log likelihood of\n", "this Gaussian form. $$\n", @@ -2302,27 +3845,28 @@ "of the covariance covariance,\n", "$\\mathbf{C}= \\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}$ and the\n", "fact that $\\mathbf{W}= \\mathbf{U}\\mathbf{L}\\mathbf{R}^\\top$.\n", - "Specifically, we first use the decomposition of $\\mathbf{W}$ to write:\n", - "$$\n", + "\n", + "Specifically, we first use the decomposition of $\\mathbf{W}$ to write $$\n", "-\\frac{n}{2} \\log \\left| \\mathbf{W}\\mathbf{W}^\\top + \\sigma^2 \\mathbf{I}\\right|\n", - "= -\\frac{n}{2} \\sum_{i=1}^q \\log (\\ell_i^2 + \\sigma^2) - \\frac{n(p-q)}{2}\\log\n", + "= -\\frac{n}{2} \\sum_{i=1}^q\\log (\\ell_i^2 + \\sigma^2) - \\frac{n(d-q)}{2}\\log\n", "\\sigma^2,\n", - "$$ where $\\ell_i$ is the $i$th diagonal element of $\\mathbf{L}$. Next,\n", - "we use the [Woodbury matrix\n", + "$$ where $\\ell_i$ is the $i$th diagonal element of $\\mathbf{L}$.\n", + "\n", + "Next, we use the [Woodbury matrix\n", "identity](http://en.wikipedia.org/wiki/Woodbury_matrix_identity) which\n", "allows us to write the inverse as a quantity which contains another\n", - "inverse in a smaller matrix: $$\n", + "inverse in a smaller matrix $$\n", "(\\sigma^2 \\mathbf{I}+ \\mathbf{W}\\mathbf{W}^\\top)^{-1} =\n", "\\sigma^{-2}\\mathbf{I}-\\sigma^{-4}\\mathbf{W}{\\underbrace{(\\mathbf{I}+\\sigma^{-2}\\mathbf{W}^\\top\\mathbf{W})}_{\\mathbf{C}_x}}^{-1}\\mathbf{W}^\\top\n", - "$$ So, it turns out that the original inversion of the $p \\times p$\n", + "$$ So, it turns out that the original inversion of the $d\\times d$\n", "matrix can be done by forming a quantity which contains the inversion of\n", - "a $q \\times q$ matrix which, moreover, turns out to be the quantity\n", + "a $q\\times q$ matrix which, moreover, turns out to be the quantity\n", "$\\mathbf{C}_x$ of the posterior.\n", "\n", "Now, we put everything together to obtain: $$\n", "\\log p(\\mathbf{Y}|\\mathbf{W}) = -\\frac{n}{2} \\sum_{i=1}^q\n", "\\log (\\ell_i^2 + \\sigma^2)\n", - "- \\frac{n(p-q)}{2}\\log \\sigma^2 - \\frac{1}{2} \\text{tr}\\left(\\mathbf{Y}^\\top \\left(\n", + "- \\frac{n(d-q)}{2}\\log \\sigma^2 - \\frac{1}{2} \\text{tr}\\left(\\mathbf{Y}^\\top \\left(\n", "\\sigma^{-2}\\mathbf{I}-\\sigma^{-4}\\mathbf{W}\\mathbf{C}_x\n", "\\mathbf{W}^\\top \\right) \\mathbf{Y}\\right) + \\text{const},\n", "$$ where we used the fact that a scalar sum can be written as\n", @@ -2335,11 +3879,12 @@ "where $c$ is a scalar and $\\mathbf{A},\\mathbf{B}$ matrices of compatible\n", "sizes. Therefore, the final log likelihood takes the form: $$\n", "\\log p(\\mathbf{Y}|\\mathbf{W}) = -\\frac{n}{2}\n", - "\\sum_{i=1}^q \\log (\\ell_i^2 + \\sigma^2) - \\frac{n(p-q)}{2}\\log \\sigma^2 -\n", + "\\sum_{i=1}^q\\log (\\ell_i^2 + \\sigma^2) - \\frac{n(d-q)}{2}\\log \\sigma^2 -\n", "\\frac{\\sigma^{-2}}{2} \\text{tr}\\left(\\mathbf{Y}^\\top \\mathbf{Y}\\right)\n", "+\\frac{\\sigma^{-4}}{2} \\text{tr}\\left(\\mathbf{B}\\mathbf{C}_x\\mathbf{B}^\\top\\right) +\n", "\\text{const}\n", "$$ where we also defined $\\mathbf{B}=\\mathbf{Y}^\\top\\mathbf{W}$.\n", + "\n", "Finally, notice that\n", "$\\text{tr}\\left(\\mathbf{Y}\\mathbf{Y}^\\top\\right)=\\text{tr}\\left(\\mathbf{Y}^\\top\\mathbf{Y}\\right)$\n", "can be computed faster as the sum of all the elements of\n", @@ -2347,7 +3892,7 @@ "[Hadamard](http://en.wikipedia.org/wiki/Hadamard_product_(matrices)))\n", "product." ], - "id": "309a02f3-c13c-4ea2-89bd-c91aed62f8c7" + "id": "7c46d2f9-6b78-4680-8613-a524f11458b9" }, { "cell_type": "markdown", @@ -2365,7 +3910,7 @@ "We will now try to reconstruct the motion capture figure form some\n", "different places in the latent plot." ], - "id": "f51c1196-a3b1-4037-8e78-49ddb119b0f3" + "id": "e0dc6428-d36d-4fe2-b0cc-7cec1627e865" }, { "cell_type": "markdown", @@ -2378,7 +3923,7 @@ "latent variables at the data points. Use two latent dimensions. What is\n", "the sum of squares error for the reconstruction?" ], - "id": "7a9a7d42-1be4-49d2-853c-37a57f506947" + "id": "a50f4a24-21e1-4521-9292-8fa4076e6262" }, { "cell_type": "markdown", @@ -2388,7 +3933,7 @@ "\n", "Write your answer to Exercise 5 here" ], - "id": "88c27dd8-5100-431e-b181-7e4d4e44bab9" + "id": "9a024a24-9edf-44cd-a36f-352f771d8947" }, { "cell_type": "markdown", @@ -2408,9 +3953,9 @@ "The data is actually quite high dimensional, and solving the eigenvalue\n", "problem in the high dimensional space can take some time. At this point\n", "we turn to a neat trick, you don’t have to solve the full eigenvalue\n", - "problem in the $p\\times p$ covariance, you can choose instead to solve\n", + "problem in the $d\\times d$ covariance, you can choose instead to solve\n", "the related eigenvalue problem in the $n\\times n$ space, and in this\n", - "case $n=200$ which is much smaller than $p$.\n", + "case $n=200$ which is much smaller than $d$.\n", "\n", "The original eigenvalue problem has the form $$\n", "\\mathbf{Y}^\\top\\mathbf{Y}\\mathbf{U} = \\mathbf{U}\\boldsymbol{\\Lambda}\n", @@ -2426,7 +3971,7 @@ "\\left.\\mathbf{U}^\\prime\\right.^\\top\\mathbf{U}^\\prime = \\mathbf{I}\n", "$$" ], - "id": "49ff0818-a98b-4ddb-858a-4f18de0b95f4" + "id": "be01777d-e3dd-4d3d-9807-33092cb0b808" }, { "cell_type": "markdown", @@ -2445,7 +3990,7 @@ "Below we load in the data and display an image of the second face in the\n", "data set (i.e., indexed by 1)." ], - "id": "4b8dab1d-9572-440d-b0a7-35bd3e4a6caf" + "id": "40c30234-4022-4edd-a709-ee4b179646b1" }, { "cell_type": "code", @@ -2455,9 +4000,10 @@ "source": [ "import pods\n", "import matplotlib.pyplot as plt\n", - "import mlai" + "import mlai\n", + "import numpy as np" ], - "id": "a4ddc275-9358-432b-809b-d8cca2ae1ef5" + "id": "e7aaa981-99fc-4224-aeda-86de9098f454" }, { "cell_type": "code", @@ -2473,13 +4019,13 @@ "plt.imshow(np.reshape(Y[display_index, :].flatten(), (64, 64)).T, cmap = cm.Greys_r)\n", "mlai.write_figure(\"olivetti_faces_data.png\", directory=\"dimred\")" ], - "id": "5684a0c4-0c26-487f-ae86-3ee80aa233c1" + "id": "e2788936-97d2-43ea-861e-a1c4ee1a561b" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Data set from Olivetti Research Laboratory of faces.\n", "\n", @@ -2493,7 +4039,7 @@ "recovers the original image into a matrix `im` by using the `np.reshape`\n", "function to return the vector to a matrix." ], - "id": "80a90275-76b6-487c-b8f2-e23ddaa7411c" + "id": "7ed2aca9-23b7-40e4-b21b-c6a0aebfdb98" }, { "cell_type": "markdown", @@ -2512,7 +4058,7 @@ "to recover the image associated with a data point above. Below we do\n", "this for the first nine eigenvectors of the Olivetti Faces data." ], - "id": "8e255154-ab20-4304-abbc-5e294bd8d49d" + "id": "e625d487-7565-4bc1-b62f-e007b61aa0f7" }, { "cell_type": "code", @@ -2534,17 +4080,17 @@ " lat += 1\n", "mlai.write_figure(\"dem_olivetti_faces_eigenvectors.png\", directory=\"dimred\")" ], - "id": "10a0e595-0c48-4813-a343-44bf48e317cf" + "id": "7ee40c21-bc70-4719-90a3-90ebeed0f4c9" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: First 9 eigenvectors of the Olivetti faces data." ], - "id": "76d8cb07-5a59-4888-bfb7-98380be686b6" + "id": "75ccfcbb-14bb-439d-890e-ea029711f52f" }, { "cell_type": "markdown", @@ -2559,7 +4105,7 @@ "the point we want to construct. Then we’ll use them to compute the\n", "weightings of each of the eigenvectors." ], - "id": "66eb2579-71d3-4723-bdff-e026792c8f5e" + "id": "4474071a-2d76-49a7-accc-7f6860a1c364" }, { "cell_type": "code", @@ -2571,7 +4117,7 @@ "reconstruction_weights = mu_x[display_index, :]*ell\n", "print(reconstruction_weights)" ], - "id": "d891ee0c-fa66-4510-a385-71758ec3f2e1" + "id": "dc5df3b2-d1b7-46d6-ac12-e0521268d8d9" }, { "cell_type": "markdown", @@ -2582,7 +4128,7 @@ "we weight these templates and combine to form the reconstructed image,\n", "and show the comparison to the original image." ], - "id": "aa0fb33f-b5f6-4570-bc5c-a4509bb78812" + "id": "c6bbb20d-7507-42ba-8e24-f9eb38919a87" }, { "cell_type": "code", @@ -2597,13 +4143,13 @@ "ax[1].set_title('Reconstruction of Example from ' + str(len(reconstruction_weights)) + ' Latent Variables')\n", "mlai.write_figure(\"dem_olivetti_faces_reconstruction.png\", directory=\"dimred\")" ], - "id": "159eb994-aab9-4f70-84a7-39f89395773d" + "id": "9d96d320-0ad1-4de3-a398-fca1db1a2eb2" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Reconstruction from the latent space of the faces data.\n", "\n", @@ -2611,7 +4157,7 @@ "increasing the number of template images used (i.e. increasing the\n", "*latent dimensionality*)." ], - "id": "6fe027b0-2d64-41ce-a304-6a95155a0a69" + "id": "8c28e296-8e75-4582-9bcc-296af22e4ca4" }, { "cell_type": "markdown", @@ -2646,7 +4192,7 @@ "\n", "First we extract the principal components of the gene expression." ], - "id": "89338e46-8cf8-4f78-9529-72894dbd22d9" + "id": "7228cc6d-bc70-4e6c-9e18-f148f1a7f531" }, { "cell_type": "code", @@ -2656,7 +4202,7 @@ "source": [ "import pods" ], - "id": "c313c717-8a56-4817-9e3f-2b08fd60ae53" + "id": "4074bd3f-2b59-41d3-a3b7-b9c15071c6f0" }, { "cell_type": "code", @@ -2671,7 +4217,7 @@ "U, ell, sigma2 = ppca(Y, q)\n", "mu_x, C_x = posterior(Y, U, ell, sigma2)" ], - "id": "c3bc1368-78ca-4dbc-9f04-51e6489d225b" + "id": "9e75cca3-d48f-4260-8bb9-49033ea952ad" }, { "cell_type": "markdown", @@ -2682,7 +4228,7 @@ "latent dimension (`index=3`) can be plotted across the time course as\n", "follows." ], - "id": "5b10a847-1097-4253-82bd-a443ad54de85" + "id": "a4dbeba3-fff8-43b7-904e-79bfdad7b81d" }, { "cell_type": "code", @@ -2692,7 +4238,7 @@ "source": [ "import matplotlib.pyplot as plt" ], - "id": "e842c768-d026-47f9-8dac-eddd990ed3ba" + "id": "5a05c336-428b-4c7d-99e1-fa8db366fe8d" }, { "cell_type": "code", @@ -2702,7 +4248,7 @@ "source": [ "plt.plot(mu_x[:, 3])" ], - "id": "76a2afa3-462f-47a3-955b-00a87bd6a19b" + "id": "e6d2c8c6-4fe1-4c66-854f-c03afa0a4028" }, { "cell_type": "markdown", @@ -2712,7 +4258,7 @@ "this shape by examining the associated column of $\\mathbf{U}$. Let’s\n", "augment our data matrix with this score." ], - "id": "c9ec42f7-1cda-44fa-8433-12f8aaff62f5" + "id": "ff056161-c7cc-40c3-86c4-7860f602aa16" }, { "cell_type": "code", @@ -2722,9 +4268,9 @@ "source": [ "gene_list = Y.T\n", "gene_list['oscilation'] = np.sqrt(U[:, 3]**2)\n", - "gene_list.sort(columns='oscilation', ascending=False).index[:4]" + "gene_list.sort_values('oscilation', ascending=False).index[:4]" ], - "id": "db813299-a089-4691-a78f-06d64decccbf" + "id": "8d4e6397-711b-4f07-82d3-796a6decbeb4" }, { "cell_type": "markdown", @@ -2748,7 +4294,7 @@ "[YBL003C](http://www.ncbi.nlm.nih.gov/gene/852283) and\n", "[YNL030W](http://www.ncbi.nlm.nih.gov/gene/855701)." ], - "id": "4bd3f3af-54be-48ca-af7a-d2ff8561c8be" + "id": "7f5f5883-376e-4eef-8b75-c3cd8be9ef44" }, { "cell_type": "markdown", @@ -2758,7 +4304,7 @@ "\n", "- Chapter 7 up to pg 249 of Rogers and Girolami (2011)" ], - "id": "9cf93049-772f-46a1-92be-d12608cceb10" + "id": "316b887a-883e-470d-b18f-c1616dc88a67" }, { "cell_type": "markdown", @@ -2769,6 +4315,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -2776,26 +4325,29 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "79af6ad7-153b-47b5-949f-f0eefb862e62" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "22a296ae-2997-464c-93a3-91f057b994b4" + "id": "a32f4ac6-4692-4e85-817c-7bf2f0cf2f85" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", + "Bishop, C.M., James, G.D., 1993. Analysis of multiphase flows using\n", + "dual-energy gamma densitometry and neural networks. Nuclear Instruments\n", + "and Methods in Physics Research A327, 580–593.\n", + "\n", + "\n", "Hastie, T., Stuetzle, W., 1989. Principal curves. Journal of the\n", "American Statistical Association 84, 502–516.\n", "\n", "Hotelling, H., 1933. Analysis of a complex of statistical variables into\n", "principal components. Journal of Educational Psychology 24, 417–441.\n", "\n", + "Lake, B.M., Lawrence, N.D., Tenenbaum, J.B., 2018. The emergence of\n", + "organizing structure in conceptual representation. Cognitive science 42\n", + "Suppl 3, 809–832. \n", + "\n", "Lawrence, N.D., 2005. Probabilistic non-linear principal component\n", "analysis with Gaussian process latent variable models. Journal of\n", "Machine Learning Research 6, 1783–1816.\n", @@ -2808,9 +4360,6 @@ "Mardia, K.V., Kent, J.T., Bibby, J.M., 1979. Multivariate analysis.\n", "Academic Press, London.\n", "\n", - "Ng, A.Y., Jordan, M.I., Weiss, Y., n.d. On spectral clustering: Analysis\n", - "and an algorithm.\n", - "\n", "Pearson, K., 1901. On lines and planes of closest fit to systems of\n", "points in space. The London, Edinburgh and Dublin Philosophical Magazine\n", "and Journal of Science, Sixth Series 2, 559–572.\n", @@ -2824,8 +4373,9 @@ "analysis as a kernel eigenvalue problem. Neural Computation 10,\n", "1299–1319. \n", "\n", - "Shi, J., Malik, J., 2000. Normalized cuts and image segmentation. IEEE\n", - "Transactions on Pattern Analysis and Machine Intelligence 22, 888–905.\n", + "Spearman, C.E., 1904. [\"General intelligence,\" objectively determined\n", + "and measured](http://www.jstor.org/stable/1412107). The American Journal\n", + "of Psychology 15, 201–292.\n", "\n", "Tipping, M.E., Bishop, C.M., 1999b. Mixtures of probabilistic principal\n", "component analysers. Neural Computation 11, 443–482.\n", @@ -2834,7 +4384,7 @@ "analysis. Journal of the Royal Statistical Society, B 6, 611–622.\n", "" ], - "id": "1313a523-07bb-44cd-9b63-0225bd31e8a9" + "id": "7f72f973-19d9-4b2e-b5f4-5b9c6ba61769" } ], "nbformat": 4, diff --git a/_notebooks/10-naive-bayes.ipynb b/_notebooks/10-naive-bayes.ipynb index a0dadc30..712aef64 100644 --- a/_notebooks/10-naive-bayes.ipynb +++ b/_notebooks/10-naive-bayes.ipynb @@ -6,7 +6,7 @@ "source": [ "# Probabilistic Classification: Naive Bayes" ], - "id": "a61156a5-8b37-4946-a2fb-312fbb349912" + "id": "398ab0ea-da35-4f9e-8a3a-c657fd4ea43e" }, { "cell_type": "markdown", @@ -18,7 +18,7 @@ "probabilistic approach to clustering. We’ll focus on a simple but often\n", "effective algorithm known as *naive Bayes*." ], - "id": "fc1f94ce-4f3d-4e24-b130-c774be268af1" + "id": "cc3fbde6-c63e-4351-b67c-3eadbca9cbae" }, { "cell_type": "markdown", @@ -27,17 +27,20 @@ "$$\n", "$$" ], - "id": "61e3488a-e5d0-42fa-9da7-962fb87cf2b1" + "id": "4fdb365c-38f0-4088-afdb-58b3dbb40449" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -45,7 +48,7 @@ "\n", "-->" ], - "id": "26ebc3d4-6e19-4817-aa83-514a120e1c9d" + "id": "af64248d-e545-49a4-88c2-4c9ae9e813d1" }, { "cell_type": "markdown", @@ -57,7 +60,7 @@ "style=\"\">\\[edit\\]" ], - "id": "a690da6a-61a5-4b9d-87c9-32fab884c1b7" + "id": "40199f8c-b855-4b50-b601-0650714381b1" }, { "cell_type": "code", @@ -66,9 +69,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "174f889f-63f7-43e5-8668-73266265c345" + "id": "c7b6ce2c-7050-4aa3-848f-d0d63ab7d8b3" }, { "cell_type": "markdown", @@ -79,7 +92,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "8ef88323-0fa5-4b38-b214-66e33393cb8e" + "id": "acde6866-f97f-4b01-ae85-1f6e7fbf5f11" }, { "cell_type": "markdown", @@ -96,7 +109,56 @@ "\n", "The software can be installed using" ], - "id": "72210c8b-4a4b-4556-a60d-69f017e492fa" + "id": "2cd55d63-e14b-4962-86da-8ac58b16de55" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "4578c3f7-477b-4ba8-a153-0a19a41d431c" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" + ], + "id": "9da1a258-6c77-48eb-a959-5ff101e77697" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "ec7eaa48-bf1c-47ce-a5df-e1cc64566550" }, { "cell_type": "code", @@ -104,9 +166,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "%system {cmd}" ], - "id": "cb1c22c5-5488-48f4-a794-836528671063" + "id": "0f724289-dc8b-402b-bfc2-943d5f157fe3" }, { "cell_type": "markdown", @@ -119,7 +181,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "b50b700d-3ef5-4b8e-9af2-5f1e47552e6f" + "id": "04cac68b-b2b4-46b3-a9e6-fc97cfc76b4c" }, { "cell_type": "code", @@ -129,7 +191,7 @@ "source": [ "import notutils" ], - "id": "660a5e85-7710-4398-9260-2e3a5e5c8ee0" + "id": "d6100300-7e56-4e77-86ad-7eab5b98fd1b" }, { "cell_type": "markdown", @@ -151,7 +213,7 @@ "\n", "The software can be installed using" ], - "id": "689c1dab-2f20-4b3a-b84a-8a76110bd40f" + "id": "2769b4e3-057b-4e68-aca2-7c72e1dcef3f" }, { "cell_type": "code", @@ -159,9 +221,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "cmd = install_command('pods')" ], - "id": "55f520fa-bf5f-4609-b0be-e5f93fb164ce" + "id": "b175a5ff-c2ea-4976-8142-3c9b38db434c" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "b7331d83-8215-423c-9164-0a49e3244407" }, { "cell_type": "markdown", @@ -173,7 +245,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "72c54265-d8f5-4bf0-ae98-db768e1fc55a" + "id": "87b98b11-f192-4c9a-936d-fe2ce7338716" }, { "cell_type": "code", @@ -183,7 +255,7 @@ "source": [ "import pods" ], - "id": "888070dc-1237-43fc-b3c0-b38c7a5e4a88" + "id": "a9599711-a189-4c6e-9bf0-c640255a5b05" }, { "cell_type": "markdown", @@ -201,7 +273,7 @@ "\n", "The software can be installed using" ], - "id": "b16166de-e0db-44cd-b178-be19ca678d03" + "id": "01671a9a-a4be-4225-a804-25806216ac14" }, { "cell_type": "code", @@ -209,9 +281,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "cmd = install_command('mlai')" ], - "id": "77a4c633-f7da-400c-9562-b00323aac96d" + "id": "eb3e4c89-49c0-4c2b-855d-e28eeeda8f8b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "c0d78598-5560-4cd8-9c2a-f035b495a5cf" }, { "cell_type": "markdown", @@ -223,7 +305,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "4782e9db-d94b-4674-9d05-64a504700418" + "id": "cb34f556-fb6c-4ef6-9861-420b0fcd9069" }, { "cell_type": "code", @@ -231,9 +313,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "751e3d9f-899e-449d-92f6-af7da8093e64" + "id": "3db84b75-c020-4d5a-856b-e7acae89de1f" }, { "cell_type": "markdown", @@ -294,7 +377,7 @@ "function*, $f(\\cdot)$, to use and (3) selecting the right parameters,\n", "$\\mathbf{ w}$." ], - "id": "ca09089e-8ef9-463a-b6d4-37ba913c161f" + "id": "d708169b-d94a-470e-bc8c-cf891b4959d3" }, { "cell_type": "markdown", @@ -306,15 +389,187 @@ "style=\"\">\\[edit\\]\n", "\n", - "- Classifiying hand written digits from binary images (automatic zip\n", - " code reading)\n", - "- Detecting faces in images (e.g. digital cameras).\n", - "- Who a detected face belongs to (e.g. Facebook, DeepFace)\n", - "- Classifying type of cancer given gene expression data.\n", - "- Categorization of document types (different types of news article on\n", - " the internet)" + "There are many difference examples of classification problems. One of\n", + "theoldest is the classification of hand written digits from binary\n", + "images. The MNIST data was for a long time considered one of the most\n", + "difficult data sets.\n", + "\n", + "When we download this data we are making use of open source code,\n", + "through the [scikit learn](https://scikit-learn.org/stable/) project,\n", + "and the [OpenML](https://www.openml.org/) project which provides open\n", + "access to data.\n", + "\n", + "This reflects a tradition of openness in machine learning that has\n", + "enabled the tools to be deployed." ], - "id": "e5e507a0-0e08-4074-90c2-87583b6ca609" + "id": "38481f8e-e02b-4615-b90d-94a45ee9ad15" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Subsample of the MNIST Data\n", + "\n", + "\\[edit\\]\n", + "\n", + "We will look at a sub-sample of the MNIST digit data set.\n", + "\n", + "THE MNIST DATABASE of handwritten digits was created by Yann LeCun,\n", + "Corinna Cortes and Christopher J. C. Burges. More details can be found\n", + "at Yann’s site \n", + "\n", + "The data is a modified version of a data set from the US National\n", + "Institute of Standards and Technology ([NIST](https://www.nist.gov/).\n", + "\n", + "First load in the MNIST data set from scikit learn. This can take a\n", + "little while because it’s large to download." + ], + "id": "2ee02d3c-484c-4d9d-917c-94b444844a24" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.datasets import fetch_openml" + ], + "id": "19aa9cf0-e018-43f5-a019-1620688547ec" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mnist = fetch_openml('mnist_784', version=1, as_frame=False, parser='liac-arff')" + ], + "id": "adf13b56-6618-41c6-a6c8-f918088702d9" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Sub-sample the dataset to make the training faster." + ], + "id": "7b6bc0b2-c392-4c54-82f4-464d11cd712e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ], + "id": "a62b5b6b-28c6-49e6-b242-40ff935db1e3" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "np.random.seed(0)\n", + "digits = [0,1,2,3,4]\n", + "N_per_digit = 100\n", + "Y = []\n", + "labels = []\n", + "data = np.asarray(mnist.data)\n", + "target = np.asarray(mnist.target).astype(str)\n", + "for d in digits:\n", + " imgs = data[target == str(d)]\n", + " Y.append(imgs[np.random.permutation(len(imgs))[:N_per_digit]])\n", + " labels.append(np.ones(N_per_digit)*d)\n", + "Y = np.vstack(Y).astype(np.float64)\n", + "labels = np.hstack(labels)\n", + "Y /= 255" + ], + "id": "31945cbd-adc6-40b4-85d6-29fe68df2e4e" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let’s visualise some examples from our subsampled dataset to get a\n", + "sense of what the data looks like." + ], + "id": "cb9660a0-acb3-4d1b-872d-32355a026267" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## MNIST Digit Examples\n", + "\n", + "\\[edit\\]" + ], + "id": "9de31548-90f4-47da-919a-e06c2baee3f1" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import mlai\n", + "import mlai.plot as plot\n", + "import matplotlib.pyplot as plt" + ], + "id": "2ed18964-5cd6-4f4d-9ba8-7675ddf4dd63" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Visualize examples of each digit\n", + "fig, axes = plt.subplots(5, 5, figsize=plot.big_figsize)\n", + "\n", + "for digit in digits:\n", + " # Get indices for this digit\n", + " digit_indices = np.where(labels == digit)[0]\n", + " \n", + " # Show first 5 examples of this digit\n", + " for i in range(5):\n", + " row = int(digit)\n", + " col = i\n", + " \n", + " # Reshape 784-dim vector back to 28x28 image\n", + " image = Y[digit_indices[i]].reshape(28, 28)\n", + " \n", + " axes[row, col].imshow(image, cmap='gray')\n", + " axes[row, col].axis('off')\n", + "\n", + "plt.tight_layout()\n", + "mlai.write_figure(\"mnist-digits-subsample-examples.svg\", directory=\"./datasets\")" + ], + "id": "66cebcc7-4b6b-4f3e-a496-2f23ad715eb7" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Examples of MNIST digits 0-4 from our subsampled dataset,\n", + "showing 5 examples of each digit.\n", + "\n", + "The visualization shows the variety in handwritten digits even within\n", + "the same class. Each row represents a different digit (0 through 4), and\n", + "each column shows a different example of that digit.\n", + "\n", + "While the MNIST data doesn’t capture all the nuances of the challenges\n", + "we face in modern machine learning. It has been an important benchmark\n", + "data set for and can still be a useful data set for learning about\n", + "classfication algorithms." + ], + "id": "7911c3b9-50b2-428d-a2a6-e52cf151932e" }, { "cell_type": "markdown", @@ -375,7 +630,7 @@ "Bernoulli distribution to realise the binomial distribution for the\n", "outcome of positive trials." ], - "id": "472c1fba-37a4-4de5-a1a5-ea4b8b94b570" + "id": "b595ccfc-1777-4821-9746-36718452cb31" }, { "cell_type": "code", @@ -386,7 +641,7 @@ "import notutils as nu\n", "nu.display_google_book(id='CF4UAAAAQAAJ', page='PA87')" ], - "id": "497a50f7-fb09-453d-adfb-748e914a499b" + "id": "fd971dab-003e-4cf4-838b-cbc38ca2be61" }, { "cell_type": "code", @@ -397,7 +652,7 @@ "import matplotlib.pyplot as plt\n", "import mlai.plot as plot" ], - "id": "1e608f78-87d0-4889-882a-3d0a133d13e4" + "id": "69ec796b-eb6f-48d6-9617-b5c0d699a936" }, { "cell_type": "code", @@ -408,13 +663,13 @@ "fig, ax = plt.subplots(figsize=plot.one_figsize)\n", "plot.bernoulli_urn(ax, diagrams='./ml/')" ], - "id": "2c59cee7-cd5a-46e6-a358-bb0ef4e012f1" + "id": "6a949f6a-868f-419f-acd4-93fcd016cc4c" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Jacob Bernoulli described the Bernoulli distribution through\n", "an urn in which there are black and red balls.\n", @@ -437,7 +692,7 @@ "For this reason in Bayes’s distribution there is considered to be\n", "*aleatoric* uncertainty about the distribution parameter." ], - "id": "6646a1e5-7875-4c14-9630-06229634b9d4" + "id": "eafdde05-f1fa-4549-a5b5-b3e14278e0e8" }, { "cell_type": "code", @@ -448,7 +703,7 @@ "import matplotlib.pyplot as plt\n", "import mlai.plot as plot" ], - "id": "0eb00302-5efe-4873-80ae-3682d259b63c" + "id": "530ad464-ab4d-4744-816d-d3734f6601f1" }, { "cell_type": "code", @@ -459,13 +714,13 @@ "fig, ax = plt.subplots(figsize=plot.one_figsize)\n", "plot.bayes_billiard(ax, diagrams='./ml/')" ], - "id": "4446b622-1d0c-4e9e-b1e4-9ee9a338d7ab" + "id": "b8504dd5-c9f0-4e98-80e1-9ac053371756" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Thomas Bayes described the Bernoulli distribution\n", "independently of Jacob Bernoulli. He used the analogy of a billiard\n", @@ -476,7 +731,7 @@ "to the first ball). This is the origin of the term Bayesian because the\n", "parameter of the distribution is drawn from a probsbility." ], - "id": "01fcfdf2-a633-47bd-a881-3d3c242e1bba" + "id": "83184ca3-3d77-429e-a5aa-70c6d955c84e" }, { "cell_type": "code", @@ -487,7 +742,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "95d19beb-873b-47fb-a8d8-51a697862c86" + "id": "96ca3a57-ba69-4bbf-a021-105cf42d6484" }, { "cell_type": "code", @@ -497,7 +752,7 @@ "source": [ "import notutils as nu" ], - "id": "b544530f-1153-47c7-a621-6f99430839ff" + "id": "07ce5846-6dc9-453d-8c7d-5d1a30a07897" }, { "cell_type": "code", @@ -509,7 +764,7 @@ " directory='./ml', \n", " counter=IntSlider(0,0,9,1))" ], - "id": "34dea00b-6d51-489b-9b95-20bbf15dcf82" + "id": "7521d21d-0a55-43ea-8a75-c31e31b2003d" }, { "cell_type": "markdown", @@ -555,7 +810,7 @@ "100 times, and recovered 47 heads, then the estimate of the probability\n", "of heads should be $\\frac{47}{100}$." ], - "id": "1c17861f-ccbf-4934-aecd-e1c2361bf502" + "id": "80db6dc2-b0cd-4e32-92f3-3589be0f76fa" }, { "cell_type": "markdown", @@ -566,7 +821,7 @@ "Show that the maximum likelihood solution we have found is a *minimum*\n", "for our objective." ], - "id": "c04147e9-7b95-4039-8317-e2a4a73af7e7" + "id": "9a83334f-f0ef-4d4c-ac7d-13eba0caca45" }, { "cell_type": "markdown", @@ -576,7 +831,7 @@ "\n", "Write your answer to Exercise 1 here" ], - "id": "eba34dbe-9605-4372-988e-c58c8819983b" + "id": "2bbc633b-e73c-4304-a96a-99a9607d49e7" }, { "cell_type": "code", @@ -587,7 +842,7 @@ "# Use this box for any code you need\n", "\n" ], - "id": "31344a0a-6721-4aba-a0c9-17ba8860ef33" + "id": "b6cd3b0f-f382-4624-8f66-346e859fe0bb" }, { "cell_type": "markdown", @@ -605,7 +860,7 @@ "3. Posterior distribution\n", "4. Marginal likelihood" ], - "id": "7f808b2d-91c4-4a7c-bd40-feaf93fea65a" + "id": "b8a9fa51-4fb6-48a1-a2f9-0d05eeaa4368" }, { "cell_type": "markdown", @@ -654,7 +909,7 @@ "In naive Bayes we make certain simplifying assumptions that allow us to\n", "perform all of the above in practice." ], - "id": "85c51f01-c291-4d9d-8f9e-652049c954d6" + "id": "1b010bbd-c851-41f4-bd16-9fcdd3917f49" }, { "cell_type": "markdown", @@ -681,7 +936,7 @@ "Computing posterior distribution in this case becomes easier, this is\n", "known as the ‘Bayes classifier’." ], - "id": "23c9a5c1-4e7f-45cf-9df5-ba72918e5197" + "id": "a4a5dd3e-a987-4638-b50e-288b65d2392b" }, { "cell_type": "markdown", @@ -690,17 +945,17 @@ "## Feature Conditional Independence\n", "\n", "$$\n", - "p(\\mathbf{ x}_i | y_i, \\boldsymbol{ \\theta}) = \\prod_{j=1}^{p} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})\n", - "$$ where $p$ is the dimensionality of our inputs.\n", + "p(\\mathbf{ x}_i | y_i, \\boldsymbol{ \\theta}) = \\prod_{j=1}^{d} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})\n", + "$$ where $d$ is the dimensionality of our inputs.\n", "\n", "The assumption that is particular to naive Bayes is to now consider that\n", "the *features* are also conditionally independent, but not only given\n", "the parameters. We assume that the features are independent given the\n", "parameters *and* the label. So for each data point we have\n", - "$$p(\\mathbf{ x}_i | y_i, \\boldsymbol{ \\theta}) = \\prod_{j=1}^{p} p(x_{i,j}|y_i,\\boldsymbol{ \\theta})$$\n", - "where $p$ is the dimensionality of our inputs." + "$$p(\\mathbf{ x}_i | y_i, \\boldsymbol{ \\theta}) = \\prod_{j=1}^{d} p(x_{i,j}|y_i,\\boldsymbol{ \\theta})$$\n", + "where $d$ is the dimensionality of our inputs." ], - "id": "bb5ef79d-6fe5-40f6-936e-7a6c594e2fba" + "id": "b0d2e727-986a-4f00-887e-45c0c83ef3b8" }, { "cell_type": "markdown", @@ -727,7 +982,7 @@ "where $\\pi$ now has the interpretation as being the *prior* probability\n", "that the classification should be positive." ], - "id": "288dddbb-08cf-4e5d-bf99-bc138a796045" + "id": "ee90ab38-6c7c-42d3-961c-829a372faa62" }, { "cell_type": "markdown", @@ -737,14 +992,14 @@ "\n", "This allows us to write down the full joint density of the training\n", "data, $$\n", - " p(\\mathbf{ y}, \\mathbf{X}|\\boldsymbol{ \\theta}, \\pi) = \\prod_{i=1}^{n} \\prod_{j=1}^{p} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi)\n", + " p(\\mathbf{ y}, \\mathbf{X}|\\boldsymbol{ \\theta}, \\pi) = \\prod_{i=1}^{n} \\prod_{j=1}^{d} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi)\n", " $$\n", "\n", "which can now be fit by maximum likelihood. As normal we form our\n", "objective as the negative log likelihood,\n", "\n", "$$\\begin{align*}\n", - "E(\\boldsymbol{ \\theta}, \\pi)& = -\\log p(\\mathbf{ y}, \\mathbf{X}|\\boldsymbol{ \\theta}, \\pi) \\\\ &= -\\sum_{i=1}^{n} \\sum_{j=1}^{p} \\log p(x_{i, j}|y_i, \\boldsymbol{ \\theta}) - \\sum_{i=1}^{n} \\log p(y_i|\\pi),\n", + "E(\\boldsymbol{ \\theta}, \\pi)& = -\\log p(\\mathbf{ y}, \\mathbf{X}|\\boldsymbol{ \\theta}, \\pi) \\\\ &= -\\sum_{i=1}^{n} \\sum_{j=1}^{d} \\log p(x_{i, j}|y_i, \\boldsymbol{ \\theta}) - \\sum_{i=1}^{n} \\log p(y_i|\\pi),\n", "\\end{align*}$$ which we note *decomposes* into two objective functions,\n", "one which is dependent on $\\pi$ alone and one which is dependent on\n", "$\\boldsymbol{ \\theta}$ alone so we have, $$\n", @@ -760,7 +1015,7 @@ "\n", "We now need to minimize the objective associated with the conditional\n", "distributions for the features, $$\n", - "E(\\boldsymbol{ \\theta}) = -\\sum_{i=1}^{n} \\sum_{j=1}^{p} \\log p(x_{i, j} |y_i, \\boldsymbol{ \\theta}),\n", + "E(\\boldsymbol{ \\theta}) = -\\sum_{i=1}^{n} \\sum_{j=1}^{d} \\log p(x_{i, j} |y_i, \\boldsymbol{ \\theta}),\n", "$$ which necessarily implies making some assumptions about the form of\n", "the conditional distributions. The right assumption will depend on the\n", "nature of our input data. For example, if we have an input which is real\n", @@ -776,14 +1031,14 @@ "variances for the different dimensions.\n", "\n", "As normal we form our objective as the negative log likelihood, $$\n", - "E(\\boldsymbol{ \\theta}, \\pi) = -\\log p(\\mathbf{ y}, \\mathbf{X}|\\boldsymbol{ \\theta}, \\pi) = -\\sum_{i=1}^{n} \\sum_{j=1}^{p} \\log p(x_{i, j}|y_i, \\boldsymbol{ \\theta}) - \\sum_{i=1}^{n} \\log p(y_i|\\pi),\n", + "E(\\boldsymbol{ \\theta}, \\pi) = -\\log p(\\mathbf{ y}, \\mathbf{X}|\\boldsymbol{ \\theta}, \\pi) = -\\sum_{i=1}^{n} \\sum_{j=1}^{d} \\log p(x_{i, j}|y_i, \\boldsymbol{ \\theta}) - \\sum_{i=1}^{n} \\log p(y_i|\\pi),\n", "$$ which we note *decomposes* into two objective functions, one which is\n", "dependent on $\\pi$ alone and one which is dependent on\n", "$\\boldsymbol{ \\theta}$ alone so we have, $$\n", "E(\\pi, \\boldsymbol{ \\theta}) = E(\\boldsymbol{ \\theta}) + E(\\pi).\n", "$$" ], - "id": "d9169958-1217-4631-859e-ccb0f31696ef" + "id": "3e1fefc0-9c73-4534-adf9-c1d7dc28ca9a" }, { "cell_type": "markdown", @@ -830,7 +1085,7 @@ "\n", "For ease of use we’ve packaged this data set in the `pods` library" ], - "id": "e25bdf9f-21a2-46ed-9b6e-4dd618d3469a" + "id": "c77642a5-bda0-47aa-a2ed-0bf7a098aaba" }, { "cell_type": "code", @@ -841,7 +1096,7 @@ "data = pods.datasets.nigeria_nmis()['Y']\n", "data.head()" ], - "id": "96c97cd2-ecc7-4b00-81fc-cde94192ce76" + "id": "c511efe7-5582-41fe-b34d-f3768932937c" }, { "cell_type": "markdown", @@ -861,7 +1116,7 @@ "Once it is loaded in the data can be summarized using the `describe`\n", "method in pandas." ], - "id": "e781c420-15b2-4b8a-8ae8-b75e89f5734e" + "id": "259b7a97-8ebc-4541-a64e-9b763e195d02" }, { "cell_type": "code", @@ -871,7 +1126,7 @@ "source": [ "data.describe()" ], - "id": "2a7f0a51-5bda-4d31-91fa-6f7843590296" + "id": "347d943f-a69e-4bea-a787-05606d194c04" }, { "cell_type": "markdown", @@ -880,7 +1135,7 @@ "We can also find out the dimensions of the dataset using the `shape`\n", "property." ], - "id": "ef960090-e5d2-4fde-8cb7-c4f6f74d1aba" + "id": "be121ab5-b8d6-44c7-bda1-74f40fc3e0c5" }, { "cell_type": "code", @@ -890,7 +1145,7 @@ "source": [ "data.shape" ], - "id": "6e653bb6-6d78-46f1-b5a6-13eca429745b" + "id": "99484ae2-4d9a-4d74-8b2a-867faeca3826" }, { "cell_type": "markdown", @@ -908,7 +1163,7 @@ "following the name with a question mark. This will open a box with\n", "documentation at the bottom which can be closed with the x button." ], - "id": "3f6a56ff-2251-48a6-977d-a3402eea4bb1" + "id": "5ecd19bb-e599-4787-a459-00af959a54f2" }, { "cell_type": "code", @@ -918,7 +1173,7 @@ "source": [ "data.describe?" ], - "id": "6f684df1-298a-4b97-9af1-d5e22eb716ef" + "id": "4c6a3679-c3e4-415a-86e9-b1fe28e0e84b" }, { "cell_type": "code", @@ -930,7 +1185,7 @@ "import mlai\n", "import mlai.plot as plot" ], - "id": "0c4a9fc9-c4c1-42fe-ac65-a92c8bc3ebf0" + "id": "499e5a9f-6cd9-43e1-adbe-fcd88455b67f" }, { "cell_type": "code", @@ -945,19 +1200,19 @@ "\n", "mlai.write_figure('nigerian-health-facilities.png', directory='./ml')" ], - "id": "df277f02-282d-4023-a5bc-5ff963dac3c0" + "id": "d6a9e58a-7f04-45d7-8afd-a5d8355a0a75" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Location of the over thirty-four thousand health facilities\n", "registered in the NMIS data across Nigeria. Each facility plotted\n", "according to its latitude and longitude." ], - "id": "86e611a0-50ca-467f-abd6-c87d45d25698" + "id": "76fd81f2-a8e5-4110-b21e-1a70e33c6f21" }, { "cell_type": "markdown", @@ -976,7 +1231,7 @@ "Now we will convert this data into a form which we can use as inputs\n", "`X`, and labels `y`." ], - "id": "69d73fd0-8afb-43ac-a146-e492fba0d8f9" + "id": "bc30dded-fae7-448d-8698-c9314a67086c" }, { "cell_type": "code", @@ -987,7 +1242,7 @@ "import pandas as pd\n", "import numpy as np" ], - "id": "489e5b4f-c974-4496-aac5-617f4a3bf4fb" + "id": "b491763a-979d-48d5-acdc-2c588b9b1402" }, { "cell_type": "code", @@ -1013,7 +1268,7 @@ "y = data['maternal_health_delivery_services']==True # set label to be whether there's a maternal health delivery service\n", "\n", "# Create series of health center types with the relevant index\n", - "s = data['facility_type_display'].apply(pd.Series, 1).stack() \n", + "s = data['facility_type_display'].apply(pd.Series).stack()\n", "s.index = s.index.droplevel(-1) # to line up with df's index\n", "\n", "# Extract from the series the unique list of types.\n", @@ -1028,7 +1283,7 @@ " X.loc[:, type_col] = 0.0 \n", " X.loc[index, type_col] = 1.0" ], - "id": "522aeeb0-3759-47cc-a6dd-113d761d62bf" + "id": "c5383d6f-5898-4413-b472-3e66cc0c2e0a" }, { "cell_type": "markdown", @@ -1037,7 +1292,7 @@ "This has given us a new data frame `X` which contains the different\n", "facility types in different columns." ], - "id": "ae884bcd-ab22-4e62-bd0c-c3485747ed54" + "id": "430388c7-016c-4d69-80f2-fd0492e82291" }, { "cell_type": "code", @@ -1047,7 +1302,7 @@ "source": [ "X.describe()" ], - "id": "7034c5ed-2e41-40b0-a9dd-f890e757a614" + "id": "06b68905-ce45-41d3-9f67-c0fe6c8c2d31" }, { "cell_type": "markdown", @@ -1064,7 +1319,7 @@ "we want to model the data as Gaussian distributed. We set up two data\n", "frames to contain the parameters for the rows and the columns below." ], - "id": "ffddb2cb-aa7d-4711-bcf2-4867274b7167" + "id": "b1750e76-e80a-4832-adeb-ddf2440c3148" }, { "cell_type": "code", @@ -1090,7 +1345,7 @@ "Bernoulli = pd.DataFrame(data=np.zeros((2,len(binary_columns))), columns=binary_columns, index=['theta_0', 'theta_1'])\n", "Gaussian = pd.DataFrame(data=np.zeros((4,len(real_columns))), columns=real_columns, index=['mu_0', 'sigma2_0', 'mu_1', 'sigma2_1'])" ], - "id": "ae6c128c-72fd-4627-9a1d-6127a26fb847" + "id": "9c226977-9389-4977-9074-ddb0fd62a33d" }, { "cell_type": "markdown", @@ -1099,7 +1354,7 @@ "Now we have the data in a form ready for analysis, let’s construct our\n", "data matrix." ], - "id": "d8039d10-1e48-43fd-85d7-63282bff9dfd" + "id": "ebe6fdae-c3f6-49ff-9d27-d6f240b84d90" }, { "cell_type": "code", @@ -1116,7 +1371,7 @@ "X_test = X.iloc[test_indices]\n", "y_test = y.iloc[test_indices]==True" ], - "id": "4e1f19c9-88f3-404f-b432-fff516c25150" + "id": "558a1b0d-6c2f-48ae-a959-3ebde1d8096a" }, { "cell_type": "markdown", @@ -1129,7 +1384,7 @@ "variance of the data for the Gaussian, which also gives us the maximum\n", "likelihood solution." ], - "id": "4dda6c2f-abed-4444-a6be-d46819e803d2" + "id": "d46702cd-f589-4a20-8dc8-5cfadde19882" }, { "cell_type": "code", @@ -1147,7 +1402,7 @@ " Bernoulli[column]['theta_0'] = X_train[column][~y_train].sum()/(~y_train).sum()\n", " Bernoulli[column]['theta_1'] = X_train[column][y_train].sum()/(y_train).sum()" ], - "id": "dbe5df37-0121-4a75-8a72-4366f64b2689" + "id": "2de70d2a-9f0a-4b01-a411-f08c2d024cc9" }, { "cell_type": "markdown", @@ -1156,7 +1411,7 @@ "We can examine the nature of the distributions we’ve fitted to the model\n", "by looking at the entries in these data frames." ], - "id": "6dd4f5c1-d84a-47a8-a8fd-5c7f6ee269f6" + "id": "2ba84527-3ca8-4bfd-b7ad-0eb51100df10" }, { "cell_type": "code", @@ -1166,7 +1421,7 @@ "source": [ "Bernoulli" ], - "id": "99fa7808-e02f-4a4b-8328-a2dedabf740e" + "id": "e6851208-2d67-498b-b899-39f34e988db4" }, { "cell_type": "markdown", @@ -1184,7 +1439,7 @@ "services is given by the product of each of these Bernoulli\n", "distributions." ], - "id": "fcae79a5-f97d-45e1-a747-a3d7953b773a" + "id": "e6756781-e30f-46c6-88e8-e213dd5394c3" }, { "cell_type": "code", @@ -1194,7 +1449,7 @@ "source": [ "Gaussian" ], - "id": "ea00d75f-c351-49e1-941c-978c255b72bc" + "id": "a80a6ab3-e92e-4ecc-91e7-2ac0dcf7bde4" }, { "cell_type": "markdown", @@ -1215,7 +1470,7 @@ "The final model parameter is the prior probability of the positive\n", "class, $\\pi$, which is computed by maximum likelihood." ], - "id": "6c642c0a-d752-4c91-be7f-5c2a9a3cd7ec" + "id": "e4ec203e-abdf-4248-9af6-af4552792b8e" }, { "cell_type": "code", @@ -1225,7 +1480,7 @@ "source": [ "prior = float(y_train.sum())/len(y_train)" ], - "id": "8fb4be02-e019-4458-bbc5-729e6cb80687" + "id": "2147669e-0408-4ba8-8c6b-69b68f1e9b88" }, { "cell_type": "markdown", @@ -1234,7 +1489,7 @@ "The prior probability tells us that slightly more facilities have\n", "maternity services than those that don’t." ], - "id": "8a23e732-78ea-425f-939d-7c3819aaa3ba" + "id": "8594e56c-e8eb-4835-b151-961143ba3dde" }, { "cell_type": "markdown", @@ -1253,7 +1508,7 @@ "$$ and we’ve already defined\n", "$p(y^*, \\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*| \\boldsymbol{ \\theta})$\n", "using our conditional independence assumptions above $$\n", - "p(y^*, \\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*| \\boldsymbol{ \\theta}) = \\prod_{j=1}^{p} p(x^*_{j}|y^*, \\boldsymbol{ \\theta})p(y^*|\\pi)\\prod_{i=1}^{n} \\prod_{j=1}^{p} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi)\n", + "p(y^*, \\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*| \\boldsymbol{ \\theta}) = \\prod_{j=1}^{d} p(x^*_{j}|y^*, \\boldsymbol{ \\theta})p(y^*|\\pi)\\prod_{i=1}^{n} \\prod_{j=1}^{d} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi)\n", "$$ The other required density is $$\n", "p(\\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*|\\boldsymbol{ \\theta})\n", "$$ which can be found from\n", @@ -1261,20 +1516,20 @@ "using the *sum rule* of probability, $$\n", "p(\\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*|\\boldsymbol{ \\theta}) = \\sum_{y^*=0}^1 p(y^*, \\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*| \\boldsymbol{ \\theta}).\n", "$$ Because of our independence assumptions that is simply equal to $$\n", - "p(\\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*| \\boldsymbol{ \\theta}) = \\sum_{y^*=0}^1 \\prod_{j=1}^{p} p(x^*_{j}|y^*_i, \\boldsymbol{ \\theta})p(y^*|\\pi)\\prod_{i=1}^{n} \\prod_{j=1}^{p} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi).\n", + "p(\\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*| \\boldsymbol{ \\theta}) = \\sum_{y^*=0}^1 \\prod_{j=1}^{d} p(x^*_{j}|y^*_i, \\boldsymbol{ \\theta})p(y^*|\\pi)\\prod_{i=1}^{n} \\prod_{j=1}^{d} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi).\n", "$$ Substituting both forms in to recover our distribution over the test\n", "label conditioned on the training data we have, $$\n", - "P(y^*| \\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*, \\boldsymbol{ \\theta}) = \\frac{\\prod_{j=1}^{p} p(x^*_{j}|y^*_i, \\boldsymbol{ \\theta})p(y^*|\\pi)\\prod_{i=1}^{n} \\prod_{j=1}^{p} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi)}{\\sum_{y^*=0}^1 \\prod_{j=1}^{p} p(x^*_{j}|y^*_i, \\boldsymbol{ \\theta})p(y^*|\\pi)\\prod_{i=1}^{n} \\prod_{j=1}^{p} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi)}\n", + "P(y^*| \\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*, \\boldsymbol{ \\theta}) = \\frac{\\prod_{j=1}^{d} p(x^*_{j}|y^*_i, \\boldsymbol{ \\theta})p(y^*|\\pi)\\prod_{i=1}^{n} \\prod_{j=1}^{d} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi)}{\\sum_{y^*=0}^1 \\prod_{j=1}^{d} p(x^*_{j}|y^*_i, \\boldsymbol{ \\theta})p(y^*|\\pi)\\prod_{i=1}^{n} \\prod_{j=1}^{d} p(x_{i,j}|y_i, \\boldsymbol{ \\theta})p(y_i|\\pi)}\n", "$$ and we notice that all the terms associated with the training data\n", "actually cancel, the test prediction is *conditionally independent* of\n", "the training data *given* the parameters. This is a result of our\n", "conditional independence assumptions over the data points. $$\n", - "p(y^*| \\mathbf{ x}^*, \\boldsymbol{ \\theta}) = \\frac{\\prod_{j=1}^{p} p(x^*_{j}|y^*_i,\n", - "\\boldsymbol{ \\theta})p(y^*|\\pi)}{\\sum_{y^*=0}^1 \\prod_{j=1}^{p} p(x^*_{j}|y^*_i, \\boldsymbol{ \\theta})p(y^*|\\pi)}\n", + "p(y^*| \\mathbf{ x}^*, \\boldsymbol{ \\theta}) = \\frac{\\prod_{j=1}^{d} p(x^*_{j}|y^*_i,\n", + "\\boldsymbol{ \\theta})p(y^*|\\pi)}{\\sum_{y^*=0}^1 \\prod_{j=1}^{d} p(x^*_{j}|y^*_i, \\boldsymbol{ \\theta})p(y^*|\\pi)}\n", "$$ This formula is also fairly straightforward to implement. First we\n", "implement the log probabilities for the Gaussian density." ], - "id": "79c253f1-5ffd-4e2b-9890-7e1616f1d89d" + "id": "3b35c331-38e1-400e-9031-fef7dc131fe4" }, { "cell_type": "code", @@ -1285,7 +1540,7 @@ "def log_gaussian(x, mu, sigma2):\n", " return -0.5* np.log(2*np.pi*sigma2)-((x-mu)**2)/(2*sigma2)" ], - "id": "a5d6b156-b075-4cae-83fa-ab555d41a0b6" + "id": "6bf3fd47-9f71-4bee-98cc-35c804b50c88" }, { "cell_type": "markdown", @@ -1300,7 +1555,7 @@ "underflow). Working in log space can ameliorate this problem. We can\n", "also compute the log probability for the Bernoulli distribution." ], - "id": "f78975ed-1e99-4429-b459-0ce0bbe581a6" + "id": "ecb8501b-c8dc-4cf5-b61f-c2c462942e78" }, { "cell_type": "code", @@ -1311,7 +1566,7 @@ "def log_bernoulli(x, theta):\n", " return x*np.log(theta) + (1-x)*np.log(1-theta)" ], - "id": "54f68ed1-f5be-476f-a07e-aee3f577ee5f" + "id": "09d1b401-e18d-45a1-b607-2dfb79c0d9fb" }, { "cell_type": "markdown", @@ -1333,7 +1588,7 @@ "smoothing, which can be found at the bottom of following page from\n", "Laplace’s ‘Essai Philosophique …’" ], - "id": "100c9438-7795-4868-a725-a1d3194359a1" + "id": "3c01cdec-b169-4e23-86f6-51b8c5ddfad1" }, { "cell_type": "code", @@ -1344,7 +1599,7 @@ "import notutils as nu\n", "nu.display_google_book(id='1YQPAAAAQAAJ', page='PA16')" ], - "id": "0d4a5387-a142-436a-94e9-77aca164b254" + "id": "4e9135bc-34a8-47e5-82d3-33ac7bcf5123" }, { "cell_type": "markdown", @@ -1390,7 +1645,7 @@ "$$ to prevent problems with certainty causing numerical issues and\n", "misclassifications. Let’s refit the Bernoulli features now." ], - "id": "a788dc2b-a601-42a9-a0c0-24b9fc369f59" + "id": "2fba08ad-2600-4803-9315-f51835ff2029" }, { "cell_type": "code", @@ -1404,7 +1659,7 @@ " Bernoulli[column]['theta_0'] = (X_train[column][~y_train].sum() + 1)/((~y_train).sum() + 2)\n", " Bernoulli[column]['theta_1'] = (X_train[column][y_train].sum() + 1)/((y_train).sum() + 2)" ], - "id": "2759c040-67c8-4e04-bccd-155ae3d75925" + "id": "b14ebc6e-54cb-409a-8860-8cdbb1ce38cf" }, { "cell_type": "markdown", @@ -1412,7 +1667,7 @@ "source": [ "That places us in a position to write the prediction function." ], - "id": "1bf3f999-216a-4210-a17d-6f11541a3d2e" + "id": "eb1edd06-50b1-4880-944f-21ad20f3e737" }, { "cell_type": "code", @@ -1423,7 +1678,7 @@ "import numpy as np\n", "import pandas as pd" ], - "id": "94ca643c-199a-4caa-a4fa-a91405b123be" + "id": "01d5b459-ac5e-476f-8b26-408e8d45be04" }, { "cell_type": "code", @@ -1449,7 +1704,7 @@ " return v\n", " #return np.exp(log_positive + np.log(prior))/(np.exp(log_positive + np.log(prior)) + np.exp(log_negative + np.log(1-prior)))" ], - "id": "fdb24534-cace-4f97-bd1a-174cfd568812" + "id": "e9ff21a5-a9f5-4024-a3d3-a21b2d4c4633" }, { "cell_type": "markdown", @@ -1457,7 +1712,7 @@ "source": [ "Now we are in a position to make the predictions for the test data." ], - "id": "3af765dd-58aa-4403-94cb-730b356e943c" + "id": "bcd4fd99-0250-4cf5-b640-c3e00d9d1047" }, { "cell_type": "code", @@ -1467,7 +1722,7 @@ "source": [ "p_y = predict(X_test, Gaussian, Bernoulli, prior)" ], - "id": "5afba56f-7a6b-44d4-b911-b71a846259cb" + "id": "8561152c-3abc-479b-952e-ec67ac1446d5" }, { "cell_type": "markdown", @@ -1479,7 +1734,7 @@ "the positive class. We can then compare to the true values, and see how\n", "many of these values we got correct. This is our total number correct." ], - "id": "3a960f54-4091-401d-a72b-e88497ecb382" + "id": "0efb8852-f59b-46a8-87c5-94f4551f6925" }, { "cell_type": "code", @@ -1491,7 +1746,7 @@ "total_correct = sum(correct)\n", "print(\"Total correct\", total_correct, \" out of \", len(y_test), \"which is\", float(total_correct)/len(y_test), \"%\")" ], - "id": "b33d65bc-0ea7-4fd6-a0fe-465d259c70be" + "id": "025b2631-8d54-4ff3-b7ca-0d82e39873d0" }, { "cell_type": "markdown", @@ -1507,7 +1762,7 @@ "the matrix we place the actual class, and along the columns we place our\n", "predicted class." ], - "id": "360c0206-ce9e-4005-bef6-492f886adc9f" + "id": "f1079610-c228-46f5-8306-3d6c1420ad2c" }, { "cell_type": "code", @@ -1524,7 +1779,7 @@ "confusion_matrix['predicted no maternity']['actual no maternity'] = (~y_test & ~(p_y>0.5)).sum()\n", "confusion_matrix" ], - "id": "33e6246e-afaa-48fe-befc-b6d9e58bb388" + "id": "0c98d95d-cf8f-4d8f-92c0-3ef6d4a99ee3" }, { "cell_type": "markdown", @@ -1537,7 +1792,7 @@ "remove features that appear to be less helpful. How might you select\n", "such features?" ], - "id": "d1ce991e-2a71-484b-9cb8-7864124358f2" + "id": "842c2b36-ea49-4d7c-b38b-da3c5338714d" }, { "cell_type": "markdown", @@ -1547,7 +1802,7 @@ "\n", "Write your answer to Exercise 2 here" ], - "id": "35c63718-efae-4f7f-9a03-d4cbffb5525c" + "id": "2b6e72a5-d315-4477-8085-eb4e26e88a4b" }, { "cell_type": "code", @@ -1558,7 +1813,7 @@ "# Use this box for any code you need\n", "\n" ], - "id": "d2c5e658-0227-45dc-adcb-a1fbb6809fe3" + "id": "8ef4c17f-8f45-44b6-b232-e3e64df84ff6" }, { "cell_type": "markdown", @@ -1574,7 +1829,7 @@ "negatives (i.e. facilities where you predicted there was no maternity,\n", "but in actuality there were?" ], - "id": "adbb743e-f78f-43ed-85cb-654423aa8d7a" + "id": "0c3d5d2b-c751-48d4-9559-f4b593307908" }, { "cell_type": "markdown", @@ -1584,7 +1839,7 @@ "\n", "Write your answer to Exercise 3 here" ], - "id": "c3d89c3e-7c12-4833-9994-14ed2505f122" + "id": "5c3138c1-8590-4b77-bf9a-41a8d7c4649e" }, { "cell_type": "code", @@ -1595,7 +1850,7 @@ "# Use this box for any code you need\n", "\n" ], - "id": "7b6761bf-f993-4f8f-b382-d422e1ceb3ba" + "id": "b276cec5-f4f5-4938-9b8d-303b3340c6e1" }, { "cell_type": "markdown", @@ -1609,7 +1864,7 @@ "P(y^*| \\mathbf{ y}, \\mathbf{X}, \\mathbf{ x}^*, \\boldsymbol{ \\theta})\n", "$$" ], - "id": "29cdd04a-f8fc-482e-9170-b33e76fe6123" + "id": "da4fe29c-12e9-4062-b21a-10a7f6f6b819" }, { "cell_type": "markdown", @@ -1626,7 +1881,7 @@ "\\sigma^2 = \\frac{\\sum_{i=1}^{n} (x_i - \\mu)^2}{n}\n", "$$" ], - "id": "1860af22-69f5-49c9-834d-0a9df723ac22" + "id": "5f884577-d084-476f-ab91-7a32838270f4" }, { "cell_type": "markdown", @@ -1636,7 +1891,7 @@ "\n", "Write your answer to Exercise 4 here" ], - "id": "eed28089-c1bb-42e4-a7ba-dd000e82c109" + "id": "ec7c1928-f1b4-4b83-864b-e9c495b18caf" }, { "cell_type": "code", @@ -1647,7 +1902,7 @@ "# Use this box for any code you need\n", "\n" ], - "id": "b23597c6-6032-4938-af46-78d0b6a70375" + "id": "14a4cdbb-2b28-492d-9675-4cb801c45590" }, { "cell_type": "markdown", @@ -1662,16 +1917,16 @@ "\n", "In either case, maximum likelihood fitting would proceed in the same\n", "way. The objective has the form, $$\n", - "E(\\boldsymbol{ \\theta}) = -\\sum_{j=1}^{p} \\sum_{i=1}^{n} \\log p(x_{i,j} |y_i, \\boldsymbol{ \\theta}),\n", + "E(\\boldsymbol{ \\theta}) = -\\sum_{j=1}^{d} \\sum_{i=1}^{n} \\log p(x_{i,j} |y_i, \\boldsymbol{ \\theta}),\n", "$$ and if, as above, the parameters of the distributions are specific to\n", "each feature vector (we had means and variances for each continuous\n", "feature, and a probability for each binary feature) then we can use the\n", "fact that these parameters separate into disjoint subsets across the\n", "features to write, $$\n", "\\begin{align*}\n", - "E(\\boldsymbol{ \\theta}) &= -\\sum_{j=1}^{p} \\sum_{i=1}^{n} \\log\n", + "E(\\boldsymbol{ \\theta}) &= -\\sum_{j=1}^{d} \\sum_{i=1}^{n} \\log\n", "p(x_{i,j} |y_i, \\boldsymbol{ \\theta}_j)\\\\\n", - "& \\sum_{j=1}^{p}\n", + "& \\sum_{j=1}^{d}\n", "E(\\boldsymbol{ \\theta}_j),\n", "\\end{align*}\n", "$$ which means we can minimize our objective on each feature\n", @@ -1682,7 +1937,7 @@ "positive class and fit parameters for that class, then we select each\n", "negative class and fit features for that class. We have code below." ], - "id": "e69afb8c-3f14-4917-abda-776e56e27889" + "id": "3e831646-75bb-4050-98f6-3149aeee49c6" }, { "cell_type": "markdown", @@ -1710,7 +1965,7 @@ "factorization assumption that allows us to do this efficiently is very\n", "strong and may lead to poor decision boundaries in practice." ], - "id": "221a5792-dac3-431b-afa4-fd5c1ec9ef24" + "id": "302fe485-1ace-429c-92d2-e67b15826759" }, { "cell_type": "markdown", @@ -1721,7 +1976,7 @@ "- Chapter 5 up to pg 179 (Section 5.1, and 5.2 up to 5.2.2) of Rogers\n", " and Girolami (2011)" ], - "id": "6a49878f-6289-4a00-8f23-6c3dfe0450f2" + "id": "839ea882-c24f-4597-b454-b3df97ad0baf" }, { "cell_type": "markdown", @@ -1732,6 +1987,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -1739,20 +1997,14 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "8d93ddb1-d4e0-46ce-9aaa-0f303622ae06" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "003dd5e2-3629-46b7-bbf5-a2cb2e7f81ae" + "id": "36d3f5f9-bcde-4120-a4da-7473212ee316" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Rogers, S., Girolami, M., 2011. A first course in machine learning. CRC\n", "Press.\n", "\n", @@ -1760,7 +2012,7 @@ "Millennium Development Goals (OSSAP-MDGs), Columbia University, 2014.\n", "Nigeria NMIS facility database." ], - "id": "7c3a6c84-75cd-4652-b5bc-98e0b40aea9c" + "id": "b287e206-d8ac-40b2-a25b-d6fa1c2fa7a6" } ], "nbformat": 4, diff --git a/_notebooks/11-logistic-and-glm.ipynb b/_notebooks/11-logistic-and-glm.ipynb index e81a178b..f3d72376 100644 --- a/_notebooks/11-logistic-and-glm.ipynb +++ b/_notebooks/11-logistic-and-glm.ipynb @@ -6,7 +6,7 @@ "source": [ "# Logistic Regression and GLMs" ], - "id": "10306567-73f3-45a7-8bac-55ade03195d0" + "id": "c4a05c2e-fcc5-434b-81c5-5cc585c2a000" }, { "cell_type": "markdown", @@ -20,7 +20,7 @@ "directly the assumption is that we model the *log-odds* with the basis\n", "functions." ], - "id": "1538a80d-5711-4a91-8579-356f6e4b0c61" + "id": "7ab56db4-97f1-4a9b-ad6e-ed49fae67b13" }, { "cell_type": "markdown", @@ -29,17 +29,20 @@ "$$\n", "$$" ], - "id": "a9b63c63-6564-4ebb-8340-a6ece0c72890" + "id": "96d0130e-73d2-4f10-b9dc-f635c0cf92cf" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -47,7 +50,7 @@ "\n", "-->" ], - "id": "594a95dd-2bbc-48cb-932e-a24788af9546" + "id": "dd435c8c-3250-429c-aa39-f96ac247a6d7" }, { "cell_type": "markdown", @@ -59,7 +62,7 @@ "style=\"\">\\[edit\\]" ], - "id": "6f2cb909-3389-4a07-96b1-3a90adcc362a" + "id": "fa86f6af-4a1c-4d1c-bb16-8a7908bb80e0" }, { "cell_type": "code", @@ -68,9 +71,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "0faed12b-08b6-459a-8541-aad0c8694958" + "id": "bd0a8554-4dee-4f12-97d7-1c9bf191febc" }, { "cell_type": "markdown", @@ -81,7 +94,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "662c319f-a013-4afc-bfd0-2e291fcf3d27" + "id": "b4a22918-dccc-4d47-9dc9-0ba5d600ecd9" }, { "cell_type": "markdown", @@ -98,7 +111,17 @@ "\n", "The software can be installed using" ], - "id": "63aa4e2e-bcf6-4eb8-b28e-29cbe8bd7b91" + "id": "0a907955-fc63-45ab-ad9e-d4a667d7c2a0" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "b03c82f4-509a-475d-8a71-66d2f229eab4" }, { "cell_type": "code", @@ -106,9 +129,48 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" ], - "id": "ca0d2b20-c35d-4575-baa3-c3f35bf1fc2e" + "id": "9659e249-22e1-4990-8c4d-7485cd36385f" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "4219666e-aba1-4ef3-8055-9576bcff7030" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "682dc08f-6744-4934-a161-99d732121865" }, { "cell_type": "markdown", @@ -121,7 +183,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "faae0d09-5348-48d0-bae0-a06b99adc4f5" + "id": "16d6b2bb-d172-4b1a-8f58-04abcd4d5d3a" }, { "cell_type": "code", @@ -131,7 +193,7 @@ "source": [ "import notutils" ], - "id": "616bf22e-6595-435e-80fa-dade0b5d3b4d" + "id": "fa9f1a26-1f74-4383-a61c-e89925bcf367" }, { "cell_type": "markdown", @@ -153,7 +215,7 @@ "\n", "The software can be installed using" ], - "id": "550dd743-bf2f-4313-9f2a-bfc9d41f0912" + "id": "df91a99f-3c22-4e3b-8455-0f119bb71576" }, { "cell_type": "code", @@ -161,9 +223,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "cmd = install_command('pods')" ], - "id": "10a4c60e-e80d-4638-adb6-d8e9e813a5b7" + "id": "9882fba5-29ac-4623-8b92-8e7972d98651" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "8e78712c-90ad-43ac-be09-c435a32edc3f" }, { "cell_type": "markdown", @@ -175,7 +247,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "8b733a7a-3765-41ef-90c5-fe32120c5667" + "id": "638742db-b843-430d-962d-91029307ed3e" }, { "cell_type": "code", @@ -185,7 +257,7 @@ "source": [ "import pods" ], - "id": "47bbe873-f615-42f1-aad8-039974363003" + "id": "0a028f4a-4815-4aa5-95d1-3c7e9731f816" }, { "cell_type": "markdown", @@ -203,7 +275,7 @@ "\n", "The software can be installed using" ], - "id": "29a31862-2813-4ae1-8f52-c35e1577a3ac" + "id": "063f7946-8c8b-4029-9d37-2bb94b0f13da" }, { "cell_type": "code", @@ -211,9 +283,19 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "cmd = install_command('mlai')" ], - "id": "cbc73649-eff3-469b-944f-b8565dfac565" + "id": "21acbac3-29ee-4eb5-8169-076e7be1d3e7" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "99f64543-8517-478a-9c52-c9005e42ad00" }, { "cell_type": "markdown", @@ -225,7 +307,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "8cdda6b3-add6-4956-915b-861d120ca6fb" + "id": "0f71f615-fe1a-4132-9141-632b0f377d40" }, { "cell_type": "code", @@ -233,9 +315,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "f82ba404-5e35-4ec9-8f31-92669c44e8d2" + "id": "8a6d9d5d-e0fb-4c83-a22e-77028a151d5d" }, { "cell_type": "markdown", @@ -282,7 +365,7 @@ "will always have all the inputs at run time, it may make sense to\n", "*directly* model the conditional density, $p(\\mathbf{ y}|\\mathbf{X})$." ], - "id": "cbb42c64-b0c6-40db-8b2e-34889d0a3f83" + "id": "90c4ef8d-71b6-4962-bf5c-9bc88e0129ac" }, { "cell_type": "markdown", @@ -292,34 +375,39 @@ "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", "A logistic regression is an approach to classification which extends the\n", - "linear basis function models we’ve already explored. Rather than\n", - "modeling the output of the function directly the assumption is that we\n", - "model the *log-odds* with the basis functions.\n", + "linear regression models we’ve already explored. Rather than modeling\n", + "the output of the function directly the assumption is that we model the\n", + "*log-odds* with the basis functions.\n", "\n", "The [odds](http://en.wikipedia.org/wiki/Odds) are defined as the ratio\n", "of the probability of a positive outcome, to the probability of a\n", - "negative outcome. If the probability of a positive outcome is denoted by\n", - "$\\pi$, then the odds are computed as $\\frac{\\pi}{1-\\pi}$. Odds are\n", - "widely used by [bookmakers](http://en.wikipedia.org/wiki/Bookmaker) in\n", - "gambling, although a bookmakers odds won’t normalise: i.e. if you look\n", - "at the equivalent probabilities, and sum over the probability of all\n", - "outcomes the bookmakers are considering, then you won’t get one. This is\n", - "how the bookmaker makes a profit. Because a probability is always\n", - "between zero and one, the odds are always between $0$ and $\\infty$. If\n", - "the positive outcome is unlikely the odds are close to zero, if it is\n", - "very likely then the odds become close to infinite. Taking the logarithm\n", - "of the odds maps the odds from the positive half space to being across\n", - "the entire real line. Odds that were between 0 and 1 (where the negative\n", - "outcome was more likely) are mapped to the range between $-\\infty$ and\n", - "$0$. Odds that are greater than 1 are mapped to the range between $0$\n", - "and $\\infty$. Considering the log odds therefore takes a number between\n", - "0 and 1 (the probability of positive outcome) and maps it to the entire\n", + "negative outcome. Just as we saw in our jumper (sweater) example where\n", + "$$ \n", + "\\log \\frac{p(\\text{bought})}{p(\\text{not bought})} = w_0 + w_1 \\text{age} + w_2 \\text{latitude} \n", + "$$ If the probability of a positive outcome is denoted by $\\pi$, then\n", + "the odds are computed as $\\frac{\\pi}{1-\\pi}$.\n", + "\n", + "Odds are widely used by\n", + "[bookmakers](http://en.wikipedia.org/wiki/Bookmaker) in gambling,\n", + "although a bookmakers odds won’t normalise: i.e. if you look at the\n", + "equivalent probabilities, and sum over the probability of all outcomes\n", + "the bookmakers are considering, then you won’t get one. This is how the\n", + "bookmaker makes a profit. Because a probability is always between zero\n", + "and one, the odds are always between $0$ and $\\infty$. If the positive\n", + "outcome is unlikely the odds are close to zero, if it is very likely\n", + "then the odds become close to infinite. Taking the logarithm of the odds\n", + "maps the odds from the positive half space to being across the entire\n", + "real line. Odds that were between 0 and 1 (where the negative outcome\n", + "was more likely) are mapped to the range between $-\\infty$ and $0$. Odds\n", + "that are greater than 1 are mapped to the range between $0$ and\n", + "$\\infty$. Considering the log odds therefore takes a number between 0\n", + "and 1 (the probability of positive outcome) and maps it to the entire\n", "real line. The function that does this is known as the [logit\n", "function](http://en.wikipedia.org/wiki/Logit),\n", - "$g^{-1}(p_i) = \\log\\frac{p_i}{1-p_i}$. This function is known as a *link\n", + "$g(p_i) = \\log\\frac{p_i}{1-p_i}$. This function is known as a *link\n", "function*.\n", "\n", "For a standard regression we take, $$\n", @@ -336,15 +424,15 @@ "$\\infty$. We have defined the link function as taking the form\n", "$g^{-1}(\\cdot)$ implying that the inverse link function is given by\n", "$g(\\cdot)$. Since we have defined, $$\n", - "g^{-1}(\\pi) =\n", + "g(\\pi) =\n", "\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x})\n", "$$ we can write $\\pi$ in terms of the *inverse link* function,\n", - "$g(\\cdot)$ as $$\n", - "\\pi = g(\\mathbf{ w}^\\top\n", + "$h(\\cdot)$ as $$\n", + "\\pi = h(\\mathbf{ w}^\\top\n", "\\boldsymbol{ \\phi}(\\mathbf{ x})).\n", "$$" ], - "id": "bae4e42b-5834-4e51-aedd-371642833adf" + "id": "61e4360c-1247-469c-b971-6af4663cdcd4" }, { "cell_type": "code", @@ -354,7 +442,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "d640a1e4-75f6-4fd9-a29e-db8151d92609" + "id": "26b213d6-97a9-4154-badf-58ec1f3b090c" }, { "cell_type": "code", @@ -362,21 +450,19 @@ "metadata": {}, "outputs": [], "source": [ - "plot.logistic('./ml/logistic.svg')" + "plot.logistic('./ml')" ], - "id": "439cf778-c728-406a-977b-15a8c1a0ef4d" + "id": "7ffd5834-a501-4dbf-8bc9-3f790d02d038" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Basis Function\n", - "\n", "We’ll define our prediction, objective and gradient functions below. But\n", "before we start, we need to define a basis function for our model. Let’s\n", "start with the linear basis." ], - "id": "3a627910-8b9c-46b0-9b92-2e5a894157d7" + "id": "1c7cd482-608d-493d-ab73-c2db9177a813" }, { "cell_type": "code", @@ -386,7 +472,7 @@ "source": [ "import numpy as np" ], - "id": "6450d98e-e124-4da6-9b33-fe4b200ec7fc" + "id": "27e22f6f-edc8-4549-aa15-0a4d66cae8d8" }, { "cell_type": "code", @@ -394,9 +480,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import linear\n", + "import inspect\n", + "file_path = inspect.getfile(linear)" ], - "id": "2e02fa70-9feb-4b5a-afca-4f2455a0c94d" + "id": "7fc95e55-4dff-4284-9dc6-acf306221a93" }, { "cell_type": "code", @@ -404,9 +492,26 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.linear" + "%load -s linear {file_path}" ], - "id": "184e226f-8e5d-40e7-b96c-0d3454a5f77a" + "id": "011a01e6-8ddb-41fe-8868-a6732b9a4817" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Soft Arg Max (Softmax) Function\n", + "\n", + "\\[edit\\]\n", + "\n", + "Given class scores\n", + "$f_k(\\mathbf{ x})=\\mathbf{ w}_k^\\top\\boldsymbol{ \\phi}(\\mathbf{ x})$,\n", + "the softmax maps scores to probabilities:\n", + "$\\pi_k = \\frac{\\exp(f_k)}{\\sum_{j=1}^K \\exp(f_j)}$." + ], + "id": "129f6717-e585-44da-b93e-34cabe9f87ef" }, { "cell_type": "markdown", @@ -414,9 +519,13 @@ "source": [ "## Prediction Function\n", "\n", + "\\[edit\\]\n", + "\n", "Now we have the basis function let’s define the prediction function." ], - "id": "d49b5a8c-fa78-4627-88b1-f88347731137" + "id": "688d2757-2461-459d-8cdd-7c4411ef4a13" }, { "cell_type": "code", @@ -426,7 +535,7 @@ "source": [ "import numpy as np" ], - "id": "b6835d89-6985-42c2-bc99-8390c5e966de" + "id": "ee5ef5eb-d736-48ae-bce2-0204506da0aa" }, { "cell_type": "code", @@ -434,13 +543,13 @@ "metadata": {}, "outputs": [], "source": [ - "def predict(w, x, basis=linear, **kwargs):\n", + "def predict(w, x, basis=mlai.linear, **kwargs):\n", " \"Generates the prediction function and the basis matrix.\"\n", " Phi = basis(x, **kwargs)\n", " f = np.dot(Phi, w)\n", " return 1./(1+np.exp(-f)), Phi" ], - "id": "22698d82-817d-48ce-9763-1856264359bd" + "id": "60c36037-a4ff-4911-8025-2ae14304b79b" }, { "cell_type": "markdown", @@ -451,55 +560,7 @@ "name logistic regression) or sometimes it is called the sigmoid\n", "function. For a particular value of the input to the link function,\n", "$f_i = \\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x}_i)$ we can plot\n", - "the value of the inverse link function as below." - ], - "id": "0165970e-45bf-4029-81e0-23d78cea8f18" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Sigmoid Function\n", - "\n", - "\\[edit\\]" - ], - "id": "005364a7-7c82-445c-843d-db684f3f3e2c" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import mlai.plot as plot" - ], - "id": "c092aa65-af71-4010-86fa-66f6a3a196a6" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot.logistic('./ml/logistic.svg')" - ], - "id": "7f2b5c22-1d3c-467a-b71e-0d5210e9bd1e" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "Figure: The logistic function.\n", - "\n", - "The function has this characeristic ‘s’-shape (from where the term\n", - "sigmoid, as in sigma, comes from). It also takes the input from the\n", - "entire real line and ‘squashes’ it into an output that is between zero\n", - "and one. For this reason it is sometimes also called a ‘squashing\n", - "function’.\n", + "the value of the inverse link function as below.\n", "\n", "By replacing the inverse link with the sigmoid we can write $\\pi$ as a\n", "function of the input and the parameter vector as, $$\n", @@ -515,7 +576,7 @@ "we can assume conditional independence of each data point given the\n", "parameters and develop a likelihod for the entire data set.\n", "\n", - "As we discussed last time, the Bernoulli likelihood is of the form, $$\n", + "The Bernoulli likelihood is of the form, $$\n", "P(y_i|\\mathbf{ w}, \\mathbf{ x}) =\n", "\\pi_i^{y_i} (1-\\pi_i)^{1-y_i}\n", "$$ which we can think of as clever trick for mathematically switching\n", @@ -533,7 +594,7 @@ "but writing it mathematically makes it easier to write our objective\n", "function within a single mathematical equation." ], - "id": "12f098ec-98d4-471e-aa23-d06b2fde5c74" + "id": "622fd612-5776-4fdb-8f09-751a802bd221" }, { "cell_type": "markdown", @@ -541,8 +602,12 @@ "source": [ "## Maximum Likelihood\n", "\n", - "To obtain the parameters of the model, we need to maximize the\n", - "likelihood, or minimize the objective function, normally taken to be the\n", + "\\[edit\\]\n", + "\n", + "To obtain the parameters of the model, we need to maximise the\n", + "likelihood, or minimise the objective function, normally taken to be the\n", "negative log likelihood. With a data conditional independence assumption\n", "the likelihood has the form, $$\n", "P(\\mathbf{ y}|\\mathbf{ w},\n", @@ -553,16 +618,28 @@ "y_i \\log \\pi_i + \\sum_{i=1}^n(1-y_i)\\log (1-\\pi_i)\n", "$$ and if we take the probability of positive outcome for the $i$th data\n", "point to be given by $$\n", - "\\pi_i = g\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x}_i)\\right),\n", - "$$ where $g(\\cdot)$ is the *inverse* link function, then this leads to\n", + "\\pi_i = h\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x}_i)\\right),\n", + "$$ where $h(\\cdot)$ is the *inverse* link function, then this leads to\n", "an objective function of the form, $$\n", "E(\\mathbf{ w}) = - \\sum_{i=1}^ny_i \\log\n", - "g\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x}_i)\\right) -\n", - "\\sum_{i=1}^n(1-y_i)\\log \\left(1-g\\left(\\mathbf{ w}^\\top\n", + "h\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x}_i)\\right) -\n", + "\\sum_{i=1}^n(1-y_i)\\log \\left(1-h\\left(\\mathbf{ w}^\\top\n", "\\boldsymbol{ \\phi}(\\mathbf{ x}_i)\\right)\\right).\n", "$$" ], - "id": "1b7ad6f2-7f30-4e1a-b9b5-4f0e96f8bfbf" + "id": "0cee564e-4297-4e82-bbbb-64f125ac191e" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Negative Log Likelihood\n", + "\n", + "\\[edit\\]" + ], + "id": "ef7c2e44-358d-467b-a966-6e9f0ecf69ca" }, { "cell_type": "code", @@ -572,7 +649,7 @@ "source": [ "import numpy as np" ], - "id": "1acc44bd-fc68-44d7-8125-d4af3eb3a150" + "id": "7ffa1930-df45-47c7-a2a8-f74e5fd9484a" }, { "cell_type": "code", @@ -580,33 +657,33 @@ "metadata": {}, "outputs": [], "source": [ - "def objective(g, y):\n", + "def objective(h, y):\n", " \"Computes the objective function.\"\n", " labs = np.asarray(y, dtype=float).flatten()\n", " posind = np.where(labs==1)\n", " negind = np.where(labs==0)\n", - " return -np.log(g[posind, :]).sum() - np.log(1-g[negind, :]).sum()" + " return -np.log(h[posind, :]).sum() - np.log(1-h[negind, :]).sum()" ], - "id": "a10d30b2-5b85-4308-a83b-c7448138ba54" + "id": "086d8d8a-2715-4258-a5e1-5fa049062577" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "As normal, we would like to minimize this objective. This can be done by\n", + "As normal, we would like to minimise this objective. This can be done by\n", "differentiating with respect to the parameters of our prediction\n", "function, $\\pi(\\mathbf{ x};\\mathbf{ w})$, for optimisation. The gradient\n", "of the likelihood with respect to $\\pi(\\mathbf{ x};\\mathbf{ w})$ is of\n", "the form, $$\n", "\\frac{\\text{d}E(\\mathbf{ w})}{\\text{d}\\mathbf{ w}} = -\\sum_{i=1}^n\n", - "\\frac{y_i}{g\\left(\\mathbf{ w}^\\top\n", - "\\boldsymbol{ \\phi}(\\mathbf{ x})\\right)}\\frac{\\text{d}g(f_i)}{\\text{d}f_i}\n", + "\\frac{y_i}{h\\left(\\mathbf{ w}^\\top\n", + "\\boldsymbol{ \\phi}(\\mathbf{ x})\\right)}\\frac{\\text{d}h(f_i)}{\\text{d}f_i}\n", "\\boldsymbol{ \\phi}(\\mathbf{ x}_i) + \\sum_{i=1}^n\n", - "\\frac{1-y_i}{1-g\\left(\\mathbf{ w}^\\top\n", - "\\boldsymbol{ \\phi}(\\mathbf{ x})\\right)}\\frac{\\text{d}g(f_i)}{\\text{d}f_i}\n", + "\\frac{1-y_i}{1-h\\left(\\mathbf{ w}^\\top\n", + "\\boldsymbol{ \\phi}(\\mathbf{ x})\\right)}\\frac{\\text{d}h(f_i)}{\\text{d}f_i}\n", "\\boldsymbol{ \\phi}(\\mathbf{ x}_i)\n", "$$ where we used the chain rule to develop the derivative in terms of\n", - "$\\frac{\\text{d}g(f_i)}{\\text{d}f_i}$, which is the gradient of the\n", + "$\\frac{\\text{d}h(f_i)}{\\text{d}f_i}$, which is the gradient of the\n", "inverse link function (in our case the gradient of the sigmoid\n", "function).\n", "\n", @@ -618,24 +695,17 @@ "$\\boldsymbol{ \\phi}(\\mathbf{ x}_i)$.\n", "\n", "The only missing term is the gradient of the inverse link function. For\n", - "the sigmoid squashing function we have, $$\\begin{align*}\n", - "g(f_i) &= \\frac{1}{1+\\exp(-f_i)}\\\\\n", - "&=(1+\\exp(-f_i))^{-1}\n", - "\\end{align*}$$ and the gradient can be computed as $$\\begin{align*}\n", - "\\frac{\\text{d}g(f_i)}{\\text{d} f_i} & =\n", - "\\exp(-f_i)(1+\\exp(-f_i))^{-2}\\\\\n", - "& = \\frac{1}{1+\\exp(-f_i)}\n", - "\\frac{\\exp(-f_i)}{1+\\exp(-f_i)} \\\\\n", - "& = g(f_i) (1-g(f_i))\n", - "\\end{align*}$$ so the full gradient can be written down as $$\n", + "the sigmoid squashing function we have,\n", + "\n", + "so the full gradient can be written down as $$\n", "\\frac{\\text{d}E(\\mathbf{ w})}{\\text{d}\\mathbf{ w}} = -\\sum_{i=1}^n\n", - "y_i\\left(1-g\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x})\\right)\\right)\n", + "y_i\\left(1-h\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x})\\right)\\right)\n", "\\boldsymbol{ \\phi}(\\mathbf{ x}_i) + \\sum_{i=1}^n\n", - "(1-y_i)\\left(g\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x})\\right)\\right)\n", + "(1-y_i)\\left(h\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x})\\right)\\right)\n", "\\boldsymbol{ \\phi}(\\mathbf{ x}_i).\n", "$$" ], - "id": "2febba22-37e7-4dd7-b6a0-c4c039ce8daf" + "id": "726cc45d-51ad-4eb8-a8c3-8be010f9ccd8" }, { "cell_type": "code", @@ -645,7 +715,7 @@ "source": [ "import numpy as np" ], - "id": "e13b7c1e-cdfa-4061-946a-899601e50827" + "id": "ff20329c-db8e-4528-bd0e-2d54996580fd" }, { "cell_type": "code", @@ -653,36 +723,42 @@ "metadata": {}, "outputs": [], "source": [ - "def gradient(g, Phi, y):\n", + "def gradient(h, Phi, y):\n", " \"Generates the gradient of the parameter vector.\"\n", " labs = np.asarray(y, dtype=float).flatten()\n", " posind = np.where(labs==1)\n", - " dw = -(Phi[posind]*(1-g[posind])).sum(0)\n", + " dw = -(Phi[posind]*(1-h[posind])).sum(0)\n", " negind = np.where(labs==0 )\n", - " dw += (Phi[negind]*g[negind]).sum(0)\n", + " dw += (Phi[negind]*h[negind]).sum(0)\n", " return dw[:, None]" ], - "id": "031c133d-a0e7-43c2-99e8-8cff8f9ec099" + "id": "0ae66518-33d3-4e0e-b260-8305c09ade0d" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Optimization of the Function\n", + "## Optimisation of the Function\n", "\n", - "Reorganizing the gradient to find a stationary point of the function\n", + "Reorganising the gradient to find a stationary point of the function\n", "with respect to the parameters $\\mathbf{ w}$ turns out to be impossible.\n", - "Optimization has to proceed by *numerical methods*. Options include the\n", + "Optimisation has to proceed by *numerical methods*. Options include the\n", "multidimensional variant of [Newton’s\n", "method](http://en.wikipedia.org/wiki/Newton%27s_method) or [gradient\n", - "based optimization\n", + "based optimisation\n", "methods](http://en.wikipedia.org/wiki/Gradient_method) like we used for\n", - "optimizing matrix factorization for the movie recommender system. We\n", - "recall from matrix factorization that, for large data, *stochastic\n", + "optimising matrix factorisation for the movie recommender system. We\n", + "recall from matrix factorisation that, for large data, *stochastic\n", "gradient descent* or the Robbins Munro (Robbins and Monro, 1951)\n", - "optimization procedure worked best for function minimization." + "optimisation procedure worked best for function minimisation.\n", + "\n", + "Multiplying everything out leads to $$\n", + "\\begin{aligned}\n", + "\\frac{\\text{d}E(\\mathbf{ w})}{\\text{d}\\mathbf{ w}} = & -\\sum_{i=1}^n\n", + "y_i- h\\left(\\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x})\\boldsymbol{ \\phi}(\\mathbf{ x}_i).\n", + "$$" ], - "id": "e264a277-aa6c-40ae-842f-ea7f9789bec4" + "id": "1376c534-fcbb-4c66-860f-65ffc3d00515" }, { "cell_type": "markdown", @@ -700,7 +776,7 @@ "Correspond to whether the subject of the image is wearing glasses. Set\n", "up the ipython environment and download the data:" ], - "id": "f2990f69-5b58-4a47-953f-8c0e3ab2d3f4" + "id": "8c726945-b514-4802-89aa-606d99df5f40" }, { "cell_type": "code", @@ -710,7 +786,7 @@ "source": [ "from scipy import io" ], - "id": "85d1bf1a-a88d-4af6-9602-cb1df68398aa" + "id": "8cf0b6b5-848c-4418-8d88-c9afa8e7d74f" }, { "cell_type": "markdown", @@ -723,7 +799,7 @@ "Here’s a simple way to visualise the data. Each pixel in the image will\n", "become an input to the GP." ], - "id": "8b5d034e-8924-4994-b3d1-6884fe670b33" + "id": "ffd6a5df-2973-45e2-9e06-210d00c94642" }, { "cell_type": "code", @@ -733,7 +809,7 @@ "source": [ "import pods" ], - "id": "577bdfce-08a0-47cb-b323-a9a5a5a37ba9" + "id": "a828200f-8305-4eec-9447-62d008b3f10b" }, { "cell_type": "code", @@ -748,7 +824,7 @@ "ytest = data['Ytest']\n", "print(data['info'], data['details'], data['citation'])" ], - "id": "aeddb4ce-2413-4ed1-9503-a578bc012beb" + "id": "7d680575-94d4-4206-8424-195ea9c525ee" }, { "cell_type": "code", @@ -760,7 +836,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "08f1c125-4d23-4b41-8008-7c21530bef9a" + "id": "6a34bd7e-8daf-4b30-b198-453e941aeaab" }, { "cell_type": "code", @@ -776,17 +852,17 @@ "mlai.write_figure(\"olivetti-glasses-image.png\", \n", " directory=\"./datasets\")" ], - "id": "3c9fa966-070d-4d18-819b-902d5c9a390f" + "id": "bb05defe-157a-4bae-8a6f-e4e977b96202" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Image from the Oivetti glasses data sets." ], - "id": "509f5f9f-4c89-4913-a672-cd1c9635d493" + "id": "1fbf976c-98e9-4c7f-a7e5-3936a2dc4606" }, { "cell_type": "markdown", @@ -801,7 +877,7 @@ "We will need to define some initial random values for our vector and\n", "then minimize the objective by descending the gradient." ], - "id": "ef5c622d-52a9-468b-9d22-7d120549f463" + "id": "1696f981-ddc5-49c0-b5a5-6e421ff58c77" }, { "cell_type": "code", @@ -811,15 +887,23 @@ "source": [ "# Separate train and test\n", "indices = np.random.permutation(X.shape[0])\n", - "num_train = np.ceil(X.shape[0]/2)r\n", + "num_train = int(np.ceil(X.shape[0]/2))\n", "train_indices = indices[:num_train]\n", "test_indices = indices[num_train:]\n", - "X_train = X.iloc[train_indices]\n", - "y_train = y.iloc[train_indices]==True\n", - "X_test = X.iloc[test_indices]\n", - "y_test = y.iloc[test_indices]==True" - ], - "id": "10250e2b-4d1f-49df-8867-a29cf9ff0650" + "if hasattr(X, \"iloc\"):\n", + " X_train = X.iloc[train_indices]\n", + " X_test = X.iloc[test_indices]\n", + "else:\n", + " X_train = X[train_indices]\n", + " X_test = X[test_indices]\n", + "if hasattr(y, \"iloc\"):\n", + " y_train = y.iloc[train_indices]==True\n", + " y_test = y.iloc[test_indices]==True\n", + "else:\n", + " y_train = np.asarray(y).flatten()[train_indices]==True\n", + " y_test = np.asarray(y).flatten()[test_indices]==True" + ], + "id": "5132bc71-606c-4043-b3ce-27a7261a178c" }, { "cell_type": "code", @@ -829,7 +913,7 @@ "source": [ "import numpy as np" ], - "id": "a225e5ee-1be7-4db0-b272-492cbfbc62cb" + "id": "87a3f249-2ab4-4fb8-9193-f5f1e6a50ee0" }, { "cell_type": "code", @@ -838,106 +922,24 @@ "outputs": [], "source": [ "# gradient descent algorithm\n", - "w = np.random.normal(size=(X.shape[1]+1, 1), scale = 0.001)\n", + "w_vals = np.random.normal(size=(X.shape[1]+1, 1), scale = 0.001)\n", + "# Convert weights to a pandas DataFrame with column names\n", + "if type(X_train) is pd.DataFrame:\n", + " w = pd.DataFrame(data=w_vals, \n", + " index=['Eins'] + list(X_train.columns), \n", + " columns=['weight'])\n", + "else:\n", + " w = w_vals\n", + "\n", "eta = 1e-9\n", "iters = 10000\n", "for i in range(iters):\n", - " g, Phi = predict(w, X_train, linear)\n", + " g, Phi = predict(w, X_train, mlai.linear)\n", " w -= eta*gradient(g, Phi, y_train) + 0.001*w\n", " if not i % 100:\n", " print(\"Iter\", i, \"Objective\", objective(g, y_train))" ], - "id": "e1381f29-7256-4c2a-ad9c-6f0f0a739334" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let’s look at the weights and how they relate to the inputs." - ], - "id": "ff3870f6-c4ba-4cfd-bee5-539c7fd604d8" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt" - ], - "id": "1b5ec6d7-e785-46fa-aba0-37d4e640f066" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(w)" - ], - "id": "3df7d30e-1de2-4f20-a0f4-ea8d1b25f62e" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What does the magnitude of the weight vectors tell you about the\n", - "different parameters and their influence on outcome? Are the weights of\n", - "roughly the same size, if not, how might you fix this?" - ], - "id": "9f960a56-0229-4c68-8d0c-6db06bd6a80b" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g_test, Phi_test = predict(w, X_test, linear)\n", - "np.sum(g_test[y_test]>0.5)" - ], - "id": "1bba54b4-1a58-4009-b202-3c5b67df5e11" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Stochastic Gradient Descent" - ], - "id": "f9c8c6bc-bb43-450f-998d-492d8c977b0a" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exercise 1\n", - "\n", - "Now construct a stochastic gradient descent algorithm and run it on the\n", - "data. Is it faster or slower than batch gradient descent? What can you\n", - "do to improve convergence speed?" - ], - "id": "910d3d50-ab00-4766-be4d-2460e2ff447a" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Exercise 1 Answer\n", - "\n", - "Write your answer to Exercise 1 here" - ], - "id": "d430949c-4d53-4795-bf32-f6327a134872" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Use this box for any code you need\n", - "\n" - ], - "id": "275b2ef9-6b99-46f1-8ee6-e70769dac2e3" + "id": "1c7cef4a-caa0-495e-91e6-443127bad97d" }, { "cell_type": "markdown", @@ -960,76 +962,7 @@ "gradients](http://en.wikipedia.org/wiki/Conjugate_gradient_method). Can\n", "you find these in python? Are they suitable for very large data sets? }" ], - "id": "c05496f5-800b-4283-be51-760776fbb7ac" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Other GLMs\n", - "\n", - "We’ve introduced the formalism for generalized linear models. Have a\n", - "think about how you might model count data using the [Poisson\n", - "distribution](http://en.wikipedia.org/wiki/Poisson_distribution) and a\n", - "log link function for the rate, $\\lambda(\\mathbf{ x})$. If you want a\n", - "data set you can try the `pods.datasets.google_trends()` for some count\n", - "data." - ], - "id": "b9014c04-1626-4360-b1f1-d4163aeeed7d" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Poisson Distribution\n", - "\n", - "\\[edit\\]" - ], - "id": "3e796058-2738-4dbd-8832-cef82e21e10a" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import mlai.plot as plot" - ], - "id": "1d5c0991-b9aa-41e7-aff4-4cc5eda5678d" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "plot.poisson('./ml/')" - ], - "id": "2a985162-0b56-492b-90f9-31b63fc2b9f9" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "Figure: The Poisson distribution." - ], - "id": "8bb3c273-fcae-4764-b535-f52d7c6ca50c" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Poisson Regression\n", - "\n", - "\\[edit\\]" - ], - "id": "06557632-8b50-4687-80ca-1957b6d4d647" + "id": "4e84594e-43b7-4534-b264-690c8129abfd" }, { "cell_type": "markdown", @@ -1037,28 +970,28 @@ "source": [ "## Bayesian Approaches" ], - "id": "61297d16-a28e-4f59-b4eb-653988c2c29c" + "id": "de2f278b-8f3f-476a-b094-02bb3376efda" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 2\n", + "### Exercise 1\n", "\n", "Can you place a prior density over the parameters $\\mathbf{ w}$ and\n", "marginalize them out like we did for linear regression? If not why not?" ], - "id": "3d5e37d3-6d7c-4681-a153-cc5d57364a13" + "id": "f1eb57e2-ed75-402f-97a8-4c2565491af0" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Exercise 2 Answer\n", + "### Exercise 1 Answer\n", "\n", - "Write your answer to Exercise 2 here" + "Write your answer to Exercise 1 here" ], - "id": "80e756a4-c548-49fc-bf1d-7bb2f25b3cd6" + "id": "c4872df6-3f7b-4c54-bb39-94c3397a6d28" }, { "cell_type": "code", @@ -1069,7 +1002,7 @@ "# Use this box for any code you need\n", "\n" ], - "id": "739a5fa4-5849-4c4e-b3d8-8720ee07aaf5" + "id": "004f63e1-388a-4fff-9f3d-16649dd0507d" }, { "cell_type": "markdown", @@ -1079,7 +1012,7 @@ "\n", "- Section 5.2.2 up to pg 182 of Rogers and Girolami (2011)" ], - "id": "62a7d291-83ba-41e1-82a9-53129429f1f0" + "id": "a5b4ec5b-cedb-4bbb-a7f3-5c87f3c2a693" }, { "cell_type": "markdown", @@ -1090,6 +1023,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -1097,27 +1033,21 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "8872dd40-6c84-48b0-a575-965ac8e78b01" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "469a4bd8-2df8-4374-8127-1f952127dbef" + "id": "ae21db1f-46ff-4b22-ad74-cbd5529d6fdc" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Robbins, H., Monro, S., 1951. A stochastic approximation method. Annals\n", "of Mathematical Statistics 22, 400–407.\n", "\n", "Rogers, S., Girolami, M., 2011. A first course in machine learning. CRC\n", "Press." ], - "id": "90b577bf-defb-4ffb-853e-b1c53680f5a7" + "id": "b3a41389-fb1c-4830-8389-37522d82196f" } ], "nbformat": 4, diff --git a/_notebooks/12-gaussian-processes.ipynb b/_notebooks/12-gaussian-processes.ipynb index 7c1a6ffa..4acda27b 100644 --- a/_notebooks/12-gaussian-processes.ipynb +++ b/_notebooks/12-gaussian-processes.ipynb @@ -6,13 +6,13 @@ "source": [ "# Special Topics: Gaussian Processes" ], - "id": "83e4a8aa-c4fa-4d77-aa89-f3966faa84fa" + "id": "132b062d-4c09-44f4-9f90-b040d4bbd0b7" }, { "cell_type": "markdown", "metadata": {}, "source": [], - "id": "5c72157f-0b6f-4b18-94db-ceea801bec46" + "id": "7ea9d94a-8e9a-4ca9-a51f-2da48ea22030" }, { "cell_type": "markdown", @@ -21,17 +21,20 @@ "$$\n", "$$" ], - "id": "f76acda1-ebd7-4eb2-89f5-a5cd78bc207d" + "id": "fad81035-929c-4957-9470-732ee6ea34d9" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "::: {.cell .markdown}\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", + "\n", "\n", "\n", "\n", @@ -39,7 +42,7 @@ "\n", "-->" ], - "id": "d771b4c4-44aa-490c-9df2-b19ac4002445" + "id": "af0b7c07-e296-4ecf-ae65-bd098ba99eeb" }, { "cell_type": "markdown", @@ -51,7 +54,7 @@ "style=\"\">\\[edit\\]" ], - "id": "b09f6edb-9a6d-4305-9b18-cf4716144d70" + "id": "090fd74d-233a-4d7c-ac1a-dbe555b7a248" }, { "cell_type": "code", @@ -60,9 +63,19 @@ "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", + "import shutil\n", + "\n", + "if shutil.which('latex') is None:\n", + " plt.rcParams['text.usetex'] = False\n", + "else:\n", + " plt.rcParams['text.usetex'] = True\n", + " plt.rcParams['text.latex.preamble'] = (\n", + " r'\\usepackage{amsmath}\\usepackage{amssymb}\\usepackage{mathrsfs}'\n", + " )\n", + "\n", "plt.rcParams.update({'font.size': 22})" ], - "id": "7933531f-c6a0-43e4-8783-a7a32eb95eb5" + "id": "ee2025f0-a98c-4c1a-a53f-265c43f380c5" }, { "cell_type": "markdown", @@ -73,7 +86,7 @@ "sns.set_context('paper')\n", "sns.set_palette('colorblind')}-->" ], - "id": "fa1a7fc8-d705-4ecc-9315-13db5ed70679" + "id": "cc7a017b-c17f-43dd-bd31-5ba6b56cd898" }, { "cell_type": "markdown", @@ -90,7 +103,56 @@ "\n", "The software can be installed using" ], - "id": "caa2a0bd-dab9-44e6-8911-31308951f1a2" + "id": "1120ee98-bd23-4634-8870-ef40e901ab26" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import importlib.util" + ], + "id": "830b71e9-cca8-4775-95bc-cb219357044f" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def install_command(imp):\n", + " packages = {\n", + " 'daft': 'daft-pgm',\n", + " 'mlai': 'git+https://github.com/lawrennd/mlai.git',\n", + " 'pods': 'pods',\n", + " 'PyDeepGP': 'git+https://github.com/SheffieldML/PyDeepGP.git',\n", + " 'notutils': 'git+https://github.com/lawrennd/notutils.git',\n", + " 'pods' : 'git+https://github.com/lawrennd/ods.git',\n", + " 'lamd': 'lamd',\n", + " 'GPy': 'gpy',\n", + " 'emukit': 'emukit',\n", + " }\n", + "\n", + " if importlib.util.find_spec(imp) is None:\n", + " if imp in packages:\n", + " return f'pip install {packages[imp]}'\n", + " else:\n", + " return f'pip install {imp}'\n", + " else:\n", + " return f'echo \"Package {imp} is already installed.\"'" + ], + "id": "ddc18fc6-fbf9-4669-bc9b-cd9d2f3b81d1" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('notutils')" + ], + "id": "837dba96-2425-491c-b17d-b41b830a863a" }, { "cell_type": "code", @@ -98,9 +160,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install notutils" + "%system {cmd}" ], - "id": "55753fe0-ca48-4b29-9e5f-79df85d584e7" + "id": "081724db-78ab-495a-9870-4f59d693500a" }, { "cell_type": "markdown", @@ -113,7 +175,7 @@ "\n", "Once `notutils` is installed, it can be imported in the usual manner." ], - "id": "e1b749d5-f50f-48e9-aa5b-6b5eb6ff8d89" + "id": "6296ad90-eed3-40a9-8ff2-55243c6fd6bc" }, { "cell_type": "code", @@ -123,7 +185,7 @@ "source": [ "import notutils" ], - "id": "bd1b9960-4ad3-4560-970b-e91eddd4107a" + "id": "5aa304be-e6df-4e54-90b3-187ead530a24" }, { "cell_type": "markdown", @@ -145,7 +207,17 @@ "\n", "The software can be installed using" ], - "id": "02d985f1-ad72-410a-8a14-377a711844a3" + "id": "d9b4fb4b-8398-4065-9cbe-a00b6ed59b49" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "56243a7a-a3fa-4406-9e4e-fb015b51392e" }, { "cell_type": "code", @@ -153,9 +225,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install pods" + "%system {cmd}" ], - "id": "8f99b576-79c0-4725-a950-64e2b8145f5a" + "id": "4b9a522a-c21b-495c-80e5-726571494327" }, { "cell_type": "markdown", @@ -167,7 +239,7 @@ "\n", "Once `pods` is installed, it can be imported in the usual manner." ], - "id": "95247742-8cd5-4f5e-b288-57520c6bc360" + "id": "3fc84d43-4dc7-4a82-8b04-2d021329052f" }, { "cell_type": "code", @@ -177,7 +249,7 @@ "source": [ "import pods" ], - "id": "49a092d9-15c0-40e6-be1d-e4d67c301b4f" + "id": "676237ca-f8e1-495e-854e-f508568ccd97" }, { "cell_type": "markdown", @@ -195,7 +267,17 @@ "\n", "The software can be installed using" ], - "id": "12942a64-6ebb-4569-9193-a9f59d3da727" + "id": "327b7bc0-9487-44fb-9a12-d269f8b4efea" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('mlai')" + ], + "id": "a4d3dde6-a366-4e5d-941d-e3e44f025a5f" }, { "cell_type": "code", @@ -203,9 +285,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install mlai" + "%system {cmd}" ], - "id": "e7268b37-fb67-4476-9f25-0cce7c4b0df6" + "id": "a8f033ee-2c09-4f68-b72a-d58055f65522" }, { "cell_type": "markdown", @@ -217,7 +299,7 @@ "\n", "Once `mlai` is installed, it can be imported in the usual manner." ], - "id": "baf90fbc-28f5-45ed-a842-bf5452249c31" + "id": "5348050e-472f-4c77-9f65-df51375fe57b" }, { "cell_type": "code", @@ -225,9 +307,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import mlai\n", + "from mlai import plot" ], - "id": "37fa4f81-888a-4ccc-b022-62d866b318c5" + "id": "c4f45a04-a674-461f-9281-85d329f13f2f" }, { "cell_type": "markdown", @@ -249,7 +332,7 @@ "conditioned on the training data to make predictions about the test\n", "data." ], - "id": "bd336a06-6612-4425-849b-56900e520b0e" + "id": "84a1d837-fe7d-4192-af8b-a2fe35462306" }, { "cell_type": "markdown", @@ -275,7 +358,7 @@ "akin to the naive Bayes approach, but actually is closely related to the\n", "generalized linear model approach." ], - "id": "0ef27544-2aaa-4730-9bdd-83db13d6a6ac" + "id": "c0745a21-9d54-4a2f-b418-712fd3eeb2c1" }, { "cell_type": "markdown", @@ -309,12 +392,12 @@ "data conditional indpendence assumption in favour of a richer assumption\n", "about the data, in a Gaussian process we assume data is *jointly\n", "Gaussian* with a particular mean and covariance, $$\n", - "\\mathbf{ y}|\\mathbf{X}\\sim \\mathcal{N}\\left(\\mathbf{m}(\\mathbf{X}),\\mathbf{K}(\\mathbf{X})\\right),\n", + "\\mathbf{ y}|\\mathbf{X}\\sim \\mathscr{N}\\left(\\mathbf{m}(\\mathbf{X}),\\mathbf{K}(\\mathbf{X})\\right),\n", "$$ where the conditioning is on the inputs $\\mathbf{X}$ which are used\n", "for computing the mean and covariance. For this reason they are known as\n", "mean and covariance functions." ], - "id": "6dc1d32c-ce7c-439b-9167-f1e8d539aa50" + "id": "228554da-07db-4588-8b24-b8eb2d2ec782" }, { "cell_type": "markdown", @@ -326,40 +409,7 @@ "style=\"\">\\[edit\\]" ], - "id": "0aa276e2-176a-4d18-8544-cf5c81f6c1c6" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "np.random.seed(4949)" - ], - "id": "20952fa6-de39-4d24-85c9-0a3516b6e77c" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import mlai.plot as plot\n", - "import pods" - ], - "id": "3977b26a-6c7f-412b-98f8-286ee7cc3755" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "np.random.seed(4949)" - ], - "id": "d1a60fba-fe6a-433e-8983-63944a0251b2" + "id": "d028b588-066f-4437-8141-33473bf7d5ca" }, { "cell_type": "markdown", @@ -369,36 +419,18 @@ "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", + "\n", + "We will consider a *multivariate Gaussian* with a particular structure\n", + "of covariance matrix. We will generate a *single* sample from this 25\n", + "dimensional Gaussian density.\n", "\n", - "We will consider a Gaussian distribution with a particular structure of\n", - "covariance matrix. We will generate *one* sample from a 25-dimensional\n", - "Gaussian density. $$\n", + "$$\n", "\\mathbf{ f}=\\left[f_{1},f_{2}\\dots f_{25}\\right].\n", "$$ in the figure below we plot these data on the $y$-axis against their\n", "*indices* on the $x$-axis." ], - "id": "1c5730c2-6f08-4ac5-a628-9f7f4697e124" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import mlai" - ], - "id": "deb3f043-623e-40a5-af64-e7ae3c7ea96a" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%load -n mlai.Kernel" - ], - "id": "1631484c-e46c-477f-84ec-80dac193dbc6" + "id": "ea316920-f962-4fa2-ba0a-9daa8211814a" }, { "cell_type": "code", @@ -406,9 +438,10 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import numpy as np\n", + "np.random.seed(4949)" ], - "id": "534daab8-3480-4069-b4c4-ef93eb970309" + "id": "f1b4b906-9f5d-4482-b057-6a3c284a969b" }, { "cell_type": "code", @@ -416,9 +449,11 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.polynomial_cov" + "from mlai import Kernel\n", + "import inspect\n", + "file_path = inspect.getfile(Kernel)" ], - "id": "c31ee293-4f54-4ca0-97f3-817ae34d7d2f" + "id": "3e175313-3d34-4839-b5ff-c336949633b9" }, { "cell_type": "code", @@ -426,9 +461,9 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "%load -s Kernel {file_path}" ], - "id": "bbca3184-e1fd-4d92-89bb-29ded5606fd2" + "id": "613dc459-5071-4ef8-bbfe-dc834218398c" }, { "cell_type": "code", @@ -436,9 +471,11 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.exponentiated_quadratic" + "from mlai import polynomial_cov\n", + "import inspect\n", + "file_path = inspect.getfile(polynomial_cov)" ], - "id": "cb304f84-ada4-49c9-87b8-6239392fc5d9" + "id": "8c65c5f1-f0dc-4f75-9124-f55beab062b0" }, { "cell_type": "code", @@ -446,10 +483,9 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai.plot as plot\n", - "from mlai import Kernel, exponentiated_quadratic" + "%load -s polynomial_cov {file_path}" ], - "id": "e4fea92f-6483-47aa-94b6-0ba3d4f219db" + "id": "36d25c6d-ef58-43e9-8187-938feee72c74" }, { "cell_type": "code", @@ -457,10 +493,11 @@ "metadata": {}, "outputs": [], "source": [ - "kernel=Kernel(function=exponentiated_quadratic, lengthscale=0.5)\n", - "plot.two_point_sample(kernel.K, diagrams='./gp')" + "from mlai import eq_cov\n", + "import inspect\n", + "file_path = inspect.getfile(eq_cov)" ], - "id": "8448baa1-c3e0-46d4-a449-fdc6f67f3b49" + "id": "f8affa98-e939-4550-b466-47b3bdf959f2" }, { "cell_type": "code", @@ -468,10 +505,9 @@ "metadata": {}, "outputs": [], "source": [ - "import notutils as nu\n", - "from ipywidgets import IntSlider" + "%load -s eq_cov {file_path}" ], - "id": "dce6f5d6-2e99-4c63-a274-c2fea41c3538" + "id": "3af0e341-4c05-41e4-82b4-aa6795ced3f4" }, { "cell_type": "code", @@ -479,9 +515,9 @@ "metadata": {}, "outputs": [], "source": [ - "import notutils as nu" + "import mlai.plot as plot" ], - "id": "6702c874-135b-4a9c-b3d1-34fdefb9376c" + "id": "b3f69ff6-2b51-4aa1-86a0-8a8cb62da535" }, { "cell_type": "code", @@ -489,32 +525,18 @@ "metadata": {}, "outputs": [], "source": [ - "nu.display_plots('two_point_sample{sample:0>3}.svg', './gp', sample=IntSlider(0, 0, 8, 1))" - ], - "id": "0f131094-aafb-4b20-8675-85ec651e4b3d" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "\n", - "Figure: A 25 dimensional correlated random variable (values ploted\n", - "against index)" + "kernel=Kernel(function=eq_cov, lengthscale=0.5)\n", + "plot.two_point_sample(kernel.K, diagrams='./gp')" ], - "id": "5de389a4-721c-4b22-a68f-d96feb1866e5" + "id": "63671cc8-c632-4cba-8d41-e8cc096699a0" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Sampling a Function from a Gaussian\n", - "\n", - "\\[edit\\]" + "### Sampling a Function from a Gaussian" ], - "id": "a013a728-661c-44c7-b599-18a312548c42" + "id": "b3f33bb9-0b75-4a6a-8c56-a5ab2509c29a" }, { "cell_type": "code", @@ -525,7 +547,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "fd046068-c1a0-482b-86c4-92cc47882a6d" + "id": "d52cf946-ffee-4602-9019-fcfa071a194e" }, { "cell_type": "code", @@ -535,7 +557,7 @@ "source": [ "import notutils as nu" ], - "id": "a2314f71-fa62-48af-aa15-24cefcd45f08" + "id": "4034a2f5-a09c-4fc1-a7b0-28625eb5c57f" }, { "cell_type": "code", @@ -547,18 +569,18 @@ " './gp', \n", " sample=IntSlider(0, 0, 8, 1))" ], - "id": "bd9ef3ba-0d0c-4e6b-9914-090e3f157695" + "id": "40f46a69-6743-4167-afa2-69f22886ef7d" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The joint Gaussian over $f_1$ and $f_2$ along with the\n", "conditional distribution of $f_2$ given $f_1$" ], - "id": "44c0794f-d427-4bca-bcf5-15aea70c4d0e" + "id": "ed4aa19b-47a4-4b69-a3cc-b7851152dd60" }, { "cell_type": "markdown", @@ -566,7 +588,7 @@ "source": [ "### Joint Density of $f_1$ and $f_2$" ], - "id": "94ab3b5f-b443-4578-b1b8-2276f8ff3551" + "id": "42866ec7-63bd-45f8-b884-87724027ddb8" }, { "cell_type": "code", @@ -577,7 +599,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "2a27db75-f394-4b16-a851-ac6f34ff0278" + "id": "da0fe0f6-ffe6-47e1-8efd-31a9c5206c08" }, { "cell_type": "code", @@ -587,7 +609,7 @@ "source": [ "import notutils as nu" ], - "id": "70aa8f47-919e-4806-bafe-8b1491865174" + "id": "517b4112-1df4-430e-85db-db9f9dcefbcd" }, { "cell_type": "code", @@ -599,18 +621,18 @@ " './gp', \n", " sample=IntSlider(9, 9, 12, 1))" ], - "id": "27617589-dd8c-4a7c-921b-ff161e78d306" + "id": "4425db88-3663-478b-bbd7-9a677282377c" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The joint Gaussian over $f_1$ and $f_2$ along with the\n", "conditional distribution of $f_2$ given $f_1$" ], - "id": "8611df45-c84a-41ea-b7f8-e66176cac344" + "id": "2ba3d707-a124-454b-b451-d26a41058260" }, { "cell_type": "markdown", @@ -618,7 +640,7 @@ "source": [ "## Uluru\n", "\n", - "\n", + "\n", "\n", "Figure: Uluru, the sacred rock in Australia. If we think of it as a\n", "probability density, viewing it from this side gives us one *marginal*\n", @@ -640,13 +662,13 @@ "would give us the conditional distribution, the value of conditioning\n", "would be the location of the slice in the direction we are facing." ], - "id": "e2affe9d-e3cb-4b69-8481-6c6cfe78352c" + "id": "cb2deb4f-4122-4f73-9946-2cd5539eb8fa" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Prediction with Correlated Gaussians\n", + "## Prediction of $f_2$ from $f_1$\n", "\n", "Of course in practice, rather than manipulating mountains physically,\n", "the advantage of the Gaussian density is that we can perform these\n", @@ -656,7 +678,7 @@ "$p(f_2|f_1)$.Another remarkable property of the Gaussian density is that\n", "this conditional distribution is *also* guaranteed to be a Gaussian\n", "density. It has the form, $$\n", - "p(f_2|f_1) = \\mathcal{N}\\left(f_2|\\frac{k_{1, 2}}{k_{1, 1}}f_1, k_{2, 2} - \\frac{k_{1,2}^2}{k_{1,1}}\\right)\n", + "p(f_2|f_1) = \\mathscr{N}\\left(f_2|\\frac{k_{1, 2}}{k_{1, 1}}f_1, k_{2, 2} - \\frac{k_{1,2}^2}{k_{1,1}}\\right)\n", "$$where we have assumed that the covariance of the original joint\n", "density was given by $$\n", "\\mathbf{K}= \\begin{bmatrix} k_{1, 1} & k_{1, 2}\\\\ k_{2, 1} & k_{2, 2}.\\end{bmatrix}\n", @@ -667,7 +689,7 @@ "$f_8$ is less correlated with $f_1$ than $f_2$. If we consider this\n", "variable we see the conditional density is more diffuse." ], - "id": "f5260ba8-3a3d-409a-aa9f-8d76c57c6804" + "id": "1a344f94-976a-4ae3-8df6-61783cb854d1" }, { "cell_type": "markdown", @@ -679,7 +701,7 @@ "style=\"\">\\[edit\\]" ], - "id": "f46e9310-e03f-47b9-8236-3480bb186b21" + "id": "f2000a9b-3c0d-454a-a5fa-d7449267332f" }, { "cell_type": "code", @@ -690,7 +712,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "cc076738-ce7c-4780-9b1a-26b8adc02cda" + "id": "8e9082c7-8f5e-4491-af49-047d6a19645b" }, { "cell_type": "code", @@ -700,7 +722,7 @@ "source": [ "import notutils as nu" ], - "id": "a25e1ab1-b709-41f4-8c9c-cbb398fe77f2" + "id": "183a98b5-f5b1-47dd-a37d-df541f5347d8" }, { "cell_type": "code", @@ -712,18 +734,18 @@ " './gp', \n", " sample=IntSlider(13, 13, 17, 1))" ], - "id": "36aace29-e968-406e-8317-763fd0e005af" + "id": "c1b29007-b0b7-48a9-beee-96dfc318280f" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Sample from the joint Gaussian model, points indexed by 1 and\n", "8 highlighted." ], - "id": "89450da0-19ef-4567-be85-c0c13e771c35" + "id": "0bf0a197-b5ea-4fbd-be3d-d91f7ac37403" }, { "cell_type": "markdown", @@ -731,7 +753,7 @@ "source": [ "### Prediction of $f_{8}$ from $f_{1}$\n", "\n", - "\n", + "\n", "\n", "Figure: The joint Gaussian over $f_1$ and $f_8$ along with the\n", "conditional distribution of $f_8$ given $f_1$\n", @@ -747,33 +769,31 @@ "\n", "- Conditional density: $p(f_8|f_1=?)$.\n", "\n", - "- Prediction of $\\mathbf{ f}_*$ from $\\mathbf{ f}$ requires\n", - " multivariate *conditional density*.\n", + "- Prediction of $\\mathbf{ f}_*$ from $\\mathbf{ f}$.\n", "\n", - "- Multivariate conditional density is *also* Gaussian. $$\n", + "- Multivariate conditional density is *also* Gaussian. $$\n", " p(\\mathbf{ f}_*|\\mathbf{ f}) = {\\mathcal{N}\\left(\\mathbf{ f}_*|\\mathbf{K}_{*,\\mathbf{ f}}\\mathbf{K}_{\\mathbf{ f},\\mathbf{ f}}^{-1}\\mathbf{ f},\\mathbf{K}_{*,*}-\\mathbf{K}_{*,\\mathbf{ f}} \\mathbf{K}_{\\mathbf{ f},\\mathbf{ f}}^{-1}\\mathbf{K}_{\\mathbf{ f},*}\\right)}\n", - " $$ \n", + " $$\n", "\n", "- Here covariance of joint density is given by $$\n", " \\mathbf{K}= \\begin{bmatrix} \\mathbf{K}_{\\mathbf{ f}, \\mathbf{ f}} & \\mathbf{K}_{*, \\mathbf{ f}}\\\\ \\mathbf{K}_{\\mathbf{ f}, *} & \\mathbf{K}_{*, *}\\end{bmatrix}\n", " $$\n", "\n", - "- Prediction of $\\mathbf{ f}_*$ from $\\mathbf{ f}$ requires\n", - " multivariate *conditional density*.\n", + "- Prediction of $\\mathbf{ f}_*$ from $\\mathbf{ f}$.\n", "\n", - "- Multivariate conditional density is *also* Gaussian. $$\n", + "- Multivariate conditional density is *also* Gaussian. $$\n", " p(\\mathbf{ f}_*|\\mathbf{ f}) = {\\mathcal{N}\\left(\\mathbf{ f}_*|\\boldsymbol{ \\mu},\\boldsymbol{ \\Sigma}\\right)}\n", " $$ $$\n", " \\boldsymbol{ \\mu}= \\mathbf{K}_{*,\\mathbf{ f}}\\mathbf{K}_{\\mathbf{ f},\\mathbf{ f}}^{-1}\\mathbf{ f}\n", " $$ $$\n", " \\boldsymbol{ \\Sigma}= \\mathbf{K}_{*,*}-\\mathbf{K}_{*,\\mathbf{ f}} \\mathbf{K}_{\\mathbf{ f},\\mathbf{ f}}^{-1}\\mathbf{K}_{\\mathbf{ f},*}\n", - " $$ \n", + " $$\n", "\n", "- Here covariance of joint density is given by $$\n", " \\mathbf{K}= \\begin{bmatrix} \\mathbf{K}_{\\mathbf{ f}, \\mathbf{ f}} & \\mathbf{K}_{*, \\mathbf{ f}}\\\\ \\mathbf{K}_{\\mathbf{ f}, *} & \\mathbf{K}_{*, *}\\end{bmatrix}\n", " $$" ], - "id": "db731d9b-c33f-4144-b464-512d1e63f41f" + "id": "ea1ab301-0026-4b9a-b551-e18c49692b48" }, { "cell_type": "markdown", @@ -797,19 +817,48 @@ "but move to thinking about sampling from the marginal likelihood\n", "directly." ], - "id": "4c8f6101-fd77-42bb-a25d-8e3a754b0302" + "id": "e851c8e0-e227-4856-9a02-f08edd948824" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Sampling from the Prior\n", + "## Generating from the Model\n", "\n", - "The first thing we’ll do is to set up the parameters of the model, these\n", - "include the parameters of the prior, the parameters of the basis\n", - "functions and the noise level." + "\\[edit\\]\n", + "\n", + "A very important aspect of probabilistic modelling is to *sample* from\n", + "your model to see what type of assumptions you are making about your\n", + "data. In this case that involves a two stage process.\n", + "\n", + "1. Sample a candiate parameter vector from the prior.\n", + "2. Place the candidate parameter vector in the likelihood and sample\n", + " functions conditiond on that candidate vector.\n", + "3. Repeat to try and characterise the type of functions you are\n", + " generating.\n", + "\n", + "Given a prior variance (as defined above) we can now sample from the\n", + "prior distribution and combine with a basis set to see what assumptions\n", + "we are making about the functions *a priori* (i.e. before we’ve seen the\n", + "data). Firstly we compute the basis function matrix. We will do it both\n", + "for our training data, and for a range of prediction locations\n", + "(`x_pred`)." + ], + "id": "cb59ec6d-0218-4f27-ad6c-01952bfd04dc" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Olympic Marathon Polynomial Sample\n", + "\n", + "\\[edit\\]" ], - "id": "c22dd128-af84-4180-8b17-910659be4919" + "id": "20dad076-cf8f-4411-9d9f-bad39f3ef836" }, { "cell_type": "code", @@ -817,26 +866,38 @@ "metadata": {}, "outputs": [], "source": [ - "# set prior variance on w\n", - "alpha = 4.\n", - "# set the order of the polynomial basis set\n", - "degree = 5\n", - "# set the noise variance\n", - "sigma2 = 0.01" + "import numpy as np\n", + "import pods" ], - "id": "4cc56eaf-253a-4e13-a4ea-85a3193908f9" + "id": "ed9aa7a4-a0a6-4512-86b8-d2ab6b18d6dd" }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "Now we have the variance, we can sample from the prior distribution to\n", - "see what form we are imposing on the functions *a priori*.\n", + "data = pods.datasets.olympic_marathon_men()\n", + "x = data['X']\n", + "y = data['Y']\n", + "\n", + "offset = y.mean()\n", + "scale = np.sqrt(y.var())\n", + "yhat = (y - offset)/scale\n", + "xlim = (1876,2044)\n", "\n", - "Let’s now compute a range of values to make predictions at, spanning the\n", - "*new* space of inputs," + "num_data = x.shape[0]" + ], + "id": "afb82521-b1ca-4d32-8eca-773ad4731f22" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let’s build the basis matrices. We define the polynomial basis as\n", + "follows." ], - "id": "12f0e100-a80e-4440-87fb-512ca14eaa73" + "id": "89d9403e-3afb-4496-acfd-975814124451" }, { "cell_type": "code", @@ -844,9 +905,31 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np" + "from mlai import polynomial\n", + "import inspect\n", + "file_path = inspect.getfile(polynomial)" + ], + "id": "bdf55c35-27cf-459b-a7d8-4a03aab1fe8e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s polynomial {file_path}" + ], + "id": "a202996a-0bf9-4152-9c9b-d0722b789f92" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import mlai" ], - "id": "d341fd5a-c09f-4a07-9be6-41f429ac76a9" + "id": "8955e331-cd4f-440f-8d4a-ab2d4c3c39d2" }, { "cell_type": "code", @@ -854,19 +937,37 @@ "metadata": {}, "outputs": [], "source": [ - "def polynomial(x, degree, loc, scale):\n", - " degrees = np.arange(degree+1)\n", - " return ((x-loc)/scale)**degrees" + "degree=4\n", + "alpha = 4\n", + "sigma2 = 0.1\n", + "num_pred_data = 100 # how many points to use for plotting predictions\n", + "x_pred = np.linspace(xlim[0], xlim[1], num_pred_data)[:, None] # input locations for predictions\n", + "data_limits=xlim\n", + "basis = mlai.Basis(polynomial, number=degree+1, data_limits=data_limits)\n", + "Phi_pred = basis.Phi(x_pred)\n", + "Phi = basis.Phi(x)" ], - "id": "bb91be2a-04b0-4259-bfe9-6aff1f795a70" + "id": "3a55275c-1bea-4eac-85d7-8b1e8a6de3b0" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "now let’s build the basis matrices. First we load in the data" + "## Sampling from the Prior\n", + "\n", + "Now we will sample from the prior to produce a vector $\\mathbf{ w}$ and\n", + "use it to plot a function which is representative of our belief *before*\n", + "we fit the data. To do this we are going to use the properties of the\n", + "Gaussian density and a sample from a *standard normal* using the\n", + "function `np.random.normal`.\n", + "\n", + "Let’s use this way of constructing samples from a Gaussian to check what\n", + "functions look like *a priori*. The process will be as follows. First,\n", + "we sample a random vector $K$ dimensional from `np.random.normal`. Then\n", + "we scale it by $\\sqrt{\\alpha}$ to obtain a prior sample of\n", + "$\\mathbf{ w}$." ], - "id": "f90b5ec0-bec2-4eec-a28e-a9a724f3f0fd" + "id": "62d30235-3947-4920-a8d7-221836d32e7c" }, { "cell_type": "code", @@ -874,9 +975,21 @@ "metadata": {}, "outputs": [], "source": [ - "import pods" + "K = degree + 1\n", + "z_vec = np.random.normal(size=K)\n", + "w_sample = z_vec*np.sqrt(alpha)\n", + "print(w_sample)" + ], + "id": "f2d17bf5-ef69-4767-a944-6dc018c47fb8" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can combine our sample from the prior with the basis functions to\n", + "create a function," ], - "id": "d60d6194-5cb2-4317-a0c2-046c9d594527" + "id": "6711a9f5-e109-4836-b5d3-712a39ef9172" }, { "cell_type": "code", @@ -884,11 +997,9 @@ "metadata": {}, "outputs": [], "source": [ - "data = pods.datasets.olympic_marathon_men()\n", - "x = data['X']\n", - "y = data['Y']" + "import numpy as np" ], - "id": "eaa95a3d-a4ef-4513-a714-a368ae7f0bec" + "id": "679eb60b-881c-4ac8-a8cb-4ce2765d5b6e" }, { "cell_type": "code", @@ -896,35 +1007,98 @@ "metadata": {}, "outputs": [], "source": [ - "loc = 1950.\n", - "scale = 100.\n", - "num_data = x.shape[0]\n", - "num_pred_data = 100 # how many points to use for plotting predictions\n", - "x_pred = np.linspace(1880, 2030, num_pred_data)[:, np.newaxis] # input locations for predictions\n", - "Phi_pred = polynomial(x_pred, degree=degree, loc=loc, scale=scale)\n", - "Phi = polynomial(x, degree=degree, loc=loc, scale=scale)" + "f_sample = Phi_pred@w_sample" ], - "id": "7e0f727e-530f-4ad0-8727-16d012dd304f" + "id": "d75043ce-45f0-4e62-8ba7-a31e212c125e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import mlai.plot" + ], + "id": "b3659166-193d-4b88-8f53-8a47f1161459" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", + "_ = ax.plot(x_pred.flatten(), f_sample.flatten(), 'r-', linewidth=3)\n", + "\n", + "mlai.write_figure(filename='polynomial-prior-sample.svg', directory='./ml')" + ], + "id": "1475a254-6329-4357-aed8-9ba52f8cde69" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Weight Space View\n", + "\n", "\n", - "To generate typical functional predictions from the model, we need a set\n", - "of model parameters. We assume that the parameters are drawn\n", - "independently from a Gaussian density, $$\n", - "\\mathbf{ w}\\sim \\mathcal{N}\\left(\\mathbf{0},\\alpha\\mathbf{I}\\right),\n", - "$$ then we can combine this with the definition of our prediction\n", - "function $f(\\mathbf{ x})$, $$\n", - "f(\\mathbf{ x}) = \\mathbf{ w}^\\top \\boldsymbol{ \\phi}(\\mathbf{ x}).\n", - "$$ We can now sample from the prior density to obtain a vector\n", - "$\\mathbf{ w}$ using the function `np.random.normal` and combine these\n", - "parameters with our basis to create some samples of what\n", - "$f(\\mathbf{ x})$ looks like," + "Figure: Prior sample of $\\boldsymbol{ \\Phi}\\mathbf{ w}$ with\n", + "$\\mathbf{ w}$ taken from the prior.\n", + "\n", + "Note that we have carefully scaled our data using `xlim` to ensure that\n", + "the polynomials don’t go too large. These samples should be well\n", + "behaved.\n", + "\n", + "Now we need to recompute the basis functions from above," + ], + "id": "12efd971-3350-43d9-b441-ce10482fc578" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Mean Output\n", + "\n", + "The prediction function can now be computed. In matrix form, the\n", + "predictions can be computed as $$\n", + "\\mathbf{ f}_s = \\boldsymbol{ \\Phi}\\mathbf{ w}_s.\n", + "$$ This involves a matrix multiplication between a fixed matrix\n", + "$\\boldsymbol{ \\Phi}$ and a vector drawn from a distribution\n", + "$p(\\mathbf{ w})$. Because $\\mathbf{ w}_s$ is drawn from a distribution,\n", + "this imples that $\\mathbf{ f}_s$ should also be drawn from a\n", + "distribution." + ], + "id": "a6c8df4f-0410-44ea-b908-520c375d1789" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np" + ], + "id": "91393a8c-ff36-4bc7-8f56-35a4827afcde" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "f_sample = Phi_pred@w_sample" + ], + "id": "9d581bcf-7664-4233-8176-5b507a3174ac" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import mlai.plot" ], - "id": "eefb2efd-37a2-42d9-b931-c66eb7f6bc89" + "id": "466f9fb1-b46d-45fd-9b18-b3101a917e21" }, { "cell_type": "code", @@ -932,9 +1106,19 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt" + "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", + "_ = ax.plot(x_pred.flatten(), f_sample.flatten(), 'r-', linewidth=3)" + ], + "id": "4b4d991d-8ebf-4739-9e6c-89ab04653f67" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let’s loop through some samples and plot various functions as\n", + "samples from this system," ], - "id": "bd73fe23-7dc1-4b62-9fd9-faec3368b7dc" + "id": "da2aa606-76f0-42bf-a078-443f8a3399e9" }, { "cell_type": "code", @@ -944,13 +1128,33 @@ "source": [ "num_samples = 10\n", "K = degree+1\n", + "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "for i in range(num_samples):\n", - " z_vec = np.random.normal(size=(K, 1))\n", + " z_vec = np.random.normal(size=K)\n", " w_sample = z_vec*np.sqrt(alpha)\n", - " f_sample = Phi_pred@w_sample\n", - " plt.plot(x_pred, f_sample)" + " f_sample = np.dot(Phi_pred,w_sample)\n", + " _ = ax.plot(x_pred.flatten(), f_sample.flatten(), linewidth=2)\n", + " \n", + "mlai.write_figure(filename='polynomial-prior-samples.svg', directory='./ml')" + ], + "id": "7d6168be-0748-47ea-b078-ae1049fbf916" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Samples of $\\boldsymbol{ \\Phi}\\mathbf{ w}$ with $\\mathbf{ w}$\n", + "taken from the prior.\n", + "\n", + "There are two distributions we are interested in though. We have just\n", + "been sampling from the *prior* distribution to see what sort of\n", + "functions we get *before* looking at the data. In Bayesian inference, we\n", + "also need to compute the *posterior* distribution and sample from that\n", + "density." ], - "id": "934c817a-732b-4fd9-9593-494a56028955" + "id": "d863cda8-55b2-4fa9-b534-2ca8e9e53574" }, { "cell_type": "markdown", @@ -960,7 +1164,7 @@ "\n", "The process we have used to generate the samples is a two stage process.\n", "To obtain each function, we first generated a sample from the prior, $$\n", - "\\mathbf{ w}\\sim \\mathcal{N}\\left(\\mathbf{0},\\alpha \\mathbf{I}\\right)\n", + "\\mathbf{ w}\\sim \\mathscr{N}\\left(\\mathbf{0},\\alpha \\mathbf{I}\\right)\n", "$$ then if we compose our basis matrix, $\\boldsymbol{ \\Phi}$ from the\n", "basis functions associated with each row then we get, $$\n", "\\boldsymbol{ \\Phi}= \\begin{bmatrix}\\boldsymbol{ \\phi}(\\mathbf{ x}_1) \\\\ \\vdots \\\\\n", @@ -968,7 +1172,7 @@ "$$ then we can write down the vector of function values, as evaluated at\n", "$$\n", "\\mathbf{ f}= \\begin{bmatrix} f_1\n", - "\\\\ \\vdots f_n\\end{bmatrix}\n", + "\\\\ \\vdots \\\\ f_n\\end{bmatrix}\n", "$$ in the form $$\n", "\\mathbf{ f}= \\boldsymbol{ \\Phi}\\mathbf{ w}.\n", "$$\n", @@ -981,7 +1185,7 @@ "deterministic matrix (i.e. it is not sampled from a probability density)\n", "then the vector $\\mathbf{ f}$ will also be distributed according to a\n", "zero mean multivariate normal as follows, $$\n", - "\\mathbf{ f}\\sim \\mathcal{N}\\left(\\mathbf{0},\\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top\\right).\n", + "\\mathbf{ f}\\sim \\mathscr{N}\\left(\\mathbf{0},\\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top\\right).\n", "$$\n", "\n", "The question now is, what happens if we sample $\\mathbf{ f}$ directly\n", @@ -992,7 +1196,7 @@ "\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top.\n", "$$" ], - "id": "04fca3e9-da8d-422f-a2d7-bfff5f9a2e81" + "id": "2db52254-8ed1-4196-bfc5-0e99934c1159" }, { "cell_type": "code", @@ -1002,7 +1206,7 @@ "source": [ "K = alpha*Phi_pred@Phi_pred.T" ], - "id": "aa8f3b3d-bbe2-49fe-89b2-c331ba426520" + "id": "7c67fa5d-17e2-4904-94cf-29d74ce6eab3" }, { "cell_type": "markdown", @@ -1012,7 +1216,7 @@ "from a multivariate normal with covariance given by $\\mathbf{K}$ and\n", "zero mean," ], - "id": "5ec01455-33bc-4ff3-ab15-ac2436fa8768" + "id": "761b5968-a831-4a17-b692-f95eb5f656a3" }, { "cell_type": "code", @@ -1027,13 +1231,13 @@ " \n", "mlai.write_figure('gp-sample-basis-function.svg', directory='./kern')" ], - "id": "c2ba8020-5e01-4e41-a2a7-bd3d83d96b2b" + "id": "ae369d21-3fae-4fdb-ae2b-0f7fa24b8d8f" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Samples directly from the covariance function implied by the\n", "basis function based covariance,\n", @@ -1046,7 +1250,7 @@ "the form of this covaraince in an image in python with a colorbar to\n", "show scale." ], - "id": "281a9b34-5269-4ec3-b3f4-439266970981" + "id": "f64ba392-cea4-4862-8540-fe591aebb35e" }, { "cell_type": "code", @@ -1057,7 +1261,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "7dd4b276-8935-441f-8f14-b2cc508c8dda" + "id": "6c730408-e76f-4770-af34-863a5f8f06b0" }, { "cell_type": "code", @@ -1071,13 +1275,13 @@ "\n", "mlai.write_figure('basis-covariance-function.svg', directory='./kern')" ], - "id": "af086c3f-39c6-43e9-a97a-de02cb68cc44" + "id": "0453b3dd-c296-4ff3-9192-2d70d359fd9f" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Covariance of the function implied by the basis set\n", "$\\alpha\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top$.\n", @@ -1088,16 +1292,16 @@ "\\mathbf{ y}= \\mathbf{ f}+ \\boldsymbol{\\epsilon}\n", "$$ where the noise is sampled from an independent Gaussian distribution\n", "with variance $\\sigma^2$, $$\n", - "\\epsilon \\sim \\mathcal{N}\\left(\\mathbf{0},\\sigma^2\\mathbf{I}\\right).\n", + "\\epsilon \\sim \\mathscr{N}\\left(\\mathbf{0},\\sigma^2\\mathbf{I}\\right).\n", "$$ we can use properties of Gaussian variables, i.e. the fact that sum\n", "of two Gaussian variables is also Gaussian, and that it’s covariance is\n", "given by the sum of the two covariances, whilst the mean is given by the\n", "sum of the means, to write down the marginal likelihood, $$\n", - "\\mathbf{ y}\\sim \\mathcal{N}\\left(\\mathbf{0},\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top +\\sigma^2\\mathbf{I}\\right).\n", + "\\mathbf{ y}\\sim \\mathscr{N}\\left(\\mathbf{0},\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top +\\sigma^2\\mathbf{I}\\right).\n", "$$ Sampling directly from this density gives us the noise corrupted\n", "functions," ], - "id": "9638d293-3db7-4de1-8194-1cdcc7eafab5" + "id": "934ced0c-d64f-4f05-97da-6fab4fea92a2" }, { "cell_type": "code", @@ -1107,7 +1311,7 @@ "source": [ "import mlai" ], - "id": "347fc5f6-67d0-4fd3-a8a2-1f32f4a4c698" + "id": "cb1c0396-567b-489c-8fc9-25969d2333cf" }, { "cell_type": "code", @@ -1124,13 +1328,13 @@ "mlai.write_figure('gp-sample-basis-function-plus-noise.svg', \n", " directory='./kern')" ], - "id": "7b9797ee-2bf3-4827-8f58-be52790d061c" + "id": "90128170-3e98-40a4-ae0e-44da5a891d15" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Samples directly from the covariance function implied by the\n", "noise corrupted basis function based covariance,\n", @@ -1140,7 +1344,7 @@ "we can also increase the variance of the noise to see a different\n", "effect," ], - "id": "8d0b3336-d18f-4048-a755-e9b160c8905c" + "id": "99288609-209a-4ddc-a85d-b2c58a2599c3" }, { "cell_type": "code", @@ -1151,7 +1355,7 @@ "sigma2 = 1.\n", "K = alpha*Phi_pred@Phi_pred.T + sigma2*np.eye(x_pred.size)" ], - "id": "351637bb-1d00-474e-b0eb-76694048ca27" + "id": "cb40108a-c162-42e9-94c5-073fe0860a6b" }, { "cell_type": "code", @@ -1167,19 +1371,19 @@ "mlai.write_figure('gp-sample-basis-function-plus-large-noise.svg', \n", " directory='./kern')" ], - "id": "2bc29466-0897-4b61-8544-01782ac7562f" + "id": "3eed2915-a6eb-4fe8-8061-17a0a1b5556a" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Samples directly from the covariance function implied by the\n", "noise corrupted basis function based covariance,\n", "$\\alpha \\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top + \\mathbf{I}$." ], - "id": "a9770f15-3099-41a2-9853-608ff0df4bca" + "id": "81f4cbaf-7bd3-402f-9ef9-3cf2b812c642" }, { "cell_type": "markdown", @@ -1197,7 +1401,7 @@ "rank. This means as we increase the amount of data to infinity, there\n", "will come a point where we can’t normalize the process because the\n", "multivariate Gaussian has the form, $$\n", - "\\mathcal{N}\\left(\\mathbf{ f}|\\mathbf{0},\\mathbf{K}\\right) = \\frac{1}{\\left(2\\pi\\right)^{\\frac{n}{2}}\\det{\\mathbf{K}}^\\frac{1}{2}} \\exp\\left(-\\frac{\\mathbf{ f}^\\top\\mathbf{K}\\mathbf{ f}}{2}\\right)\n", + "\\mathscr{N}\\left(\\mathbf{ f}|\\mathbf{0},\\mathbf{K}\\right) = \\frac{1}{\\left(2\\pi\\right)^{\\frac{n}{2}}\\det{\\mathbf{K}}^\\frac{1}{2}} \\exp\\left(-\\frac{\\mathbf{ f}^\\top\\mathbf{K}\\mathbf{ f}}{2}\\right)\n", "$$ and a non-degenerate kernel matrix leads to $\\det{\\mathbf{K}} = 0$\n", "defeating the normalization (it’s equivalent to finding a projection in\n", "the high dimensional Gaussian where the variance of the the resulting\n", @@ -1205,35 +1409,14 @@ "covariance, or alternatively you can imagine there are one or more\n", "directions where the Gaussian has become the delta function).\n", "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "Radford Neal\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "In the machine learning field, it was Radford Neal (Neal, 1994) that\n", - "realized the potential of the next step. In his 1994 thesis, he was\n", - "considering Bayesian neural networks, of the type we described above,\n", - "and in considered what would happen if you took the number of hidden\n", - "nodes, or neurons, to infinity, i.e. $h\\rightarrow \\infty$.\n", + "\\radfordNealPicture{15%} In the machine learning field, it was Radford\n", + "Neal (Neal, 1994) that realized the potential of the next step. In his\n", + "1994 thesis, he was considering Bayesian neural networks, of the type we\n", + "described above, and in considered what would happen if you took the\n", + "number of hidden nodes, or neurons, to infinity,\n", + "i.e. $h\\rightarrow \\infty$.\n", "\n", - "\n", + "\n", "\n", "Figure: Page 37 of [Radford Neal’s 1994\n", "thesis](http://www.cs.toronto.edu/~radford/ftp/thesis.pdf)\n", @@ -1246,7 +1429,9 @@ " \\end{align*}\n", " $$ if instead of considering a finite number you sample infinitely\n", "many of these activation functions, sampling parameters from a prior\n", - "density, $p(\\mathbf{ v})$, for each one, $$\n", + "density, $p(\\mathbf{ v})$, for each one,\n", + "\n", + "$$\n", "k_f\\left(\\mathbf{ x}_i, \\mathbf{ x}_j\\right) = \\alpha \\int \\phi\\left(\\mathbf{ w}^{(1)}, \\mathbf{ x}_i\\right) \\phi\\left(\\mathbf{ w}^{(1)}, \\mathbf{ x}_j\\right) p(\\mathbf{ w}^{(1)}) \\text{d}\\mathbf{ w}^{(1)}\n", "$$ And that’s not *only* for Gaussian $p(\\mathbf{ v})$. In fact this\n", "result holds for a range of activations, and a range of prior densities\n", @@ -1262,7 +1447,7 @@ " $$ has finite variance, then the result of taking the number of hidden\n", "units to infinity, with appropriate scaling, is also a Gaussian process." ], - "id": "75d66c38-5694-4edc-b470-52261cc5fb17" + "id": "8b6cc3c4-eb19-4ee7-a2c6-2ce87f9398a1" }, { "cell_type": "markdown", @@ -1280,7 +1465,7 @@ "pioneers in making their software widely available and publishing\n", "material on the web." ], - "id": "2f418f64-61e3-4d20-a987-57f9c42d074e" + "id": "741bc509-83cc-40ea-816b-ebf99eedc02d" }, { "cell_type": "markdown", @@ -1290,7 +1475,7 @@ "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", "In our we sampled from the prior over paraemters. Through the properties\n", "of multivariate Gaussian densities this prior over parameters implies a\n", @@ -1312,7 +1497,7 @@ "$$ Let’s build a covariance matrix based on this function. First we\n", "define the form of the covariance function," ], - "id": "8fcc4796-3637-42cc-8751-163ecb928f20" + "id": "66540a35-e880-4bde-8862-bfa32e8a2686" }, { "cell_type": "code", @@ -1320,9 +1505,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import eq_cov\n", + "import inspect\n", + "file_path = inspect.getfile(eq_cov)" ], - "id": "83f91d5f-e1cc-4e88-8ecb-edf09c4e492a" + "id": "fe0d086e-e335-4c1c-bd5f-b5970d5a7bfc" }, { "cell_type": "code", @@ -1330,9 +1517,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.eq_cov" + "%load -s eq_cov {file_path}" ], - "id": "cc33de39-ad29-4028-879d-9bc43a5a5fae" + "id": "bc4d83bd-abe1-4d8c-9900-07d3112fc476" }, { "cell_type": "markdown", @@ -1342,7 +1529,7 @@ "at the points given by `x_pred`. Let’s define a new function `K()` which\n", "does this," ], - "id": "ac936b9d-d08f-40bb-aa43-eaf4f3312e63" + "id": "42f6394a-4a85-4882-af0d-32f8a1b0dfde" }, { "cell_type": "code", @@ -1350,9 +1537,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import Kernel\n", + "import inspect\n", + "file_path = inspect.getfile(Kernel)" ], - "id": "4a1bda89-8024-46f1-a0b3-c282dab51cc5" + "id": "09b3cfa5-ece4-41dd-ad12-095d09ba1fd6" }, { "cell_type": "code", @@ -1360,9 +1549,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.Kernel" + "%load -s Kernel {file_path}" ], - "id": "1ab4fded-25f3-43a1-b30a-8a55e01d2c36" + "id": "ec88b784-59ec-418a-852d-9ded3e5a85c7" }, { "cell_type": "markdown", @@ -1370,7 +1559,7 @@ "source": [ "Now we can image the resulting covariance," ], - "id": "3a8aafab-79c5-4e1b-ba3c-39095aa46b98" + "id": "275c1302-8548-4a6c-90c7-2fcf5f1da440" }, { "cell_type": "code", @@ -1378,10 +1567,10 @@ "metadata": {}, "outputs": [], "source": [ - "kernel = Kernel(function=eq_cov, variance=1., lengthscale=10.)\n", + "kernel = mlai.Kernel(function=mlai.eq_cov, variance=1., lengthscale=10.)\n", "K = kernel.K(x_pred, x_pred)" ], - "id": "427e1439-13ca-4f17-af35-de9217c79328" + "id": "ec1bcd92-5991-422e-ad60-7862b7421b2e" }, { "cell_type": "markdown", @@ -1390,7 +1579,7 @@ "To visualise the covariance between the points we can use the `imshow`\n", "function in matplotlib." ], - "id": "b56c46a8-6f56-4f8b-9fef-b6291b710832" + "id": "229627b6-af86-4438-95d1-4df4821bd337" }, { "cell_type": "code", @@ -1402,7 +1591,7 @@ "im = ax.imshow(K, interpolation='none')\n", "fig.colorbar(im)" ], - "id": "d5ed6e7a-2f2a-40e1-861c-b56296151d37" + "id": "311651d6-8de5-4033-b4c2-389af06793e3" }, { "cell_type": "markdown", @@ -1410,7 +1599,7 @@ "source": [ "Finally, we can sample functions from the marginal likelihood." ], - "id": "9d57b338-bfd9-4e5f-8c44-df3c4780c56a" + "id": "c1427ab9-ad7b-45af-bb65-228862e7042d" }, { "cell_type": "code", @@ -1423,7 +1612,7 @@ " y_sample = np.random.multivariate_normal(mean=np.zeros(x_pred.size), cov=K)\n", " ax.plot(x_pred.flatten(), y_sample.flatten())" ], - "id": "b96f6517-28c5-4c1f-b998-449977995fa5" + "id": "0db376a8-14f1-41b4-b3e8-6e9b3d0d56c2" }, { "cell_type": "markdown", @@ -1435,7 +1624,7 @@ "covariance function (the lengthscale and the variance) and see what\n", "effects the parameters have on the types of functions you observe." ], - "id": "59bd73c5-9558-4c7f-8351-1f5411622a93" + "id": "bfcb1305-7140-401b-b398-d92c19859f78" }, { "cell_type": "markdown", @@ -1445,7 +1634,7 @@ "\n", "Write your answer to Exercise 1 here" ], - "id": "7660ac22-9e0b-413b-978d-0123b8887924" + "id": "4703ea24-7356-46cd-bce0-6da73c10470c" }, { "cell_type": "code", @@ -1456,7 +1645,7 @@ "# Use this box for any code you need\n", "\n" ], - "id": "b865a76a-12d8-4de8-af40-3b95849a86ff" + "id": "f1fa7b02-7bd9-414d-807a-87b6bb83563b" }, { "cell_type": "markdown", @@ -1500,7 +1689,7 @@ "First, we will load in two python functions for computing the covariance\n", "function." ], - "id": "dccd8b7f-d2f6-4b58-af0d-d0e58dd46979" + "id": "34fba0e1-1062-4a4f-b591-cc0545ec5fbc" }, { "cell_type": "code", @@ -1508,9 +1697,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import Kernel\n", + "import inspect\n", + "file_path = inspect.getfile(Kernel)" ], - "id": "54635d1e-141d-47c3-9e71-77911c41a3c1" + "id": "b9205ea4-d269-451b-940f-4a9a9b890c27" }, { "cell_type": "code", @@ -1518,9 +1709,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.Kernel" + "%load -s Kernel {file_path}" ], - "id": "420ce08b-9ba0-44bd-8742-7179db826dc9" + "id": "ad794109-023d-4c23-a541-93badf105d1a" }, { "cell_type": "code", @@ -1528,54 +1719,11 @@ "metadata": {}, "outputs": [], "source": [ - "# %load -n mlai.Kernel\n", - "class Kernel():\n", - " \"\"\"Covariance function\n", - " :param function: covariance function\n", - " :type function: function\n", - " :param name: name of covariance function\n", - " :type name: string\n", - " :param shortname: abbreviated name of covariance function\n", - " :type shortname: string\n", - " :param formula: latex formula of covariance function\n", - " :type formula: string\n", - " :param function: covariance function\n", - " :type function: function\n", - " :param \\**kwargs:\n", - " See below\n", - "\n", - " :Keyword Arguments:\n", - " * \"\"\"\n", - "\n", - " def __init__(self, function, name=None, shortname=None, formula=None, **kwargs): \n", - " self.function=function\n", - " self.formula = formula\n", - " self.name = name\n", - " self.shortname = shortname\n", - " self.parameters=kwargs\n", - " \n", - " def K(self, X, X2=None):\n", - " \"\"\"Compute the full covariance function given a kernel function for two data points.\"\"\"\n", - " if X2 is None:\n", - " X2 = X\n", - " K = np.zeros((X.shape[0], X2.shape[0]))\n", - " for i in np.arange(X.shape[0]):\n", - " for j in np.arange(X2.shape[0]):\n", - " K[i, j] = self.function(X[i, :], X2[j, :], **self.parameters)\n", - "\n", - " return K\n", - "\n", - " def diag(self, X):\n", - " \"\"\"Compute the diagonal of the covariance function\"\"\"\n", - " diagK = np.zeros((X.shape[0], 1))\n", - " for i in range(X.shape[0]): \n", - " diagK[i] = self.function(X[i, :], X[i, :], **self.parameters)\n", - " return diagK\n", - "\n", - " def _repr_html_(self):\n", - " raise NotImplementedError" + "from mlai import eq_cov\n", + "import inspect\n", + "file_path = inspect.getfile(eq_cov)" ], - "id": "2e1e31b9-02c1-413e-9bc8-8eb7cad2fffd" + "id": "12a981ac-20e3-4a24-b1b1-84593d1b6ad0" }, { "cell_type": "code", @@ -1583,33 +1731,9 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "%load -s eq_cov {file_path}" ], - "id": "20f18ade-c830-4dc3-a983-f96d5f8747cd" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%load -n mlai.eq_cov" - ], - "id": "dd64d72b-6066-4932-ba71-c8acff44bf91" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# %load -n mlai.eq_cov\n", - "def eq_cov(x, x_prime, variance=1., lengthscale=1.):\n", - " \"\"\"Exponentiated quadratic covariance function.\"\"\"\n", - " diffx = x - x_prime\n", - " return variance*np.exp(-0.5*np.dot(diffx, diffx)/lengthscale**2)" - ], - "id": "bc490207-c7d9-4815-834f-caa245f3fa04" + "id": "bfe7b516-2890-4828-9849-9d06e3cf7e91" }, { "cell_type": "code", @@ -1618,11 +1742,11 @@ "outputs": [], "source": [ "kernel = Kernel(function=eq_cov,\n", - " name='Exponentiated Quadratic',\n", - " shortname='eq', \n", - " lengthscale=0.25)" + " name='Exponentiated Quadratic',\n", + " shortname='eq', \n", + " lengthscale=0.25)" ], - "id": "f5814e52-ddc6-4905-b9ba-e4b46eb3c41c" + "id": "0352a34c-4011-4a65-a21e-e754c83b68f8" }, { "cell_type": "markdown", @@ -1631,7 +1755,7 @@ "Next, we sample from a multivariate normal density (a multivariate\n", "Gaussian), using the covariance function as the covariance matrix." ], - "id": "58bd94ce-bd78-4822-8f7c-f781afb98c1e" + "id": "92c379e4-e3c7-4e43-b38e-33f9e6511a66" }, { "cell_type": "code", @@ -1643,7 +1767,7 @@ "np.random.seed(10)\n", "import mlai.plot as plot" ], - "id": "e7ac3aa8-7505-435e-ae3d-eee4a09493d3" + "id": "6ee952c7-4778-47b9-8551-9bd60e5cb437" }, { "cell_type": "code", @@ -1654,7 +1778,7 @@ "plot.rejection_samples(kernel=kernel, \n", " diagrams='./gp')" ], - "id": "fd0df39f-b4ac-4469-92ce-9844ae77779e" + "id": "aec918a5-f0b3-48fc-a386-e939ee3559f7" }, { "cell_type": "code", @@ -1665,7 +1789,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "ace544a0-a40b-4947-bbb3-cdfbeb0acf5f" + "id": "a12bddc5-23c0-42ef-bf1a-6fa7a1cbbc2d" }, { "cell_type": "code", @@ -1677,15 +1801,15 @@ " directory='./gp', \n", " sample=IntSlider(1,1,5,1))" ], - "id": "9e85490b-9ca3-4e9e-be30-01f4d381c6ea" + "id": "684add37-d486-41eb-a407-550d3ae26595" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "Figure: One view of Bayesian inference is we have a machine for\n", "generating samples (the *prior*), and we discard all samples\n", @@ -1694,106 +1818,40 @@ "The Gaussian process allows us to do this analytically by multiplying\n", "the *prior* by the *likelihood*." ], - "id": "d80a8c6d-c23c-4b92-80e5-ef278eade6b1" + "id": "3cbe8f46-bdf6-4373-bdde-617ac03cdf35" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Gaussian Process\n", + "## Making Predictions with Gaussian Process\n", + "\n", + "\\[edit\\]\n", "\n", "The Gaussian process perspective takes the marginal likelihood of the\n", "data to be a joint Gaussian density with a covariance given by\n", "$\\mathbf{K}$. So the model likelihood is of the form, $$\n", "p(\\mathbf{ y}|\\mathbf{X}) =\n", "\\frac{1}{(2\\pi)^{\\frac{n}{2}}|\\mathbf{K}|^{\\frac{1}{2}}}\n", - "\\exp\\left(-\\frac{1}{2}\\mathbf{ y}^\\top \\left(\\mathbf{K}+\\sigma^2\n", - "\\mathbf{I}\\right)^{-1}\\mathbf{ y}\\right)\n", - "$$ where the input data, $\\mathbf{X}$, influences the density through\n", - "the covariance matrix, $\\mathbf{K}$ whose elements are computed through\n", - "the covariance function, $k(\\mathbf{ x}, \\mathbf{ x}^\\prime)$.\n", - "\n", - "This means that the negative log likelihood (the objective function) is\n", - "given by, $$\n", - "E(\\boldsymbol{\\theta}) = \\frac{1}{2} \\log |\\mathbf{K}|\n", - "+ \\frac{1}{2} \\mathbf{ y}^\\top \\left(\\mathbf{K}+\n", - "\\sigma^2\\mathbf{I}\\right)^{-1}\\mathbf{ y}\n", - "$$ where the *parameters* of the model are also embedded in the\n", - "covariance function, they include the parameters of the kernel (such as\n", - "lengthscale and variance), and the noise variance, $\\sigma^2$. Let’s\n", - "create a set of classes in python for storing these variables." - ], - "id": "036e6d25-6448-474f-9e1c-111a64084018" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import mlai" - ], - "id": "7591c99a-d6dd-4b09-9bae-84792563f95d" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%load -n mlai.Model" - ], - "id": "7a6954d9-6285-4f31-aed3-c37edcc786fd" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import mlai" - ], - "id": "53bdfd3e-20bd-402b-a715-cb6cbab2b718" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%load -n mlai.MapModel" - ], - "id": "5343dbe6-c635-4d60-baab-875b0af4ea1a" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import mlai" - ], - "id": "0c4faae5-f2ea-461c-b6e2-72a4eed4368e" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%load -n mlai.ProbModel" - ], - "id": "bc91178e-f08c-4533-a584-85c52b945e5c" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import mlai" + "\\exp\\left(-\\frac{1}{2}\\mathbf{ y}^\\top \\left(\\mathbf{K}+\\sigma^2\n", + "\\mathbf{I}\\right)^{-1}\\mathbf{ y}\\right)\n", + "$$ where the input data, $\\mathbf{X}$, influences the density through\n", + "the covariance matrix, $\\mathbf{K}$ whose elements are computed through\n", + "the covariance function, $k(\\mathbf{ x}, \\mathbf{ x}^\\prime)$.\n", + "\n", + "This means that the negative log likelihood (the objective function) is\n", + "given by, $$\n", + "E(\\boldsymbol{\\theta}) = \\frac{1}{2} \\log |\\mathbf{K}|\n", + "+ \\frac{1}{2} \\mathbf{ y}^\\top \\left(\\mathbf{K}+\n", + "\\sigma^2\\mathbf{I}\\right)^{-1}\\mathbf{ y}\n", + "$$ where the *parameters* of the model are also embedded in the\n", + "covariance function, they include the parameters of the kernel (such as\n", + "lengthscale and variance), and the noise variance, $\\sigma^2$. Let’s\n", + "create a set of classes in python for storing these variables." ], - "id": "02f19e2c-0ba4-4622-a642-68230813d0e3" + "id": "82087791-532f-47ab-9585-94c9d43748eb" }, { "cell_type": "code", @@ -1801,9 +1859,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.ProbMapModel" + "from mlai import ProbMapModel" ], - "id": "85e2b7a3-c364-4004-a488-43d2aed5fbf4" + "id": "5688b24a-c59b-41d1-b767-e877f0154b8c" }, { "cell_type": "code", @@ -1811,9 +1869,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import GP\n", + "import inspect\n", + "file_path = inspect.getfile(GP)" ], - "id": "bf6da43e-dee0-4ace-86f7-2ffb6666a81e" + "id": "48769053-dd5d-40bc-bd2d-03d70ebc9790" }, { "cell_type": "code", @@ -1821,9 +1881,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.GP" + "%load -s GP {file_path}" ], - "id": "9898ddbe-4822-43f2-92ca-fedfe6242340" + "id": "6193de76-0a9b-49e9-87c3-04192383cf58" }, { "cell_type": "markdown", @@ -1841,7 +1901,7 @@ "and the test observations, $\\mathbf{ f}^*$. This joint probability\n", "density will be Gaussian, with a covariance matrix given by our\n", "covariance function, $k(\\mathbf{ x}_i, \\mathbf{ x}_j)$. $$\n", - "\\begin{bmatrix}\\mathbf{ f}\\\\ \\mathbf{ f}^*\\end{bmatrix} \\sim \\mathcal{N}\\left(\\mathbf{0},\\begin{bmatrix} \\mathbf{K}& \\mathbf{K}_\\ast \\\\\n", + "\\begin{bmatrix}\\mathbf{ f}\\\\ \\mathbf{ f}^*\\end{bmatrix} \\sim \\mathscr{N}\\left(\\mathbf{0},\\begin{bmatrix} \\mathbf{K}& \\mathbf{K}_\\ast \\\\\n", "\\mathbf{K}_\\ast^\\top & \\mathbf{K}_{\\ast,\\ast}\\end{bmatrix}\\right)\n", "$$ where here $\\mathbf{K}$ is the covariance computed between all the\n", "training points, $\\mathbf{K}_\\ast$ is the covariance matrix computed\n", @@ -1851,7 +1911,7 @@ "for our example, using `x` and `y` for the training data (although `y`\n", "doesn’t enter the covariance) and `x_pred` as the test locations." ], - "id": "d7a9f4b9-c918-4235-8991-4ee28daea9c1" + "id": "7cc3d0b5-f4bf-4c31-b72d-e6a7276060c6" }, { "cell_type": "code", @@ -1870,7 +1930,7 @@ "K_star = kernel.K(x, x_pred)\n", "K_starstar = kernel.K(x_pred, x_pred)" ], - "id": "7da0b36c-8a65-46a9-ac12-65bd324d5f99" + "id": "a6a9e567-6859-40c5-bfea-df2e9a95d54b" }, { "cell_type": "markdown", @@ -1891,7 +1951,7 @@ "& \\mathbf{K}_{\\ast,\\ast}\\end{bmatrix}\n", "$$" ], - "id": "00acbd09-038b-4f93-b34d-4cd7a150e8f5" + "id": "ad2a262a-5db6-4319-a152-2bc0d614bff8" }, { "cell_type": "code", @@ -1901,7 +1961,7 @@ "source": [ "import mlai" ], - "id": "790dbff5-59d3-4172-bc4b-5574fdc1541d" + "id": "9ccabaa0-1cc6-45fc-99a7-91aa08e89ca6" }, { "cell_type": "code", @@ -1916,15 +1976,15 @@ "ax.axhline(x.shape[0]-1, color='w')\n", "fig.colorbar(im)\n", "\n", - "mlai.write_figure('block-predictive-covariance.svg', diagrams='./gp')" + "mlai.write_figure('block-predictive-covariance.svg', directory='./gp')" ], - "id": "e81bc1d8-e532-4591-a6b9-b9c9191ff14b" + "id": "e42a9ad5-b90b-4c24-b4c3-fcdc6cd792f4" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Different blocks of the covariance function. The upper left\n", "block is the covariance of the training data with itself, $\\mathbf{K}$.\n", @@ -1946,7 +2006,7 @@ "$\\mathbf{K}_*$ then our belief about the test data simply matches our\n", "prior." ], - "id": "60335c53-c896-4791-8f8c-d19bb597a2ef" + "id": "9fce180e-cac1-48d3-b0d6-0f6b0e9cdb9a" }, { "cell_type": "markdown", @@ -1976,7 +2036,7 @@ "burden which is $O(n^2)$. This makes working with Gaussian processes\n", "computationally intensive for the situation where $n>10,000$." ], - "id": "01b1d272-db3c-4f31-bf9f-f8ee290a20b8" + "id": "9485f6b5-4989-41eb-b240-3cc190a4be7a" }, { "cell_type": "code", @@ -1987,7 +2047,7 @@ "from IPython.lib.display import YouTubeVideo\n", "YouTubeVideo('ewJ3AxKclOg')" ], - "id": "e2ae0a0e-4e6d-4c26-97df-5144052a6c33" + "id": "8c176103-68ab-4d03-8fcf-5afd86ede2e5" }, { "cell_type": "markdown", @@ -1997,7 +2057,7 @@ "the 2014 Gaussian process Winter School at the University of\n", "Sheffield." ], - "id": "eb8a89fa-9e49-48aa-831b-5f3b6ca31a59" + "id": "b90f65a7-73df-4948-bba5-a5ce26f8a8e8" }, { "cell_type": "markdown", @@ -2014,7 +2074,7 @@ "decomposition* of the kernel matrix. The log determinant of the\n", "covariance can also be derived from the Cholesky decomposition." ], - "id": "4d0d224f-7bab-4949-8276-4a1c4ca5798c" + "id": "6858aef2-2b01-4bbf-830b-631b9bf5ae41" }, { "cell_type": "code", @@ -2022,9 +2082,43 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "import scipy.linalg as la" + ], + "id": "2ba0a66f-3412-437e-8c61-2e54cea4a7c5" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import GP\n", + "import inspect\n", + "file_path = inspect.getfile(GP)" + ], + "id": "ec59988c-0fb0-4cb4-9457-33e7da67b3cb" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load -s GP {file_path}" + ], + "id": "67b224a5-2fb9-43cf-a2e5-79c87361bc43" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from mlai import update_inverse\n", + "import inspect\n", + "file_path = inspect.getfile(update_inverse)" ], - "id": "86a237d6-bf3f-43df-9ae5-0f8a46072ecf" + "id": "f345deb0-676b-4024-8d38-9efe6318d7b5" }, { "cell_type": "code", @@ -2032,9 +2126,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.update_inverse" + "%load -s update_inverse {file_path}" ], - "id": "f96b6dd2-31b6-4143-b619-88428742ad61" + "id": "ce4ca3b8-7d5f-43c8-9a9a-af03d072c4b2" }, { "cell_type": "code", @@ -2042,9 +2136,10 @@ "metadata": {}, "outputs": [], "source": [ + "# Bind the updated inverse to our GP model\n", "GP.update_inverse = update_inverse" ], - "id": "2818ed67-b373-46dc-b353-43383a2b6959" + "id": "bf76341b-a867-4ac0-a15d-22033daf9ce3" }, { "cell_type": "markdown", @@ -2076,7 +2171,7 @@ "likelihood. This involves taking gradients of the likelihood with\n", "respect to the parameters of the covariance function." ], - "id": "69dd84f7-37c4-4af9-941a-7225503695c3" + "id": "89fbe767-a03d-4254-b6e8-c90233ac1f89" }, { "cell_type": "markdown", @@ -2090,7 +2185,7 @@ "considering the gradient of the covariance function with respect to its\n", "parameters." ], - "id": "b6c335af-eee1-4d2e-8895-99f593e78a2c" + "id": "340328b4-cbf5-4d82-901b-dead5dbf6ea3" }, { "cell_type": "markdown", @@ -2102,7 +2197,7 @@ "function through fixed point equations, we will need to do gradient\n", "based optimization." ], - "id": "a08a4972-314d-4fa2-92b1-9955add05923" + "id": "b33e1938-5f94-41d1-a27c-d52e2ac6d663" }, { "cell_type": "markdown", @@ -2115,29 +2210,7 @@ "determinant of the covariance. The data fit term is the matrix inner\n", "product between the data and the inverse covariance." ], - "id": "edd79e14-13e3-4ac8-961d-29c3c4b2f092" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def rotateObject(rotationMatrix, handle):\n", - "for i = 1:prod(size(handle))\n", - " type = get(handle(i), 'type');\n", - " if strcmp(type, 'text'):\n", - " xy = get(handle(i), 'position');\n", - " xy(1:2) = rotationMatrix*xy(1:2)';\n", - " set(handle(i), 'position', xy);\n", - " else:\n", - " xd = get(handle(i), 'xdata');\n", - " yd = get(handle(i), 'ydata');\n", - " new = rotationMatrix*[xd(:)'; yd(:)'];\n", - " set(handle(i), 'xdata', new(1, :));\n", - " set(handle(i), 'ydata', new(2, :));" - ], - "id": "1ed4e97a-e741-4617-bb88-b7bfd831f341" + "id": "43c36fe2-3dce-4c61-8d6d-011bf20aa059" }, { "cell_type": "markdown", @@ -2152,18 +2225,18 @@ "Can we determine covariance parameters from the data?\n", "\n", "$$\n", - "\\mathcal{N}\\left(\\mathbf{ y}|\\mathbf{0},\\mathbf{K}\\right)=\\frac{1}{(2\\pi)^\\frac{n}{2}{\\det{\\mathbf{K}}^{\\frac{1}{2}}}}{\\exp\\left(-\\frac{\\mathbf{ y}^{\\top}\\mathbf{K}^{-1}\\mathbf{ y}}{2}\\right)}\n", + "\\mathscr{N}\\left(\\mathbf{ y}|\\mathbf{0},\\mathbf{K}\\right)=\\frac{1}{(2\\pi)^\\frac{n}{2}{\\det{\\mathbf{K}}^{\\frac{1}{2}}}}{\\exp\\left(-\\frac{\\mathbf{ y}^{\\top}\\mathbf{K}^{-1}\\mathbf{ y}}{2}\\right)}\n", "$$\n", "\n", "$$\n", "\\begin{aligned}\n", - " \\mathcal{N}\\left(\\mathbf{ y}|\\mathbf{0},\\mathbf{K}\\right)=\\frac{1}{(2\\pi)^\\frac{n}{2}\\color{blue}{\\det{\\mathbf{K}}^{\\frac{1}{2}}}}\\color{red}{\\exp\\left(-\\frac{\\mathbf{ y}^{\\top}\\mathbf{K}^{-1}\\mathbf{ y}}{2}\\right)}\n", + " \\mathscr{N}\\left(\\mathbf{ y}|\\mathbf{0},\\mathbf{K}\\right)=\\frac{1}{(2\\pi)^\\frac{n}{2}\\color{blue}{\\det{\\mathbf{K}}^{\\frac{1}{2}}}}\\color{red}{\\exp\\left(-\\frac{\\mathbf{ y}^{\\top}\\mathbf{K}^{-1}\\mathbf{ y}}{2}\\right)}\n", "\\end{aligned}\n", "$$\n", "\n", "$$\n", "\\begin{aligned}\n", - " \\log \\mathcal{N}\\left(\\mathbf{ y}|\\mathbf{0},\\mathbf{K}\\right)=&\\color{blue}{-\\frac{1}{2}\\log\\det{\\mathbf{K}}}\\color{red}{-\\frac{\\mathbf{ y}^{\\top}\\mathbf{K}^{-1}\\mathbf{ y}}{2}} \\\\ &-\\frac{n}{2}\\log2\\pi\n", + " \\log \\mathscr{N}\\left(\\mathbf{ y}|\\mathbf{0},\\mathbf{K}\\right)=&\\color{blue}{-\\frac{1}{2}\\log\\det{\\mathbf{K}}}\\color{red}{-\\frac{\\mathbf{ y}^{\\top}\\mathbf{K}^{-1}\\mathbf{ y}}{2}} \\\\ &-\\frac{n}{2}\\log2\\pi\n", "\\end{aligned}\n", "$$\n", "\n", @@ -2171,7 +2244,7 @@ "E(\\boldsymbol{ \\theta}) = \\color{blue}{\\frac{1}{2}\\log\\det{\\mathbf{K}}} + \\color{red}{\\frac{\\mathbf{ y}^{\\top}\\mathbf{K}^{-1}\\mathbf{ y}}{2}}\n", "$$" ], - "id": "0fd4a951-f19f-45bf-973c-be386a21cd2e" + "id": "531f6127-c9c5-4c7b-a6e3-04ee29c36f55" }, { "cell_type": "markdown", @@ -2188,28 +2261,17 @@ "\n", "$$\\mathbf{K}= \\mathbf{R}\\boldsymbol{ \\Lambda}^2 \\mathbf{R}^\\top$$\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", "$\\boldsymbol{ \\Lambda}$ represents distance on axes. $\\mathbf{R}$ gives\n", "rotation.\n", "\n", - "
\n", - "\n", "- $\\boldsymbol{ \\Lambda}$ is *diagonal*,\n", " $\\mathbf{R}^\\top\\mathbf{R}= \\mathbf{I}$.\n", "- Useful representation since\n", " $\\det{\\mathbf{K}} = \\det{\\boldsymbol{ \\Lambda}^2} = \\det{\\boldsymbol{ \\Lambda}}^2$." ], - "id": "5181f526-0574-4e2e-a715-1a5b7ac7b605" + "id": "ed7a7597-4ae1-46ff-b01b-e3cd7ea2b883" }, { "cell_type": "code", @@ -2222,7 +2284,7 @@ "import mlai\n", "import mlai.plot as plot" ], - "id": "722d3a30-fa2a-4987-836d-528276763036" + "id": "ba987d63-a481-4475-a328-b3d79237ad32" }, { "cell_type": "code", @@ -2232,7 +2294,7 @@ "source": [ "plot.covariance_capacity(rotate_angle=np.pi/4, lambda1 = 0.5, lambda2 = 0.3, diagrams = './gp/')" ], - "id": "24d8b7fc-4231-4fbf-98d9-15a8d76e520c" + "id": "42437088-f007-4eed-a50a-03dd7a62dbb7" }, { "cell_type": "code", @@ -2243,7 +2305,7 @@ "import notutils as nu\n", "from ipywidgets import IntSlider" ], - "id": "1b907241-ad76-411a-a2b6-816b268df1a3" + "id": "4ba215fd-d125-4a1c-802b-a1269f7d8c65" }, { "cell_type": "code", @@ -2255,18 +2317,18 @@ " directory='./gp', \n", " sample=IntSlider(0, 0, 9, 1))" ], - "id": "1b9d5263-210c-433e-ab38-ea0fc44087aa" + "id": "a2eb4fbd-c356-4f1d-9ebd-36d6f2c35cd5" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The determinant of the covariance is dependent only on the\n", "eigenvalues. It represents the ‘footprint’ of the Gaussian." ], - "id": "b751d2dc-2218-4eda-a031-139bad9c7b84" + "id": "d2085835-122c-4a7d-b4cf-97fcdeb9ef05" }, { "cell_type": "markdown", @@ -2278,7 +2340,7 @@ "style=\"\">\\[edit\\]" ], - "id": "139ea3e1-7ab5-45b8-b930-74e538e14e79" + "id": "7c3e119f-5822-4cbe-a857-8c74dc7d116d" }, { "cell_type": "code", @@ -2286,80 +2348,61 @@ "metadata": {}, "outputs": [], "source": [ - " clf\n", - " includeText = [];\n", - " counter = 0;\n", - " plotWidth = 0.6*textWidth;\n", - " lambda1 = 3;\n", - " lambda2 = 1;\n", - " t = linspace(-pi, pi, 200);\n", - " R = [sqrt(2)/2 -sqrt(2)/2; sqrt(2)/2 sqrt(2)/2];\n", - " xy = [lambda1*sin(t); lambda2*cos(t)];\n", - " contourHand = line(xy(1, :), xy(2, :), 'color', blackColor);\n", - " xy = [lambda1*sin(t); lambda2*cos(t)]*2;\n", - " lim = [-1 1]*max([lambda1 lambda2])*2.2;\n", - " set(gca, 'xlim', lim, 'ylim', lim)\n", - " axis equal\n", - "\n", - "\n", - " contourHand = [contourHand line(xy(1, :), xy(2, :), 'color', blackColor)];\n", - " set(contourHand, 'linewidth', 2, 'color', redColor)\n", - " arrowHand = arrow([0 lambda1], [0 0]);\n", - " arrowHand = [arrowHand arrow([0 0], [0 lambda2])];\n", - " set(arrowHand, 'linewidth', 3, 'color', blackColor);\n", - " xlim = get(gca, 'xlim');\n", - " xspan = xlim(2) - xlim(1);\n", - " ylim = get(gca, 'ylim');\n", - " yspan = ylim(2) - ylim(1);\n", - " eigLabel = text(lambda1*0.5, -yspan*0.05, '$\\eigenvalue_1$', 'horizontalalignment', 'center');\n", - " eigLabel = [eigLabel text(-0.05*xspan, lambda2*0.5, '$\\eigenvalue_2$', 'horizontalalignment', 'center')];\n", - " xlabel('$\\dataScalar_1$')\n", - " ylabel('$\\dataScalar_2$')\n", - " \n", - " box off\n", - " xlim = get(gca, 'xlim');\n", - " ylim = get(gca, 'ylim');\n", - " line([xlim(1) xlim(1)], ylim, 'color', blackColor)\n", - " line(xlim, [ylim(1) ylim(1)], 'color', blackColor)\n", - " \n", - " fileName = ['gpOptimiseQuadratic' num2str(counter)];\n", - " printLatexPlot(fileName, directory, plotWidth);\n", - " includeText = [includeText '\\only<' num2str(counter) '>{\\input{' directory fileName '.svg}}'];\n", - " counter = counter + 1;\n", - "\n", - " y = [1.2 1.4];\n", - " dataHand = line(y(1), y(2), 'marker', 'x', 'markersize', markerSize, 'linewidth', markerWidth, 'color', blackColor);\n", - " \n", - " fileName = ['gpOptimiseQuadratic' num2str(counter)];\n", - " printLatexPlot(fileName, directory, plotWidth);\n", - " includeText = [includeText '\\only<' num2str(counter) '>{\\input{' directory fileName '.svg}}'];\n", - " counter = counter + 1;\n", - "\n", - " \n", - " rotateObject(rotationMatrix, arrowHand);\n", - " rotateObject(rotationMatrix, contourHand);\n", - " rotateObject(rotationMatrix, eigLabel);\n", - " \n", - " fileName = ['gpOptimiseQuadratic' num2str(counter)];\n", - " printLatexPlot(fileName, directory, plotWidth);\n", - " includeText = [includeText '\\only<' num2str(counter) '>{\\input{' directory fileName '.svg}}'];\n", - " counter = counter + 1;\n", - " \n", - " printLatexText(includeText, 'gpOptimiseQuadraticIncludeText.tex', directory)" + "from mlai import plot" + ], + "id": "05b6530a-4e50-41d6-a5fb-8b6f5b4b4fbb" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plot.gp_optimize_quadratic(\n", + " lambda1=3,\n", + " lambda2=1, \n", + " directory='./gp',\n", + " fontsize=20,\n", + " plot_width=0.6,\n", + " generate_frames=True\n", + ") " + ], + "id": "567bd2d4-4d6d-4d53-9568-bb0eff65c803" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import notutils as nu\n", + "from ipywidgets import IntSlider" + ], + "id": "9159025a-f320-41fa-b518-669da4dc8f3b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.display_plots('gp-optimise-quadratic{sample:0>3}.svg', \n", + " directory='./gp', \n", + " sample=IntSlider(0, 0, 2, 1))" ], - "id": "92c6a456-2645-41d5-be35-8e8e5d0a46f3" + "id": "f166fad0-68ec-49bd-afc6-f83c7d9ca140" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: The data fit term of the Gaussian process is a quadratic loss\n", "centered around zero. This has eliptical contours, the principal axes of\n", "which are given by the covariance matrix." ], - "id": "be9daaf0-1c7f-413f-8f65-3d0306cb0b7f" + "id": "a921e249-9292-4e22-85e8-857372463925" }, { "cell_type": "markdown", @@ -2371,19 +2414,7 @@ "style=\"\">\\[edit\\]" ], - "id": "a6638d27-d2a3-47ec-911b-c7329f889fd8" - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "import os" - ], - "id": "dd2d7a44-6de1-4094-ba95-4ccb46b18e03" + "id": "42ac64a1-7367-43f2-a3f9-686f67bb9ac0" }, { "cell_type": "code", @@ -2391,12 +2422,12 @@ "metadata": {}, "outputs": [], "source": [ - "import GPy\n", "import mlai.plot as plot\n", "import mlai\n", - "import gp_tutorial" + "from mlai import Kernel, eq_cov, GP, gp_tutorial\n", + "import os" ], - "id": "ac4dc066-ba89-46f2-b6a4-4f6f1ab048f9" + "id": "bc29991a-bf3f-4267-8422-9720a44c2cc6" }, { "cell_type": "code", @@ -2413,7 +2444,7 @@ "magenta_color=[1., 0., 1.]\n", "fontsize=18" ], - "id": "68e4ec45-48aa-4f6d-ae32-d4ce748d8668" + "id": "54b1c70e-0a75-4160-b89d-55e1584e0cdd" }, { "cell_type": "code", @@ -2431,7 +2462,7 @@ "markersize=15\n", "markertype='.'" ], - "id": "b40e9fed-c02c-4780-a01b-98b4ebfd42c7" + "id": "bf5269d5-9ab8-4c2c-a2bb-ce1b9f6f0aed" }, { "cell_type": "code", @@ -2443,13 +2474,11 @@ "xtest = np.linspace(x_lim[0], x_lim[1], 200)[:, np.newaxis]\n", "\n", "# True data\n", - "true_kern = GPy.kern.RBF(1) + GPy.kern.White(1)\n", - "true_kern.rbf.lengthscale = 1.0\n", - "true_kern.white.variance = 0.01\n", - "K = true_kern.K(x) \n", + "true_kernel = Kernel(eq_cov, name='Exponentiated Quadratic', shortname='eq', lengthscale=1)\n", + "K = true_kernel.K(x) + np.eye(6)*0.01 \n", "y = np.random.multivariate_normal(np.zeros((6,)), K, 1).T" ], - "id": "ee502001-45f9-4fbf-a1d6-257f078dac2f" + "id": "8f2e51fe-4d32-433a-a5c1-278657810aec" }, { "cell_type": "code", @@ -2459,21 +2488,30 @@ "source": [ "\n", "# Fitted model\n", - "kern = GPy.kern.RBF(1) + GPy.kern.White(1)\n", - "kern.rbf.lengthscale = 1.0\n", - "kern.white.variance = 0.01\n", + "kernel = Kernel(eq_cov, name='Exponentiated Quadratic', shortname='eq', lengthscale=1)\n", + "gp = GP(x, y, sigma2=0.01, kernel=kernel)" + ], + "id": "64ce3432-ccea-41c3-bc78-3a4e7d23a14e" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "\n", "lengthscales = np.asarray([0.01, 0.05, 0.1, 0.25, 0.5, 1, 2, 4, 8, 16, 100])\n", "\n", - "fig1, ax1 = plt.subplots(figsize=plot.one_figsize) \n", - "fig2, ax2 = plt.subplots(figsize=plot.one_figsize) \n", - "line = ax2.semilogx(np.NaN, np.NaN, 'x-', \n", + "fig, ax = plt.subplots(1, 2, figsize=plot.big_wide_figsize) \n", + "plt.subplots_adjust(wspace=0.3)\n", + "plt.tight_layout()\n", + "line = ax[1].semilogx(np.nan, np.nan, 'x-', \n", " color=black_color)\n", - "ax.set_ylim(err_y_lim)\n", - "ax.set_xlim([0.025, 32])\n", - "ax.grid(True)\n", - "ax.set_xticks([0.01, 0.1, 1, 10, 100])\n", - "ax.set_xticklabels(['$10^{-2}$', '$10^{-1}$', '$10^0$', '$10^1$', '$10^2$'])\n", + "ax[1].set_ylim(err_y_lim)\n", + "ax[1].set_xlim([0.01, 100])\n", + "ax[1].grid(True)\n", + "ax[1].set_xticks([0.01, 0.1, 1, 10, 100])\n", + "ax[1].set_xticklabels(['$10^{-2}$', '$10^{-1}$', '$10^0$', '$10^1$', '$10^2$'])\n", "\n", "\n", "err = np.zeros_like(lengthscales)\n", @@ -2482,112 +2520,106 @@ "\n", "counter = 0\n", "for i, ls in enumerate(lengthscales):\n", - " kern.rbf.lengthscale=ls\n", - " K = kern.K(x) \n", - " invK, L, Li, log_det_K = GPy.util.linalg.pdinv(K)\n", - " err[i] = 0.5*(log_det_K + np.dot(np.dot(y.T,invK),y))\n", - " err_log_det[i] = 0.5*log_det_K\n", - " err_fit[i] = 0.5*np.dot(np.dot(y.T,invK), y)\n", - " Kx = kern.K(x, xtest)\n", - " ypred_mean = np.dot(np.dot(Kx.T, invK), y)\n", - " ypred_var = kern.Kdiag(xtest) - np.sum((np.dot(Kx.T,invK))*Kx.T, 1)\n", - " ypred_sd = np.sqrt(ypred_var)\n", - " ax1.clear()\n", - " _ = gp_tutorial.gpplot(xtest.flatten(),\n", - " ypred_mean.flatten(),\n", - " ypred_mean.flatten()-2*ypred_sd.flatten(),\n", - " ypred_mean.flatten()+2*ypred_sd.flatten(), \n", - " ax=ax1)\n", - " x_lim = ax1.get_xlim()\n", - " ax1.set_ylabel('$f(x)$', fontsize=fontsize)\n", - " ax1.set_xlabel('$x$', fontsize=fontsize)\n", - "\n", - " p = ax1.plot(x, y, markertype, color=black_color, markersize=markersize, linewidth=linewidth)\n", - " ax1.set_ylim(y_lim)\n", - " ax1.set_xlim(x_lim) \n", - " ax1.set_xticks(x_ticks)\n", + " gp.kernel.parameters[\"lengthscale\"] = ls\n", + " gp.update_kernel_matrix() \n", + " err[i] = float(np.asarray(0.5*(gp.logdetK + y.T@gp.Kinv@y)).squeeze())\n", + " err_log_det[i] = float(np.asarray(0.5*gp.logdetK).squeeze())\n", + " err_fit[i] = float(np.asarray(0.5*y.T@gp.Kinv@y).squeeze())\n", + " ypred_mean, ypred_var = gp.predict(xtest)\n", + " ypred_sd = np.sqrt(ypred_var)\n", + " ax[0].clear()\n", + " _ = gp_tutorial.gpplot(xtest.flatten(),\n", + " ypred_mean.flatten(),\n", + " ypred_mean.flatten()-2*ypred_sd.flatten(),\n", + " ypred_mean.flatten()+2*ypred_sd.flatten(), \n", + " ax=ax[0])\n", + " x_lim = ax[0].get_xlim()\n", + " ax[0].set_ylabel('$f(x)$', fontsize=fontsize)\n", + " ax[0].set_xlabel('$x$', fontsize=fontsize)\n", + "\n", + " p = ax[0].plot(x, y, markertype, color=black_color, markersize=markersize, linewidth=linewidth)\n", + " ax[0].set_ylim(y_lim)\n", + " ax[0].set_xlim(x_lim) \n", + " ax[0].set_xticks(x_ticks)\n", " #ax.set(box=False)\n", " \n", - " ax1.plot([x_lim[0], x_lim[0]], y_lim, color=black_color)\n", - " ax1.plot(x_lim, [y_lim[0], y_lim[0]], color=black_color)\n", - "\n", - " file_name = 'gp-optimise{counter:0>3}.svg'.format(counter=counter)\n", - " mlai.write_figure(os.path.join(diagrams, file_name),\n", - " figure=fig1,\n", - " transparent=True)\n", - " counter += 1\n", - "\n", - " ax2.clear()\n", - " t = ax2.semilogx(lengthscales[0:i+1], err[0:i+1], 'x-', \n", - " color=magenta_color, \n", - " markersize=markersize,\n", - " linewidth=linewidth)\n", - " t2 = ax2.semilogx(lengthscales[0:i+1], err_log_det[0:i+1], 'x-', \n", - " color=blue_color, \n", - " markersize=markersize,\n", - " linewidth=linewidth)\n", - " t3 = ax2.semilogx(lengthscales[0:i+1], err_fit[0:i+1], 'x-', \n", - " color=red_color, \n", - " markersize=markersize,\n", - " linewidth=linewidth)\n", - " ax2.set_ylim(err_y_lim)\n", - " ax2.set_xlim([0.025, 32])\n", - " ax2.set_xticks([0.01, 0.1, 1, 10, 100])\n", - " ax2.set_xticklabels(['$10^{-2}$', '$10^{-1}$', '$10^0$', '$10^1$', '$10^2$'])\n", - "\n", - " ax2.grid(True)\n", - "\n", - " ax2.set_ylabel('negative log likelihood', fontsize=fontsize)\n", - " ax2.set_xlabel('length scale, $\\ell$', fontsize=fontsize)\n", - " file_name = 'gp-optimise{counter:0>3}.svg'.format(counter=counter)\n", - " mlai.write_figure(os.path.join(diagrams, file_name),\n", - " figure=fig2,\n", - " transparent=True)\n", - " counter += 1\n", - " #ax.set_box(False)\n", - " xlim = ax2.get_xlim()\n", - " ax2.plot([xlim[0], xlim[0]], err_y_lim, color=black_color)\n", - " ax2.plot(xlim, [err_y_lim[0], err_y_lim[0]], color=black_color)" - ], - "id": "cfa6cffa-0c8e-4b59-9d0d-d5d55eeb51ab" + " ax[0].plot([x_lim[0], x_lim[0]], y_lim, color=black_color)\n", + " ax[0].plot(x_lim, [y_lim[0], y_lim[0]], color=black_color)\n", + "\n", + "\n", + " ax[1].clear()\n", + " t = ax[1].semilogx(lengthscales[0:i+1], err[0:i+1], 'x-', \n", + " color=magenta_color, \n", + " markersize=markersize,\n", + " linewidth=linewidth)\n", + " t2 = ax[1].semilogx(lengthscales[0:i+1], err_log_det[0:i+1], 'x-', \n", + " color=blue_color, \n", + " markersize=markersize,\n", + " linewidth=linewidth)\n", + " t3 = ax[1].semilogx(lengthscales[0:i+1], err_fit[0:i+1], 'x-', \n", + " color=red_color, \n", + " markersize=markersize,\n", + " linewidth=linewidth)\n", + " ax[1].set_ylim(err_y_lim)\n", + " ax[1].set_xlim([0.025, 32])\n", + " ax[1].set_xticks([0.01, 0.1, 1, 10, 100])\n", + " ax[1].set_xticklabels(['$10^{-2}$', '$10^{-1}$', '$10^0$', '$10^1$', '$10^2$'])\n", + "\n", + " ax[1].grid(True)\n", + "\n", + " ax[1].set_ylabel('negative log likelihood', fontsize=fontsize)\n", + " ax[1].set_xlabel('length scale, $\\ell$', fontsize=fontsize)\n", + " xlim = ax[1].get_xlim()\n", + " ax[1].plot([xlim[0], xlim[0]], err_y_lim, color=black_color)\n", + " ax[1].plot(xlim, [err_y_lim[0], err_y_lim[0]], color=black_color)\n", + " \n", + " file_name = 'gp-optimise{counter:0>3}.svg'.format(counter=counter)\n", + " mlai.write_figure(os.path.join(diagrams, file_name),\n", + " figure=fig,\n", + " transparent=True)\n", + " counter += 1" + ], + "id": "11767fdd-dcba-442b-9ba5-08757ac1bc4b" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import notutils as nu\n", + "from ipywidgets import IntSlider" + ], + "id": "5db1b54d-de6b-4fa8-a706-79a96c812021" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nu.display_plots('gp-optimise{sample:0>3}.svg', \n", + " directory='./gp', \n", + " sample=IntSlider(0, 0, 10, 1))" + ], + "id": "7876d136-c012-4579-9b82-e5d404dda075" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "
\n", + "\n", "\n", "Figure: Variation in the data fit term, the capacity term and the\n", "negative log likelihood for different lengthscales." ], - "id": "b2c3871b-1df9-4bff-88d5-9920d692d3bf" + "id": "a61bd448-a63f-4879-ad99-04d4bce88f62" }, { "cell_type": "markdown", @@ -2599,7 +2631,7 @@ "style=\"\">\\[edit\\]" ], - "id": "c077c94a-9c71-49ac-8457-0588b9d3ccc8" + "id": "7788f00e-c87b-48ca-be3e-30cf6db0fb26" }, { "cell_type": "code", @@ -2607,9 +2639,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import Kernel\n", + "import inspect\n", + "file_path = inspect.getfile(Kernel)" ], - "id": "fc3018a5-0120-483e-a893-bccda4c2fbc4" + "id": "1716e1fd-c7f1-4cab-a758-796cec46968b" }, { "cell_type": "code", @@ -2617,9 +2651,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.Kernel" + "%load -s Kernel {file_path}" ], - "id": "245c798d-4685-48e2-bae2-9729b5d985e4" + "id": "4a37a838-2c64-4822-b95d-0a12774d2ca4" }, { "cell_type": "code", @@ -2627,9 +2661,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import eq_cov\n", + "import inspect\n", + "file_path = inspect.getfile(eq_cov)" ], - "id": "51db2abe-ebbc-447d-aae0-db7f804b803e" + "id": "d17cfaad-d352-49ec-997a-af1fcd66b465" }, { "cell_type": "code", @@ -2637,9 +2673,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.eq_cov" + "%load -s eq_cov {file_path}" ], - "id": "86362908-76e9-4b37-97cf-13c995ee2be2" + "id": "ec17a517-26df-4536-b269-d24e93c7dc0c" }, { "cell_type": "code", @@ -2647,13 +2683,13 @@ "metadata": {}, "outputs": [], "source": [ - "kernel = Kernel(function=eq_cov,\n", + "kernel = mlai.Kernel(function=mlai.eq_cov,\n", " name='Exponentiated Quadratic',\n", " shortname='eq', \n", " formula='\\kernelScalar(\\inputVector, \\inputVector^\\prime) = \\alpha \\exp\\left(-\\frac{\\ltwoNorm{\\inputVector-\\inputVector^\\prime}^2}{2\\lengthScale^2}\\right)',\n", " lengthscale=0.2)" ], - "id": "5b1e7941-db84-446a-9f2d-181b4f43ef06" + "id": "ffac41ea-9b69-4ed8-8144-88ba7a20a9f0" }, { "cell_type": "code", @@ -2663,7 +2699,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "c06b2f45-6b85-4908-ab2c-7ed4a3c1172c" + "id": "2667473f-28eb-4c14-8fb7-c162ca109dd9" }, { "cell_type": "code", @@ -2673,7 +2709,7 @@ "source": [ "plot.covariance_func(kernel=kernel, diagrams='./kern/')" ], - "id": "b512e6a7-bdaf-4765-bc2b-53c668fdd551" + "id": "134e1655-8e93-418a-89f0-d84b7810cc33" }, { "cell_type": "markdown", @@ -2698,24 +2734,14 @@ "$$k(\\mathbf{ x}, \\mathbf{ x}^\\prime) = \\alpha \\exp\\left(-\\frac{\\left\\Vert \\mathbf{ x}-\\mathbf{ x}^\\prime \\right\\Vert_2^2}{2\\ell^2}\\right)$$\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "
\n", + "\n", "\n", "Figure: The exponentiated quadratic covariance function." ], - "id": "d04d69d9-779c-4474-a3de-41aeecc01ee8" + "id": "9cc89681-c62a-4df9-a62f-12ee9285ae06" }, { "cell_type": "markdown", @@ -2727,31 +2753,41 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", "- Gold medal times for Olympic Marathon since 1896.\n", "- Marathons before 1924 didn’t have a standardized distance.\n", "- Present results using pace per km.\n", "- In 1904 Marathon was badly organized leading to very slow times.\n", "\n", - "\n", - "\n", - "\n", - "Image from Wikimedia Commons \n", - "\n", - "
\n", + "\n", + "Image from [Wikimedia\n", + "Commons](https://commons.wikimedia.org/wiki/File:Eliud_Kipchoge_in_Berlin_-_2015_(cropped).jpg)\n", "\n", - "The first thing we will do is load a standard data set for regression\n", + "The Olympic Marathon data is a standard dataset for regression\n", "modelling. The data consists of the pace of Olympic Gold Medal Marathon\n", "winners for the Olympics from 1896 to present. Let’s load in the data\n", "and plot." ], - "id": "2b418ecf-5afa-4514-bc2d-7058c0c411cd" + "id": "1a5d5fc9-ff65-4bf9-b410-3adc9c410967" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('pods')" + ], + "id": "33fecbe5-bf0e-486a-9762-709e8d3d6cf1" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%system {cmd}" + ], + "id": "b7119c9b-851d-4ea5-9cd8-0d64c3d86f96" }, { "cell_type": "code", @@ -2762,7 +2798,7 @@ "import numpy as np\n", "import pods" ], - "id": "a7fd70ff-945c-44b9-8eb5-4b65be858ac7" + "id": "0981407b-9377-4e1c-9f9c-4024b0eacedb" }, { "cell_type": "code", @@ -2778,7 +2814,7 @@ "scale = np.sqrt(y.var())\n", "yhat = (y - offset)/scale" ], - "id": "26c28e42-fc6b-4fc1-811c-ff517372bc63" + "id": "5bccc841-aa9b-4101-9777-f20fd64cc398" }, { "cell_type": "code", @@ -2790,7 +2826,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "dd83c9c4-1c1d-4dce-8488-bb4a37e86f21" + "id": "4eace87b-3359-42f6-927d-d7196a2facd4" }, { "cell_type": "code", @@ -2799,7 +2835,7 @@ "outputs": [], "source": [ "\n", - "xlim = (1875,2030)\n", + "xlim = (1876,2044)\n", "ylim = (2.5, 6.5)\n", "\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", @@ -2812,13 +2848,13 @@ "mlai.write_figure(filename='olympic-marathon.svg', \n", " directory='./datasets')" ], - "id": "92499a8d-d5f0-4ede-b409-ffce11942048" + "id": "3fbbc129-721f-4548-92e6-e8378a08700a" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Olympic marathon pace times since 1896.\n", "\n", @@ -2828,7 +2864,7 @@ "participants got lost, and only very few participants completed. More\n", "recent years see more consistently quick marathons." ], - "id": "02a59425-9f1a-46d0-b292-3065f6804ca0" + "id": "5dea2bf9-a4a6-40da-aeee-f78a528b6b8a" }, { "cell_type": "markdown", @@ -2840,20 +2876,9 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", + "\n", "\n", "Figure: Alan Turing, in 1946 he was only 11 minutes slower than the\n", "winner of the 1948 games. Would he have won a hypothetical games held in\n", @@ -2883,7 +2908,7 @@ "the concept of generalization let’s introduce some formal representation\n", "of what it means to generalize in machine learning." ], - "id": "d86310d4-4534-4704-944b-488eca3425fa" + "id": "ea9abdb5-e367-453b-b27d-f785d7adbdfb" }, { "cell_type": "markdown", @@ -2893,13 +2918,13 @@ "\n", "\\[edit\\]\n", + "style=\"\">edit\\]\n", "\n", "Our first objective will be to perform a Gaussian process fit to the\n", "data, we’ll do this using the [GPy\n", "software](https://github.com/SheffieldML/GPy)." ], - "id": "1e6fe52f-c755-49c6-a8b9-593567d91583" + "id": "27651fba-9676-4400-9c22-807954f32fb1" }, { "cell_type": "code", @@ -2909,7 +2934,7 @@ "source": [ "import GPy" ], - "id": "7eada6f8-f059-4f0b-a6ab-fdd12fd26cbe" + "id": "87b6ad08-c565-49bf-967e-3336864f5960" }, { "cell_type": "code", @@ -2920,7 +2945,7 @@ "m_full = GPy.models.GPRegression(x,yhat)\n", "_ = m_full.optimize() # Optimize parameters of covariance function" ], - "id": "d20281a3-8457-41dd-a40f-cd1c784e7cba" + "id": "a8bf82b1-cf71-4844-b0d1-002fb2d37d52" }, { "cell_type": "markdown", @@ -2934,7 +2959,7 @@ "plot the mean function and the standard deviation at 200 locations. We\n", "can obtain the predictions using `y_mean, y_var = m_full.predict(xt)`" ], - "id": "8ac6ffc9-b6bc-4ca2-a16e-25303a22753d" + "id": "dd6cabbd-f089-4c0d-9fe1-3cb1a414178d" }, { "cell_type": "code", @@ -2946,7 +2971,7 @@ "yt_mean, yt_var = m_full.predict(xt)\n", "yt_sd=np.sqrt(yt_var)" ], - "id": "cfa091c6-41e3-4883-93dd-4ffe3bd2a1de" + "id": "e21bd4e6-449d-4aba-9c44-d144454b5984" }, { "cell_type": "markdown", @@ -2954,7 +2979,7 @@ "source": [ "Now we plot the results using the helper function in `mlai.plot`." ], - "id": "2d5c19fe-5dc1-4e38-aba7-041c00097ce5" + "id": "b2ef7dbe-e267-43f7-86c0-29291be771ae" }, { "cell_type": "code", @@ -2966,7 +2991,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "fc353f8c-c3ac-46a3-9e00-558112737e4f" + "id": "37628bd4-ccf9-4be9-84c6-bba283136c97" }, { "cell_type": "code", @@ -2979,22 +3004,22 @@ "ax.set_xlim(xlim)\n", "ax.set_ylim(ylim)\n", "mlai.write_figure(figure=fig,\n", - " filename=\"olympic-marathon-gp.svg\", \n", + " filename=\"olympic-marathon-gp.svg\",\n", " directory = \"./gp\",\n", " transparent=True, frameon=True)" ], - "id": "61d5a85c-5c8d-4ca8-a22b-86fc6890e408" + "id": "e580c834-2b35-4607-9ee6-3e0e8c55f0db" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Gaussian process fit to the Olympic Marathon data. The error\n", "bars are too large, perhaps due to the outlier from 1904." ], - "id": "87e8577d-4846-4098-83f2-f6d9805cf3ed" + "id": "55891322-58b8-493c-ba16-4c0e87afb228" }, { "cell_type": "markdown", @@ -3008,7 +3033,7 @@ "see this make a version of the model, `m_clean`, where that point is\n", "removed." ], - "id": "a3ddb9c2-083c-427a-bf64-521592edc8d8" + "id": "60547ba5-4887-417d-880a-30ce8cce1f84" }, { "cell_type": "code", @@ -3022,7 +3047,7 @@ "m_clean = GPy.models.GPRegression(x_clean,y_clean)\n", "_ = m_clean.optimize()" ], - "id": "47b12a08-e2cc-4492-999f-a0bfc4ec947f" + "id": "cbde7366-329d-4266-8219-d0cae552de8a" }, { "cell_type": "code", @@ -3034,7 +3059,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "be9237d6-1c48-4451-bfec-a178dfa94807" + "id": "376e5069-12fd-4bdd-9fde-f7fed4a46d52" }, { "cell_type": "code", @@ -3047,10 +3072,21 @@ "ax.set_xlim(xlim)\n", "ax.set_ylim(ylim)\n", "mlai.write_figure(figure=fig,\n", - " filename='./gp/olympic-marathon-gp.svg', \n", + " filename='./gp/olympic-marathon-gp-outlier-removed.svg', \n", " transparent=True, frameon=True)" ], - "id": "abf483c8-1c69-4625-aa8b-18fe3f83b066" + "id": "4bbf9ec3-46c9-4c64-982a-79bc2614d42e" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "Figure: Gaussian process fit to the Olympic Marathon data. Here we’ve\n", + "removed the outlier from 1904." + ], + "id": "d6941d3c-ae72-447b-9167-7c189f7e1ed2" }, { "cell_type": "markdown", @@ -3068,7 +3104,7 @@ "use a Gaussian process to determine whether a given gene is active, or\n", "we are merely observing a noise response." ], - "id": "4e5ac008-9289-4afd-bdb0-e7b052cd5429" + "id": "b4ae2fef-87e4-43c1-8b43-bc1c396235b2" }, { "cell_type": "markdown", @@ -3083,7 +3119,7 @@ "- Given given expression levels in the form of a time series from\n", " Della Gatta et al. (2008)." ], - "id": "e6c68070-ba03-4646-8c17-90f4383b48bd" + "id": "a1cce2ca-9708-4a57-8da7-0a2340fd5b0f" }, { "cell_type": "code", @@ -3094,7 +3130,7 @@ "import numpy as np\n", "import pods" ], - "id": "87a4a21a-83ce-4e2d-9af8-4c000f7eb7c6" + "id": "6c491ced-b676-4ca0-9abb-d4a225258f78" }, { "cell_type": "code", @@ -3110,7 +3146,7 @@ "offset = y.mean()\n", "scale = np.sqrt(y.var())" ], - "id": "1ae9d09e-b409-425d-a403-9a3c3f9eacd5" + "id": "9ace9fbc-ef31-44a2-acf0-d88af74df9e4" }, { "cell_type": "code", @@ -3122,7 +3158,7 @@ "import mlai.plot as plot\n", "import mlai" ], - "id": "4abc4a80-4168-4996-88fb-68dd41ccc1ba" + "id": "703da459-fecc-4acc-85f7-e96747ee837f" }, { "cell_type": "code", @@ -3147,13 +3183,13 @@ " transparent=True, \n", " frameon=True)" ], - "id": "27acef27-f5eb-4235-8ba9-495f137d4866" + "id": "1b8bb1c2-725d-4a14-9e0f-3a0ec84419ca" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Gene expression levels over time for a gene from data\n", "provided by Della Gatta et al. (2008). We would like to understand\n", @@ -3162,29 +3198,9 @@ "- Want to detect if a gene is expressed or not, fit a GP to each gene\n", " Kalaitzis and Lawrence (2011).\n", "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "Freddie Kalaitzis\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\\freddieKalaitzisPicture{15%}\n", "\n", - "\n", + "\n", "\n", "Figure: The example is taken from the paper “A Simple Approach to\n", "Ranking Differentially Expressed Gene Expression Time Courses through\n", @@ -3200,7 +3216,7 @@ "data, we’ll do this using the [GPy\n", "software](https://github.com/SheffieldML/GPy)." ], - "id": "6f986368-d293-439a-9eb2-79cc1251dcb5" + "id": "d0ab6b2d-595b-4c04-be70-19ec245cd26a" }, { "cell_type": "code", @@ -3210,7 +3226,7 @@ "source": [ "import GPy" ], - "id": "f9f082d1-0b29-4811-8ef4-163c2a4281ba" + "id": "16a14d4a-2fc3-4879-a2d2-ec6427bc79d0" }, { "cell_type": "code", @@ -3222,7 +3238,7 @@ "m_full.kern.lengthscale=50\n", "_ = m_full.optimize() # Optimize parameters of covariance function" ], - "id": "df48b84f-ed77-41e9-835f-fd018b03730b" + "id": "1ad7e803-a6ab-4976-a969-4675d8427daf" }, { "cell_type": "markdown", @@ -3233,7 +3249,7 @@ "would be 1, but here we set it to 50 minutes, given points are arriving\n", "across zero to 250 minutes." ], - "id": "d2ce569a-d50d-4e5a-92de-b0b1b821627d" + "id": "c6b343b4-7f7c-4596-b223-46431de5b9de" }, { "cell_type": "code", @@ -3245,7 +3261,7 @@ "yt_mean, yt_var = m_full.predict(xt)\n", "yt_sd=np.sqrt(yt_var)" ], - "id": "206241c4-2ae2-4fee-9d0c-c7c9c1e039a2" + "id": "ff99d33a-02d3-4a91-a69e-fa2f5cbf9e3b" }, { "cell_type": "markdown", @@ -3253,7 +3269,7 @@ "source": [ "Now we plot the results using the helper function in `mlai.plot`." ], - "id": "f461445f-0d84-40bc-be84-42d096da68cc" + "id": "2d1dcb2f-bd6c-4312-8458-b5f2d6da871a" }, { "cell_type": "code", @@ -3263,7 +3279,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "b0264ea5-68af-4920-8fd9-03967a84bfeb" + "id": "5ed4ea3b-41e9-4a8c-8ce3-96bf112daef9" }, { "cell_type": "code", @@ -3280,20 +3296,20 @@ " filename='./gp/della-gatta-gene-gp.svg', \n", " transparent=True, frameon=True)" ], - "id": "40621402-ceb5-426d-9f52-d703edac8c85" + "id": "90c0b35d-69ab-4edc-9123-5be6125debcd" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Result of the fit of the Gaussian process model with the time\n", "scale parameter initialized to 50 minutes.\n", "\n", "Now we try a model initialized with a longer length scale." ], - "id": "e279f4c3-345c-498e-800b-1c0a6e724f6b" + "id": "e4a9514d-796c-4d21-b595-a7ada883e8eb" }, { "cell_type": "code", @@ -3305,7 +3321,7 @@ "m_full2.kern.lengthscale=2000\n", "_ = m_full2.optimize() # Optimize parameters of covariance function" ], - "id": "79d6de53-fd61-4de3-be03-044889da86d9" + "id": "cbdb93aa-b94d-458d-a2d6-013b89cf403f" }, { "cell_type": "code", @@ -3315,7 +3331,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "dce92969-5605-41ad-b66a-7b4a36491e16" + "id": "c65706c2-a2c6-457c-85a8-18449d1f0bd9" }, { "cell_type": "code", @@ -3332,20 +3348,20 @@ " filename='./gp/della-gatta-gene-gp2.svg', \n", " transparent=True, frameon=True)" ], - "id": "3aa86443-3fdf-4c2b-b055-7fed1a076a1f" + "id": "f0fe5613-f556-476e-9bea-5a33612fd4c1" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Result of the fit of the Gaussian process model with the time\n", "scale parameter initialized to 2000 minutes.\n", "\n", "Now we try a model initialized with a lower noise." ], - "id": "9d53be84-d14a-4fe7-a01c-1ffd7b2b1b3d" + "id": "d96263ab-5884-445b-a6c4-95981d068027" }, { "cell_type": "code", @@ -3358,7 +3374,7 @@ "m_full3.likelihood.variance=0.001\n", "_ = m_full3.optimize() # Optimize parameters of covariance function" ], - "id": "32de7aea-257f-4ba7-a3d6-7ebb5d0df96f" + "id": "3999e134-9007-4148-900b-3525aa3a370d" }, { "cell_type": "code", @@ -3368,7 +3384,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "308d2583-fc29-4709-aba1-a2921abff012" + "id": "e6e68eb7-d107-4284-9186-944ce67eb398" }, { "cell_type": "code", @@ -3385,19 +3401,19 @@ " filename='./gp/della-gatta-gene-gp3.svg', \n", " transparent=True, frameon=True)" ], - "id": "873faa9e-6391-4674-aae8-7ed1e8d232f4" + "id": "efa53551-3422-4693-b1f6-9854da703354" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: Result of the fit of the Gaussian process model with the\n", "noise initialized low (standard deviation 0.1) and the time scale\n", "parameter initialized to 20 minutes." ], - "id": "f0d4c915-4b03-4beb-9f90-0a3334eea9f4" + "id": "98cfb664-fdab-4493-9037-3ed4c164670f" }, { "cell_type": "code", @@ -3407,7 +3423,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "a9e16360-6f86-4eaa-a75a-693e773e2aaa" + "id": "e9dd9507-58d7-4a9b-bce3-84eb6dc505d6" }, { "cell_type": "code", @@ -3415,23 +3431,23 @@ "metadata": {}, "outputs": [], "source": [ - "plot.multiple_optima(diagrams='./gp')" + "plot.multiple_optima(directory='./gp')" ], - "id": "d29d4dd1-bce1-47dc-bd30-7f409aa125c3" + "id": "893bc34e-fd76-475e-97a3-f0e55fd54524" }, { "cell_type": "markdown", "metadata": {}, "source": [ - "\n", + "\n", "\n", "Figure: \n", "\n", "" + "-->" ], - "id": "b21ccfd4-cf9b-4865-b57e-7ab1fe506b8d" + "id": "9be74edc-ec84-4615-af7b-920f4e2c31b7" }, { "cell_type": "markdown", @@ -3445,7 +3461,7 @@ "\n", "\n", "\n", - " \n", + " \n", "\n", "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", "\n", - "Ricardo Andrade Pacecho\n", + "\\ricardoAndradePachecoPicture{15%}\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", "\n", "\n", - " \n", + " \n", "\n", "\n", "\n", @@ -3533,14 +3531,14 @@ "location within a district, such as Nagongera which is a sentinel site\n", "based in the Tororo district.\n", "\n", - "\n", + "\n", "\n", "Figure: Ugandan districts. Data SRTM/NASA from\n", ".\n", "\n", "(Andrade-Pacheco et al., 2014; Mubangizi et al., 2014)\n", "\n", - "\n", + "\n", "\n", "Figure: The Kapchorwa District, home district of Stephen\n", "Kiprotich.\n", @@ -3554,7 +3552,7 @@ "data suffers from missing values (Gething et al., 2006) and diagnosis of\n", "diseases like typhoid and malaria may be confounded.\n", "\n", - "\n", + "\n", "\n", "Figure: The Tororo district, where the sentinel site, Nagongera, is\n", "located.\n", @@ -3566,7 +3564,7 @@ "sites give accurate assessment of malaria disease levels in Uganda,\n", "including a site in Nagongera.\n", "\n", - "\n", + "\n", "\n", "Figure: Sentinel and HMIS data along with rainfall and temperature\n", "for the Nagongera sentinel station in the Tororo district.\n", @@ -3581,21 +3579,21 @@ "and temperature, to improve predictions from HMIS data of levels of\n", "malaria.\n", "\n", - "\n", + "\n", "\n", "Figure: The Mubende District.\n", "\n", - "\n", + "\n", "\n", "Figure: Prediction of malaria incidence in Mubende.\n", "\n", - "\n", + "\n", "\n", "Figure: The project arose out of the Gaussian process summer school\n", "held at Makerere in Kampala in 2013. The school led, in turn, to the\n", "Data Science Africa initiative." ], - "id": "e6a7830d-7d2f-4ed3-b596-520dbad274b2" + "id": "e76c864d-5007-4843-9595-ec186a5e6a98" }, { "cell_type": "markdown", @@ -3603,11 +3601,11 @@ "source": [ "## Early Warning Systems\n", "\n", - "\n", + "\n", "\n", "Figure: The Kabarole district in Uganda.\n", "\n", - "\n", + "\n", "\n", "Figure: Estimate of the current disease situation in the Kabarole\n", "district over time. Estimate is constructed with a Gaussian process with\n", @@ -3635,7 +3633,7 @@ "Finally, there is a gray region which represents when the scale of the\n", "effect is small.\n", "\n", - "\n", + "\n", "\n", "Figure: The map of Ugandan districts with an overview of the Malaria\n", "situation in each district.\n", @@ -3644,7 +3642,7 @@ "districts to give an immediate impression of the current status of the\n", "disease across the country." ], - "id": "028f7ae3-3527-4a3c-bb32-f1b687a901f1" + "id": "e267a31d-ea96-462c-9f39-d2d48052692a" }, { "cell_type": "markdown", @@ -3656,7 +3654,7 @@ "style=\"\">\\[edit\\]" ], - "id": "5cf8a547-1206-4e69-aeb7-483597e474d6" + "id": "3a68a958-6889-4919-8ee6-fee71b9fed89" }, { "cell_type": "code", @@ -3664,9 +3662,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import Kernel\n", + "import inspect\n", + "file_path = inspect.getfile(Kernel)" ], - "id": "1a082779-eb0e-4150-922e-01c7db18280d" + "id": "997e2600-42c2-4a0d-a3e3-be5eb706f05e" }, { "cell_type": "code", @@ -3674,9 +3674,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.Kernel" + "%load -s Kernel {file_path}" ], - "id": "ed94090e-a4d6-4cb7-8cc8-9b73082ee734" + "id": "e3e649f9-ad30-4040-b760-300bdff2ec83" }, { "cell_type": "code", @@ -3684,9 +3684,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import linear_cov\n", + "import inspect\n", + "file_path = inspect.getfile(linear_cov)" ], - "id": "e1e8f82f-0c61-4213-afc2-c0142d609e20" + "id": "4b8e6086-478d-43bb-a005-feec74aab906" }, { "cell_type": "code", @@ -3694,9 +3696,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.linear_cov" + "%load -s linear_cov {file_path}" ], - "id": "25468f38-761e-4bd3-80d9-b808f22225ed" + "id": "a5c3f041-5c68-4c3b-bc4b-2856404aba37" }, { "cell_type": "code", @@ -3704,9 +3706,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import eq_cov\n", + "import inspect\n", + "file_path = inspect.getfile(eq_cov)" ], - "id": "c4738d24-f6c6-4a82-aea0-860f9a21fc76" + "id": "162d9586-c9c0-4537-9426-ed47f8732e35" }, { "cell_type": "code", @@ -3714,9 +3718,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.eq_cov" + "%load -s eq_cov {file_path}" ], - "id": "5ae8cd19-795b-443a-b26d-c642d2ff7d31" + "id": "6648c757-0158-466a-a6d4-cc0577caa4a7" }, { "cell_type": "code", @@ -3724,9 +3728,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import add_cov\n", + "import inspect\n", + "file_path = inspect.getfile(add_cov)" ], - "id": "a7cdbe6b-83b9-4b22-bae9-241d1c6a1515" + "id": "99836bd3-442a-49ad-8622-e1cf7df7b219" }, { "cell_type": "code", @@ -3734,9 +3740,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.add_cov" + "%load -s add_cov {file_path}" ], - "id": "1ed71423-348f-4afc-b36d-7be99ab06c81" + "id": "78495937-826d-421f-98d2-17eb86f30db8" }, { "cell_type": "code", @@ -3744,14 +3750,14 @@ "metadata": {}, "outputs": [], "source": [ - "kernel = Kernel(function=add_cov,\n", + "kernel = Kernel(function=mlai.add_cov,\n", " name='Additive',\n", " shortname='add', \n", " formula='\\kernelScalar_f(\\inputVector, \\inputVector^\\prime) = \\kernelScalar_g(\\inputVector, \\inputVector^\\prime) + \\kernelScalar_h(\\inputVector, \\inputVector^\\prime)', \n", " kerns=[linear_cov, eq_cov], \n", " kern_args=[{'variance': 25}, {'lengthscale' : 0.2}])" ], - "id": "b6e34d4d-af75-4455-ba4a-04dad7e4afaf" + "id": "5e2ecf1e-7434-484e-878b-4655d5cfa3bb" }, { "cell_type": "code", @@ -3761,7 +3767,7 @@ "source": [ "import mlai.plot as plot" ], - "id": "b086bbb3-8e92-4610-938b-c479f1ceb731" + "id": "339d9080-2cbd-4478-bf75-6846b6cbfa0e" }, { "cell_type": "code", @@ -3771,7 +3777,7 @@ "source": [ "plot.covariance_func(kernel=kernel, diagrams='./kern/')" ], - "id": "c69e8b5f-4f18-447f-a064-d512660d9f43" + "id": "61c0748a-da21-41ea-8149-70697cbff336" }, { "cell_type": "markdown", @@ -3792,25 +3798,15 @@ "$$k_f(\\mathbf{ x}, \\mathbf{ x}^\\prime) = k_g(\\mathbf{ x}, \\mathbf{ x}^\\prime) + k_h(\\mathbf{ x}, \\mathbf{ x}^\\prime)$$\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", + "\n", "\n", "Figure: An additive covariance function formed by combining a linear\n", "and an exponentiated quadratic covariance functions." ], - "id": "a423ea29-9907-450b-a9f1-ffe566be0709" + "id": "b68c4aa3-e877-48d3-9be8-70717a57b9cc" }, { "cell_type": "markdown", @@ -3822,29 +3818,9 @@ "style=\"\">\\[edit\\]\n", "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - " \n", - "\n", - "\n", - "\n", - "Aki Vehtari\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\\akiVehtariPicture{15%}\n", "\n", - "\n", + "\n", "\n", "Figure: This is a retrospective analysis of US births by Aki Vehtari.\n", "The challenges of forecasting. Even with seasonal and weekly effects\n", @@ -3855,25 +3831,14 @@ "covariance functions are used to take account of weekly and yearly\n", "trends. The analysis is summarized on the cover of the book.\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", + "\n", "\n", "Figure: Two different editions of Bayesian Data Analysis (Gelman et\n", "al., 2013)." ], - "id": "1a4d3a22-0ec6-4290-bb64-1da38dc31a76" + "id": "64d01396-e87b-444e-ae38-d3388927ab8d" }, { "cell_type": "markdown", @@ -3889,17 +3854,17 @@ "multivariate Gaussian, if we decide $$\n", "\\mathbf{ f}=\\boldsymbol{ \\Phi}\\mathbf{ w}\n", "$$ and then we assume $$\n", - "\\mathbf{ w}\\sim \\mathcal{N}\\left(\\mathbf{0},\\alpha\\mathbf{I}\\right)\n", + "\\mathbf{ w}\\sim \\mathscr{N}\\left(\\mathbf{0},\\alpha\\mathbf{I}\\right)\n", "$$ then it follows from the properties of a multivariate Gaussian that\n", "$$\n", - "\\mathbf{ f}\\sim \\mathcal{N}\\left(\\mathbf{0},\\alpha\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top\\right)\n", + "\\mathbf{ f}\\sim \\mathscr{N}\\left(\\mathbf{0},\\alpha\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top\\right)\n", "$$ meaning that the vector of observations from the function is jointly\n", "distributed as a Gaussian process and the covariance matrix is\n", "$\\mathbf{K}= \\alpha\\boldsymbol{ \\Phi}\\boldsymbol{ \\Phi}^\\top$, each\n", "element of the covariance matrix can then be found as the inner product\n", "between two rows of the basis funciton matrix." ], - "id": "2eadec59-efdc-4f8f-933b-35a0fe78c1f5" + "id": "9a31413c-8568-44a4-9fd4-bf7319c6c2ec" }, { "cell_type": "code", @@ -3907,9 +3872,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import basis_cov\n", + "import inspect\n", + "file_path = inspect.getfile(basis_cov)" ], - "id": "c6b1fb60-ddfe-4618-88df-5d73636760a5" + "id": "f72bfc4b-fb27-479d-8066-8177d8200046" }, { "cell_type": "code", @@ -3917,9 +3884,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.basis_cov" + "%load -s basis_cov {file_path}" ], - "id": "41de1447-67eb-4477-8721-11bbbb47b64b" + "id": "1e31e2b6-6667-459a-839c-e8db5baa861e" }, { "cell_type": "code", @@ -3927,9 +3894,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import radial\n", + "import inspect\n", + "file_path = inspect.getfile(radial)" ], - "id": "94134a8c-376d-4b80-a4b7-b9d1379e4b3b" + "id": "954b73e1-1610-442f-a282-36249f0a15fd" }, { "cell_type": "code", @@ -3937,9 +3906,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.radial" + "%load -s radial {file_path}" ], - "id": "1cd12665-3074-4cf2-8248-225d9abf9cff" + "id": "90901f92-10f2-4e19-a00c-c464709c0e09" }, { "cell_type": "code", @@ -3951,7 +3920,7 @@ "import mlai\n", "import numpy as np" ], - "id": "fa9da6ce-2f6e-4d4f-a43c-12932306cf55" + "id": "bf8d576e-94bb-4fde-876b-2b62dc6da266" }, { "cell_type": "code", @@ -3960,11 +3929,11 @@ "outputs": [], "source": [ "\n", - "basis = mlai.Basis(function=radial, \n", + "basis = mlai.Basis(function=mlai.radial, \n", " number=3,\n", " data_limits=[-0.5, 0.5], \n", " width=0.125)\n", - "kernel = mlai.Kernel(function=basis_cov,\n", + "kernel = mlai.Kernel(function=mlai.basis_cov,\n", " name='Basis',\n", " shortname='basis', \n", " formula='\\kernel(\\inputVector, \\inputVector^\\prime) = \\basisVector(\\inputVector)^\\top \\basisVector(\\inputVector^\\prime)',\n", @@ -3972,7 +3941,7 @@ " \n", "plot.covariance_func(kernel, diagrams='./kern/')" ], - "id": "51c1566e-d621-44d1-be30-80441d188cdf" + "id": "76333ad3-8035-477d-8188-4a0cabd20377" }, { "cell_type": "markdown", @@ -3983,25 +3952,15 @@ "$$k(\\mathbf{ x}, \\mathbf{ x}^\\prime) = \\boldsymbol{ \\phi}(\\mathbf{ x})^\\top \\boldsymbol{ \\phi}(\\mathbf{ x}^\\prime)$$\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "
\n", + "\n", "\n", "Figure: A covariance function based on a non-linear basis given by\n", "$\\boldsymbol{ \\phi}(\\mathbf{ x})$." ], - "id": "f659f224-1b3f-4e7a-9d6e-9c6f01b65d7b" + "id": "3503da84-8229-4db2-b5c7-c2210e02cba9" }, { "cell_type": "markdown", @@ -4013,7 +3972,7 @@ "style=\"\">\\[edit\\]" ], - "id": "b1679d80-8cc6-4a52-96dd-52dc3e899d07" + "id": "5fb60e9a-2c50-4e75-aaf9-9215c6639608" }, { "cell_type": "code", @@ -4021,9 +3980,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import brownian_cov\n", + "import inspect\n", + "file_path = inspect.getfile(brownian_cov)" ], - "id": "3afc953e-4227-42bf-af9a-f9064c906224" + "id": "d7b16aa6-07c3-489e-9ff2-5691dd002876" }, { "cell_type": "code", @@ -4031,9 +3992,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.brownian_cov" + "%load -s brownian_cov {file_path}" ], - "id": "b28be7e4-e08f-4572-bd4b-9f4b75723059" + "id": "655e5ad5-cc49-4f05-8eb5-a9c20a893912" }, { "cell_type": "code", @@ -4045,7 +4006,7 @@ "import mlai\n", "import numpy as np" ], - "id": "a19d847b-cfb3-40d0-b5ca-4e79112b693d" + "id": "78d16616-6f44-4eaf-a684-bd060051ea5c" }, { "cell_type": "code", @@ -4054,13 +4015,13 @@ "outputs": [], "source": [ "t=np.linspace(0, 2, 200)[:, np.newaxis]\n", - "kernel = mlai.Kernel(function=brownian_cov,\n", + "kernel = mlai.Kernel(function=mlai.brownian_cov,\n", " name='Brownian',\n", " formula='\\kernelScalar(t, t^\\prime)=\\alpha \\min(t, t^\\prime)',\n", " shortname='brownian')\n", "plot.covariance_func(kernel, t, diagrams='./kern/')" ], - "id": "84d35356-8405-40ff-94c2-adb38b679c2d" + "id": "5aa81759-2ea6-470e-a6b6-608a9b655e50" }, { "cell_type": "markdown", @@ -4078,24 +4039,14 @@ "$$k(t, t^\\prime)=\\alpha \\min(t, t^\\prime)$$\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", + "\n", "\n", "Figure: Brownian motion covariance function." ], - "id": "1f23ca1b-8cd9-4b69-911e-03b0912d3413" + "id": "22942983-d272-4834-b789-262a3c7fd300" }, { "cell_type": "markdown", @@ -4107,7 +4058,7 @@ "style=\"\">\\[edit\\]" ], - "id": "7bcceefa-a404-4f43-a960-36e7f9ccb2d8" + "id": "c5ac21ec-db0f-431e-b5cd-dd932ce39134" }, { "cell_type": "code", @@ -4115,9 +4066,11 @@ "metadata": {}, "outputs": [], "source": [ - "import mlai" + "from mlai import mlp_cov\n", + "import inspect\n", + "file_path = inspect.getfile(mlp_cov)" ], - "id": "eeae91fa-b27f-4e07-a5f1-14efcef5d1bb" + "id": "8c07d554-36c8-471f-81c4-1b87b27051fc" }, { "cell_type": "code", @@ -4125,9 +4078,9 @@ "metadata": {}, "outputs": [], "source": [ - "%load -n mlai.mlp_cov" + "%load -s mlp_cov {file_path}" ], - "id": "3ae2d155-909f-4fb0-bf7e-9644e417d6e1" + "id": "56066c55-9ff3-42c5-8726-1d698b3ff172" }, { "cell_type": "code", @@ -4139,7 +4092,7 @@ "import mlai\n", "import numpy as np" ], - "id": "38e2e735-9195-40e0-b56a-4b3ab70fd537" + "id": "9e74b807-4d1a-46bd-8997-be7f172e7f7c" }, { "cell_type": "code", @@ -4155,7 +4108,7 @@ " \n", "plot.covariance_func(kernel, diagrams='./kern/')" ], - "id": "7948236b-734c-4cfc-968e-266da2853182" + "id": "a0b5fce2-6457-48d9-bbff-df558c206f55" }, { "cell_type": "markdown", @@ -4170,26 +4123,16 @@ "$$k(\\mathbf{ x}, \\mathbf{ x}^\\prime) = \\alpha \\arcsin\\left(\\frac{w \\mathbf{ x}^\\top \\mathbf{ x}^\\prime + b}{\\sqrt{\\left(w \\mathbf{ x}^\\top \\mathbf{ x}+ b + 1\\right)\\left(w \\left.\\mathbf{ x}^\\prime\\right.^\\top \\mathbf{ x}^\\prime + b + 1\\right)}}\\right)$$\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "\n", - "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "
\n", + "\n", "\n", "Figure: The multi-layer perceptron covariance function. This is\n", "derived by considering the infinite limit of a neural network with\n", "probit activation functions." ], - "id": "ebf19543-affb-483a-b8ba-0c928b7c5d62" + "id": "defa63f0-2d06-4a59-9fe6-fb60a41bfb00" }, { "cell_type": "markdown", @@ -4206,7 +4149,17 @@ "material on line. Details of the school, future events and past events\n", "can be found at the website ." ], - "id": "93e45f1d-ec09-4b18-9311-89a27df6a661" + "id": "e676c028-6db7-4901-9917-7169523407ed" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cmd = install_command('GPy')" + ], + "id": "de5ba5c7-eb83-4478-aa1c-eaa6d12f2603" }, { "cell_type": "code", @@ -4214,9 +4167,9 @@ "metadata": {}, "outputs": [], "source": [ - "%pip install gpy" + "%system {cmd}" ], - "id": "12144f51-79eb-4445-9b6a-9ccaece5d850" + "id": "5d11c950-2de3-4461-bb46-c89b89fc9405" }, { "cell_type": "markdown", @@ -4233,7 +4186,7 @@ "to use interface to GPs. One which allowed the user to focus on the\n", "modelling rather than the mathematics.\n", "\n", - "\n", + "\n", "\n", "Figure: GPy is a BSD licensed software code base for implementing\n", "Gaussian process models in Python. It is designed for teaching and\n", @@ -4257,7 +4210,7 @@ "The documentation for GPy can be found\n", "[here](https://gpy.readthedocs.io/en/latest/)." ], - "id": "3f748006-6c9d-4c40-8863-9d5460badcda" + "id": "2768237f-eb85-4814-b69c-91dc21a539f6" }, { "cell_type": "markdown", @@ -4268,6 +4221,9 @@ "For more information on these subjects and more you might want to check\n", "the following resources.\n", "\n", + "- company: [Trent AI](https://trent.ai)\n", + "- book: [The Atomic\n", + " Human](https://www.penguin.co.uk/books/455130/the-atomic-human-by-lawrence-neil-d/9780241625248)\n", "- twitter: [@lawrennd](https://twitter.com/lawrennd)\n", "- podcast: [The Talking Machines](http://thetalkingmachines.com)\n", "- newspaper: [Guardian Profile\n", @@ -4275,23 +4231,17 @@ "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)" ], - "id": "e85399fc-7ac6-4f20-8082-ec96b15ea676" - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## References" - ], - "id": "110cf9ed-dfd5-4608-a695-6144fb349c41" + "id": "f44129f4-b0b5-4349-b7dd-9034fb1373ec" }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## References\n", + "\n", "Andrade-Pacheco, R., Mubangizi, M., Quinn, J., Lawrence, N.D., 2014.\n", "Consistent mapping of government malaria records across a changing\n", - "territory delimitation. Malaria Journal 13.\n", + "territory delimitation. Malaria Journal 13, 5.\n", "\n", "\n", "Della Gatta, G., Bansal, M., Ambesi-Impiombato, A., Antonini, D.,\n", @@ -4325,7 +4275,7 @@ "Neal, R.M., 1994. Bayesian learning for neural networks (PhD thesis).\n", "Dept. of Computer Science, University of Toronto." ], - "id": "ae96f2e6-6480-4ee6-87fa-6a04b62b44d8" + "id": "d26221f4-1672-4d05-88b6-2a88f32b43ea" } ], "nbformat": 4, diff --git a/requirements-smoke.txt b/requirements-smoke.txt new file mode 100644 index 00000000..fb9c4ad2 --- /dev/null +++ b/requirements-smoke.txt @@ -0,0 +1,28 @@ +# Runtime + execution harness for lecture notebook smoke tests. +# Install: pip install -r requirements-smoke.txt + +# Notebook execution +nbclient>=0.10,<1 +nbformat>=5.10,<6 +ipykernel>=6.29 +ipywidgets>=8.1 +ipython>=8.0 + +# Teaching stack (match lecture %pip install cells) +GPy>=1.13 +git+https://github.com/lawrennd/mlai.git#egg=mlai +git+https://github.com/lawrennd/ods.git#egg=pods +git+https://github.com/lawrennd/notutils.git#egg=notutils + +# Used by lecture notebooks +matplotlib>=3.8 +numpy>=1.26 +scipy>=1.11 +pandas>=2.0 +scikit-learn>=1.3 +scikit-image>=0.22 +Pillow>=10.0 +PyYAML>=6.0 +plotly>=5.0 +requests>=2.31 +daft-pgm>=0.1 diff --git a/scripts/smoke_notebooks.py b/scripts/smoke_notebooks.py new file mode 100755 index 00000000..df2f3d08 --- /dev/null +++ b/scripts/smoke_notebooks.py @@ -0,0 +1,451 @@ +#!/usr/bin/env python3 +"""Execute MLAI lecture notebooks end-to-end for smoke testing. + +CI-oriented defaults: + - matplotlib Agg backend + - mock stdin + - short-circuit ipywidgets.interact / notutils.display_plots (avoid hangs) + - cap GPy optimize iterations + - execute `%load -n module.attr` cells by importing the name + - fill student name / live-rating placeholders + - blank incomplete exercise cells that are not valid Python + (Bayesian posterior assignment is filled from the lecture formulae) + - skip lecture 05 by default (MariaDB + credential widgets) + +Usage (from repo root):: + + python scripts/smoke_notebooks.py + python scripts/smoke_notebooks.py --only 01 12 + python scripts/smoke_notebooks.py --skip + python scripts/smoke_notebooks.py --notebooks-dir _notebooks --results-dir _notebooks/test-results +""" + +from __future__ import annotations + +import argparse +import ast +import json +import os +import re +import sys +import time +import traceback +from copy import deepcopy +from pathlib import Path + +import nbformat +from nbclient import NotebookClient +from nbclient.exceptions import CellExecutionError + +# Injected as the first code cell of every notebook under test. +PRELUDE = r""" +import builtins +import numpy as np +import pandas as pd + +if not hasattr(np, "NaN"): + np.NaN = np.nan + + +def _mock_input(prompt=""): + p = str(prompt).lower() + if "distance" in p: + print(f"[mock input] {prompt!r} -> 50.0") + return "50.0" + if ( + (not prompt) + or ("yes" in p) + or ("proceed" in p) + or ("continue" in p) + or ("[y/n]" in p) + or ("[yes/no]" in p) + ): + print(f"[mock input] {prompt!r} -> y") + return "y" + print(f"[mock input] {prompt!r} -> 1.0") + return "1.0" + + +builtins.input = _mock_input +try: + ip = get_ipython() + if ip is not None and getattr(ip, "kernel", None) is not None: + ip.kernel._allow_stdin = True + ip.kernel.raw_input = _mock_input +except Exception as e: + print("stdin patch skipped", e) + +import matplotlib + +matplotlib.use("Agg") + +# Lecture notebooks write figures under these relative dirs (cwd = notebooks dir). +from pathlib import Path as _Path + +for _d in ( + "./datasets", + "./data-science", + "./dimred", + "./gp", + "./kern", + "./ml", + "./mlai", +): + _Path(_d).mkdir(parents=True, exist_ok=True) + + +# Avoid ipywidgets.interact deadlocks under nbclient. +def _once_interact(func=None, **kwargs): + if func is None: + return lambda f: _once_interact(f, **kwargs) + call = {} + for k, v in kwargs.items(): + if hasattr(v, "value"): + call[k] = v.value + elif ( + isinstance(v, tuple) + and len(v) >= 2 + and all(isinstance(x, (int, float)) for x in v[:3] if not isinstance(x, bool)) + ): + call[k] = v[0] + else: + call[k] = v + try: + return func(**call) + except Exception as e: + print("[smoke] interact once failed:", e) + + +try: + import ipywidgets + + ipywidgets.interact = _once_interact + ipywidgets.interact_manual = _once_interact +except Exception as e: + print("ipywidgets patch skipped", e) + +try: + import notutils + + notutils.display_plots = lambda *a, **k: print( + "[smoke] skip display_plots", a[:1] if a else "" + ) + if hasattr(notutils, "display_prediction"): + + def _skip_pred(*a, **k): + print("[smoke] skip display_prediction") + + notutils.display_prediction = _skip_pred +except Exception as e: + print("notutils patch skipped", e) + + +def _cap_optimize(cls, method_name="optimize", max_iters=40): + if not hasattr(cls, method_name): + return + orig = getattr(cls, method_name) + + def wrapped(self, *args, **kwargs): + if "max_iters" in kwargs and kwargs["max_iters"] is not None: + kwargs["max_iters"] = min(int(kwargs["max_iters"]), max_iters) + else: + kwargs["max_iters"] = max_iters + if "messages" in kwargs: + kwargs["messages"] = False + if "max_f_eval" in kwargs and kwargs["max_f_eval"] is not None: + kwargs["max_f_eval"] = min(int(kwargs["max_f_eval"]), max_iters) + return orig(self, *args, **kwargs) + + setattr(cls, method_name, wrapped) + + +try: + import GPy + + _cap_optimize(GPy.core.gp.GP, "optimize", 40) +except Exception as e: + print("GPy patch skipped", e) + +try: + import mlai + from mlai import linear_models as _lm + + for _name in ("relu", "hyperbolic_tangent"): + if not hasattr(mlai, _name) and hasattr(_lm, _name): + setattr(mlai, _name, getattr(_lm, _name)) + print(f"[smoke] attached mlai.{_name} from linear_models") + + _write_figure = mlai.write_figure + + def _write_figure_diagrams(filename, figure=None, directory=None, frameon=None, **kwargs): + if directory is None: + directory = kwargs.pop("diagrams", None) + else: + kwargs.pop("diagrams", None) + return _write_figure( + filename, figure=figure, directory=directory, frameon=frameon, **kwargs + ) + + mlai.write_figure = _write_figure_diagrams +except Exception as e: + print("mlai basis attach skipped", e) + +try: + import plotly.io as pio + + pio.renderers.default = "json" +except Exception as e: + print("plotly renderer patch skipped", e) +""" + +_LOAD_MAGIC = re.compile(r"^%load\s+-n\s+([\w.]+)\s*$") + + +def _cell_source(cell) -> str: + src = cell.get("source", "") + if isinstance(src, list): + src = "".join(src) + return src + + +def expand_load_magics(nb: nbformat.NotebookNode) -> None: + """Execute `%load -n module.attr` by importing the name. + + IPython `%load` pastes source into the cell but does not run it. Students + re-execute interactively; under nbclient the name would never be defined. + """ + for cell in nb.cells: + if cell.get("cell_type") != "code": + continue + lines = [ + ln + for ln in _cell_source(cell).strip().splitlines() + if ln.strip() and not ln.strip().startswith("#") + ] + if len(lines) != 1: + continue + match = _LOAD_MAGIC.match(lines[0].strip()) + if not match: + continue + target = match.group(1) + if "." not in target: + continue + mod_name, attr = target.rsplit(".", 1) + cell["source"] = ( + f"# smoke: execute `%load -n {target}`\n" + f"from {mod_name} import {attr}\n" + ) + + +def patch_student_placeholders(nb: nbformat.NotebookNode) -> None: + """Fill interactive lecture placeholders that cannot run unattended.""" + dummy_ratings = ( + "Y = pd.DataFrame({\n" + " 'user': ['smoke-a', 'smoke-a', 'smoke-b', 'smoke-b', 'smoke-c', 'smoke-c'],\n" + " 'film': ['Inception', 'Avatar', 'Inception', 'Titanic', 'Avatar', 'Titanic'],\n" + " 'rating': [8, 7, 9, 6, 5, 8],\n" + "})\n" + ) + for cell in nb.cells: + if cell.get("cell_type") != "code": + continue + src = _cell_source(cell) + changed = False + if re.search(r"assert\s+name\s*!=\s*['\"]todo['\"]", src): + src = re.sub(r"name\s*=\s*['\"]todo['\"]", "name = 'smoke-test'", src, count=1) + src = re.sub( + r"assert\s+name\s*!=\s*['\"]todo['\"]", + "pass # smoke: student name placeholder", + src, + count=1, + ) + changed = True + if re.search(r"^Y\s*=\s*responses\(\)\s*$", src, re.M): + src = re.sub( + r"^Y\s*=\s*responses\(\)\s*$", + dummy_ratings.rstrip(), + src, + count=1, + flags=re.M, + ) + changed = True + if changed: + cell["source"] = src + + +def blank_incomplete_exercises(nb: nbformat.NotebookNode) -> None: + for cell in nb.cells: + if cell.get("cell_type") != "code": + continue + src = cell.get("source", "") + if isinstance(src, list): + src = "".join(src) + if "Write code for you answer" not in src and "Write your answer to Exercise" not in src: + continue + try: + ast.parse(src) + except SyntaxError: + if "w_cov" in src and "w_mean" in src: + cell["source"] = ( + "# smoke: Bayesian linear-model posterior from lecture formulae\n" + "sigma2 = 0.01\n" + "w_cov = np.linalg.inv(Phi.T @ Phi / sigma2 + np.eye(Phi.shape[1]) / alpha)\n" + "w_mean = w_cov @ (Phi.T @ y) / sigma2\n" + ) + else: + cell["source"] = "# skipped incomplete exercise cell for smoke test\npass\n" + + +def prepare(nb: nbformat.NotebookNode) -> nbformat.NotebookNode: + nb = deepcopy(nb) + blank_incomplete_exercises(nb) + expand_load_magics(nb) + patch_student_placeholders(nb) + nb.cells.insert(0, nbformat.v4.new_code_cell(PRELUDE)) + return nb + + +def _matches_token(name: str, token: str) -> bool: + token = token.strip() + return bool(token) and (name.startswith(token) or name == token) + + +def list_notebooks(nb_dir: Path, only: list[str] | None, skip: list[str] | None) -> list[str]: + names = sorted(p.name for p in nb_dir.glob("[0-9][0-9]-*.ipynb")) + if only: + selected = [] + for token in only: + matches = [n for n in names if _matches_token(n, token)] + if not matches: + raise SystemExit(f"No notebook matches --only {token!r} under {nb_dir}") + for m in matches: + if m not in selected: + selected.append(m) + return selected + skip = skip or [] + selected = [] + for name in names: + skipped = next((tok for tok in skip if _matches_token(name, tok)), None) + if skipped is not None: + print(f"[smoke] skip {name} (--skip {skipped})", flush=True) + continue + selected.append(name) + return selected + + +def run_one( + name: str, + nb_dir: Path, + out_dir: Path, + timeout: int, +) -> dict: + path = nb_dir / name + print(f"\n======== {name} ========", flush=True) + t0 = time.time() + try: + nb = nbformat.read(path, as_version=4) + nb = prepare(nb) + client = NotebookClient( + nb, + timeout=timeout, + kernel_name="python3", + resources={"metadata": {"path": str(nb_dir)}}, + allow_errors=False, + allow_stdin=True, + ) + client.execute() + elapsed = time.time() - t0 + print(f"OK ({elapsed:.1f}s)", flush=True) + (out_dir / f"{name}.ok").write_text(f"OK {elapsed:.1f}s\n") + fail = out_dir / f"{name}.fail" + if fail.exists(): + fail.unlink() + return {"name": name, "status": "OK", "elapsed": elapsed, "error": ""} + except CellExecutionError as e: + elapsed = time.time() - t0 + msg = str(e) + print(f"FAIL ({elapsed:.1f}s)\n{msg[:4000]}", flush=True) + (out_dir / f"{name}.fail").write_text(msg) + return {"name": name, "status": "FAIL", "elapsed": elapsed, "error": msg} + except Exception: + elapsed = time.time() - t0 + msg = traceback.format_exc() + print(f"ERROR ({elapsed:.1f}s)\n{msg[:4000]}", flush=True) + (out_dir / f"{name}.fail").write_text(msg) + return {"name": name, "status": "ERROR", "elapsed": elapsed, "error": msg} + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--notebooks-dir", + type=Path, + default=Path("_notebooks"), + help="Directory containing NN-*.ipynb lecture notebooks", + ) + parser.add_argument( + "--results-dir", + type=Path, + default=Path("_notebooks/test-results"), + help="Where to write .ok/.fail markers and summary JSON", + ) + parser.add_argument( + "--only", + nargs="+", + default=None, + help="Restrict to notebooks whose names start with these prefixes (e.g. 01 12)", + ) + parser.add_argument( + "--skip", + nargs="*", + default=["05"], + help="Skip notebooks whose names start with these prefixes. " + "Default: 05 (MariaDB). Pass --skip with no values to run all.", + ) + parser.add_argument( + "--timeout", + type=int, + default=900, + help="Per-notebook cell execution timeout in seconds (default 900)", + ) + parser.add_argument( + "--summary-name", + default="summary.json", + help="Summary JSON filename under --results-dir", + ) + args = parser.parse_args(argv) + + repo_root = Path.cwd() + nb_dir = (repo_root / args.notebooks_dir).resolve() + out_dir = (repo_root / args.results_dir).resolve() + out_dir.mkdir(parents=True, exist_ok=True) + + mpl_dir = out_dir / "mpl" + mpl_dir.mkdir(parents=True, exist_ok=True) + os.environ.setdefault("MPLBACKEND", "Agg") + os.environ.setdefault("JUPYTER_PLATFORM_DIRS", "1") + os.environ.setdefault("MPLCONFIGDIR", str(mpl_dir)) + + if not nb_dir.is_dir(): + print(f"Notebooks directory not found: {nb_dir}", file=sys.stderr) + return 2 + + order = list_notebooks(nb_dir, args.only, args.skip) + print("Notebooks:", order, flush=True) + + results = [] + for name in order: + result = run_one(name, nb_dir, out_dir, args.timeout) + results.append(result) + (out_dir / args.summary_name).write_text(json.dumps(results, indent=2) + "\n") + + print("\n==== SUMMARY ====") + for r in results: + print(f"{r['status']:5} {r['elapsed']:7.1f}s {r['name']}") + n_ok = sum(r["status"] == "OK" for r in results) + print(f"{n_ok}/{len(results)} passed") + return 0 if n_ok == len(results) and results else 1 + + +if __name__ == "__main__": + raise SystemExit(main())