{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Deep Probabilistic Modelling with with Gaussian Processes\n", "### [Neil D. Lawrence](http://inverseprobability.com), Amazon and University of Sheffield\n", "\n", "**Abstract**: Neural network models are algorithmically simple, but mathematically\n", "complex. Gaussian process models are mathematically simple, but\n", "algorithmically complex. In this tutorial we will explore Deep Gaussian\n", "Process models. They bring advantages in their mathematical simplicity\n", "but are challenging in their algorithmic complexity. We will give an\n", "overview of Gaussian processes and highlight the algorithmic\n", "approximations that allow us to stack Gaussian process models: they are\n", "based on variational methods. In the last part of the tutorial will\n", "explore a use case exemplar: uncertainty quantification. We end with\n", "open questions.\n", "\n", "$$\n", "\\newcommand{\\Amatrix}{\\mathbf{A}}\n", "\\newcommand{\\KL}{\\text{KL}\\left( #1\\,\\|\\,#2 \\right)}\n", "\\newcommand{\\Kaast}{\\kernelMatrix_{\\mathbf{ \\ast}\\mathbf{ \\ast}}}\n", "\\newcommand{\\Kastu}{\\kernelMatrix_{\\mathbf{ \\ast} \\inducingVector}}\n", "\\newcommand{\\Kff}{\\kernelMatrix_{\\mappingFunctionVector \\mappingFunctionVector}}\n", "\\newcommand{\\Kfu}{\\kernelMatrix_{\\mappingFunctionVector \\inducingVector}}\n", "\\newcommand{\\Kuast}{\\kernelMatrix_{\\inducingVector \\bf\\ast}}\n", "\\newcommand{\\Kuf}{\\kernelMatrix_{\\inducingVector \\mappingFunctionVector}}\n", "\\newcommand{\\Kuu}{\\kernelMatrix_{\\inducingVector \\inducingVector}}\n", "\\newcommand{\\Kuui}{\\Kuu^{-1}}\n", "\\newcommand{\\Qaast}{\\mathbf{Q}_{\\bf \\ast \\ast}}\n", "\\newcommand{\\Qastf}{\\mathbf{Q}_{\\ast \\mappingFunction}}\n", "\\newcommand{\\Qfast}{\\mathbf{Q}_{\\mappingFunctionVector \\bf \\ast}}\n", "\\newcommand{\\Qff}{\\mathbf{Q}_{\\mappingFunctionVector \\mappingFunctionVector}}\n", "\\newcommand{\\aMatrix}{\\mathbf{A}}\n", "\\newcommand{\\aScalar}{a}\n", "\\newcommand{\\aVector}{\\mathbf{a}}\n", "\\newcommand{\\acceleration}{a}\n", "\\newcommand{\\bMatrix}{\\mathbf{B}}\n", "\\newcommand{\\bScalar}{b}\n", "\\newcommand{\\bVector}{\\mathbf{b}}\n", "\\newcommand{\\basisFunc}{\\phi}\n", "\\newcommand{\\basisFuncVector}{\\boldsymbol{ \\basisFunc}}\n", "\\newcommand{\\basisFunction}{\\phi}\n", "\\newcommand{\\basisLocation}{\\mu}\n", "\\newcommand{\\basisMatrix}{\\boldsymbol{ \\Phi}}\n", "\\newcommand{\\basisScalar}{\\basisFunction}\n", "\\newcommand{\\basisVector}{\\boldsymbol{ \\basisFunction}}\n", "\\newcommand{\\activationFunction}{\\phi}\n", "\\newcommand{\\activationMatrix}{\\boldsymbol{ \\Phi}}\n", "\\newcommand{\\activationScalar}{\\basisFunction}\n", "\\newcommand{\\activationVector}{\\boldsymbol{ \\basisFunction}}\n", "\\newcommand{\\bigO}{\\mathcal{O}}\n", "\\newcommand{\\binomProb}{\\pi}\n", "\\newcommand{\\cMatrix}{\\mathbf{C}}\n", "\\newcommand{\\cbasisMatrix}{\\hat{\\boldsymbol{ \\Phi}}}\n", "\\newcommand{\\cdataMatrix}{\\hat{\\dataMatrix}}\n", "\\newcommand{\\cdataScalar}{\\hat{\\dataScalar}}\n", "\\newcommand{\\cdataVector}{\\hat{\\dataVector}}\n", "\\newcommand{\\centeredKernelMatrix}{\\mathbf{ \\MakeUppercase{\\centeredKernelScalar}}}\n", "\\newcommand{\\centeredKernelScalar}{b}\n", "\\newcommand{\\centeredKernelVector}{\\centeredKernelScalar}\n", "\\newcommand{\\centeringMatrix}{\\mathbf{H}}\n", "\\newcommand{\\chiSquaredDist}{\\chi_{#1}^{2}\\left(#2\\right)}\n", "\\newcommand{\\chiSquaredSamp}{\\chi_{#1}^{2}}\n", "\\newcommand{\\conditionalCovariance}{\\boldsymbol{ \\Sigma}}\n", "\\newcommand{\\coregionalizationMatrix}{\\mathbf{B}}\n", "\\newcommand{\\coregionalizationScalar}{b}\n", "\\newcommand{\\coregionalizationVector}{\\mathbf{ \\coregionalizationScalar}}\n", "\\newcommand{\\covDist}{\\text{cov}_{#2}\\left(#1\\right)}\n", "\\newcommand{\\covSamp}{\\text{cov}\\left(#1\\right)}\n", "\\newcommand{\\covarianceScalar}{c}\n", "\\newcommand{\\covarianceVector}{\\mathbf{ \\covarianceScalar}}\n", "\\newcommand{\\covarianceMatrix}{\\mathbf{C}}\n", "\\newcommand{\\covarianceMatrixTwo}{\\boldsymbol{ \\Sigma}}\n", "\\newcommand{\\croupierScalar}{s}\n", "\\newcommand{\\croupierVector}{\\mathbf{ \\croupierScalar}}\n", "\\newcommand{\\croupierMatrix}{\\mathbf{ \\MakeUppercase{\\croupierScalar}}}\n", "\\newcommand{\\dataDim}{p}\n", "\\newcommand{\\dataIndex}{i}\n", "\\newcommand{\\dataIndexTwo}{j}\n", "\\newcommand{\\dataMatrix}{\\mathbf{Y}}\n", "\\newcommand{\\dataScalar}{y}\n", "\\newcommand{\\dataSet}{\\mathcal{D}}\n", "\\newcommand{\\dataStd}{\\sigma}\n", "\\newcommand{\\dataVector}{\\mathbf{ \\dataScalar}}\n", "\\newcommand{\\decayRate}{d}\n", "\\newcommand{\\degreeMatrix}{\\mathbf{ \\MakeUppercase{\\degreeScalar}}}\n", "\\newcommand{\\degreeScalar}{d}\n", "\\newcommand{\\degreeVector}{\\mathbf{ \\degreeScalar}}\n", "% Already defined by latex\n", "%\\newcommand{\\det}{\\left|#1\\right|}\n", "\\newcommand{\\diag}{\\text{diag}\\left(#1\\right)}\n", "\\newcommand{\\diagonalMatrix}{\\mathbf{D}}\n", "\\newcommand{\\diff}{\\frac{\\text{d}#1}{\\text{d}#2}}\n", "\\newcommand{\\diffTwo}{\\frac{\\text{d}^2#1}{\\text{d}#2^2}}\n", "\\newcommand{\\displacement}{x}\n", "\\newcommand{\\displacementVector}{\\textbf{\\displacement}}\n", "\\newcommand{\\distanceMatrix}{\\mathbf{ \\MakeUppercase{\\distanceScalar}}}\n", "\\newcommand{\\distanceScalar}{d}\n", "\\newcommand{\\distanceVector}{\\mathbf{ \\distanceScalar}}\n", "\\newcommand{\\eigenvaltwo}{\\ell}\n", "\\newcommand{\\eigenvaltwoMatrix}{\\mathbf{L}}\n", "\\newcommand{\\eigenvaltwoVector}{\\mathbf{l}}\n", "\\newcommand{\\eigenvalue}{\\lambda}\n", "\\newcommand{\\eigenvalueMatrix}{\\boldsymbol{ \\Lambda}}\n", "\\newcommand{\\eigenvalueVector}{\\boldsymbol{ \\lambda}}\n", "\\newcommand{\\eigenvector}{\\mathbf{ \\eigenvectorScalar}}\n", "\\newcommand{\\eigenvectorMatrix}{\\mathbf{U}}\n", "\\newcommand{\\eigenvectorScalar}{u}\n", "\\newcommand{\\eigenvectwo}{\\mathbf{v}}\n", "\\newcommand{\\eigenvectwoMatrix}{\\mathbf{V}}\n", "\\newcommand{\\eigenvectwoScalar}{v}\n", "\\newcommand{\\entropy}{\\mathcal{H}\\left(#1\\right)}\n", "\\newcommand{\\errorFunction}{E}\n", "\\newcommand{\\expDist}{\\left<#1\\right>_{#2}}\n", "\\newcommand{\\expSamp}{\\left<#1\\right>}\n", "\\newcommand{\\expectation}{\\left\\langle #1 \\right\\rangle }\n", "\\newcommand{\\expectationDist}{\\left\\langle #1 \\right\\rangle _{#2}}\n", "\\newcommand{\\expectedDistanceMatrix}{\\mathcal{D}}\n", "\\newcommand{\\eye}{\\mathbf{I}}\n", "\\newcommand{\\fantasyDim}{r}\n", "\\newcommand{\\fantasyMatrix}{\\mathbf{ \\MakeUppercase{\\fantasyScalar}}}\n", "\\newcommand{\\fantasyScalar}{z}\n", "\\newcommand{\\fantasyVector}{\\mathbf{ \\fantasyScalar}}\n", "\\newcommand{\\featureStd}{\\varsigma}\n", "\\newcommand{\\gammaCdf}{\\mathcal{GAMMA CDF}\\left(#1|#2,#3\\right)}\n", "\\newcommand{\\gammaDist}{\\mathcal{G}\\left(#1|#2,#3\\right)}\n", "\\newcommand{\\gammaSamp}{\\mathcal{G}\\left(#1,#2\\right)}\n", "\\newcommand{\\gaussianDist}{\\mathcal{N}\\left(#1|#2,#3\\right)}\n", "\\newcommand{\\gaussianSamp}{\\mathcal{N}\\left(#1,#2\\right)}\n", "\\newcommand{\\given}{|}\n", "\\newcommand{\\half}{\\frac{1}{2}}\n", "\\newcommand{\\heaviside}{H}\n", "\\newcommand{\\hiddenMatrix}{\\mathbf{ \\MakeUppercase{\\hiddenScalar}}}\n", "\\newcommand{\\hiddenScalar}{h}\n", "\\newcommand{\\hiddenVector}{\\mathbf{ \\hiddenScalar}}\n", "\\newcommand{\\identityMatrix}{\\eye}\n", "\\newcommand{\\inducingInputScalar}{z}\n", "\\newcommand{\\inducingInputVector}{\\mathbf{ \\inducingInputScalar}}\n", "\\newcommand{\\inducingInputMatrix}{\\mathbf{Z}}\n", "\\newcommand{\\inducingScalar}{u}\n", "\\newcommand{\\inducingVector}{\\mathbf{ \\inducingScalar}}\n", "\\newcommand{\\inducingMatrix}{\\mathbf{U}}\n", "\\newcommand{\\inlineDiff}{\\text{d}#1/\\text{d}#2}\n", "\\newcommand{\\inputDim}{q}\n", "\\newcommand{\\inputMatrix}{\\mathbf{X}}\n", "\\newcommand{\\inputScalar}{x}\n", "\\newcommand{\\inputSpace}{\\mathcal{X}}\n", "\\newcommand{\\inputVals}{\\inputVector}\n", "\\newcommand{\\inputVector}{\\mathbf{ \\inputScalar}}\n", "\\newcommand{\\iterNum}{k}\n", "\\newcommand{\\kernel}{\\kernelScalar}\n", "\\newcommand{\\kernelMatrix}{\\mathbf{K}}\n", "\\newcommand{\\kernelScalar}{k}\n", "\\newcommand{\\kernelVector}{\\mathbf{ \\kernelScalar}}\n", "\\newcommand{\\kff}{\\kernelScalar_{\\mappingFunction \\mappingFunction}}\n", "\\newcommand{\\kfu}{\\kernelVector_{\\mappingFunction \\inducingScalar}}\n", "\\newcommand{\\kuf}{\\kernelVector_{\\inducingScalar \\mappingFunction}}\n", "\\newcommand{\\kuu}{\\kernelVector_{\\inducingScalar \\inducingScalar}}\n", "\\newcommand{\\lagrangeMultiplier}{\\lambda}\n", "\\newcommand{\\lagrangeMultiplierMatrix}{\\boldsymbol{ \\Lambda}}\n", "\\newcommand{\\lagrangian}{L}\n", "\\newcommand{\\laplacianFactor}{\\mathbf{ \\MakeUppercase{\\laplacianFactorScalar}}}\n", "\\newcommand{\\laplacianFactorScalar}{m}\n", "\\newcommand{\\laplacianFactorVector}{\\mathbf{ \\laplacianFactorScalar}}\n", "\\newcommand{\\laplacianMatrix}{\\mathbf{L}}\n", "\\newcommand{\\laplacianScalar}{\\ell}\n", "\\newcommand{\\laplacianVector}{\\mathbf{ \\ell}}\n", "\\newcommand{\\latentDim}{q}\n", "\\newcommand{\\latentDistanceMatrix}{\\boldsymbol{ \\Delta}}\n", "\\newcommand{\\latentDistanceScalar}{\\delta}\n", "\\newcommand{\\latentDistanceVector}{\\boldsymbol{ \\delta}}\n", "\\newcommand{\\latentForce}{f}\n", "\\newcommand{\\latentFunction}{u}\n", "\\newcommand{\\latentFunctionVector}{\\mathbf{ \\latentFunction}}\n", "\\newcommand{\\latentFunctionMatrix}{\\mathbf{ \\MakeUppercase{\\latentFunction}}}\n", "\\newcommand{\\latentIndex}{j}\n", "\\newcommand{\\latentScalar}{z}\n", "\\newcommand{\\latentVector}{\\mathbf{ \\latentScalar}}\n", "\\newcommand{\\latentMatrix}{\\mathbf{Z}}\n", "\\newcommand{\\learnRate}{\\eta}\n", "\\newcommand{\\lengthScale}{\\ell}\n", "\\newcommand{\\rbfWidth}{\\ell}\n", "\\newcommand{\\likelihoodBound}{\\mathcal{L}}\n", "\\newcommand{\\likelihoodFunction}{L}\n", "\\newcommand{\\locationScalar}{\\mu}\n", "\\newcommand{\\locationVector}{\\boldsymbol{ \\locationScalar}}\n", "\\newcommand{\\locationMatrix}{\\mathbf{M}}\n", "\\newcommand{\\variance}{\\text{var}\\left( #1 \\right)}\n", "\\newcommand{\\mappingFunction}{f}\n", "\\newcommand{\\mappingFunctionMatrix}{\\mathbf{F}}\n", "\\newcommand{\\mappingFunctionTwo}{g}\n", "\\newcommand{\\mappingFunctionTwoMatrix}{\\mathbf{G}}\n", "\\newcommand{\\mappingFunctionTwoVector}{\\mathbf{ \\mappingFunctionTwo}}\n", "\\newcommand{\\mappingFunctionVector}{\\mathbf{ \\mappingFunction}}\n", "\\newcommand{\\scaleScalar}{s}\n", "\\newcommand{\\mappingScalar}{w}\n", "\\newcommand{\\mappingVector}{\\mathbf{ \\mappingScalar}}\n", "\\newcommand{\\mappingMatrix}{\\mathbf{W}}\n", "\\newcommand{\\mappingScalarTwo}{v}\n", "\\newcommand{\\mappingVectorTwo}{\\mathbf{ \\mappingScalarTwo}}\n", "\\newcommand{\\mappingMatrixTwo}{\\mathbf{V}}\n", "\\newcommand{\\maxIters}{K}\n", "\\newcommand{\\meanMatrix}{\\mathbf{M}}\n", "\\newcommand{\\meanScalar}{\\mu}\n", "\\newcommand{\\meanTwoMatrix}{\\mathbf{M}}\n", "\\newcommand{\\meanTwoScalar}{m}\n", "\\newcommand{\\meanTwoVector}{\\mathbf{ \\meanTwoScalar}}\n", "\\newcommand{\\meanVector}{\\boldsymbol{ \\meanScalar}}\n", "\\newcommand{\\mrnaConcentration}{m}\n", "\\newcommand{\\naturalFrequency}{\\omega}\n", "\\newcommand{\\neighborhood}{\\mathcal{N}\\left( #1 \\right)}\n", "\\newcommand{\\neilurl}{http://inverseprobability.com/}\n", "\\newcommand{\\noiseMatrix}{\\boldsymbol{ E}}\n", "\\newcommand{\\noiseScalar}{\\epsilon}\n", "\\newcommand{\\noiseVector}{\\boldsymbol{ \\epsilon}}\n", "\\newcommand{\\norm}{\\left\\Vert #1 \\right\\Vert}\n", "\\newcommand{\\normalizedLaplacianMatrix}{\\hat{\\mathbf{L}}}\n", "\\newcommand{\\normalizedLaplacianScalar}{\\hat{\\ell}}\n", "\\newcommand{\\normalizedLaplacianVector}{\\hat{\\mathbf{ \\ell}}}\n", "\\newcommand{\\numActive}{m}\n", "\\newcommand{\\numBasisFunc}{m}\n", "\\newcommand{\\numComponents}{m}\n", "\\newcommand{\\numComps}{K}\n", "\\newcommand{\\numData}{n}\n", "\\newcommand{\\numFeatures}{K}\n", "\\newcommand{\\numHidden}{h}\n", "\\newcommand{\\numInducing}{m}\n", "\\newcommand{\\numLayers}{\\ell}\n", "\\newcommand{\\numNeighbors}{K}\n", "\\newcommand{\\numSequences}{s}\n", "\\newcommand{\\numSuccess}{s}\n", "\\newcommand{\\numTasks}{m}\n", "\\newcommand{\\numTime}{T}\n", "\\newcommand{\\numTrials}{S}\n", "\\newcommand{\\outputIndex}{j}\n", "\\newcommand{\\paramVector}{\\boldsymbol{ \\theta}}\n", "\\newcommand{\\parameterMatrix}{\\boldsymbol{ \\Theta}}\n", "\\newcommand{\\parameterScalar}{\\theta}\n", "\\newcommand{\\parameterVector}{\\boldsymbol{ \\parameterScalar}}\n", "\\newcommand{\\partDiff}{\\frac{\\partial#1}{\\partial#2}}\n", "\\newcommand{\\precisionScalar}{j}\n", "\\newcommand{\\precisionVector}{\\mathbf{ \\precisionScalar}}\n", "\\newcommand{\\precisionMatrix}{\\mathbf{J}}\n", "\\newcommand{\\pseudotargetScalar}{\\widetilde{y}}\n", "\\newcommand{\\pseudotargetVector}{\\mathbf{ \\pseudotargetScalar}}\n", "\\newcommand{\\pseudotargetMatrix}{\\mathbf{ \\widetilde{Y}}}\n", "\\newcommand{\\rank}{\\text{rank}\\left(#1\\right)}\n", "\\newcommand{\\rayleighDist}{\\mathcal{R}\\left(#1|#2\\right)}\n", "\\newcommand{\\rayleighSamp}{\\mathcal{R}\\left(#1\\right)}\n", "\\newcommand{\\responsibility}{r}\n", "\\newcommand{\\rotationScalar}{r}\n", "\\newcommand{\\rotationVector}{\\mathbf{ \\rotationScalar}}\n", "\\newcommand{\\rotationMatrix}{\\mathbf{R}}\n", "\\newcommand{\\sampleCovScalar}{s}\n", "\\newcommand{\\sampleCovVector}{\\mathbf{ \\sampleCovScalar}}\n", "\\newcommand{\\sampleCovMatrix}{\\mathbf{s}}\n", "\\newcommand{\\scalarProduct}{\\left\\langle{#1},{#2}\\right\\rangle}\n", "\\newcommand{\\sign}{\\text{sign}\\left(#1\\right)}\n", "\\newcommand{\\sigmoid}{\\sigma\\left(#1\\right)}\n", "\\newcommand{\\singularvalue}{\\ell}\n", "\\newcommand{\\singularvalueMatrix}{\\mathbf{L}}\n", "\\newcommand{\\singularvalueVector}{\\mathbf{l}}\n", "\\newcommand{\\sorth}{\\mathbf{u}}\n", "\\newcommand{\\spar}{\\lambda}\n", "\\newcommand{\\trace}{\\text{tr}\\left(#1\\right)}\n", "\\newcommand{\\BasalRate}{B}\n", "\\newcommand{\\DampingCoefficient}{C}\n", "\\newcommand{\\DecayRate}{D}\n", "\\newcommand{\\Displacement}{X}\n", "\\newcommand{\\LatentForce}{F}\n", "\\newcommand{\\Mass}{M}\n", "\\newcommand{\\Sensitivity}{S}\n", "\\newcommand{\\basalRate}{b}\n", "\\newcommand{\\dampingCoefficient}{c}\n", "\\newcommand{\\mass}{m}\n", "\\newcommand{\\sensitivity}{s}\n", "\\newcommand{\\springScalar}{\\kappa}\n", "\\newcommand{\\springVector}{\\boldsymbol{ \\kappa}}\n", "\\newcommand{\\springMatrix}{\\boldsymbol{ \\mathcal{K}}}\n", "\\newcommand{\\tfConcentration}{p}\n", "\\newcommand{\\tfDecayRate}{\\delta}\n", "\\newcommand{\\tfMrnaConcentration}{f}\n", "\\newcommand{\\tfVector}{\\mathbf{ \\tfConcentration}}\n", "\\newcommand{\\velocity}{v}\n", "\\newcommand{\\sufficientStatsScalar}{g}\n", "\\newcommand{\\sufficientStatsVector}{\\mathbf{ \\sufficientStatsScalar}}\n", "\\newcommand{\\sufficientStatsMatrix}{\\mathbf{G}}\n", "\\newcommand{\\switchScalar}{s}\n", "\\newcommand{\\switchVector}{\\mathbf{ \\switchScalar}}\n", "\\newcommand{\\switchMatrix}{\\mathbf{S}}\n", "\\newcommand{\\tr}{\\text{tr}\\left(#1\\right)}\n", "\\newcommand{\\loneNorm}{\\left\\Vert #1 \\right\\Vert_1}\n", "\\newcommand{\\ltwoNorm}{\\left\\Vert #1 \\right\\Vert_2}\n", "\\newcommand{\\onenorm}{\\left\\vert#1\\right\\vert_1}\n", "\\newcommand{\\twonorm}{\\left\\Vert #1 \\right\\Vert}\n", "\\newcommand{\\vScalar}{v}\n", "\\newcommand{\\vVector}{\\mathbf{v}}\n", "\\newcommand{\\vMatrix}{\\mathbf{V}}\n", "\\newcommand{\\varianceDist}{\\text{var}_{#2}\\left( #1 \\right)}\n", "% Already defined by latex\n", "%\\newcommand{\\vec}{#1:}\n", "\\newcommand{\\vecb}{\\left(#1\\right):}\n", "\\newcommand{\\weightScalar}{w}\n", "\\newcommand{\\weightVector}{\\mathbf{ \\weightScalar}}\n", "\\newcommand{\\weightMatrix}{\\mathbf{W}}\n", "\\newcommand{\\weightedAdjacencyMatrix}{\\mathbf{A}}\n", "\\newcommand{\\weightedAdjacencyScalar}{a}\n", "\\newcommand{\\weightedAdjacencyVector}{\\mathbf{ \\weightedAdjacencyScalar}}\n", "\\newcommand{\\onesVector}{\\mathbf{1}}\n", "\\newcommand{\\zerosVector}{\\mathbf{0}}\n", "$$\n", "\n", "## What is Machine Learning?\n", "\n", "What is machine learning? At its most basic level machine learning is a\n", "combination of\n", "\n", "$$\\text{data} + \\text{model} \\xrightarrow{\\text{compute}} \\text{prediction}$$\n", "\n", "where *data* is our observations. They can be actively or passively\n", "acquired (meta-data). The *model* contains our assumptions, based on\n", "previous experience. That experience can be other data, it can come from\n", "transfer learning, or it can merely be our beliefs about the\n", "regularities of the universe. In humans our models include our inductive\n", "biases. The *prediction* is an action to be taken or a categorization or\n", "a quality score. The reason that machine learning has become a mainstay\n", "of artificial intelligence is the importance of predictions in\n", "artificial intelligence. The data and the model are combined through\n", "computation.\n", "\n", "In practice we normally perform machine learning using two functions. To\n", "combine data with a model we typically make use of:\n", "\n", "**a prediction function** a function which is used to make the\n", "predictions. It includes our beliefs about the regularities of the\n", "universe, our assumptions about how the world works, e.g. smoothness,\n", "spatial similarities, temporal similarities.\n", "\n", "**an objective function** a function which defines the cost of\n", "misprediction. Typically it includes knowledge about the world's\n", "generating processes (probabilistic objectives) or the costs we pay for\n", "mispredictions (empiricial risk minimization).\n", "\n", "The combination of data and model through the prediction function and\n", "the objectie function leads to a *learning algorithm*. The class of\n", "prediction functions and objective functions we can make use of is\n", "restricted by the algorithms they lead to. If the prediction function or\n", "the objective function are too complex, then it can be difficult to find\n", "an appropriate learning algorithm. Much of the acdemic field of machine\n", "learning is the quest for new learning algorithms that allow us to bring\n", "different types of models and data together.\n", "\n", "A useful reference for state of the art in machine learning is the UK\n", "Royal Society Report, [Machine Learning: Power and Promise of Computers\n", "that Learn by\n", "Example](https://royalsociety.org/~/media/policy/projects/machine-learning/publications/machine-learning-report.pdf).\n", "\n", "You can also check my blog post on [\"What is Machine\n", "Learning?\"](http://inverseprobability.com/2017/07/17/what-is-machine-learning)\n", "\n", "### Artificial Intelligence\n", "\n", "### Uncertainty\n", "\n", "In practice, we normally also have uncertainty associated with these\n", "functions. Uncertainty in the prediction function arises from\n", "\n", "1. scarcity of training data and\n", "2. mismatch between the set of prediction functions we choose and all\n", " possible prediction functions.\n", "\n", "There are also challenges around specification of the objective\n", "function, but for we will save those for another day. For the moment,\n", "let us focus on the prediction function.\n", "\n", "### Neural Networks and Prediction Functions\n", "\n", "Neural networks are adaptive non-linear function models. Originally,\n", "they were studied (by McCulloch and Pitts [@McCulloch:neuron43]) as\n", "simple models for neurons, but over the last decade they have become\n", "popular because they are a flexible approach to modelling complex data.\n", "A particular characteristic of neural network models is that they can be\n", "composed to form highly complex functions which encode many of our\n", "expectations of the real world. They allow us to encode our assumptions\n", "about how the world works.\n", "\n", "We will return to composition later, but for the moment, let's focus on\n", "a one hidden layer neural network. We are interested in the prediction\n", "function, so we'll ignore the objective function (which is often called\n", "an error function) for the moment, and just describe the mathematical\n", "object of interest\n", "\n", "$$\n", "\\mappingFunction(\\inputVector) = \\mappingMatrix^\\top \\activationVector(\\mappingMatrixTwo, \\inputVector)\n", "$$\n", "\n", "Where in this case $\\mappingFunction(\\cdot)$ is a scalar function with\n", "vector inputs, and $\\activationVector(\\cdot)$ is a vector function with\n", "vector inputs. The dimensionality of the vector function is known as the\n", "number of hidden units, or the number of neurons. The elements of this\n", "vector function are known as the *activation* function of the neural\n", "network and $\\mappingMatrixTwo$ are the parameters of the activation\n", "functions.\n", "\n", "### Relations with Classical Statistics\n", "\n", "In statistics activation functions are traditionally known as *basis\n", "functions*. And we would think of this as a *linear model*. It's doesn't\n", "make linear predictions, but it's linear because in statistics\n", "estimation focuses on the parameters, $\\mappingMatrix$, not the\n", "parameters, $\\mappingMatrixTwo$. The linear model terminology refers to\n", "the fact that the model is *linear in the parameters*, but it is *not*\n", "linear in the data unless the activation functions are chosen to be\n", "linear.\n", "\n", "### Adaptive Basis Functions\n", "\n", "The first difference in the (early) neural network literature to the\n", "classical statistical literature is the decision to optimize these\n", "parameters, $\\mappingMatrixTwo$, as well as the parameters,\n", "$\\mappingMatrix$ (which would normally be denoted in statistics by\n", "$\\boldsymbol{\\beta}$)[^1].\n", "\n", "In this tutorial, we're going to go revisit that decision, and follow\n", "the path of Radford Neal [@Neal:bayesian94] who, inspired by work of\n", "David MacKay [@MacKay:bayesian92] and others did his PhD thesis on\n", "Bayesian Neural Networks. If we take a Bayesian approach to parameter\n", "inference (note I am using inference here in the classical sense, not in\n", "the sense of prediction of test data, which seems to be a newer usage),\n", "then we don't wish to fit parameters at all, rather we wish to integrate\n", "them away and understand the family of functions that the model\n", "describes.\n", "\n", "### Probabilistic Modelling\n", "\n", "This Bayesian approach is designed to deal with uncertainty arising from\n", "fitting our prediction function to the data we have, a reduced data set.\n", "\n", "The Bayesian approach can be derived from a broader understanding of\n", "what our objective is. If we accept that we can jointly represent all\n", "things that happen in the world with a probability distribution, then we\n", "can interogate that probability to make predictions. So, if we are\n", "interested in predictions, $\\dataScalar_*$ at future points input\n", "locations of interest, $\\inputVector_*$ given previously training data,\n", "$\\dataVector$ and corresponding inputs, $\\inputMatrix$, then we are\n", "really interogating the following probability density, $$\n", "p(\\dataScalar_*|\\dataVector, \\inputMatrix, \\inputVector_*),\n", "$$ there is nothing controversial here, as long as you accept that you\n", "have a good joint model of the world around you that relates test data\n", "to training data,\n", "$p(\\dataScalar_*, \\dataVector, \\inputMatrix, \\inputVector_*)$ then this\n", "conditional distribution can be recovered through standard rules of\n", "probability\n", "($\\text{data} + \\text{model} \\rightarrow \\text{prediction}$).\n", "\n", "We can construct this joint density through the use of the following\n", "decomposition: $$\n", "p(\\dataScalar_*|\\dataVector, \\inputMatrix, \\inputVector_*) = \\int p(\\dataScalar_*|\\inputVector_*, \\mappingMatrix) p(\\mappingMatrix | \\dataVector, \\inputMatrix) \\text{d} \\mappingMatrix\n", "$$\n", "\n", "where, for convenience, we are assuming *all* the parameters of the\n", "model are now represented by $\\parameterVector$ (which contains\n", "$\\mappingMatrix$ and $\\mappingMatrixTwo$) and\n", "$p(\\parameterVector | \\dataVector, \\inputMatrix)$ is recognised as the\n", "posterior density of the parameters given data and\n", "$p(\\dataScalar_*|\\inputVector_*, \\parameterVector)$ is the *likelihood*\n", "of an individual test data point given the parameters.\n", "\n", "The likelihood of the data is normally assumed to be independent across\n", "the parameters, $$\n", "p(\\dataVector|\\inputMatrix, \\mappingMatrix) \\prod_{i=1}^\\numData p(\\dataScalar_i|\\inputVector_i, \\mappingMatrix),$$\n", "\n", "and if that is so, it is easy to extend our predictions across all\n", "future, potential, locations, $$\n", "p(\\dataVector_*|\\dataVector, \\inputMatrix, \\inputMatrix_*) = \\int p(\\dataVector_*|\\inputMatrix_*, \\parameterVector) p(\\parameterVector | \\dataVector, \\inputMatrix) \\text{d} \\parameterVector.\n", "$$\n", "\n", "The likelihood is also where the *prediction function* is incorporated.\n", "For example in the regression case, we consider an objective based\n", "around the Gaussian density, $$\n", "p(\\dataScalar_i | \\mappingFunction(\\inputVector_i)) = \\frac{1}{\\sqrt{2\\pi \\dataStd^2}} \\exp\\left(-\\frac{\\left(\\dataScalar_i - \\mappingFunction(\\inputVector_i)\\right)^2}{2\\dataStd^2}\\right)\n", "$$\n", "\n", "In short, that is the classical approach to probabilistic inference, and\n", "all approaches to Bayesian neural networks fall within this path. For a\n", "deep probabilistic model, we can simply take this one stage further and\n", "place a probability distribution over the input locations, $$\n", "p(\\dataVector_*|\\dataVector) = \\int p(\\dataVector_*|\\inputMatrix_*, \\parameterVector) p(\\parameterVector | \\dataVector, \\inputMatrix) p(\\inputMatrix) p(\\inputMatrix_*) \\text{d} \\parameterVector \\text{d} \\inputMatrix \\text{d}\\inputMatrix_*\n", "$$ and we have *unsupervised learning* (from where we can get deep\n", "generative models).\n", "\n", "### Graphical Models\n", "\n", "One way of representing a joint distribution is to consider conditional\n", "dependencies between data. Conditional dependencies allow us to\n", "factorize the distribution. For example, a Markov chain is a\n", "factorization of a distribution into components that represent the\n", "conditional relationships between points that are neighboring, often in\n", "time or space. It can be decomposed in the following form.\n", "$$p(\\dataVector) = p(\\dataScalar_\\numData | \\dataScalar_{\\numData-1}) p(\\dataScalar_{\\numData-1}|\\dataScalar_{\\numData-2}) \\dots p(\\dataScalar_{2} | \\dataScalar_{1})$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import daft\n", "from matplotlib import rc\n", "\n", "rc(\"font\", **{'family':'sans-serif','sans-serif':['Helvetica']}, size=30)\n", "rc(\"text\", usetex=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pgm = daft.PGM(shape=[3, 1],\n", " origin=[0, 0], \n", " grid_unit=5, \n", " node_unit=1.9, \n", " observed_style='shaded',\n", " line_width=3)\n", "\n", "\n", "pgm.add_node(daft.Node(\"y_1\", r\"$y_1$\", 0.5, 0.5, fixed=False))\n", "pgm.add_node(daft.Node(\"y_2\", r\"$y_2$\", 1.5, 0.5, fixed=False))\n", "pgm.add_node(daft.Node(\"y_3\", r\"$y_3$\", 2.5, 0.5, fixed=False))\n", "pgm.add_edge(\"y_1\", \"y_2\")\n", "pgm.add_edge(\"y_2\", \"y_3\")\n", "\n", "pgm.render().figure.savefig(\"../slides/diagrams/ml/markov.svg\", transparent=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "\n", "By specifying conditional independencies we can reduce the\n", "parameterization required for our data, instead of directly specifying\n", "the parameters of the joint distribution, we can specify each set of\n", "parameters of the conditonal independently. This can also give an\n", "advantage in terms of interpretability. Understanding a conditional\n", "independence structure gives a structured understanding of data. If\n", "developed correctly, according to causal methodology, it can even inform\n", "how we should intervene in the system to drive a desired result\n", "[@Pearl:causality95].\n", "\n", "However, a challenge arise when the data becomes more complex. Consider\n", "the graphical model shown below, used to predict the perioperative risk\n", "of *C Difficile* infection following colon surgery\n", "[@Steele:predictive12].\n", "\n", " \n", "\n", "To capture the complexity in the interelationship between the data the\n", "graph becomes more complex, and less interpretable.\n", "\n", "### Performing Inference\n", "\n", "As far as combining our data and our model to form our prediction, the\n", "devil is in the detail. While everything is easy to write in terms of\n", "probability densities, as we move from $\\text{data}$ and $\\text{model}$\n", "to $\\text{prediction}$ there is that simple\n", "$\\xrightarrow{\\text{compute}}$ sign, which is now burying a wealth of\n", "difficulties. Each integral sign above is a high dimensional integral\n", "which will typically need approximation. Approximations also come with\n", "computational demands. As we consider more complex classes of functions,\n", "the challenges around the integrals become harder and prediction of\n", "future test data given our model and the data becomes so involved as to\n", "be impractical or impossible.\n", "\n", "Statisticians realized these challenges early on, indeed, so early that\n", "they were actually physicists, both Laplace and Gauss worked on models\n", "such as this, in Gauss's case he made his career on prediction of the\n", "location of the lost planet (later reclassified as a asteroid, then\n", "dwarf planet), Ceres. Gauss and Laplace made use of maximum a posteriori\n", "estimates for simplifying their computations and Laplace developed\n", "Laplace's method (and invented the Gaussian density) to expand around\n", "that mode. But classical statistics needs better guarantees around model\n", "performance and interpretation, and as a result has focussed more on the\n", "*linear* model implied by $$\n", " \\mappingFunction(\\inputVector) = \\left.\\mappingVector^{(2)}\\right.^\\top \\activationVector(\\mappingMatrix_1, \\inputVector)\n", "$$\n", "\n", "$$\n", " \\mappingVector^{(2)} \\sim \\gaussianSamp{\\zerosVector}{\\covarianceMatrix}.\n", "$$\n", "\n", "The Gaussian likelihood given above implies that the data observation is\n", "related to the function by noise corruption so we have, $$\n", " \\dataScalar_i = \\mappingFunction(\\inputVector_i) + \\noiseScalar_i,\n", "$$ where $$\n", " \\noiseScalar_i \\sim \\gaussianSamp{0}{\\dataStd^2}\n", "$$ and while normally integrating over high dimensional parameter\n", "vectors is highly complex, here it is *trivial*. That is because of a\n", "property of the multivariate Gaussian.\n", "\n", "### Multivariate Gaussian Properties\n", "\n", "Gaussian processes are initially of interest because\n", "\n", "1. linear Gaussian models are easier to deal with\n", "2. Even the parameters *within* the process can be handled, by\n", " considering a particular limit.\n", "\n", "Let's first of all review the properties of the multivariate Gaussian\n", "distribution that make linear Gaussian models easier to deal with. We'll\n", "return to the, perhaps surprising, result on the parameters within the\n", "nonlinearity, $\\parameterVector$, shortly.\n", "\n", "To work with linear Gaussian models, to find the marginal likelihood all\n", "you need to know is the following rules. If $$\n", "\\dataVector = \\mappingMatrix \\inputVector + \\noiseVector,\n", "$$ where $\\dataVector$, $\\inputVector$ and $\\noiseVector$ are vectors\n", "and we assume that $\\inputVector$ and $\\noiseVector$ are drawn from\n", "multivariate Gaussians, \\begin{align}\n", "\\inputVector & \\sim \\gaussianSamp{\\meanVector}{\\covarianceMatrix}\\\\\n", "\\noiseVector & \\sim \\gaussianSamp{\\zerosVector}{\\covarianceMatrixTwo}\n", "\\end{align} then we know that $\\dataVector$ is also drawn from a\n", "multivariate Gaussian with, $$\n", "\\dataVector \\sim \\gaussianSamp{\\mappingMatrix\\meanVector}{\\mappingMatrix\\covarianceMatrix\\mappingMatrix^\\top + \\covarianceMatrixTwo}.\n", "$$ With apprioriately defined covariance, $\\covarianceTwoMatrix$, this\n", "is actually the marginal likelihood for Factor Analysis, or\n", "Probabilistic Principal Component Analysis [@Tipping:probpca99], because\n", "we integrated out the inputs (or *latent* variables they would be called\n", "in that case).\n", "\n", "However, we are focussing on what happens in models which are non-linear\n", "in the inputs, whereas the above would be *linear* in the inputs. To\n", "consider these, we introduce a matrix, called the design matrix. We set\n", "each activation function computed at each data point to be $$\n", "\\activationScalar_{i,j} = \\activationScalar(\\mappingVector^{(1)}_{j}, \\inputVector_{i})\n", "$$ and define the matrix of activations (known as the *design matrix* in\n", "statistics) to be, $$\n", "\\activationMatrix = \n", "\\begin{bmatrix}\n", "\\activationScalar_{1, 1} & \\activationScalar_{1, 2} & \\dots & \\activationScalar_{1, \\numHidden} \\\\\n", "\\activationScalar_{1, 2} & \\activationScalar_{1, 2} & \\dots & \\activationScalar_{1, \\numData} \\\\\n", "\\vdots & \\vdots & \\ddots & \\vdots \\\\\n", "\\activationScalar_{\\numData, 1} & \\activationScalar_{\\numData, 2} & \\dots & \\activationScalar_{\\numData, \\numHidden}\n", "\\end{bmatrix}.\n", "$$ By convention this matrix always has $\\numData$ rows and $\\numHidden$\n", "columns, now if we define the vector of all noise corruptions,\n", "$\\noiseVector = \\left[\\noiseScalar_1, \\dots \\noiseScalar_\\numData\\right]^\\top$.\n", "\n", "### Matrix Representation of a Neural Network\n", "\n", "$$\\dataScalar\\left(\\inputVector\\right) = \\activationVector\\left(\\inputVector\\right)^\\top \\mappingVector + \\noiseScalar$$\n", "\n", ". . .\n", "\n", "$$\\dataVector = \\activationMatrix\\mappingVector + \\noiseVector$$\n", "\n", ". . .\n", "\n", "$$\\noiseVector \\sim \\gaussianSamp{\\zerosVector}{\\dataStd^2\\eye}$$\n", "\n", "{ If we define the prior distribution over the vector $\\mappingVector$\n", "to be Gaussian,} $$\n", "\\mappingVector \\sim \\gaussianSamp{\\zerosVector}{\\alpha\\eye},\n", "$$\n", "\n", "{ then we can use rules of multivariate Gaussians to see that,} $$\n", "\\dataVector \\sim \\gaussianSamp{\\zerosVector}{\\alpha \\activationMatrix \\activationMatrix^\\top + \\dataStd^2 \\eye}.\n", "$$\n", "\n", "In other words, our training data is distributed as a multivariate\n", "Gaussian, with zero mean and a covariance given by $$\n", "\\kernelMatrix = \\alpha \\activationMatrix \\activationMatrix^\\top + \\dataStd^2 \\eye.\n", "$$\n", "\n", "This is an $\\numData \\times \\numData$ size matrix. Its elements are in\n", "the form of a function. The maths shows that any element, index by $i$\n", "and $j$, is a function *only* of inputs associated with data points $i$\n", "and $j$, $\\dataVector_i$, $\\dataVector_j$.\n", "$\\kernel_{i,j} = \\kernel\\left(\\inputVector_i, \\inputVector_j\\right)$\n", "\n", "If we look at the portion of this function associated only with\n", "$\\mappingFunction(\\cdot)$, i.e. we remove the noise, then we can write\n", "down the covariance associated with our neural network, $$\n", "\\kernel_\\mappingFunction\\left(\\inputVector_i, \\inputVector_j\\right) = \\alpha \\activationVector\\left(\\mappingMatrix_1, \\inputVector_i\\right)^\\top \\activationVector\\left(\\mappingMatrix_1, \\inputVector_j\\right)\n", "$$ so the elements of the covariance or *kernel* matrix are formed by\n", "inner products of the rows of the *design matrix*.\n", "\n", "This is the essence of a Gaussian process. Instead of making assumptions\n", "about our density over each data point, $\\dataScalar_i$ as i.i.d. we\n", "make a joint Gaussian assumption over our data. The covariance matrix is\n", "now a function of both the parameters of the activation function,\n", "$\\mappingMatrixTwo$, and the input variables, $\\inputMatrix$. This comes\n", "about through integrating out the parameters of the model,\n", "$\\mappingVector$.\n", "\n", "We can basically put anything inside the basis functions, and many\n", "people do. These can be deep kernels [@Cho:deep09] or we can learn the\n", "parameters of a convolutional neural network inside there.\n", "\n", "Viewing a neural network in this way is also what allows us to beform\n", "sensible *batch* normalizations [@Ioffe:batch15].\n", "\n", "### Non-degenerate Gaussian Processes\n", "\n", "The process described above is degenerate. The covariance function is of\n", "rank at most $\\numHidden$ and since the theoretical amount of data could\n", "always increase $\\numData \\rightarrow \\infty$, the covariance function\n", "is not full rank. This means as we increase the amount of data to\n", "infinity, there will come a point where we can't normalize the process\n", "because the multivariate Gaussian has the form, $$\n", "\\gaussianDist{\\mappingFunctionVector}{\\zerosVector}{\\kernelMatrix} = \\frac{1}{\\left(2\\pi\\right)^{\\frac{\\numData}{2}}\\det{\\kernelMatrix}^\\frac{1}{2}} \\exp\\left(-\\frac{\\mappingFunctionVector^\\top\\kernelMatrix \\mappingFunctionVector}{2}\\right)\n", "$$ and a non-degenerate kernel matrix leads to $\\det{\\kernelMatrix} = 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", "univariate Gaussian is zero, i.e. there is a null space on the\n", "covariance, or alternatively you can imagine there are one or more\n", "directions where the Gaussian has become the delta function).\n", "\n", "In the machine learning field, it was Radford Neal [@Neal:bayesian94]\n", "that 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. $\\numHidden \\rightarrow \\infty$.\n", "\n", "[ ](http://www.cs.toronto.edu/~radford/ftp/thesis.pdf)\n", "\n", "*Page 37 of Radford Neal's 1994 thesis*\n", "\n", "In loose terms, what Radford considers is what happens to the elements\n", "of the covariance function, \n", " \\begin{align*}\n", " \\kernel_\\mappingFunction\\left(\\inputVector_i, \\inputVector_j\\right) & = \\alpha \\activationVector\\left(\\mappingMatrix_1, \\inputVector_i\\right)^\\top \\activationVector\\left(\\mappingMatrix_1, \\inputVector_j\\right)\\\\\n", " & = \\alpha \\sum_k \\activationScalar\\left(\\mappingVector^{(1)}_k, \\inputVector_i\\right) \\activationScalar\\left(\\mappingVector^{(1)}_k, \\inputVector_j\\right)\n", " \\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(\\mappingVectorTwo)$, for each one, $$\n", "\\kernel_\\mappingFunction\\left(\\inputVector_i, \\inputVector_j\\right) = \\alpha \\int \\activationScalar\\left(\\mappingVector^{(1)}, \\inputVector_i\\right) \\activationScalar\\left(\\mappingVector^{(1)}, \\inputVector_j\\right) p(\\mappingVector^{(1)}) \\text{d}\\mappingVector^{(1)}\n", "$$ And that's not *only* for Gaussian $p(\\mappingVectorTwo)$. In fact\n", "this result holds for a range of activations, and a range of prior\n", "densities because of the *central limit theorem*.\n", "\n", "To write it in the form of a probabilistic program, as long as the\n", "distribution for $\\phi_i$ implied by this short probabilistic program,\n", "\n", " \\begin{align*}\n", " \\mappingVectorTwo & \\sim p(\\cdot)\\\\\n", " \\phi_i & = \\activationScalar\\left(\\mappingVectorTwo, \\inputVector_i\\right), \n", " \\end{align*}\n", " has finite variance, then the result of taking the number of hidden\n", "units to infinity, with appropriate scaling, is also a Gaussian process.\n", "\n", "### Further Reading\n", "\n", "To understand this argument in more detail, I highly recommend reading\n", "chapter 2 of Neal's thesis, which remains easy to read and clear today.\n", "Indeed, for readers interested in Bayesian neural networks, both Raford\n", "Neal's and David MacKay's PhD thesis [@MacKay:bayesian92] remain\n", "essential reading. Both theses embody a clarity of thought, and an\n", "ability to weave together threads from different fields that was the\n", "business of machine learning in the 1990s. Radford and David were also\n", "pioneers in making their software widely available and publishing\n", "material on the web." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import teaching_plots as plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load -s compute_kernel mlai.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load -s eq_cov mlai.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(10)\n", "plot.rejection_samples(compute_kernel, kernel=eq_cov, \n", " lengthscale=0.25, diagrams='../slides/diagrams/gp')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pods\n", "pods.notebook.display_plots('gp_rejection_samples{sample:0>3}.svg', \n", " '../slides/diagrams/gp', sample=(1,5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "### Distributions over Functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "np.random.seed(4949)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot\n", "import pods" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Sampling a Function {#sampling-a-function data-transition=\"none\"}\n", "\n", "**Multi-variate Gaussians**\n", "\n", "- We will consider a Gaussian with a particular structure of\n", " covariance matrix.\n", "\n", "- Generate a single sample from this 25 dimensional Gaussian\n", " distribution,\n", " $\\mappingFunctionVector=\\left[\\mappingFunction_{1},\\mappingFunction_{2}\\dots \\mappingFunction_{25}\\right]$.\n", "\n", "- We will plot these points against their index." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load -s compute_kernel mlai.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load -s polynomial_cov mlai.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load -s exponentiated_quadratic mlai.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.two_point_sample(compute_kernel, kernel=exponentiated_quadratic, \n", " lengthscale=0.5, diagrams='../slides/diagrams/gp')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pods.notebook.display_plots('two_point_sample{sample:0>3}.svg', \n", " '../slides/diagrams/gp', sample=(0,13))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Uluru\n", "\n", " \n", "\n", "### Prediction with Correlated Gaussians\n", "\n", "- Prediction of $\\mappingFunction_2$ from $\\mappingFunction_1$\n", " requires *conditional density*.\n", "\n", "- Conditional density is *also* Gaussian. $$\n", " p(\\mappingFunction_2|\\mappingFunction_1) = \\gaussianDist{\\mappingFunction_2}{\\frac{\\kernelScalar_{1, 2}}{\\kernelScalar_{1, 1}}\\mappingFunction_1}{ \\kernelScalar_{2, 2} - \\frac{\\kernelScalar_{1,2}^2}{\\kernelScalar_{1,1}}}\n", "$$ where covariance of joint density is given by $$\n", " \\kernelMatrix = \\begin{bmatrix} \\kernelScalar_{1, 1} & \\kernelScalar_{1, 2}\\\\ \\kernelScalar_{2, 1} & \\kernelScalar_{2, 2}\\end{bmatrix}\n", "$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pods.notebook.display_plots('two_point_sample{sample:0>3}.svg', \n", " '../slides/diagrams/gp', sample=(13,17))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Key Object\n", "\n", "- Covariance function, $\\kernelMatrix$\n", "\n", "- Determines properties of samples.\n", "\n", "- Function of $\\inputMatrix$,\n", " $$\\kernelScalar_{i,j} = \\kernelScalar(\\inputVector_i, \\inputVector_j)$$\n", "\n", "### Linear Algebra\n", "\n", "- Posterior mean" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "$$\\mappingFunction_D(\\inputVector_*) = \\kernelVector(\\inputVector_*, \\inputMatrix) \\kernelMatrix^{-1}\n", "\\mathbf{y}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Posterior covariance\n", " $$\\mathbf{C}_* = \\kernelMatrix_{*,*} - \\kernelMatrix_{*,\\mappingFunctionVector}\n", " \\kernelMatrix^{-1} \\kernelMatrix_{\\mappingFunctionVector, *}$$\n", "\n", "### Linear Algebra\n", "\n", "- Posterior mean" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "$$\\mappingFunction_D(\\inputVector_*) = \\kernelVector(\\inputVector_*, \\inputMatrix) \\boldsymbol{\\alpha}$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- Posterior covariance\n", " $$\\covarianceMatrix_* = \\kernelMatrix_{*,*} - \\kernelMatrix_{*,\\mappingFunctionVector}\n", " \\kernelMatrix^{-1} \\kernelMatrix_{\\mappingFunctionVector, *}$$\n", "\n", "### \n", "\n", " \n", "\n", "### \n", "\n", " \n", "\n", "### \n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load -s eq_cov mlai.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot\n", "import mlai\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "K, anim=plot.animate_covariance_function(mlai.compute_kernel, \n", " kernel=eq_cov, lengthscale=0.2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "HTML(anim.to_jshtml())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.save_animation(anim, \n", " diagrams='../slides/diagrams/kern', \n", " filename='eq_covariance.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Exponentiated Quadratic Covariance\n", "\n", "The exponentiated quadratic covariance, also known as the Gaussian\n", "covariance or the RBF covariance and the squared exponential. Covariance\n", "between two points is related to the negative exponential of the squared\n", "distnace between those points. This covariance function can be derived\n", "in a few different ways: as the infinite limit of a radial basis\n", "function neural network, as diffusion in the heat equation, as a\n", "Gaussian filter in *Fourier space* or as the composition as a series of\n", "linear filters applied to a base function.\n", "\n", "The covariance takes the following form, $$\n", "\\kernelScalar(\\inputVector, \\inputVector^\\prime) = \\alpha \\exp\\left(-\\frac{\\ltwoNorm{\\inputVector - \\inputVector^\\prime}^2}{2\\ell^2}\\right)\n", "$$ where $\\ell$ is the *length scale* or *time scale* of the process and\n", "$\\alpha$ represents the overall process variance.\n", "\n", "\n", "\n", "\n", "\n", "
 \n", " \n", " \n", "\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import pods\n", "import teaching_plots as plot\n", "import mlai" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Olympic Marathon 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. First we load in the data\n", "and plot." ] }, { "cell_type": "code", "execution_count": null, "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", "\n", "xlim = (1875,2030)\n", "ylim = (2.5, 6.5)\n", "yhat = (y-offset)/scale\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(figure=fig, filename='../slides/diagrams/datasets/olympic-marathon.svg', transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Olympic Marathon Data\n", "\n", "\n", "\n", "\n", "\n", "\n", "
 \n", "- Gold medal times for Olympic Marathon since 1896.\n", "\n", "- Marathons before 1924 didn’t have a standardised distance.\n", "\n", "- Present results using pace per km.\n", "\n", "- In 1904 Marathon was badly organised leading to very slow times.\n", "\n", " \n", "![image](../slides/diagrams/Stephen_Kiprotich.jpg) Image from\n", "Wikimedia Commons \n", "
\n", " \n", "\n", "Things to notice about the data include the outlier in 1904, in this\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.\n", "\n", "More recent years see more consistently quick marathons.\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)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m_full = GPy.models.GPRegression(x,yhat)\n", "_ = m_full.optimize() # Optimize parameters of covariance function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first command sets up the model, then" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m_full.optimize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "optimizes the parameters of the covariance function and the noise level\n", "of the model. Once the fit is complete, we'll try creating some test\n", "points, and computing the output of the GP model in terms of the mean\n", "and standard deviation of the posterior functions between 1870 and 2030.\n", "We plot the mean function and the standard deviation at 200 locations.\n", "We can obtain the predictions using" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y_mean, y_var = m_full.predict(xt)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xt = np.linspace(1870,2030,200)[:,np.newaxis]\n", "yt_mean, yt_var = m_full.predict(xt)\n", "yt_sd=np.sqrt(yt_var)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we plot the results using the helper function in teaching_plots." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_output(m_full, scale=scale, offset=offset, ax=ax, xlabel='year', ylabel='pace min/km', fontsize=20, portion=0.2)\n", "ax.set_xlim(xlim)\n", "ax.set_ylim(ylim)\n", "mlai.write_figure(figure=fig,\n", " filename='../slides/diagrams/gp/olympic-marathon-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "\n", "### Fit Quality\n", "\n", "In the fit we see that the error bars (coming mainly from the noise\n", "variance) are quite large. This is likely due to the outlier point in\n", "1904, ignoring that point we can see that a tighter fit is obtained. To\n", "see this making a version of the model, m_clean, where that point is\n", "removed." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x_clean=np.vstack((x[0:2, :], x[3:, :]))\n", "y_clean=np.vstack((y[0:2, :], y[3:, :]))\n", "\n", "m_clean = GPy.models.GPRegression(x_clean,y_clean)\n", "_ = m_clean.optimize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Data is fine for answering very specific questions, like \"Who won the\n", "Olympic Marathon in 2012?\", because we have that answer stored, however,\n", "we are not given the answer to many other questions. For example, Alan\n", "Turing was a formidable marathon runner, in 1946 he ran a time 2 hours\n", "46 minutes (just under four minutes per kilometer, faster than I and\n", "most of the other [Endcliffe Park\n", "Run](http://www.parkrun.org.uk/sheffieldhallam/) runners can do 5 km).\n", "What is the probability he would have won an Olympics if one had been\n", "held in 1946?\n", "\n", "\n", "\n", "\n", "\n", "\n", "
 \n", " \n", " \n", " \n", "
\n", "
\n", "*Alan Turing, in 1946 he was only 11 minutes slower than the winner of\n", "the 1948 games. Would he have won a hypothetical games held in 1946?\n", "Source: [Alan Turing Internet\n", "Scrapbook](http://www.turing.org.uk/scrapbook/run.html) *\n", "
\n", "### Basis Function Covariance\n", "\n", "The fixed basis function covariane just comes from the properties of a\n", "multivariate Gaussian, if we decide $$\n", "\\mappingFunctionVector=\\basisMatrix\\mappingVector\n", "$$ and then we assume $$\n", "\\mappingVector \\sim \\gaussianSamp{\\zerosVector}{\\alpha\\eye}\n", "$$ then it follows from the properties of a multivariate Gaussian that\n", "$$\n", "\\mappingFunctionVector \\sim \\gaussianSamp{\\zerosVector}{\\alpha\\basisMatrix\\basisMatrix^\\top}\n", "$$ meaning that the vector of observations from the function is jointly\n", "distributed as a Gaussian process and the covariance matrix is\n", "$\\kernelMatrix = \\alpha\\basisMatrix \\basisMatrix^\\top$, each element of\n", "the covariance matrix can then be found as the inner product between two\n", "rows of the basis funciton matrix. $$\n", "\\kernel(\\inputVector, \\inputVector^\\prime) = \\basisVector(\\inputVector)^\\top \\basisVector(\\inputVector^\\prime)\n", "$$\n", "\n", "\n", "\n", "\n", "\n", "\n", "
 \n", " \n", " \n", " \n", "
\n", "### Brownian Covariance" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load -s brownian_cov mlai.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot\n", "import mlai\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t=np.linspace(0, 2, 200)[:, np.newaxis]\n", "K, anim=plot.animate_covariance_function(mlai.compute_kernel, \n", " t, \n", " kernel=brownian_cov)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "HTML(anim.to_jshtml())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.save_animation(anim, \n", " diagrams='../slides/diagrams/kern', \n", " filename='brownian_covariance.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Brownian motion is also a Gaussian process. It follows a Gaussian random\n", "walk, with diffusion occuring at each time point driven by a Gaussian\n", "input. This implies it is both Markov and Gaussian. The covariane\n", "function for Brownian motion has the form $$\n", "\\kernelScalar(t, t^\\prime) = \\alpha \\min(t, t^\\prime)\n", "$$\n", "\n", "\n", "\n", "\n", "\n", "
 \n", " \n", " \n", "\n", "\n", "
\n", "
\n", "*The covariance of Brownian motion, and some samples from the covariance\n", "showing the functional form. *\n", "
\n", "### MLP Covariance" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load -s mlp_cov mlai.py" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot\n", "import mlai\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "K, anim=plot.animate_covariance_function(mlai.compute_kernel, \n", " kernel=mlp_cov, lengthscale=0.2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "HTML(anim.to_jshtml())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.save_animation(anim, \n", " diagrams='../slides/diagrams/kern', \n", " filename='mlp_covariance.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The multi-layer perceptron (MLP) covariance, also known as the neural\n", "network covariance or the arcsin covariance, is derived by considering\n", "the infinite limit of a neural network. $$\n", "\\kernelScalar(\\inputVector, \\inputVector^\\prime) = \\alpha \\arcsin\\left(\\frac{w \\inputVector^\\top \\inputVector^\\prime + b}{\\sqrt{\\left(w \\inputVector^\\top \\inputVector + b + 1\\right)\\left(w \\left.\\inputVector^\\prime\\right.^\\top \\inputVector^\\prime + b + 1\\right)}}\\right)\n", "$$\n", "\n", "\n", "\n", "\n", "\n", "\n", "
 \n", " \n", " \n", "\n", "\n", "
\n", "
\n", "*The multi-layer perceptron covariance function. This is derived by\n", "considering the infinite limit of a neural network with probit\n", "activation functions. *\n", "
\n", "
\n", "\n", " $=f\\Bigg($\n", " $\\Bigg)$\n", "\n", "
\n", "\n", "
\n", "*The cosmic microwave background is, to a very high degree of precision,\n", "a Gaussian process. The parameters of its covariance function are given\n", "by fundamental parameters of the universe, such as the amount of dark\n", "matter and mass. *\n", "
\n", "### Deep Gaussian Processes\n", "\n", "
\n", "*Image credit: Kai Arulkumaran *\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from IPython.display import display\n", "import GPy\n", "\n", "import mlai\n", "import teaching_plots as plot \n", "from gp_tutorial import gpplot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.random.seed(101)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### A Simple Regression Problem\n", "\n", "Here we set up a simple one dimensional regression problem. The input\n", "locations, $\\inputMatrix$, are in two separate clusters. The response\n", "variable, $\\dataVector$, is sampled from a Gaussian process with an\n", "exponentiated quadratic covariance." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "N = 50\n", "noise_var = 0.01\n", "X = np.zeros((50, 1))\n", "X[:25, :] = np.linspace(0,3,25)[:,None] # First cluster of inputs/covariates\n", "X[25:, :] = np.linspace(7,10,25)[:,None] # Second cluster of inputs/covariates\n", "\n", "# Sample response variables from a Gaussian process with exponentiated quadratic covariance.\n", "k = GPy.kern.RBF(1)\n", "y = np.random.multivariate_normal(np.zeros(N),k.K(X)+np.eye(N)*np.sqrt(noise_var)).reshape(-1,1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First we perform a full Gaussian process regression on the data. We\n", "create a GP model, m_full, and fit it to the data, plotting the\n", "resulting fit." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m_full = GPy.models.GPRegression(X,y)\n", "_ = m_full.optimize(messages=True) # Optimize parameters of covariance function" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_output(m_full, ax=ax, xlabel='$x$', ylabel='$y$', fontsize=20, portion=0.2)\n", "xlim = ax.get_xlim()\n", "ylim = ax.get_ylim()\n", "mlai.write_figure(figure=fig,\n", " filename='../slides/diagrams/gp/sparse-demo-full-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "*Full Gaussian process fitted to the data set. *\n", "
\n", "Now we set up the inducing variables, $\\mathbf{u}$. Each inducing\n", "variable has its own associated input index, $\\mathbf{Z}$, which lives\n", "in the same space as $\\inputMatrix$. Here we are using the true\n", "covariance function parameters to generate the fit." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "kern = GPy.kern.RBF(1)\n", "Z = np.hstack(\n", " (np.linspace(2.5,4.,3),\n", " np.linspace(7,8.5,3)))[:,None]\n", "m = GPy.models.SparseGPRegression(X,y,kernel=kern,Z=Z)\n", "m.noise_var = noise_var\n", "m.inducing_inputs.constrain_fixed()\n", "display(m)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_output(m, ax=ax, xlabel='$x$', ylabel='$y$', fontsize=20, portion=0.2, xlim=xlim, ylim=ylim)\n", "mlai.write_figure(figure=fig,\n", " filename='../slides/diagrams/gp/sparse-demo-constrained-inducing-6-unlearned-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "*Sparse Gaussian process fitted with six inducing variables, no\n", "optimization of parameters or inducing variables. *\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_ = m.optimize(messages=True)\n", "display(m)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_output(m, ax=ax, xlabel='$x$', ylabel='$y$', fontsize=20, portion=0.2, xlim=xlim, ylim=ylim)\n", "mlai.write_figure(figure=fig,\n", " filename='../slides/diagrams/gp/sparse-demo-full-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "*Gaussian process fitted with inducing variables fixed and parameters\n", "optimized *\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.randomize()\n", "m.inducing_inputs.unconstrain()\n", "_ = m.optimize(messages=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_output(m, ax=ax, xlabel='$x$', ylabel='$y$', fontsize=20, portion=0.2,xlim=xlim, ylim=ylim)\n", "mlai.write_figure(figure=fig,\n", " filename='../slides/diagrams/gp/sparse-demo-unconstrained-inducing-6-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "*Gaussian process fitted with location of inducing variables and\n", "parameters both optimized *\n", "
\n", "Now we will vary the number of inducing points used to form the\n", "approximation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.num_inducing=8\n", "m.randomize()\n", "M = 8\n", "m.set_Z(np.random.rand(M,1)*12)\n", "\n", "_ = m.optimize(messages=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_output(m, ax=ax, xlabel='$x$', ylabel='$y$', fontsize=20, portion=0.2, xlim=xlim, ylim=ylim)\n", "mlai.write_figure(figure=fig,\n", " filename='../slides/diagrams/gp/sparse-demo-sparse-inducing-8-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "*Comparison of the full Gaussian process fit with a sparse Gaussian\n", "process using eight inducing varibles. Both inducing variables and\n", "parameters are optimized. *\n", "
\n", "And we can compare the probability of the result to the full model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(m.log_likelihood(), m_full.log_likelihood())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Modern Review\n", "\n", "- *A Unifying Framework for Gaussian Process Pseudo-Point\n", " Approximations using Power Expectation Propagation*\n", " @Thang:unifying17\n", "\n", "- *Deep Gaussian Processes and Variational Propagation of Uncertainty*\n", " @Damianou:thesis2015" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.deep_nn(diagrams='../slides/diagrams/deepgp/')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "*A deep neural network. Input nodes are shown at the bottom. Each hidden\n", "layer is the result of applying an affine transformation to the previous\n", "layer and placing through an activation function. *\n", "
\n", "Mathematically, each layer of a neural network is given through\n", "computing the activation function, $\\basisFunction(\\cdot)$, contingent\n", "on the previous layer, or the inputs. In this way the activation\n", "functions, are composed to generate more complex interactions than would\n", "be possible with any single layer. \n", "\\begin{align}\n", " \\hiddenVector_{1} &= \\basisFunction\\left(\\mappingMatrix_1 \\inputVector\\right)\\\\\n", " \\hiddenVector_{2} &= \\basisFunction\\left(\\mappingMatrix_2\\hiddenVector_{1}\\right)\\\\\n", " \\hiddenVector_{3} &= \\basisFunction\\left(\\mappingMatrix_3 \\hiddenVector_{2}\\right)\\\\\n", " \\dataVector &= \\mappingVector_4 ^\\top\\hiddenVector_{3}\n", "\\end{align}\n", "\n", "\n", "### Overfitting\n", "\n", "One potential problem is that as the number of nodes in two adjacent\n", "layers increases, the number of parameters in the affine transformation\n", "between layers, $\\mappingMatrix$, increases. If there are $k_{i-1}$\n", "nodes in one layer, and $k_i$ nodes in the following, then that matrix\n", "contains $k_i k_{i-1}$ parameters, when we have layer widths in the\n", "1000s that leads to millions of parameters.\n", "\n", "One proposed solution is known as *dropout* where only a sub-set of the\n", "neural network is trained at each iteration. An alternative solution\n", "would be to reparameterize $\\mappingMatrix$ with its *singular value\n", "decomposition*. $$\n", " \\mappingMatrix = \\eigenvectorMatrix\\eigenvalueMatrix\\eigenvectwoMatrix^\\top\n", "$$ or $$\n", " \\mappingMatrix = \\eigenvectorMatrix\\eigenvectwoMatrix^\\top\n", "$$ where if $\\mappingMatrix \\in \\Re^{k_1\\times k_2}$ then\n", "$\\eigenvectorMatrix\\in \\Re^{k_1\\times q}$ and\n", "$\\eigenvectwoMatrix \\in \\Re^{k_2\\times q}$, i.e. we have a low rank\n", "matrix factorization for the weights." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.low_rank_approximation(diagrams='../slides/diagrams')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "
\n", "*Pictorial representation of the low rank form of the matrix\n", "$\\mappingMatrix$ *\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.deep_nn_bottleneck(diagrams='../slides/diagrams/deepgp')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Including the low rank decomposition of $\\mappingMatrix$ in the neural\n", "network, we obtain a new mathematical form. Effectively, we are adding\n", "additional *latent* layers, $\\latentVector$, in between each of the\n", "existing hidden layers. In a neural network these are sometimes known as\n", "*bottleneck* layers. The network can now be written mathematically as \n", "\\begin{align}\n", " \\latentVector_{1} &= \\eigenvectwoMatrix^\\top_1 \\inputVector\\\\\n", " \\hiddenVector_{1} &= \\basisFunction\\left(\\eigenvectorMatrix_1 \\latentVector_{1}\\right)\\\\\n", " \\latentVector_{2} &= \\eigenvectwoMatrix^\\top_2 \\hiddenVector_{1}\\\\\n", " \\hiddenVector_{2} &= \\basisFunction\\left(\\eigenvectorMatrix_2 \\latentVector_{2}\\right)\\\\\n", " \\latentVector_{3} &= \\eigenvectwoMatrix^\\top_3 \\hiddenVector_{2}\\\\\n", " \\hiddenVector_{3} &= \\basisFunction\\left(\\eigenvectorMatrix_3 \\latentVector_{3}\\right)\\\\\n", " \\dataVector &= \\mappingVector_4^\\top\\hiddenVector_{3}.\n", "\\end{align}\n", "\n", "\n", "### A Cascade of Neural Networks\n", "\n", "\n", "\\begin{align}\n", " \\latentVector_{1} &= \\eigenvectwoMatrix^\\top_1 \\inputVector\\\\\n", " \\latentVector_{2} &= \\eigenvectwoMatrix^\\top_2 \\basisFunction\\left(\\eigenvectorMatrix_1 \\latentVector_{1}\\right)\\\\\n", " \\latentVector_{3} &= \\eigenvectwoMatrix^\\top_3 \\basisFunction\\left(\\eigenvectorMatrix_2 \\latentVector_{2}\\right)\\\\\n", " \\dataVector &= \\mappingVector_4 ^\\top \\latentVector_{3}\n", "\\end{align}\n", "\n", "\n", "### Cascade of Gaussian Processes\n", "\n", "- Replace each neural network with a Gaussian process \n", " \\begin{align}\n", " \\latentVector_{1} &= \\mappingFunctionVector_1\\left(\\inputVector\\right)\\\\\n", " \\latentVector_{2} &= \\mappingFunctionVector_2\\left(\\latentVector_{1}\\right)\\\\\n", " \\latentVector_{3} &= \\mappingFunctionVector_3\\left(\\latentVector_{2}\\right)\\\\\n", " \\dataVector &= \\mappingFunctionVector_4\\left(\\latentVector_{3}\\right)\n", " \\end{align}\n", "\n", "\n", "- Equivalent to prior over parameters, take width of each layer to\n", " infinity.\n", "\n", "Mathematically, a deep Gaussian process can be seen as a composite\n", "*multivariate* function, $$\n", " \\mathbf{g}(\\inputVector)=\\mappingFunctionVector_5(\\mappingFunctionVector_4(\\mappingFunctionVector_3(\\mappingFunctionVector_2(\\mappingFunctionVector_1(\\inputVector))))).\n", "$$ Or if we view it from the probabilistic perspective we can see that\n", "a deep Gaussian process is specifying a factorization of the joint\n", "density, the standard deep model takes the form of a Markov chain." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from matplotlib import rc\n", "\n", "rc(\"font\", **{'family':'sans-serif','sans-serif':['Helvetica'],'size':30})\n", "rc(\"text\", usetex=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pgm = plot.horizontal_chain(depth=5)\n", "pgm.render().figure.savefig(\"../slides/diagrams/deepgp/deep-markov.svg\", transparent=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", " p(\\dataVector|\\inputVector)= p(\\dataVector|\\mappingFunctionVector_5)p(\\mappingFunctionVector_5|\\mappingFunctionVector_4)p(\\mappingFunctionVector_4|\\mappingFunctionVector_3)p(\\mappingFunctionVector_3|\\mappingFunctionVector_2)p(\\mappingFunctionVector_2|\\mappingFunctionVector_1)p(\\mappingFunctionVector_1|\\inputVector)\n", "$$\n", "\n", " \n", "
\n", "*Probabilistically the deep Gaussian process can be represented as a\n", "Markov chain. *\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from matplotlib import rc\n", "rc(\"font\", **{'family':'sans-serif','sans-serif':['Helvetica'], 'size':15})\n", "rc(\"text\", usetex=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pgm = plot.vertical_chain(depth=5)\n", "pgm.render().figure.savefig(\"../slides/diagrams/deepgp/deep-markov-vertical.svg\", transparent=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "\n", "### Why Deep?\n", "\n", "If the result of composing many functions together is simply another\n", "function, then why do we bother? The key point is that we can change the\n", "class of functions we are modeling by composing in this manner. A\n", "Gaussian process is specifying a prior over functions, and one with a\n", "number of elegant properties. For example, the derivative process (if it\n", "exists) of a Gaussian process is also Gaussian distributed. That makes\n", "it easy to assimilate, for example, derivative observations. But that\n", "also might raise some alarm bells. That implies that the *marginal\n", "derivative distribution* is also Gaussian distributed. If that's the\n", "case, then it means that functions which occasionally exhibit very large\n", "derivatives are hard to model with a Gaussian process. For example, a\n", "function with jumps in.\n", "\n", "A one off discontinuity is easy to model with a Gaussian process, or\n", "even multiple discontinuities. They can be introduced in the mean\n", "function, or independence can be forced between two covariance functions\n", "that apply in different areas of the input space. But in these cases we\n", "will need to specify the number of discontinuities and where they occur.\n", "In otherwords we need to *parameterise* the discontinuities. If we do\n", "not know the number of discontinuities and don't wish to specify where\n", "they occur, i.e. if we want a non-parametric representation of\n", "discontinuities, then the standard Gaussian process doesn't help.\n", "\n", "### Stochastic Process Composition\n", "\n", "The deep Gaussian process leads to *non-Gaussian* models, and\n", "non-Gaussian characteristics in the covariance function. In effect, what\n", "we are proposing is that we change the properties of the functions we\n", "are considering by \\*composing stochastic processes\\$. This is an\n", "approach to creating new stochastic processes from well known processes.\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pgm = plot.vertical_chain(depth=5, shape=[2, 7])\n", "pgm.add_node(daft.Node('y_2', r'$\\mathbf{y}_2', 1.5, 3.5, observed=True))\n", "pgm.add_edge('f_2', 'y_2')\n", "pgm.render().figure.savefig(\"../slides/diagrams/deepgp/deep-markov-vertical-side.svg\", transparent=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Additionally, we are not constrained to the formalism of the chain. For\n", "example, we can easily add single nodes emerging from some point in the\n", "depth of the chain. This allows us to combine the benefits of the\n", "graphical modelling formalism, but with a powerful framework for\n", "relating one set of variables to another, that of Gaussian processes\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.non_linear_difficulty_plot_3(diagrams='../../slides/diagrams/dimred/')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Difficulty for Probabilistic Approaches {#difficulty-for-probabilistic-approaches data-transition=\"None\"}\n", "\n", "- Propagate a probability distribution through a non-linear mapping.\n", "\n", "- Normalisation of distribution becomes intractable.\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.non_linear_difficulty_plot_2(diagrams='../../slides/diagrams/dimred/')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Difficulty for Probabilistic Approaches {#difficulty-for-probabilistic-approaches-1 data-transition=\"None\"}\n", "\n", "- Propagate a probability distribution through a non-linear mapping.\n", "\n", "- Normalisation of distribution becomes intractable.\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.non_linear_difficulty_plot_1(diagrams='../../slides/diagrams/dimred')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Difficulty for Probabilistic Approaches {#difficulty-for-probabilistic-approaches-2 data-transition=\"None\"}\n", "\n", "- Propagate a probability distribution through a non-linear mapping.\n", "\n", "- Normalisation of distribution becomes intractable.\n", "\n", " \n", "\n", "### Deep Gaussian Processes\n", "\n", "- Deep architectures allow abstraction of features\n", " [@Bengio:deep09; @Hinton:fast06; @Salakhutdinov:quantitative08]\n", "\n", "- We use variational approach to stack GP models." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.stack_gp_sample(kernel=GPy.kern.Linear,\n", " diagrams=\"../../slides/diagrams/deepgp\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pods.notebook.display_plots('stack-gp-sample-Linear-{sample:0>1}.svg', \n", " directory='../../slides/diagrams/deepgp', sample=(0,4))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Stacked PCA\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot.stack_gp_sample(kernel=GPy.kern.RBF,\n", " diagrams=\"../../slides/diagrams/deepgp\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pods.notebook.display_plots('stack-gp-sample-RBF-{sample:0>1}.svg', \n", " directory='../../slides/diagrams/deepgp', sample=(0,4))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Stacked GP\n", "\n", " \n", "\n", "### Analysis of Deep GPs\n", "\n", "- *Avoiding pathologies in very deep networks* @Duvenaud:pathologies14\n", " show that the derivative distribution of the process becomes more\n", " *heavy tailed* as number of layers increase.\n", "\n", "- *How Deep Are Deep Gaussian Processes?* @Dunlop:deep2017 perform a\n", " theoretical analysis possible through conditional Gaussian Markov\n", " property." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.lib.display import YouTubeVideo\n", "YouTubeVideo('XhIvygQYFFQ')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import pods\n", "import teaching_plots as plot\n", "import mlai" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Olympic Marathon 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. First we load in the data\n", "and plot." ] }, { "cell_type": "code", "execution_count": null, "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", "\n", "xlim = (1875,2030)\n", "ylim = (2.5, 6.5)\n", "yhat = (y-offset)/scale\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(figure=fig, filename='../slides/diagrams/datasets/olympic-marathon.svg', transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Olympic Marathon Data\n", "\n", "\n", "\n", "\n", "\n", "\n", "  \n", "- Gold medal times for Olympic Marathon since 1896.\n", "\n", "- Marathons before 1924 didn’t have a standardised distance.\n", "\n", "- Present results using pace per km.\n", "\n", "- In 1904 Marathon was badly organised leading to very slow times.\n", "\n", " \n", "![image](../slides/diagrams/Stephen_Kiprotich.jpg) Image from\n", "Wikimedia Commons \n", " \n", " \n", "\n", "Things to notice about the data include the outlier in 1904, in this\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.\n", "\n", "More recent years see more consistently quick marathons.\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)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m_full = GPy.models.GPRegression(x,yhat)\n", "_ = m_full.optimize() # Optimize parameters of covariance function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first command sets up the model, then" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m_full.optimize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "optimizes the parameters of the covariance function and the noise level\n", "of the model. Once the fit is complete, we'll try creating some test\n", "points, and computing the output of the GP model in terms of the mean\n", "and standard deviation of the posterior functions between 1870 and 2030.\n", "We plot the mean function and the standard deviation at 200 locations.\n", "We can obtain the predictions using" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y_mean, y_var = m_full.predict(xt)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "xt = np.linspace(1870,2030,200)[:,np.newaxis]\n", "yt_mean, yt_var = m_full.predict(xt)\n", "yt_sd=np.sqrt(yt_var)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we plot the results using the helper function in teaching_plots." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import teaching_plots as plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_output(m_full, scale=scale, offset=offset, ax=ax, xlabel='year', ylabel='pace min/km', fontsize=20, portion=0.2)\n", "ax.set_xlim(xlim)\n", "ax.set_ylim(ylim)\n", "mlai.write_figure(figure=fig,\n", " filename='../slides/diagrams/gp/olympic-marathon-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "\n", "### Fit Quality\n", "\n", "In the fit we see that the error bars (coming mainly from the noise\n", "variance) are quite large. This is likely due to the outlier point in\n", "1904, ignoring that point we can see that a tighter fit is obtained. To\n", "see this making a version of the model, m_clean, where that point is\n", "removed." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "x_clean=np.vstack((x[0:2, :], x[3:, :]))\n", "y_clean=np.vstack((y[0:2, :], y[3:, :]))\n", "\n", "m_clean = GPy.models.GPRegression(x_clean,y_clean)\n", "_ = m_clean.optimize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Data is fine for answering very specific questions, like \"Who won the\n", "Olympic Marathon in 2012?\", because we have that answer stored, however,\n", "we are not given the answer to many other questions. For example, Alan\n", "Turing was a formidable marathon runner, in 1946 he ran a time 2 hours\n", "46 minutes (just under four minutes per kilometer, faster than I and\n", "most of the other [Endcliffe Park\n", "Run](http://www.parkrun.org.uk/sheffieldhallam/) runners can do 5 km).\n", "What is the probability he would have won an Olympics if one had been\n", "held in 1946?\n", "\n", "\n", "\n", "\n", "\n", "\n", "  \n", " \n", " \n", " \n", " \n", " \n", "*Alan Turing, in 1946 he was only 11 minutes slower than the winner of\n", "the 1948 games. Would he have won a hypothetical games held in 1946?\n", "Source: [Alan Turing Internet\n", "Scrapbook](http://www.turing.org.uk/scrapbook/run.html) *\n", " \n", "### Deep GP Fit\n", "\n", "Let's see if a deep Gaussian process can help here. We will construct a\n", "deep Gaussian process with one hidden layer (i.e. one Gaussian process\n", "feeding into another).\n", "\n", "Build a Deep GP with an additional hidden layer (one dimensional) to fit\n", "the model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hidden = 1\n", "m = deepgp.DeepGP([y.shape,hidden,x.shape],Y=yhat, X=x, inits=['PCA','PCA'], \n", " kernels=[GPy.kern.RBF(hidden,ARD=True),\n", " GPy.kern.RBF(x.shape,ARD=True)], # the kernels for each layer\n", " num_inducing=50, back_constraint=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Deep Gaussian process models also can require some thought in\n", "initialization. Here we choose to start by setting the noise variance to\n", "be one percent of the data variance.\n", "\n", "Optimization requires moving variational parameters in the hidden layer\n", "representing the mean and variance of the expected values in that layer.\n", "Since all those values can be scaled up, and this only results in a\n", "downscaling in the output of the first GP, and a downscaling of the\n", "input length scale to the second GP. It makes sense to first of all fix\n", "the scales of the covariance function in each of the GPs.\n", "\n", "Sometimes, deep Gaussian processes can find a local minima which\n", "involves increasing the noise level of one or more of the GPs. This\n", "often occurs because it allows a minimum in the KL divergence term in\n", "the lower bound on the likelihood. To avoid this minimum we habitually\n", "train with the likelihood variance (the noise on the output of the GP)\n", "fixed to some lower value for some iterations.\n", "\n", "Let's create a helper function to initialize the models we use in the\n", "notebook." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def initialize(self, noise_factor=0.01, linear_factor=1):\n", " \"\"\"Helper function for deep model initialization.\"\"\"\n", " self.obslayer.likelihood.variance = self.Y.var()*noise_factor\n", " for layer in self.layers:\n", " if type(layer.X) is GPy.core.parameterization.variational.NormalPosterior:\n", " if layer.kern.ARD:\n", " var = layer.X.mean.var(0)\n", " else:\n", " var = layer.X.mean.var()\n", " else:\n", " if layer.kern.ARD:\n", " var = layer.X.var(0)\n", " else:\n", " var = layer.X.var()\n", "\n", " # Average 0.5 upcrossings in four standard deviations. \n", " layer.kern.lengthscale = linear_factor*np.sqrt(layer.kern.input_dim)*2*4*np.sqrt(var)/(2*np.pi)\n", "# Bind the new method to the Deep GP object.\n", "deepgp.DeepGP.initialize=initialize" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Call the initalization\n", "m.initialize()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now optimize the model. The first stage of optimization is working on\n", "variational parameters and lengthscales only." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.optimize(messages=False,max_iters=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we remove the constraints on the scale of the covariance functions\n", "associated with each GP and optimize again." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for layer in m.layers:\n", " pass #layer.kern.variance.constrain_positive(warning=False)\n", "m.obslayer.kern.variance.constrain_positive(warning=False)\n", "m.optimize(messages=False,max_iters=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, we allow the noise variance to change and optimize for a large\n", "number of iterations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for layer in m.layers:\n", " layer.likelihood.variance.constrain_positive(warning=False)\n", "m.optimize(messages=True,max_iters=10000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For our optimization process we define a new function." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def staged_optimize(self, iters=(1000,1000,10000), messages=(False, False, True)):\n", " \"\"\"Optimize with parameters constrained and then with parameters released\"\"\"\n", " for layer in self.layers:\n", " # Fix the scale of each of the covariance functions.\n", " layer.kern.variance.fix(warning=False)\n", " layer.kern.lengthscale.fix(warning=False)\n", "\n", " # Fix the variance of the noise in each layer.\n", " layer.likelihood.variance.fix(warning=False)\n", "\n", " self.optimize(messages=messages,max_iters=iters)\n", " \n", " for layer in self.layers:\n", " layer.kern.lengthscale.constrain_positive(warning=False)\n", " self.obslayer.kern.variance.constrain_positive(warning=False)\n", "\n", "\n", " self.optimize(messages=messages,max_iters=iters)\n", "\n", " for layer in self.layers:\n", " layer.kern.variance.constrain_positive(warning=False)\n", " layer.likelihood.variance.constrain_positive(warning=False)\n", " self.optimize(messages=messages,max_iters=iters)\n", " \n", "# Bind the new method to the Deep GP object.\n", "deepgp.DeepGP.staged_optimize=staged_optimize" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.staged_optimize(messages=(True,True,True))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Plot the prediction\n", "\n", "The prediction of the deep GP can be extracted in a similar way to the\n", "normal GP. Although, in this case, it is an approximation to the true\n", "distribution, because the true distribution is not Gaussian." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_output(m, scale=scale, offset=offset, ax=ax, xlabel='year', ylabel='pace min/km', \n", " fontsize=20, portion=0.2)\n", "ax.set_xlim(xlim)\n", "\n", "ax.set_ylim(ylim)\n", "mlai.write_figure(figure=fig, filename='../slides/diagrams/deepgp/olympic-marathon-deep-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Olympic Marathon Data Deep GP\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def posterior_sample(self, X, **kwargs):\n", " \"\"\"Give a sample from the posterior of the deep GP.\"\"\"\n", " Z = X\n", " for i, layer in enumerate(reversed(self.layers)):\n", " Z = layer.posterior_samples(Z, size=1, **kwargs)[:, :, 0]\n", " \n", " return Z\n", "deepgp.DeepGP.posterior_sample = posterior_sample" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "plot.model_sample(m, scale=scale, offset=offset, samps=10, ax=ax, \n", " xlabel='year', ylabel='pace min/km', portion = 0.225)\n", "ax.set_xlim(xlim)\n", "ax.set_ylim(ylim)\n", "mlai.write_figure(figure=fig, filename='../slides/diagrams/deepgp/olympic-marathon-deep-gp-samples.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Olympic Marathon Data Deep GP {#olympic-marathon-data-deep-gp-1 data-transition=\"None\"}\n", "\n", " \n", "\n", "### Fitted GP for each layer\n", "\n", "Now we explore the GPs the model has used to fit each layer. First of\n", "all, we look at the hidden layer." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def visualize(self, scale=1.0, offset=0.0, xlabel='input', ylabel='output', \n", " xlim=None, ylim=None, fontsize=20, portion=0.2,dataset=None, \n", " diagrams='../diagrams'):\n", " \"\"\"Visualize the layers in a deep GP with one-d input and output.\"\"\"\n", " depth = len(self.layers)\n", " if dataset is None:\n", " fname = 'deep-gp-layer'\n", " else:\n", " fname = dataset + '-deep-gp-layer'\n", " filename = os.path.join(diagrams, fname)\n", " last_name = xlabel\n", " last_x = self.X\n", " for i, layer in enumerate(reversed(self.layers)):\n", " if i>0:\n", " plt.plot(last_x, layer.X.mean, 'r.',markersize=10)\n", " last_x=layer.X.mean\n", " ax=plt.gca()\n", " name = 'layer ' + str(i)\n", " plt.xlabel(last_name, fontsize=fontsize)\n", " plt.ylabel(name, fontsize=fontsize)\n", " last_name=name\n", " mlai.write_figure(filename=filename + '-' + str(i-1) + '.svg', \n", " transparent=True, frameon=True)\n", " \n", " if i==0 and xlim is not None:\n", " xt = plot.pred_range(np.array(xlim), portion=0.0)\n", " elif i>0:\n", " xt = plot.pred_range(np.array(next_lim), portion=0.0)\n", " else:\n", " xt = plot.pred_range(last_x, portion=portion)\n", " yt_mean, yt_var = layer.predict(xt)\n", " if layer==self.obslayer:\n", " yt_mean = yt_mean*scale + offset\n", " yt_var *= scale*scale\n", " yt_sd = np.sqrt(yt_var)\n", " gpplot(xt,yt_mean,yt_mean-2*yt_sd,yt_mean+2*yt_sd)\n", " ax = plt.gca()\n", " if i>0:\n", " ax.set_xlim(next_lim)\n", " elif xlim is not None:\n", " ax.set_xlim(xlim)\n", " next_lim = plt.gca().get_ylim()\n", " \n", " plt.plot(last_x, self.Y*scale + offset, 'r.',markersize=10)\n", " plt.xlabel(last_name, fontsize=fontsize)\n", " plt.ylabel(ylabel, fontsize=fontsize)\n", " mlai.write_figure(filename=filename + '-' + str(i) + '.svg', \n", " transparent=True, frameon=True)\n", "\n", " if ylim is not None:\n", " ax=plt.gca()\n", " ax.set_ylim(ylim)\n", "\n", "# Bind the new method to the Deep GP object.\n", "deepgp.DeepGP.visualize=visualize" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.visualize(scale=scale, offset=offset, xlabel='year',\n", " ylabel='pace min/km',xlim=xlim, ylim=ylim,\n", " dataset='olympic-marathon',\n", " diagrams='../slides/diagrams/deepgp')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pods\n", "pods.notebook.display_plots('olympic-marathon-deep-gp-layer-{sample:0>1}.svg', \n", " '../slides/diagrams/deepgp', sample=(0,1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def scale_data(x, portion): \n", " scale = (x.max()-x.min())/(1-2*portion)\n", " offset = x.min() - portion*scale\n", " return (x-offset)/scale, scale, offset\n", "\n", "def visualize_pinball(self, ax=None, scale=1.0, offset=0.0, xlabel='input', ylabel='output', \n", " xlim=None, ylim=None, fontsize=20, portion=0.2, points=50, vertical=True):\n", " \"\"\"Visualize the layers in a deep GP with one-d input and output.\"\"\"\n", "\n", " if ax is None:\n", " fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "\n", " depth = len(self.layers)\n", "\n", " last_name = xlabel\n", " last_x = self.X\n", "\n", " # Recover input and output scales from output plot\n", " plot_model_output(self, scale=scale, offset=offset, ax=ax, \n", " xlabel=xlabel, ylabel=ylabel, \n", " fontsize=fontsize, portion=portion)\n", " xlim=ax.get_xlim()\n", " xticks=ax.get_xticks()\n", " xtick_labels=ax.get_xticklabels().copy()\n", " ylim=ax.get_ylim()\n", " yticks=ax.get_yticks()\n", " ytick_labels=ax.get_yticklabels().copy()\n", "\n", " # Clear axes and start again\n", " ax.cla()\n", " if vertical:\n", " ax.set_xlim((0, 1))\n", " ax.invert_yaxis()\n", "\n", " ax.set_ylim((depth, 0))\n", " else:\n", " ax.set_ylim((0, 1))\n", " ax.set_xlim((0, depth))\n", " \n", " ax.set_axis_off()#frame_on(False)\n", "\n", "\n", " def pinball(x, y, y_std, color_scale=None, \n", " layer=0, depth=1, ax=None, \n", " alpha=1.0, portion=0.0, vertical=True): \n", "\n", " scaledx, xscale, xoffset = scale_data(x, portion=portion)\n", " scaledy, yscale, yoffset = scale_data(y, portion=portion)\n", " y_std /= yscale\n", "\n", " # Check whether data is anti-correlated on output\n", " if np.dot((scaledx-0.5).T, (scaledy-0.5))<0:\n", " scaledy=1-scaledy\n", " flip=-1\n", " else:\n", " flip=1\n", "\n", " if color_scale is not None:\n", " color_scale, _, _=scale_data(color_scale, portion=0)\n", " scaledy = (1-alpha)*scaledx + alpha*scaledy\n", "\n", " def color_value(x, cmap=None, width=None, centers=None):\n", " \"\"\"Return color as a function of position along x axis\"\"\"\n", " if cmap is None:\n", " cmap = np.asarray([[1, 0, 0], [1, 1, 0], [0, 1, 0]])\n", " ncenters = cmap.shape\n", " if centers is None:\n", " centers = np.linspace(0+0.5/ncenters, 1-0.5/ncenters, ncenters)\n", " if width is None:\n", " width = 0.25/ncenters\n", " \n", " r = (x-centers)/width\n", " weights = np.exp(-0.5*r*r).flatten()\n", " weights /=weights.sum()\n", " weights = weights[:, np.newaxis]\n", " return np.dot(cmap.T, weights).flatten()\n", "\n", "\n", " for i in range(x.shape):\n", " if color_scale is not None:\n", " color = color_value(color_scale[i])\n", " else:\n", " color=(1, 0, 0)\n", " x_plot = np.asarray((scaledx[i], scaledy[i])).flatten()\n", " y_plot = np.asarray((layer, layer+alpha)).flatten()\n", " if vertical:\n", " ax.plot(x_plot, y_plot, color=color, alpha=0.5, linewidth=3)\n", " ax.plot(x_plot, y_plot, color='k', alpha=0.5, linewidth=0.5)\n", " else:\n", " ax.plot(y_plot, x_plot, color=color, alpha=0.5, linewidth=3)\n", " ax.plot(y_plot, x_plot, color='k', alpha=0.5, linewidth=0.5)\n", "\n", " # Plot error bars that increase as sqrt of distance from start.\n", " std_points = 50\n", " stdy = np.linspace(0, alpha,std_points)\n", " stdx = np.sqrt(stdy)*y_std[i]\n", " stdy += layer\n", " mean_vals = np.linspace(scaledx[i], scaledy[i], std_points)\n", " upper = mean_vals+stdx \n", " lower = mean_vals-stdx \n", " fillcolor=color\n", " x_errorbars=np.hstack((upper,lower[::-1]))\n", " y_errorbars=np.hstack((stdy,stdy[::-1]))\n", " if vertical:\n", " ax.fill(x_errorbars,y_errorbars,\n", " color=fillcolor, alpha=0.1)\n", " ax.plot(scaledy[i], layer+alpha, '.',markersize=10, color=color, alpha=0.5)\n", " else:\n", " ax.fill(y_errorbars,x_errorbars,\n", " color=fillcolor, alpha=0.1)\n", " ax.plot(layer+alpha, scaledy[i], '.',markersize=10, color=color, alpha=0.5)\n", " # Marker to show end point\n", " return flip\n", "\n", "\n", " # Whether final axis is flipped\n", " flip = 1\n", " first_x=last_x\n", " for i, layer in enumerate(reversed(self.layers)): \n", " if i==0:\n", " xt = plot.pred_range(last_x, portion=portion, points=points)\n", " color_scale=xt\n", " yt_mean, yt_var = layer.predict(xt)\n", " if layer==self.obslayer:\n", " yt_mean = yt_mean*scale + offset\n", " yt_var *= scale*scale\n", " yt_sd = np.sqrt(yt_var)\n", " flip = flip*pinball(xt,yt_mean,yt_sd,color_scale,portion=portion, \n", " layer=i, ax=ax, depth=depth,vertical=vertical)#yt_mean-2*yt_sd,yt_mean+2*yt_sd)\n", " xt = yt_mean\n", " # Make room for axis labels\n", " if vertical:\n", " ax.set_ylim((2.1, -0.1))\n", " ax.set_xlim((-0.02, 1.02))\n", " ax.set_yticks(range(depth,0,-1))\n", " else:\n", " ax.set_xlim((-0.1, 2.1))\n", " ax.set_ylim((-0.02, 1.02))\n", " ax.set_xticks(range(0, depth))\n", " \n", " def draw_axis(ax, scale=1.0, offset=0.0, level=0.0, flip=1, \n", " label=None,up=False, nticks=10, ticklength=0.05,\n", " vertical=True,\n", " fontsize=20):\n", " def clean_gap(gap):\n", " nsf = np.log10(gap)\n", " if nsf>0:\n", " nsf = np.ceil(nsf)\n", " else:\n", " nsf = np.floor(nsf)\n", " lower_gaps = np.asarray([0.005, 0.01, 0.02, 0.03, 0.04, 0.05, \n", " 0.1, 0.25, 0.5, \n", " 1, 1.5, 2, 2.5, 3, 4, 5, 10, 25, 50, 100])\n", " upper_gaps = np.asarray([1, 2, 3, 4, 5, 10])\n", " if nsf >2 or nsf<-2:\n", " d = np.abs(gap-upper_gaps*10**nsf)\n", " ind = np.argmin(d)\n", " return upper_gaps[ind]*10**nsf\n", " else:\n", " d = np.abs(gap-lower_gaps)\n", " ind = np.argmin(d)\n", " return lower_gaps[ind]\n", " \n", " tickgap = clean_gap(scale/(nticks-1))\n", " nticks = int(scale/tickgap) + 1\n", " tickstart = np.round(offset/tickgap)*tickgap\n", " ticklabels = np.asarray(range(0, nticks))*tickgap + tickstart\n", " ticks = (ticklabels-offset)/scale\n", " axargs = {'color':'k', 'linewidth':1}\n", " \n", " if not up:\n", " ticklength=-ticklength\n", " tickspot = np.linspace(0, 1, nticks)\n", " if flip < 0:\n", " ticks = 1-ticks\n", " for tick, ticklabel in zip(ticks, ticklabels):\n", " if vertical:\n", " ax.plot([tick, tick], [level, level-ticklength], **axargs)\n", " ax.text(tick, level-ticklength*2, ticklabel, horizontalalignment='center', \n", " fontsize=fontsize/2)\n", " ax.text(0.5, level-5*ticklength, label, horizontalalignment='center', fontsize=fontsize)\n", " else:\n", " ax.plot([level, level-ticklength], [tick, tick], **axargs)\n", " ax.text(level-ticklength*2, tick, ticklabel, horizontalalignment='center', \n", " fontsize=fontsize/2)\n", " ax.text(level-5*ticklength, 0.5, label, horizontalalignment='center', fontsize=fontsize)\n", " \n", " if vertical:\n", " xlim = list(ax.get_xlim())\n", " if ticks.min()xlim:\n", " xlim = ticks.max()\n", " ax.set_xlim(xlim)\n", " \n", " ax.plot([ticks.min(), ticks.max()], [level, level], **axargs)\n", " else:\n", " ylim = list(ax.get_ylim())\n", " if ticks.min()ylim:\n", " ylim = ticks.max()\n", " ax.set_ylim(ylim)\n", " ax.plot([level, level], [ticks.min(), ticks.max()], **axargs)\n", "\n", "\n", " _, xscale, xoffset = scale_data(first_x, portion=portion)\n", " _, yscale, yoffset = scale_data(yt_mean, portion=portion)\n", " draw_axis(ax=ax, scale=xscale, offset=xoffset, level=0.0, label=xlabel, \n", " up=True, vertical=vertical)\n", " draw_axis(ax=ax, scale=yscale, offset=yoffset, \n", " flip=flip, level=depth, label=ylabel, up=False, vertical=vertical)\n", " \n", " #for txt in xticklabels:\n", " # txt.set\n", "# Bind the new method to the Deep GP object.\n", "deepgp.DeepGP.visualize_pinball=visualize_pinball" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "m.visualize_pinball(ax=ax, scale=scale, offset=offset, points=30, portion=0.1,\n", " xlabel='year', ylabel='pace km/min', vertical=True)\n", "mlai.write_figure(figure=fig, filename='../slides/diagrams/deepgp/olympic-marathon-deep-gp-pinball.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Olympic Marathon Pinball Plot\n", "\n", " \n", "\n", "The pinball plot shows the flow of any input ball through the deep\n", "Gaussian process. In a pinball plot a series of vertical parallel lines\n", "would indicate a purely linear function. For the olypmic marathon data\n", "we can see the first layer begins to shift from input towards the right.\n", "Note it also does so with some uncertainty (indicated by the shaded\n", "backgrounds). The second layer has less uncertainty, but bunches the\n", "inputs more strongly to the right. This input layer of uncertainty,\n", "followed by a layer that pushes inputs to the right is what gives the\n", "heteroschedastic noise.\n", "\n", "### Step Function\n", "\n", "Next we consider a simple step function data set." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "num_low=25\n", "num_high=25\n", "gap = -.1\n", "noise=0.0001\n", "x = np.vstack((np.linspace(-1, -gap/2.0, num_low)[:, np.newaxis],\n", " np.linspace(gap/2.0, 1, num_high)[:, np.newaxis]))\n", "y = np.vstack((np.zeros((num_low, 1)), np.ones((num_high,1))))\n", "scale = np.sqrt(y.var())\n", "offset = y.mean()\n", "yhat = (y-offset)/scale" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "_ = ax.plot(x, y, 'r.',markersize=10)\n", "_ = ax.set_xlabel('x$', fontsize=20)\n", "_ = ax.set_ylabel('$y$', fontsize=20)\n", "xlim = (-2, 2)\n", "ylim = (-0.6, 1.6)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/datasets/step-function.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step Function Data {#step-function-data data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m_full = GPy.models.GPRegression(x,yhat)\n", "_ = m_full.optimize() # Optimize parameters of covariance function" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_output(m_full, scale=scale, offset=offset, ax=ax, fontsize=20, portion=0.5)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "\n", "mlai.write_figure(figure=fig,filename='../../slides/diagrams/gp/step-function-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step Function Data GP {#step-function-data-gp data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layers = [y.shape, 1, 1, 1,x.shape]\n", "inits = ['PCA']*(len(layers)-1)\n", "kernels = []\n", "for i in layers[1:]:\n", " kernels += [GPy.kern.RBF(i)]\n", "m = deepgp.DeepGP(layers,Y=yhat, X=x, \n", " inits=inits, \n", " kernels=kernels, # the kernels for each layer\n", " num_inducing=20, back_constraint=False)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.initialize()\n", "m.staged_optimize()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_output(m, scale=scale, offset=offset, ax=ax, fontsize=20, portion=0.5)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "mlai.write_figure(filename='../../slides/diagrams/deepgp/step-function-deep-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step Function Data Deep GP {#step-function-data-deep-gp data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_sample(m, scale=scale, offset=offset, samps=10, ax=ax, portion = 0.5)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/deepgp/step-function-deep-gp-samples.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step Function Data Deep GP {#step-function-data-deep-gp-1 data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.visualize(offset=offset, scale=scale, xlim=xlim, ylim=ylim,\n", " dataset='step-function',\n", " diagrams='../../slides/diagrams/deepgp')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step Function Data Latent 1 {#step-function-data-latent-1 data-transition=\"None\"}\n", "\n", " \n", "\n", "### Step Function Data Latent 2 {#step-function-data-latent-2 data-transition=\"None\"}\n", "\n", " \n", "\n", "### Step Function Data Latent 3 {#step-function-data-latent-3 data-transition=\"None\"}\n", "\n", " \n", "\n", "### Step Function Data Latent 4 {#step-function-data-latent-4 data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "m.visualize_pinball(offset=offset, ax=ax, scale=scale, xlim=xlim, ylim=ylim, portion=0.1, points=50)\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/deepgp/step-function-deep-gp-pinball.svg', \n", " transparent=True, frameon=True, ax=ax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Step Function Pinball Plot {#step-function-pinball-plot data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pods\n", "data = pods.datasets.mcycle()\n", "x = data['X']\n", "y = data['Y']\n", "scale=np.sqrt(y.var())\n", "offset=y.mean()\n", "yhat = (y - offset)/scale" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "_ = ax.plot(x, y, 'r.',markersize=10)\n", "_ = ax.set_xlabel('time', fontsize=20)\n", "_ = ax.set_ylabel('acceleration', fontsize=20)\n", "ax.set_xlim(xlim)\n", "ax.set_ylim(ylim)\n", "mlai.write_figure(filename='../../slides/diagrams/datasets/motorcycle-helmet.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Motorcycle Helmet Data {#motorcycle-helmet-data data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m_full = GPy.models.GPRegression(x,yhat)\n", "_ = m_full.optimize() # Optimize parameters of covariance function" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_output(m_full, scale=scale, offset=offset, ax=ax, xlabel='time', ylabel='acceleration/$g$', fontsize=20, portion=0.5)\n", "xlim=(-20,80)\n", "ylim=(-180,120)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "mlai.write_figure(figure=fig,filename='../../slides/diagrams/gp/motorcycle-helmet-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Motorcycle Helmet Data GP {#motorcycle-helmet-data-gp data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layers = [y.shape, 1, x.shape]\n", "inits = ['PCA']*(len(layers)-1)\n", "kernels = []\n", "for i in layers[1:]:\n", " kernels += [GPy.kern.RBF(i)]\n", "m = deepgp.DeepGP(layers,Y=yhat, X=x, \n", " inits=inits, \n", " kernels=kernels, # the kernels for each layer\n", " num_inducing=20, back_constraint=False)\n", "\n", "\n", "\n", "m.initialize()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.staged_optimize(iters=(1000,1000,10000), messages=(True, True, True))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_output(m, scale=scale, offset=offset, ax=ax, xlabel='time', ylabel='acceleration/$g$', fontsize=20, portion=0.5)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "mlai.write_figure(filename='../../slides/diagrams/deepgp/motorcycle-helmet-deep-gp.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Motorcycle Helmet Data Deep GP {#motorcycle-helmet-data-deep-gp data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_sample(m, scale=scale, offset=offset, samps=10, ax=ax, xlabel='time', ylabel='acceleration/$g$', portion = 0.5)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/deepgp/motorcycle-helmet-deep-gp-samples.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Motorcycle Helmet Data Deep GP {#motorcycle-helmet-data-deep-gp-1 data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.visualize(xlim=xlim, ylim=ylim, scale=scale,offset=offset, \n", " xlabel=\"time\", ylabel=\"acceleration/$g$\", portion=0.5,\n", " dataset='motorcycle-helmet',\n", " diagrams='../../slides/diagrams/deepgp')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Motorcycle Helmet Data Latent 1 {#motorcycle-helmet-data-latent-1 data-transition=\"None\"}\n", "\n", " \n", "\n", "### Motorcycle Helmet Data Latent 2 {#motorcycle-helmet-data-latent-2 data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "m.visualize_pinball(ax=ax, xlabel='time', ylabel='acceleration/g', \n", " points=50, scale=scale, offset=offset, portion=0.1)\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/deepgp/motorcycle-helmet-deep-gp-pinball.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Motorcycle Helmet Pinball Plot {#motorcycle-helmet-pinball-plot data-transition=\"None\"}\n", "\n", " \n", "\n", "### Robot Wireless Data\n", "\n", "The robot wireless data is taken from an experiment run by Brian Ferris\n", "at University of Washington. It consists of the measurements of WiFi\n", "access point signal strengths as Brian walked in a loop." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "data=pods.datasets.robot_wireless()\n", "\n", "x = np.linspace(0,1,215)[:, np.newaxis]\n", "y = data['Y']\n", "offset = y.mean()\n", "scale = np.sqrt(y.var())\n", "yhat = (y-offset)/scale" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ground truth is recorded in the data, the actual loop is given in\n", "the plot below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "plt.plot(data['X'][:, 1], data['X'][:, 2], 'r.', markersize=5)\n", "ax.set_xlabel('x position', fontsize=20)\n", "ax.set_ylabel('y position', fontsize=20)\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/datasets/robot-wireless-ground-truth.svg', transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Robot Wireless Ground Truth {#robot-wireless-ground-truth data-transition=\"None\"}\n", "\n", " \n", "\n", "We will ignore this ground truth in making our predictions, but see if\n", "the model can recover something similar in one of the latent layers." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "output_dim=1\n", "xlim = (-0.3, 1.3)\n", "fig, ax = plt.subplots(figsize=plot.big_wide_figsize)\n", "_ = ax.plot(x.flatten(), y[:, output_dim], \n", " 'r.', markersize=5)\n", "\n", "ax.set_xlabel('time', fontsize=20)\n", "ax.set_ylabel('signal strength', fontsize=20)\n", "xlim = (-0.2, 1.2)\n", "ylim = (-0.6, 2.0)\n", "ax.set_xlim(xlim)\n", "ax.set_ylim(ylim)\n", "\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/datasets/robot-wireless-dim-' + str(output_dim) + '.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Robot WiFi Data {#robot-wifi-data data-transition=\"None\"}\n", "\n", " \n", "\n", "Perform a Gaussian process fit on the data using GPy." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m_full = GPy.models.GPRegression(x,yhat)\n", "_ = m_full.optimize() # Optimize parameters of covariance function" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_output(m_full, output_dim=output_dim, scale=scale, offset=offset, ax=ax, \n", " xlabel='time', ylabel='signal strength', fontsize=20, portion=0.5)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "mlai.write_figure(filename='../../slides/diagrams/gp/robot-wireless-gp-dim-' + str(output_dim)+ '.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Robot WiFi Data GP {#robot-wifi-data-gp data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layers = [y.shape, 10, 5, 2, 2, x.shape]\n", "inits = ['PCA']*(len(layers)-1)\n", "kernels = []\n", "for i in layers[1:]:\n", " kernels += [GPy.kern.RBF(i, ARD=True)]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = deepgp.DeepGP(layers,Y=y, X=x, inits=inits, \n", " kernels=kernels,\n", " num_inducing=50, back_constraint=False)\n", "m.initialize()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.staged_optimize(messages=(True,True,True))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_output(m, output_dim=output_dim, scale=scale, offset=offset, ax=ax, \n", " xlabel='time', ylabel='signal strength', fontsize=20, portion=0.5)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/deepgp/robot-wireless-deep-gp-dim-' + str(output_dim)+ '.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Robot WiFi Data Deep GP {#robot-wifi-data-deep-gp data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax=plt.subplots(figsize=plot.big_wide_figsize)\n", "plot_model_sample(m, output_dim=output_dim, scale=scale, offset=offset, samps=10, ax=ax,\n", " xlabel='time', ylabel='signal strength', fontsize=20, portion=0.5)\n", "ax.set_ylim(ylim)\n", "ax.set_xlim(xlim)\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/deepgp/robot-wireless-deep-gp-samples-dim-' + str(output_dim)+ '.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Robot WiFi Data Deep GP {#robot-wifi-data-deep-gp-1 data-transition=\"None\"}\n", "\n", " \n", "\n", "### Robot WiFi Data Latent Space {#robot-wifi-data-latent-space data-transition=\"None\"}\n", "\n", " " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "ax.plot(m.layers[-2].latent_space.mean[:, 0], \n", " m.layers[-2].latent_space.mean[:, 1], \n", " 'r.-', markersize=5)\n", "\n", "ax.set_xlabel('latent dimension 1', fontsize=20)\n", "ax.set_ylabel('latent dimension 2', fontsize=20)\n", "\n", "mlai.write_figure(figure=fig, filename='../../slides/diagrams/deepgp/robot-wireless-latent-space.svg', \n", " transparent=True, frameon=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Robot WiFi Data Latent Space {#robot-wifi-data-latent-space-1 data-transition=\"None\"}\n", "\n", " \n", "\n", "### Motion Capture {#motion-capture data-transition=\"none\"}\n", "\n", "- ‘High five’ data.\n", "\n", "- Model learns structure between two interacting subjects.\n", "\n", "### Shared LVM {#shared-lvm data-transition=\"none\"}\n", "\n", " \n", "\n", "### {#section-3 data-transition=\"none\"}\n", "\n", " \n", "\n", "\\credit{Zhenwen Dai and Neil D. Lawrence}\n", "\n", "This notebook explores the deep Gaussian processes' capacity to perform\n", "unsupervised learning.\n", "\n", "We will look at a sub-sample of the MNIST digit data set.\n", "\n", "This notebook depends on GPy and PyDeepGP. These libraries can be\n", "installed via pip:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pip install GPy\n", "pip install git+https://github.com/SheffieldML/PyDeepGP.git" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from matplotlib import rc\n", "from IPython.display import display\n", "\n", "import deepgp\n", "import GPy\n", "\n", "from gp_tutorial import ax_default, meanplot, gpplot\n", "import mlai\n", "import teaching_plots as plot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First load in the MNIST data set from scikit learn. This can take a\n", "little while because it's large to download." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from sklearn.datasets import fetch_mldata\n", "mnist = fetch_mldata('MNIST original')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sub-sample the dataset to make the training faster." ] }, { "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", "for d in digits:\n", " imgs = mnist['data'][mnist['target']==d]\n", " Y.append(imgs[np.random.permutation(imgs.shape)][: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." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Fit a Deep GP\n", "\n", "We're going to fit a Deep Gaussian process model to the MNIST data with\n", "two hidden layers. Each of the two Gaussian processes (one from the\n", "first hidden layer to the second, one from the second hidden layer to\n", "the data) has an exponentiated quadratic covariance." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "num_latent = 2\n", "num_hidden_2 = 5\n", "m = deepgp.DeepGP([Y.shape,num_hidden_2,num_latent],\n", " Y,\n", " kernels=[GPy.kern.RBF(num_hidden_2,ARD=True), \n", " GPy.kern.RBF(num_latent,ARD=False)], \n", " num_inducing=50, back_constraint=False, \n", " encoder_dims=[,])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Initialization\n", "\n", "Just like deep neural networks, there are some tricks to intitializing\n", "these models. The tricks we use here include some early training of the\n", "model with model parameters constrained. This gives the variational\n", "inducing parameters some scope to tighten the bound for the case where\n", "the noise variance is small and the variances of the Gaussian processes\n", "are around 1." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.obslayer.likelihood.variance[:] = Y.var()*0.01\n", "for layer in m.layers:\n", " layer.kern.variance.fix(warning=False)\n", " layer.likelihood.variance.fix(warning=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We now we optimize for a hundred iterations with the constrained model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.optimize(messages=False,max_iters=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we remove the fixed constraint on the kernel variance parameters,\n", "but keep the noise output constrained, and run for a further 100\n", "iterations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for layer in m.layers:\n", " layer.kern.variance.constrain_positive(warning=False)\n", "m.optimize(messages=False,max_iters=100)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally we unconstrain the layer likelihoods and allow the full model to\n", "be trained for 1000 iterations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "for layer in m.layers:\n", " layer.likelihood.variance.constrain_positive(warning=False)\n", "m.optimize(messages=True,max_iters=10000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Visualize the latent space of the top layer\n", "\n", "Now the model is trained, let's plot the mean of the posterior\n", "distributions in the top latent layer of the model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rc(\"font\", **{'family':'sans-serif','sans-serif':['Helvetica'],'size':20})\n", "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "for d in digits:\n", " ax.plot(m.layer_1.X.mean[labels==d,0],m.layer_1.X.mean[labels==d,1],'.',label=str(d))\n", "_ = plt.legend()\n", "mlai.write_figure(figure=fig, filename=\"../../slides/diagrams/deepgp/usps-digits-latent.svg\", transparent=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "\n", "### Visualize the latent space of the intermediate layer\n", "\n", "We can also visualize dimensions of the intermediate layer. First the\n", "lengthscale of those dimensions is given by" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m.obslayer.kern.lengthscale" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots(figsize=plot.big_figsize)\n", "for i in range(5):\n", " for j in range(i):\n", " dims=[i, j]\n", " ax.cla()\n", " for d in digits:\n", " ax.plot(m.obslayer.X.mean[labels==d,dims],\n", " m.obslayer.X.mean[labels==d,dims],\n", " '.', label=str(d))\n", " plt.legend()\n", " plt.xlabel('dimension ' + str(dims))\n", " plt.ylabel('dimension ' + str(dims))\n", " mlai.write_figure(figure=fig, filename=\"../../slides/diagrams/deepgp/usps-digits-hidden-\" + str(dims) + '-' + str(dims) + '.svg', transparent=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### {#section-4 data-transition=\"none\"}\n", "\n", " \n", "\n", "### {#section-5 data-transition=\"none\"}\n", "\n", " \n", "\n", "### {#section-6 data-transition=\"none\"}\n", "\n", " \n", "\n", "### {#section-7 data-transition=\"none\"}\n", "\n", " \n", "\n", "### Generate From Model\n", "\n", "Now we can take a look at a sample from the model, by drawing a Gaussian\n", "random sample in the latent space and propagating it through the model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "\n", "rows = 10\n", "cols = 20\n", "t=np.linspace(-1, 1, rows*cols)[:, None]\n", "kern = GPy.kern.RBF(1,lengthscale=0.05)\n", "cov = kern.K(t, t)\n", "x = np.random.multivariate_normal(np.zeros(rows*cols), cov, num_latent).T" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "yt = m.predict(x)\n", "fig, axs = plt.subplots(rows,cols,figsize=(10,6))\n", "for i in range(rows):\n", " for j in range(cols):\n", " #v = np.random.normal(loc=yt[i*cols+j, :], scale=np.sqrt(yt[i*cols+j, :]))\n", " v = yt[i*cols+j, :]\n", " axs[i,j].imshow(v.reshape(28,28), \n", " cmap='gray', interpolation='none',\n", " aspect='equal')\n", " axs[i,j].set_axis_off()\n", "mlai.write_figure(figure=fig, filename=\"../../slides/diagrams/deepgp/digit-samples-deep-gp.svg\", transparent=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### {#section-8 data-transition=\"none\"}\n", "\n", " \n", "\n", "### Deep Health {#deep-health data-transition=\"None\"}\n", "\n", " \n", "\n", "### At this Year's NIPS\n", "\n", "- *Gaussian process based nonlinear latent structure discovery in\n", " multivariate spike train data* @Anqi:gpspike2017\n", "- *Doubly Stochastic Variational Inference for Deep Gaussian\n", " Processes* @Salimbeni:doubly2017\n", "- *Deep Multi-task Gaussian Processes for Survival Analysis with\n", " Competing Risks* @Alaa:deep2017\n", "- *Counterfactual Gaussian Processes for Reliable Decision-making and\n", " What-if Reasoning* @Schulam:counterfactual17\n", "\n", "### Some Other Works\n", "\n", "- *Deep Survival Analysis* @Ranganath-survival16\n", "- *Recurrent Gaussian Processes* @Mattos:recurrent15\n", "- *Gaussian Process Based Approaches for Survival Analysis*\n", " @Saul:thesis2016\n", "\n", "### Uncertainty Quantification\n", "\n", "- Deep nets are powerful approach to images, speech, language.\n", "\n", "- Proposal: Deep GPs may also be a great approach, but better to\n", " deploy according to natural strengths.\n", "\n", "### Uncertainty Quantification\n", "\n", "- Probabilistic numerics, surrogate modelling, emulation, and UQ.\n", "\n", "- Not a fan of AI as a term.\n", "\n", "- But we are faced with increasing amounts of *algorithmic decision\n", " making*.\n", "\n", "### ML and Decision Making\n", "\n", "- When trading off decisions: compute or acquire data?\n", "\n", "- There is a critical need for uncertainty.\n", "\n", "### Uncertainty Quantification\n", "\n", "> Uncertainty quantification (UQ) is the science of quantitative\n", "> characterization and reduction of uncertainties in both computational\n", "> and real world applications. It tries to determine how likely certain\n", "> outcomes are if some aspects of the system are not exactly known.\n", "\n", "- Interaction between physical and virtual worlds of major interest\n", " for Amazon.\n", "\n", "We will to illustrate different concepts of [Uncertainty\n", "Quantification](https://en.wikipedia.org/wiki/Uncertainty_quantification)\n", "(UQ) and the role that Gaussian processes play in this field. Based on a\n", "simple simulator of a car moving between a valley and a mountain, we are\n", "going to illustrate the following concepts:\n", "\n", "- **Systems emulation**. Many real world decisions are based on\n", " simulations that can be computationally very demanding. We will show\n", " how simulators can be replaced by *emulators*: Gaussian process\n", " models fitted on a few simulations that can be used to replace the\n", " *simulator*. Emulators are cheap to compute, fast to run, and always\n", " provide ways to quantify the uncertainty of how precise they are\n", " compared the original simulator.\n", "\n", "- **Emulators in optimization problems**. We will show how emulators\n", " can be used to optimize black-box functions that are expensive to\n", " evaluate. This field is also called Bayesian Optimization and has\n", " gained an increasing relevance in machine learning as emulators can\n", " be used to optimize computer simulations (and machine learning\n", " algorithms) quite efficiently.\n", "\n", "- **Multi-fidelity emulation methods**. In many scenarios we have\n", " simulators of different quality about the same measure of interest.\n", " In these cases the goal is to merge all sources of information under\n", " the same model so the final emulator is cheaper and more accurate\n", " than an emulator fitted only using data from the most accurate and\n", " expensive simulator.\n", "\n", "### Example: Formula One Racing\n", "\n", "- Designing an F1 Car requires CFD, Wind Tunnel, Track Testing etc.\n", "\n", "- How to combine them?\n", "\n", "### Mountain Car Simulator\n", "\n", "To illustrate the above mentioned concepts we we use the [mountain car\n", "simulator](https://github.com/openai/gym/wiki/MountainCarContinuous-v0).\n", "This simulator is widely used in machine learning to test reinforcement\n", "learning algorithms. The goal is to define a control policy on a car\n", "whose objective is to climb a mountain. Graphically, the problem looks\n", "as follows:\n", "\n", " \n", "\n", "The goal is to define a sequence of actions (push the car right or left\n", "with certain intensity) to make the car reach the flag after a number\n", "$T$of time steps.\n", "\n", "At each time step$t$, the car is characterized by a vector\n", "$\\inputVector_{t} = (p_t,v_t)$of states which are respectively the the\n", "position and velocity of the car at time$t$. For a sequence of states\n", "(an episode), the dynamics of the car is given by\n", "\n", "$$\\inputVector_{t+1} = \\mappingFunction(\\inputVector_{t},\\textbf{u}_{t})$$\n", "\n", "where$\\textbf{u}_{t}$is the value of an action force, which in this\n", "example corresponds to push car to the left (negative value) or to the\n", "right (positive value). The actions across a full episode are\n", "represented in a policy$\\textbf{u}_{t} = \\pi(\\inputVector_{t},\\theta)$\n", "that acts according to the current state of the car and some parameters\n", "$\\theta$. In the following examples we will assume that the policy is\n", "linear which allows us to write$\\pi(\\inputVector_{t},\\theta)$as\n", "\n", "$$\\pi(\\inputVector,\\theta)= \\theta_0 + \\theta_p p + \\theta_vv.$$\n", "\n", "For$t=1,\\dots,T$now given some initial state$\\inputVector_{0}$and\n", "some some values of each$\\textbf{u}_{t}$, we can **simulate** the full\n", "dynamics of the car for a full episode using\n", "[Gym](https://gym.openai.com/envs/). The values of$\\textbf{u}_{t}$are\n", "fully determined by the parameters of the linear controller.\n", "\n", "After each episode of length$T$is complete, a reward function\n", "$R_{T}(\\theta)$is computed. In the mountain car example the reward is\n", "computed as 100 for reaching the target of the hill on the right hand\n", "side, minus the squared sum of actions (a real negative to push to the\n", "left and a real positive to push to the right) from start to goal. Note\n", "that our reward depend on$\\theta$as we make it dependent on the\n", "parameters of the linear controller.\n", "\n", "### Emulate the Mountain Car" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import gym" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "env = gym.make('MountainCarContinuous-v0')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our goal in this section is to find the parameters$\\theta$of the\n", "linear controller such that\n", "\n", "$$\\theta^* = arg \\max_{\\theta} R_T(\\theta).$$\n", "\n", "In this section, we directly use Bayesian optimization to solve this\n", "problem. We will use [GPyOpt](https://sheffieldml.github.io/GPyOpt/) so\n", "we first define the objective function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import mountain_car as mc\n", "import GPyOpt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "obj_func = lambda x: mc.run_simulation(env, x)\n", "objective = GPyOpt.core.task.SingleObjective(obj_func)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For each set of parameter values of the linear controller we can run an\n", "episode of the simulator (that we fix to have a horizon of$T=500$) to\n", "generate the reward. Using as input the parameters of the controller and\n", "as outputs the rewards we can build a Gaussian process emulator of the\n", "reward.\n", "\n", "We start defining the input space, which is three-dimensional:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## --- We define the input space of the emulator\n", "\n", "space= [{'name':'postion_parameter', 'type':'continuous', 'domain':(-1.2, +1)},\n", " {'name':'velocity_parameter', 'type':'continuous', 'domain':(-1/0.07, +1/0.07)},\n", " {'name':'constant', 'type':'continuous', 'domain':(-1, +1)}]\n", "\n", "design_space = GPyOpt.Design_space(space=space)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we initizialize a Gaussian process emulator." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = GPyOpt.models.GPModel(optimize_restarts=5, verbose=False, exact_feval=True, ARD=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In Bayesian optimization an acquisition function is used to balance\n", "exploration and exploitation to evaluate new locations close to the\n", "optimum of the objective. In this notebook we select the expected\n", "improvement (EI). For further details have a look to the review paper of\n", "[Shahriari et al\n", "(2015)](http://www.cs.ox.ac.uk/people/nando.defreitas/publications/BayesOptLoop.pdf)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "aquisition_optimizer = GPyOpt.optimization.AcquisitionOptimizer(design_space)\n", "acquisition = GPyOpt.acquisitions.AcquisitionEI(model, design_space, optimizer=aquisition_optimizer)\n", "evaluator = GPyOpt.core.evaluators.Sequential(acquisition) # Collect points sequentially, no parallelization." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To initalize the model we start sampling some initial points (25) for\n", "the linear controler randomly." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from GPyOpt.experiment_design.random_design import RandomDesign" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "n_initial_points = 25\n", "random_design = RandomDesign(design_space)\n", "initial_design = random_design.get_samples(n_initial_points)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Before we start any optimization, lets have a look to the behavior of\n", "the car with the first of these initial points that we have selected\n", "randomly." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "random_controller = initial_design[0,:]\n", "_, _, _, frames = mc.run_simulation(env, np.atleast_2d(random_controller), render=True)\n", "anim=mc.animate_frames(frames, 'Random linear controller')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "HTML(anim.to_jshtml())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mc.save_frames(frames, \n", " diagrams='../slides/diagrams/uq', \n", " filename='mountain_car_random.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "As we can see the random linear controller does not manage to push the\n", "car to the top of the mountain. Now, let's optimize the regret using\n", "Bayesian optimization and the emulator for the reward. We try 50 new\n", "parameters chosen by the EI." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "max_iter = 50\n", "bo = GPyOpt.methods.ModularBayesianOptimization(model, design_space, objective, acquisition, evaluator, initial_design)\n", "bo.run_optimization(max_iter = max_iter )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we visualize the result for the best controller that we have found\n", "with Bayesian optimization." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_, _, _, frames = mc.run_simulation(env, np.atleast_2d(bo.x_opt), render=True)\n", "anim=mc.animate_frames(frames, 'Best controller after 50 iterations of Bayesian optimization')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "HTML(anim.to_jshtml())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mc.save_frames(frames, \n", " diagrams='../slides/diagrams/uq', \n", " filename='mountain_car_simulated.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "he car can now make it to the top of the mountain! Emulating the reward\n", "function and using the EI helped as to find a linear controller that\n", "solves the problem.\n", "\n", "### Data Efficient Emulation\n", "\n", "In the previous section we solved the mountain car problem by directly\n", "emulating the reward but no considerations about the dynamics\n", "$\\inputVector_{t+1} = \\mappingFunction(\\inputVector_{t},\\textbf{u}_{t})$\n", "of the system were made. Note that we had to run 75 episodes of 500\n", "steps each to solve the problem, which required to call the simulator\n", "$500\\times 75 =37500$times. In this section we will show how it is\n", "possible to reduce this number by building an emulator for$f$that can\n", "later be used to directly optimize the control.\n", "\n", "The inputs of the model for the dynamics are the velocity, the position\n", "and the value of the control so create this space accordingly." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import gym" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "env = gym.make('MountainCarContinuous-v0')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import GPyOpt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "space_dynamics = [{'name':'position', 'type':'continuous', 'domain':[-1.2, +0.6]},\n", " {'name':'velocity', 'type':'continuous', 'domain':[-0.07, +0.07]},\n", " {'name':'action', 'type':'continuous', 'domain':[-1, +1]}]\n", "design_space_dynamics = GPyOpt.Design_space(space=space_dynamics)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The outputs are the velocity and the position. Indeed our model will\n", "capture the change in position and velocity on time. That is, we will\n", "model\n", "\n", "$$\\Delta v_{t+1} = v_{t+1} - v_{t}$$\n", "\n", "$$\\Delta x_{t+1} = p_{t+1} - p_{t}$$\n", "\n", "with Gaussian processes with prior mean$v_{t}$and$p_{t}$\n", "respectively. As a covariance function, we use a Matern52. We need\n", "therefore two models to capture the full dynamics of the system." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_model = GPyOpt.models.GPModel(optimize_restarts=5, verbose=False, exact_feval=True, ARD=True)\n", "velocity_model = GPyOpt.models.GPModel(optimize_restarts=5, verbose=False, exact_feval=True, ARD=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we sample some input parameters and use the simulator to compute\n", "the outputs. Note that in this case we are not running the full\n", "episodes, we are just using the simulator to compute\n", "$\\inputVector_{t+1}$given$\\inputVector_{t}$and$\\textbf{u}_{t}$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "from GPyOpt.experiment_design.random_design import RandomDesign\n", "import mountain_car as mc" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### --- Random locations of the inputs\n", "n_initial_points = 500\n", "random_design_dynamics = RandomDesign(design_space_dynamics)\n", "initial_design_dynamics = random_design_dynamics.get_samples(n_initial_points)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### --- Simulation of the (normalized) outputs\n", "y = np.zeros((initial_design_dynamics.shape, 2))\n", "for i in range(initial_design_dynamics.shape):\n", " y[i, :] = mc.simulation(initial_design_dynamics[i, :])\n", "\n", "# Normalize the data from the simulation\n", "y_normalisation = np.std(y, axis=0)\n", "y_normalised = y/y_normalisation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In general we might use much smarter strategies to design our emulation\n", "of the simulator. For example, we could use the variance of the\n", "predictive distributions of the models to collect points using\n", "uncertainty sampling, which will give us a better coverage of the space.\n", "For simplicity, we move ahead with the 500 randomly selected points.\n", "\n", "Now that we have a data set, we can update the emulators for the\n", "location and the velocity." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "position_model.updateModel(initial_design_dynamics, y[:, ], None, None)\n", "velocity_model.updateModel(initial_design_dynamics, y[:, ], None, None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now have a look to how the emulator and the simulator match.\n", "First, we show a contour plot of the car aceleration for each pair of\n", "can position and velocity. You can use the bar bellow to play with the\n", "values of the controler to compare the emulator and the simulator." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.html.widgets import interact" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "control = mc.plot_control(velocity_model)\n", "interact(control.plot_slices, control=(-1, 1, 0.05))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "We can see how the emulator is doing a fairly good job approximating the\n", "simulator. On the edges, however, it struggles to captures the dynamics\n", "of the simulator.\n", "\n", "Given some input parameters of the linear controlling, how do the\n", "dynamics of the emulator and simulator match? In the following figure we\n", "show the position and velocity of the car for the 500 time steps of an\n", "episode in which the parameters of the linear controller have been fixed\n", "beforehand. The value of the input control is also shown." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "controller_gains = np.atleast_2d([0, .6, 1]) # change the valus of the linear controller to observe the trayectories." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mc.emu_sim_comparison(env, controller_gains, [position_model, velocity_model], \n", " max_steps=500, diagrams='../slides/diagrams/uq')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " \n", "\n", "We now make explicit use of the emulator, using it to replace the\n", "simulator and optimize the linear controller. Note that in this\n", "optimization, we don't need to query the simulator anymore as we can\n", "reproduce the full dynamics of an episode using the emulator. For\n", "illustrative purposes, in this example we fix the initial location of\n", "the car.\n", "\n", "We define the objective reward function in terms of the simulator." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### --- Optimize control parameters with emulator\n", "car_initial_location = np.asarray([-0.58912799, 0]) \n", "\n", "### --- Reward objective function using the emulator\n", "obj_func_emulator = lambda x: mc.run_emulation([position_model, velocity_model], x, car_initial_location)\n", "objective_emulator = GPyOpt.core.task.SingleObjective(obj_func_emulator)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And as before, we use Bayesian optimization to find the best possible\n", "linear controller." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### --- Elements of the optimization that will use the multi-fidelity emulator\n", "model = GPyOpt.models.GPModel(optimize_restarts=5, verbose=False, exact_feval=True, ARD=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The design space is the three continuous variables that make up the\n", "linear controller." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "space= [{'name':'linear_1', 'type':'continuous', 'domain':(-1/1.2, +1)},\n", " {'name':'linear_2', 'type':'continuous', 'domain':(-1/0.07, +1/0.07)},\n", " {'name':'constant', 'type':'continuous', 'domain':(-1, +1)}]\n", "\n", "design_space = GPyOpt.Design_space(space=space)\n", "aquisition_optimizer = GPyOpt.optimization.AcquisitionOptimizer(design_space)\n", "\n", "random_design = RandomDesign(design_space)\n", "initial_design = random_design.get_samples(25)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We set the acquisition function to be expected improvement using\n", "GPyOpt." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "acquisition = GPyOpt.acquisitions.AcquisitionEI(model, design_space, optimizer=aquisition_optimizer)\n", "evaluator = GPyOpt.core.evaluators.Sequential(acquisition)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bo_emulator = GPyOpt.methods.ModularBayesianOptimization(model, design_space, objective_emulator, acquisition, evaluator, initial_design)\n", "bo_emulator.run_optimization(max_iter=50)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_, _, _, frames = mc.run_simulation(env, np.atleast_2d(bo_emulator.x_opt), render=True)\n", "anim=mc.animate_frames(frames, 'Best controller using the emulator of the dynamics')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "HTML(anim.to_jshtml())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mc.save_frames(frames, \n", " diagrams='../slides/diagrams/uq', \n", " filename='mountain_car_emulated.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "And the problem is again solved, but in this case we have replaced the\n", "simulator of the car dynamics by a Gaussian process emulator that we\n", "learned by calling the simulator only 500 times. Compared to the 37500\n", "calls that we needed when applying Bayesian optimization directly on the\n", "simulator this is a great gain.\n", "\n", "In some scenarios we have simulators of the same environment that have\n", "different fidelities, that is that reflect with different level of\n", "accuracy the dynamics of the real world. Running simulations of the\n", "different fidelities also have a different cost: hight fidelity\n", "simulations are more expensive the cheaper ones. If we have access to\n", "these simulators we can combine high and low fidelity simulations under\n", "the same model.\n", "\n", "So let's assume that we have two simulators of the mountain car\n", "dynamics, one of high fidelity (the one we have used) and another one of\n", "low fidelity. The traditional approach to this form of multi-fidelity\n", "emulation is to assume that\n", "\n", "$$\\mappingFunction_i\\left(\\inputVector\\right) = \\rho\\mappingFunction_{i-1}\\left(\\inputVector\\right) + \\delta_i\\left(\\inputVector \\right)$$\n", "\n", "where$\\mappingFunction_{i-1}\\left(\\inputVector\\right)$is a low\n", "fidelity simulation of the problem of interest and\n", "$\\mappingFunction_i\\left(\\inputVector\\right)$is a higher fidelity\n", "simulation. The function$\\delta_i\\left(\\inputVector \\right)$represents\n", "the difference between the lower and higher fidelity simulation, which\n", "is considered additive. The additive form of this covariance means that\n", "if$\\mappingFunction_{0}\\left(\\inputVector\\right)$and\n", "$\\left\\{\\delta_i\\left(\\inputVector \\right)\\right\\}_{i=1}^m$are all\n", "Gaussian processes, then the process over all fidelities of simuation\n", "will be a joint Gaussian process.\n", "\n", "But with Deep Gaussian processes we can consider the form\n", "\n", "$$\\mappingFunction_i\\left(\\inputVector\\right) = \\mappingFunctionTwo_{i}\\left(\\mappingFunction_{i-1}\\left(\\inputVector\\right)\\right) + \\delta_i\\left(\\inputVector \\right),$$\n", "\n", "where the low fidelity representation is non linearly transformed by\n", "$\\mappingFunctionTwo(\\cdot)$before use in the process. This is the\n", "approach taken in @Perdikaris:multifidelity17. But once we accept that\n", "these models can be composed, a highly flexible framework can emerge. A\n", "key point is that the data enters the model at different levels, and\n", "represents different aspects. For example these correspond to the two\n", "fidelities of the mountain car simulator.\n", "\n", "We start by sampling both of them at 250 random input locations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import gym" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "env = gym.make('MountainCarContinuous-v0')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import GPyOpt" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### --- Collect points from low and high fidelity simulator --- ###\n", "\n", "space = GPyOpt.Design_space([\n", " {'name':'position', 'type':'continuous', 'domain':(-1.2, +1)},\n", " {'name':'velocity', 'type':'continuous', 'domain':(-0.07, +0.07)},\n", " {'name':'action', 'type':'continuous', 'domain':(-1, +1)}])\n", "\n", "n_points = 250\n", "random_design = GPyOpt.experiment_design.RandomDesign(space)\n", "x_random = random_design.get_samples(n_points)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, we evaluate the high and low fidelity simualtors at those\n", "locations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import mountain_car as mc" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "d_position_hf = np.zeros((n_points, 1))\n", "d_velocity_hf = np.zeros((n_points, 1))\n", "d_position_lf = np.zeros((n_points, 1))\n", "d_velocity_lf = np.zeros((n_points, 1))\n", "\n", "# --- Collect high fidelity points\n", "for i in range(0, n_points):\n", " d_position_hf[i], d_velocity_hf[i] = mc.simulation(x_random[i, :])\n", "\n", "# --- Collect low fidelity points \n", "for i in range(0, n_points):\n", " d_position_lf[i], d_velocity_lf[i] = mc.low_cost_simulation(x_random[i, :])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is time to build the multi-fidelity model for both the position and\n", "the velocity.\n", "\n", "As we did in the previous section we use the emulator to optimize the\n", "simulator. In this case we use the high fidelity output of the emulator." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "### --- Optimize controller parameters \n", "obj_func = lambda x: mc.run_simulation(env, x)\n", "obj_func_emulator = lambda x: mc.run_emulation([position_model, velocity_model], x, car_initial_location)\n", "objective_multifidelity = GPyOpt.core.task.SingleObjective(obj_func)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And we optimize using Bayesian optimzation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from GPyOpt.experiment_design.random_design import RandomDesign" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = GPyOpt.models.GPModel(optimize_restarts=5, verbose=False, exact_feval=True, ARD=True)\n", "space= [{'name':'linear_1', 'type':'continuous', 'domain':(-1/1.2, +1)},\n", " {'name':'linear_2', 'type':'continuous', 'domain':(-1/0.07, +1/0.07)},\n", " {'name':'constant', 'type':'continuous', 'domain':(-1, +1)}]\n", "\n", "design_space = GPyOpt.Design_space(space=space)\n", "aquisition_optimizer = GPyOpt.optimization.AcquisitionOptimizer(design_space)\n", "\n", "n_initial_points = 25\n", "random_design = RandomDesign(design_space)\n", "initial_design = random_design.get_samples(n_initial_points)\n", "acquisition = GPyOpt.acquisitions.AcquisitionEI(model, design_space, optimizer=aquisition_optimizer)\n", "evaluator = GPyOpt.core.evaluators.Sequential(acquisition)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "bo_multifidelity = GPyOpt.methods.ModularBayesianOptimization(model, design_space, objective_multifidelity, acquisition, evaluator, initial_design)\n", "bo_multifidelity.run_optimization(max_iter=50)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "_, _, _, frames = mc.run_simulation(env, np.atleast_2d(bo_multifidelity.x_opt), render=True)\n", "anim=mc.animate_frames(frames, 'Best controller with multi-fidelity emulator')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "HTML(anim.to_jshtml())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mc.save_frames(frames, \n", " diagrams='../slides/diagrams/uq', \n", " filename='mountain_car_multi_fidelity.html')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "And problem solved! We see how the problem is also solved with 250\n", "observations of the high fidelity simulator and 250 of the low fidelity\n", "simulator.\n", "\n", "### Acknowledgments\n", "\n", "Stefanos Eleftheriadis, John Bronskill, Hugh Salimbeni, Rich Turner,\n", "Zhenwen Dai, Javier Gonzalez, Andreas Damianou, Mark Pullin.\n", "\n", "### Ongoing Code\n", "\n", "- Powerful framework but\n", "\n", "- Software isn't there yet.\n", "\n", "- Our focus: Gaussian Processes driven by MXNet\n", "\n", "- Composition of GPs, Neural Networks, Other Models\n", "\n", "### Thanks!\n", "\n", "- twitter: @lawrennd\n", "- blog:\n", " [http://inverseprobability.com](http://inverseprobability.com/blog.html)\n", "\n", "### References {#references .unnumbered .allowframebreaks}\n", "\n", "[^1]: In classical statistics we often interpret these parameters,\n", "$\\beta$, whereas in machine learning we are normally more interested\n", " in the result of the prediction, and less in the prediction.\n", " Although this is changing with more need for accountability. In\n", " honour of this I normally use$\\boldsymbol{\\beta}$when I care about\n", " the value of these parameters, and$\\mappingVector\$ when I care more\n", " about the quality of the prediction." ] } ], "metadata": {}, "nbformat": 4, "nbformat_minor": 2 }