diff --git a/doc/BookChapters/chapter1.dlog b/doc/BookChapters/chapter1.dlog index 875fd7507..20c0c21c2 100644 --- a/doc/BookChapters/chapter1.dlog +++ b/doc/BookChapters/chapter1.dlog @@ -43,3 +43,10 @@ Translating doconce text in chapter1.do.txt to ipynb Failed to remove ans_at_end environment Failed to remove sol_at_end environment output in chapter1.ipynb +Translating doconce text in chapter1.do.txt to ipynb +*** replacing \bm{...} by \boldsymbol{...} (\bm is not supported by MathJax) + +*** warning: latex envir \begin{bmatrix} does not work well in Markdown. Stick to \[ ... \], equation, equation*, align, or align* environments in math environments. + +*** warning: latex envir \begin{bmatrix} does not work well in Markdown. Stick to \[ ... \], equation, equation*, align, or align* environments in math environments. +output in chapter1.ipynb diff --git a/doc/BookChapters/chapter1.ipynb b/doc/BookChapters/chapter1.ipynb new file mode 100644 index 000000000..9da67625f --- /dev/null +++ b/doc/BookChapters/chapter1.ipynb @@ -0,0 +1,4320 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0da18600", + "metadata": { + "editable": true + }, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "46f505d3", + "metadata": { + "editable": true + }, + "source": [ + "# Linear Regression" + ] + }, + { + "cell_type": "markdown", + "id": "bba93bfc", + "metadata": { + "editable": true + }, + "source": [ + "## Introduction\n", + "\n", + "Our emphasis throughout this series of lectures is on understanding\n", + "the mathematical aspects of different algorithms used in the fields of\n", + "data analysis and machine learning.\n", + "\n", + "However, where possible we will emphasize the importance of using\n", + "available software. We start thus with a hands-on and top-down\n", + "approach to machine learning. The aim is thus to start with relevant\n", + "data or data we have produced and use these to introduce statistical\n", + "data analysis concepts and machine learning algorithms before we delve\n", + "into the algorithms themselves. The examples we will use in the\n", + "beginning, start with simple polynomials with random noise added. We\n", + "will use the Python software package\n", + "[Scikit-Learn](http://scikit-learn.org/stable/) and introduce various\n", + "machine learning algorithms to make fits of the data and\n", + "predictions. We move thereafter to more interesting cases such as data\n", + "from say experiments (below we will look at experimental nuclear\n", + "binding energies as an example). These are examples where we can\n", + "easily set up the data and then use machine learning algorithms\n", + "included in for example **Scikit-Learn**.\n", + "\n", + "These examples will serve us the purpose of getting\n", + "started. Furthermore, they allow us to catch more than two birds with\n", + "a stone. They will allow us to bring in some programming specific\n", + "topics and tools as well as showing the power of various Python\n", + "libraries for machine learning and statistical data analysis.\n", + "\n", + "Here, we will mainly focus on two specific Python packages for Machine\n", + "Learning, Scikit-Learn and Tensorflow (see below for links etc).\n", + "Moreover, the examples we introduce will serve as inputs to many of\n", + "our discussions later, as well as allowing you to set up models and\n", + "produce your own data and get started with programming." + ] + }, + { + "cell_type": "markdown", + "id": "c6fa0403", + "metadata": { + "editable": true + }, + "source": [ + "## What is Machine Learning?\n", + "\n", + "Statistics, data science and machine learning form important fields of\n", + "research in modern science. They describe how to learn and make\n", + "predictions from data, as well as allowing us to extract important\n", + "correlations about physical process and the underlying laws of motion\n", + "in large data sets. The latter, big data sets, appear frequently in\n", + "essentially all disciplines, from the traditional Science, Technology,\n", + "Mathematics and Engineering fields to Life Science, Law, education\n", + "research, the Humanities and the Social Sciences. \n", + "\n", + "It has become more\n", + "and more common to see research projects on big data in for example\n", + "the Social Sciences where extracting patterns from complicated survey\n", + "data is one of many research directions. Having a solid grasp of data\n", + "analysis and machine learning is thus becoming central to scientific\n", + "computing in many fields, and competences and skills within the fields\n", + "of machine learning and scientific computing are nowadays strongly\n", + "requested by many potential employers. The latter cannot be\n", + "overstated, familiarity with machine learning has almost become a\n", + "prerequisite for many of the most exciting employment opportunities,\n", + "whether they are in bioinformatics, life science, physics or finance,\n", + "in the private or the public sector. This author has had several\n", + "students or met students who have been hired recently based on their\n", + "skills and competences in scientific computing and data science, often\n", + "with marginal knowledge of machine learning.\n", + "\n", + "Machine learning is a subfield of computer science, and is closely\n", + "related to computational statistics. It evolved from the study of\n", + "pattern recognition in artificial intelligence (AI) research, and has\n", + "made contributions to AI tasks like computer vision, natural language\n", + "processing and speech recognition. Many of the methods we will study are also \n", + "strongly rooted in basic mathematics and physics research. \n", + "\n", + "Ideally, machine learning represents the science of giving computers\n", + "the ability to learn without being explicitly programmed. The idea is\n", + "that there exist generic algorithms which can be used to find patterns\n", + "in a broad class of data sets without having to write code\n", + "specifically for each problem. The algorithm will build its own logic\n", + "based on the data. You should however always keep in mind that\n", + "machines and algorithms are to a large extent developed by humans. The\n", + "insights and knowledge we have about a specific system, play a central\n", + "role when we develop a specific machine learning algorithm. \n", + "\n", + "Machine learning is an extremely rich field, in spite of its young\n", + "age. The increases we have seen during the last three decades in\n", + "computational capabilities have been followed by developments of\n", + "methods and techniques for analyzing and handling large date sets,\n", + "relying heavily on statistics, computer science and mathematics. The\n", + "field is rather new and developing rapidly. Popular software packages\n", + "written in Python for machine learning like\n", + "[Scikit-learn](http://scikit-learn.org/stable/),\n", + "[Tensorflow](https://www.tensorflow.org/),\n", + "[PyTorch](http://pytorch.org/) and [Keras](https://keras.io/), all\n", + "freely available at their respective GitHub sites, encompass\n", + "communities of developers in the thousands or more. And the number of\n", + "code developers and contributors keeps increasing. Not all the\n", + "algorithms and methods can be given a rigorous mathematical\n", + "justification, opening up thereby large rooms for experimenting and\n", + "trial and error and thereby exciting new developments. However, a\n", + "solid command of linear algebra, multivariate theory, probability\n", + "theory, statistical data analysis, understanding errors and Monte\n", + "Carlo methods are central elements in a proper understanding of many\n", + "of algorithms and methods we will discuss.\n", + "\n", + "The approaches to machine learning are many, but are often split into\n", + "two main categories. In *supervised learning* we know the answer to a\n", + "problem, and let the computer deduce the logic behind it. On the other\n", + "hand, *unsupervised learning* is a method for finding patterns and\n", + "relationship in data sets without any prior knowledge of the system.\n", + "Some authours also operate with a third category, namely\n", + "*reinforcement learning*. This is a paradigm of learning inspired by\n", + "behavioral psychology, where learning is achieved by trial-and-error,\n", + "solely from rewards and punishment.\n", + "\n", + "Another way to categorize machine learning tasks is to consider the\n", + "desired output of a system. Some of the most common tasks are:\n", + "\n", + " * Classification: Outputs are divided into two or more classes. The goal is to produce a model that assigns inputs into one of these classes. An example is to identify digits based on pictures of hand-written ones. Classification is typically supervised learning.\n", + "\n", + " * Regression: Finding a functional relationship between an input data set and a reference data set. The goal is to construct a function that maps input data to continuous output values.\n", + "\n", + " * Clustering: Data are divided into groups with certain common traits, without knowing the different groups beforehand. It is thus a form of unsupervised learning.\n", + "\n", + "The methods we cover have three main topics in common, irrespective of\n", + "whether we deal with supervised or unsupervised learning.\n", + "* The first ingredient is normally our data set (which can be subdivided into training, validation and test data). Many find the most difficult part of using Machine Learning to be the set up of your data in a meaningful way. \n", + "\n", + "* The second item is a model which is normally a function of some parameters. The model reflects our knowledge of the system (or lack thereof). As an example, if we know that our data show a behavior similar to what would be predicted by a polynomial, fitting our data to a polynomial of some degree would then determin our model. \n", + "\n", + "* The last ingredient is a so-called **cost/loss** function (or error or risk function) which allows us to present an estimate on how good our model is in reproducing the data it is supposed to train. \n", + "\n", + "At the heart of basically all Machine Learning algorithms we will encounter so-called minimization or optimization algorithms. A large family of such methods are so-called **gradient methods**." + ] + }, + { + "cell_type": "markdown", + "id": "87e67bf0", + "metadata": { + "editable": true + }, + "source": [ + "### A Frequentist approach to data analysis\n", + "\n", + "When you hear phrases like **predictions and estimations** and\n", + "**correlations and causations**, what do you think of? May be you think\n", + "of the difference between classifying new data points and generating\n", + "new data points.\n", + "Or perhaps you consider that correlations represent some kind of symmetric statements like\n", + "if $A$ is correlated with $B$, then $B$ is correlated with\n", + "$A$. Causation on the other hand is directional, that is if $A$ causes $B$, $B$ does not\n", + "necessarily cause $A$.\n", + "\n", + "These concepts are in some sense the difference between machine\n", + "learning and statistics. In machine learning and prediction based\n", + "tasks, we are often interested in developing algorithms that are\n", + "capable of learning patterns from given data in an automated fashion,\n", + "and then using these learned patterns to make predictions or\n", + "assessments of newly given data. In many cases, our primary concern\n", + "is the quality of the predictions or assessments, and we are less\n", + "concerned about the underlying patterns that were learned in order\n", + "to make these predictions.\n", + "\n", + "In machine learning we normally use [a so-called frequentist approach](https://en.wikipedia.org/wiki/Frequentist_inference),\n", + "where the aim is to make predictions and find correlations. We focus\n", + "less on for example extracting a probability distribution function (PDF). The PDF can be\n", + "used in turn to make estimations and find causations such as given $A$\n", + "what is the likelihood of finding $B$." + ] + }, + { + "cell_type": "markdown", + "id": "116ff5d7", + "metadata": { + "editable": true + }, + "source": [ + "### What is a good model?\n", + "\n", + "In science and engineering we often end up in situations where we want to infer (or learn) a\n", + "quantitative model $M$ for a given set of sample points $\\boldsymbol{X} \\in [x_1, x_2,\\dots x_N]$.\n", + "\n", + "As we will see repeatedely in these lectures, we could try to fit these data points to a model given by a\n", + "straight line, or if we wish to be more sophisticated to a more complex\n", + "function.\n", + "\n", + "The reason for inferring such a model is that it\n", + "serves many useful purposes. On the one hand, the model can reveal information\n", + "encoded in the data or underlying mechanisms from which the data were generated. For instance, we could discover important\n", + "corelations that relate interesting physics interpretations.\n", + "\n", + "In addition, it can simplify the representation of the given data set and help\n", + "us in making predictions about future data samples.\n", + "\n", + "A first important consideration to keep in mind is that inferring the *correct* model\n", + "for a given data set is an elusive, if not impossible, task. The fundamental difficulty\n", + "is that if we are not specific about what we mean by a *correct* model, there\n", + "could easily be many different models that fit the given data set *equally well*.\n", + "\n", + "The central question is this: what leads us to say that a model is correct or\n", + "optimal for a given data set? To make the model inference problem well posed, i.e.,\n", + "to guarantee that there is a unique optimal model for the given data, we need to\n", + "impose additional assumptions or restrictions on the class of models considered. To\n", + "this end, we should not be looking for just any model that can describe the data.\n", + "Instead, we should look for a **model** $M$ that is the best among a restricted class\n", + "of models. In addition, to make the model inference problem computationally\n", + "tractable, we need to specify how restricted the class of models needs to be. A\n", + "common strategy is to start \n", + "with the simplest possible class of models that is just necessary to describe the data\n", + "or solve the problem at hand. More precisely, the model class should be rich enough\n", + "to contain at least one model that can fit the data to a desired accuracy and yet be\n", + "restricted enough that it is relatively simple to find the best model for the given data.\n", + "\n", + "Thus, the most popular strategy is to start from the\n", + "simplest class of models and increase the complexity of the models only when the\n", + "simpler models become inadequate. For instance, if we work with a regression problem to fit a set of sample points, one\n", + "may first try the simplest class of models, namely linear models, followed obviously by more complex models.\n", + "\n", + "How to evaluate which model fits best the data is something we will come back to over and over again in these sets of lectures." + ] + }, + { + "cell_type": "markdown", + "id": "e813d7e7", + "metadata": { + "editable": true + }, + "source": [ + "## Simple linear regression model using **scikit-learn**\n", + "\n", + "We start with perhaps our simplest possible example, using\n", + "**Scikit-Learn** to perform linear regression analysis on a data set\n", + "produced by us.\n", + "\n", + "What follows is a simple Python code where we have defined a function\n", + "$y$ in terms of the variable $x$. Both are defined as vectors with $100$ entries. \n", + "The numbers in the vector $\\boldsymbol{x}$ are given\n", + "by random numbers generated with a uniform distribution with entries\n", + "$x_i \\in [0,1]$ (more about probability distribution functions\n", + "later). These values are then used to define a function $y(x)$\n", + "(tabulated again as a vector) with a linear dependence on $x$ plus a\n", + "random noise added via the normal distribution.\n", + "\n", + "The Numpy functions are imported used the **import numpy as np**\n", + "statement and the random number generator for the uniform distribution\n", + "is called using the function **np.random.rand()**, where we specificy\n", + "that we want $100$ random variables. Using Numpy we define\n", + "automatically an array with the specified number of elements, $100$ in\n", + "our case. With the Numpy function **randn()** we can compute random\n", + "numbers with the normal distribution (mean value $\\mu$ equal to zero and\n", + "variance $\\sigma^2$ set to one) and produce the values of $y$ assuming a linear\n", + "dependence as function of $x$" + ] + }, + { + "cell_type": "markdown", + "id": "3bdb20d3", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y = 2x+N(0,1),\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "ff28cbbb", + "metadata": { + "editable": true + }, + "source": [ + "where $N(0,1)$ represents random numbers generated by the normal\n", + "distribution. From **Scikit-Learn** we import then the\n", + "**LinearRegression** functionality and make a prediction $\\tilde{y} =\n", + "\\alpha + \\beta x$ using the function **fit(x,y)**. We call the set of\n", + "data $(\\boldsymbol{x},\\boldsymbol{y})$ for our training data. The Python package\n", + "**scikit-learn** has also a functionality which extracts the above\n", + "fitting parameters $\\alpha$ and $\\beta$ (see below). Later we will\n", + "distinguish between training data and test data.\n", + "\n", + "For plotting we use the Python package\n", + "[matplotlib](https://matplotlib.org/) which produces publication\n", + "quality figures. Feel free to explore the extensive\n", + "[gallery](https://matplotlib.org/gallery/index.html) of examples. In\n", + "this example we plot our original values of $x$ and $y$ as well as the\n", + "prediction **ypredict** ($\\tilde{y}$), which attempts at fitting our\n", + "data with a straight line. Note also that **Scikit-Learn** requires a\n", + "matrix as input for the input values $x$ and $y$. In the above code we\n", + "have solved this by declaring $x$ and $y$ as arrays of dimension\n", + "$n\\times 1$.\n", + "\n", + "In the code here we have also made a new array for $x\\in [0,1]$. Our\n", + "prediction is computed for these values, meaning that they were not\n", + "included in the data set used to *train* (or fit) the model.\n", + "This is a recurrring theme in machine learning and data analysis. We would like to train a model on a specific given data set.\n", + "Thereafter we wish to apply it to data which were not included in the training. Below we will encounter this again in the so-called *train-validate-test* spliting. We will typically split our data into different sets, oen for training, one for validation and finally, our data from the untouched test vault!\n", + "\n", + "The Python code follows here." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "849e2d5e", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "\n", + "# Importing various packages\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from sklearn.linear_model import LinearRegression\n", + "\n", + "x = np.random.rand(100,1)\n", + "y = 2*x+np.random.randn(100,1)\n", + "linreg = LinearRegression()\n", + "linreg.fit(x,y)\n", + "# This is our new x-array to which we test our model\n", + "xnew = np.array([[0],[1]])\n", + "ypredict = linreg.predict(xnew)\n", + "\n", + "plt.plot(xnew, ypredict, \"r-\")\n", + "plt.plot(x, y ,'ro')\n", + "plt.axis([0,1.0,0, 5.0])\n", + "plt.xlabel(r'$x$')\n", + "plt.ylabel(r'$y$')\n", + "plt.title(r'Simple Linear Regression')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "734feee8", + "metadata": { + "editable": true + }, + "source": [ + "This example serves several aims. It allows us to demonstrate several\n", + "aspects of data analysis and later machine learning algorithms. The\n", + "immediate visualization shows that our linear fit is not\n", + "impressive. It goes through the data points, but there are many\n", + "outliers which are not reproduced by our linear regression. We could\n", + "now play around with this small program and change for example the\n", + "factor in front of $x$ and the normal distribution. Try to change the\n", + "function $y$ to" + ] + }, + { + "cell_type": "markdown", + "id": "c5bcc732", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y = 10x+0.01 \\times N(0,1),\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "cb80335e", + "metadata": { + "editable": true + }, + "source": [ + "where $x$ is defined as before. Does the fit look better? Indeed, by\n", + "reducing the role of the noise given by the normal distribution we see immediately that\n", + "our linear prediction seemingly reproduces better the training\n", + "set. However, this testing 'by the eye' is obviouly not satisfactory in the\n", + "long run. Here we have only defined the training data and our model, and \n", + "have not discussed a more rigorous approach to the **cost** function.\n", + "\n", + "We need more rigorous criteria in defining whether we have succeeded or\n", + "not in modeling our training data. You will be surprised to see that\n", + "many scientists seldomly venture beyond this 'by the eye' approach. A\n", + "standard approach for the *cost* function is the so-called $\\chi^2$\n", + "function (a variant of the mean-squared error (MSE))" + ] + }, + { + "cell_type": "markdown", + "id": "03d140b0", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\chi^2 = \\frac{1}{n}\n", + "\\sum_{i=0}^{n-1}\\frac{(y_i-\\tilde{y}_i)^2}{\\sigma_i^2},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "6dbeb67e", + "metadata": { + "editable": true + }, + "source": [ + "where $\\sigma_i^2$ is the variance (to be defined later) of the entry\n", + "$y_i$. We may not know the explicit value of $\\sigma_i^2$, it serves\n", + "however the aim of scaling the equations and make the cost function\n", + "dimensionless. \n", + "\n", + "Minimizing the cost function is a central aspect of\n", + "our discussions to come. Finding its minima as function of the model\n", + "parameters ($\\alpha$ and $\\beta$ in our case) will be a recurring\n", + "theme in these series of lectures. Essentially all machine learning\n", + "algorithms we will discuss center around the minimization of the\n", + "chosen cost function. This depends in turn on our specific\n", + "model for describing the data, a typical situation in supervised\n", + "learning. Automatizing the search for the minima of the cost function is a\n", + "central ingredient in all algorithms. Typical methods which are\n", + "employed are various variants of **gradient** methods. These will be\n", + "discussed in more detail later. Again, you'll be surprised to hear that\n", + "many practitioners minimize the above function ''by the eye', popularly dubbed as \n", + "'chi by the eye'. That is, change a parameter and see (visually and numerically) that \n", + "the $\\chi^2$ function becomes smaller. \n", + "\n", + "There are many ways to define the cost function. A simpler approach is to look at the relative difference between the training data and the predicted data, that is we define \n", + "the relative error (why would we prefer the MSE instead of the relative error?) as" + ] + }, + { + "cell_type": "markdown", + "id": "0198ef79", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\epsilon_{\\mathrm{relative}}= \\frac{\\vert \\boldsymbol{y} -\\boldsymbol{\\tilde{y}}\\vert}{\\vert \\boldsymbol{y}\\vert}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "633dd2af", + "metadata": { + "editable": true + }, + "source": [ + "The squared cost function results in an arithmetic mean-unbiased\n", + "estimator, and the absolute-value cost function results in a\n", + "median-unbiased estimator (in the one-dimensional case, and a\n", + "geometric median-unbiased estimator for the multi-dimensional\n", + "case). The squared cost function has the disadvantage that it has the tendency\n", + "to be dominated by outliers.\n", + "\n", + "We can modify easily the above Python code and plot the relative error instead" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bf583ac3", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from sklearn.linear_model import LinearRegression\n", + "# Number of data points\n", + "n = 100\n", + "x = np.random.rand(100,1)\n", + "y = 5*x+0.01*np.random.randn(100,1)\n", + "linreg = LinearRegression()\n", + "linreg.fit(x,y)\n", + "ypredict = linreg.predict(x)\n", + "\n", + "plt.plot(x, np.abs(ypredict-y)/abs(y), \"ro\")\n", + "plt.axis([0,1.0,0.0, 0.5])\n", + "plt.xlabel(r'$x$')\n", + "plt.ylabel(r'$\\epsilon_{\\mathrm{relative}}$')\n", + "plt.title(r'Relative error')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "6b069c91", + "metadata": { + "editable": true + }, + "source": [ + "Depending on the parameter in front of the normal distribution, we may\n", + "have a small or larger relative error. Try to play around with\n", + "different training data sets and study (graphically) the value of the\n", + "relative error.\n", + "\n", + "As mentioned above, **Scikit-Learn** has an impressive functionality.\n", + "We can for example extract the values of $\\alpha$ and $\\beta$ and\n", + "their error estimates, or the variance and standard deviation and many\n", + "other properties from the statistical data analysis. \n", + "\n", + "Here we show an\n", + "example of the functionality of **Scikit-Learn**." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "9d29be99", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import numpy as np \n", + "import matplotlib.pyplot as plt \n", + "from sklearn.linear_model import LinearRegression \n", + "from sklearn.metrics import mean_squared_error, r2_score, mean_squared_log_error, mean_absolute_error\n", + "\n", + "x = np.random.rand(100,1)\n", + "y = 2.0+ 5*x+0.5*np.random.randn(100,1)\n", + "linreg = LinearRegression()\n", + "linreg.fit(x,y)\n", + "ypredict = linreg.predict(x)\n", + "print('The intercept alpha: \\n', linreg.intercept_)\n", + "print('Coefficient beta : \\n', linreg.coef_)\n", + "# The mean squared error \n", + "print(\"Mean squared error: %.2f\" % mean_squared_error(y, ypredict))\n", + "# Explained variance score: 1 is perfect prediction \n", + "print('Variance score: %.2f' % r2_score(y, ypredict))\n", + "# Mean squared log error \n", + "print('Mean squared log error: %.2f' % mean_squared_log_error(y, ypredict) )\n", + "# Mean absolute error \n", + "print('Mean absolute error: %.2f' % mean_absolute_error(y, ypredict))\n", + "plt.plot(x, ypredict, \"r-\")\n", + "plt.plot(x, y ,'ro')\n", + "plt.axis([0.0,1.0,1.5, 7.0])\n", + "plt.xlabel(r'$x$')\n", + "plt.ylabel(r'$y$')\n", + "plt.title(r'Linear Regression fit ')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "dd9af8a3", + "metadata": { + "editable": true + }, + "source": [ + "The function **coef** gives us the parameter $\\beta$ of our fit while **intercept** yields \n", + "$\\alpha$. Depending on the constant in front of the normal distribution, we get values near or far from $alpha =2$ and $\\beta =5$. Try to play around with different parameters in front of the normal distribution. The function **meansquarederror** gives us the mean square error, a risk metric corresponding to the expected value of the squared (quadratic) error or loss defined as" + ] + }, + { + "cell_type": "markdown", + "id": "221214a2", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "MSE(\\boldsymbol{y},\\boldsymbol{\\tilde{y}}) = \\frac{1}{n}\n", + "\\sum_{i=0}^{n-1}(y_i-\\tilde{y}_i)^2,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "85c5a23a", + "metadata": { + "editable": true + }, + "source": [ + "The smaller the value, the better the fit. Ideally we would like to\n", + "have an MSE equal zero. The attentive reader has probably recognized\n", + "this function as being similar to the $\\chi^2$ function defined above.\n", + "\n", + "The **r2score** function computes $R^2$, the coefficient of\n", + "determination. It provides a measure of how well future samples are\n", + "likely to be predicted by the model. Best possible score is 1.0 and it\n", + "can be negative (because the model can be arbitrarily worse). A\n", + "constant model that always predicts the expected value of $\\boldsymbol{y}$,\n", + "disregarding the input features, would get a $R^2$ score of $0.0$.\n", + "\n", + "If $\\tilde{\\boldsymbol{y}}_i$ is the predicted value of the $i-th$ sample and $y_i$ is the corresponding true value, then the score $R^2$ is defined as" + ] + }, + { + "cell_type": "markdown", + "id": "13bb3c27", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "R^2(\\boldsymbol{y}, \\tilde{\\boldsymbol{y}}) = 1 - \\frac{\\sum_{i=0}^{n - 1} (y_i - \\tilde{y}_i)^2}{\\sum_{i=0}^{n - 1} (y_i - \\bar{y})^2},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "14b239cf", + "metadata": { + "editable": true + }, + "source": [ + "where we have defined the mean value of $\\boldsymbol{y}$ as" + ] + }, + { + "cell_type": "markdown", + "id": "6ec9dd22", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\bar{y} = \\frac{1}{n} \\sum_{i=0}^{n - 1} y_i.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "0535fc5e", + "metadata": { + "editable": true + }, + "source": [ + "Another quantity taht we will meet again in our discussions of regression analysis is \n", + " the mean absolute error (MAE), a risk metric corresponding to the expected value of the absolute error loss or what we call the $l1$-norm loss. In our discussion above we presented the relative error.\n", + "The MAE is defined as follows" + ] + }, + { + "cell_type": "markdown", + "id": "bdd2ca32", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\text{MAE}(\\boldsymbol{y}, \\boldsymbol{\\tilde{y}}) = \\frac{1}{n} \\sum_{i=0}^{n-1} \\left| y_i - \\tilde{y}_i \\right|.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "c09fe672", + "metadata": { + "editable": true + }, + "source": [ + "We present the \n", + "squared logarithmic (quadratic) error" + ] + }, + { + "cell_type": "markdown", + "id": "fca276bc", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\text{MSLE}(\\boldsymbol{y}, \\boldsymbol{\\tilde{y}}) = \\frac{1}{n} \\sum_{i=0}^{n - 1} (\\log_e (1 + y_i) - \\log_e (1 + \\tilde{y}_i) )^2,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "c4815a94", + "metadata": { + "editable": true + }, + "source": [ + "where $\\log_e (x)$ stands for the natural logarithm of $x$. This error\n", + "estimate is best to use when targets having exponential growth, such\n", + "as population counts, average sales of a commodity over a span of\n", + "years etc. \n", + "\n", + "Finally, another cost function is the Huber cost function used in robust regression.\n", + "\n", + "The rationale behind this possible cost function is its reduced\n", + "sensitivity to outliers in the data set. In our discussions on\n", + "dimensionality reduction and normalization of data we will meet other\n", + "ways of dealing with outliers.\n", + "\n", + "The Huber cost function is defined as" + ] + }, + { + "cell_type": "markdown", + "id": "7571aad6", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "H_{\\delta}(\\boldsymbol{a})=\\left\\{\\begin{array}{cc}\\frac{1}{2} \\boldsymbol{a}^{2}& \\text{for }|\\boldsymbol{a}|\\leq \\delta\\\\ \\delta (|\\boldsymbol{a}|-\\frac{1}{2}\\delta ),&\\text{otherwise}.\\end{array}\\right.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "dec20489", + "metadata": { + "editable": true + }, + "source": [ + "Here $\\boldsymbol{a}=\\boldsymbol{y} - \\boldsymbol{\\tilde{y}}$.\n", + "\n", + "We will discuss in more\n", + "detail these and other functions in the various lectures. We conclude this part with another example. Instead of \n", + "a linear $x$-dependence we study now a cubic polynomial and use the polynomial regression analysis tools of scikit-learn." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d47b8bb2", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import random\n", + "from sklearn.linear_model import Ridge\n", + "from sklearn.preprocessing import PolynomialFeatures\n", + "from sklearn.pipeline import make_pipeline\n", + "from sklearn.linear_model import LinearRegression\n", + "\n", + "x=np.linspace(0.02,0.98,200)\n", + "noise = np.asarray(random.sample((range(200)),200))\n", + "y=x**3*noise\n", + "yn=x**3*100\n", + "poly3 = PolynomialFeatures(degree=3)\n", + "X = poly3.fit_transform(x[:,np.newaxis])\n", + "clf3 = LinearRegression()\n", + "clf3.fit(X,y)\n", + "\n", + "Xplot=poly3.fit_transform(x[:,np.newaxis])\n", + "poly3_plot=plt.plot(x, clf3.predict(Xplot), label='Cubic Fit')\n", + "plt.plot(x,yn, color='red', label=\"True Cubic\")\n", + "plt.scatter(x, y, label='Data', color='orange', s=15)\n", + "plt.legend()\n", + "plt.show()\n", + "\n", + "def error(a):\n", + " for i in y:\n", + " err=(y-yn)/yn\n", + " return abs(np.sum(err))/len(err)\n", + "\n", + "print (error(y))" + ] + }, + { + "cell_type": "markdown", + "id": "6540ba53", + "metadata": { + "editable": true + }, + "source": [ + "Let us now dive into nuclear physics and remind ourselves briefly about some basic features about binding\n", + "energies. A basic quantity which can be measured for the ground\n", + "states of nuclei is the atomic mass $M(N, Z)$ of the neutral atom with\n", + "atomic mass number $A$ and charge $Z$. The number of neutrons is $N$. There are indeed several sophisticated experiments worldwide which allow us to measure this quantity to high precision (parts per million even). \n", + "\n", + "Atomic masses are usually tabulated in terms of the mass excess defined by" + ] + }, + { + "cell_type": "markdown", + "id": "197a8385", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\Delta M(N, Z) = M(N, Z) - uA,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "92f8ee33", + "metadata": { + "editable": true + }, + "source": [ + "where $u$ is the Atomic Mass Unit" + ] + }, + { + "cell_type": "markdown", + "id": "2028de5a", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "u = M(^{12}\\mathrm{C})/12 = 931.4940954(57) \\hspace{0.1cm} \\mathrm{MeV}/c^2.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "fb4249db", + "metadata": { + "editable": true + }, + "source": [ + "The nucleon masses are" + ] + }, + { + "cell_type": "markdown", + "id": "7f85c04c", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "m_p = 1.00727646693(9)u,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "f1b4a593", + "metadata": { + "editable": true + }, + "source": [ + "and" + ] + }, + { + "cell_type": "markdown", + "id": "5a1a2210", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "m_n = 939.56536(8)\\hspace{0.1cm} \\mathrm{MeV}/c^2 = 1.0086649156(6)u.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "9364b95e", + "metadata": { + "editable": true + }, + "source": [ + "In the [2016 mass evaluation of by W.J.Huang, G.Audi, M.Wang, F.G.Kondev, S.Naimi and X.Xu](http://nuclearmasses.org/resources_folder/Wang_2017_Chinese_Phys_C_41_030003.pdf)\n", + "there are data on masses and decays of 3437 nuclei.\n", + "\n", + "The nuclear binding energy is defined as the energy required to break\n", + "up a given nucleus into its constituent parts of $N$ neutrons and $Z$\n", + "protons. In terms of the atomic masses $M(N, Z)$ the binding energy is\n", + "defined by" + ] + }, + { + "cell_type": "markdown", + "id": "fb9273ea", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "BE(N, Z) = ZM_H c^2 + Nm_n c^2 - M(N, Z)c^2 ,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "0cce43b5", + "metadata": { + "editable": true + }, + "source": [ + "where $M_H$ is the mass of the hydrogen atom and $m_n$ is the mass of the neutron.\n", + "In terms of the mass excess the binding energy is given by" + ] + }, + { + "cell_type": "markdown", + "id": "ead7f32f", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "BE(N, Z) = Z\\Delta_H c^2 + N\\Delta_n c^2 -\\Delta(N, Z)c^2 ,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "e22d63ca", + "metadata": { + "editable": true + }, + "source": [ + "where $\\Delta_H c^2 = 7.2890$ MeV and $\\Delta_n c^2 = 8.0713$ MeV.\n", + "\n", + "A popular and physically intuitive model which can be used to parametrize \n", + "the experimental binding energies as function of $A$, is the so-called \n", + "**liquid drop model**. The ansatz is based on the following expression" + ] + }, + { + "cell_type": "markdown", + "id": "5afa5ac0", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "BE(N,Z) = a_1A-a_2A^{2/3}-a_3\\frac{Z^2}{A^{1/3}}-a_4\\frac{(N-Z)^2}{A},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "2e34f131", + "metadata": { + "editable": true + }, + "source": [ + "where $A$ stands for the number of nucleons and the $a_i$s are parameters which are determined by a fit \n", + "to the experimental data. \n", + "\n", + "To arrive at the above expression we have assumed that we can make the following assumptions:\n", + "\n", + " * There is a volume term $a_1A$ proportional with the number of nucleons (the energy is also an extensive quantity). When an assembly of nucleons of the same size is packed together into the smallest volume, each interior nucleon has a certain number of other nucleons in contact with it. This contribution is proportional to the volume.\n", + "\n", + " * There is a surface energy term $a_2A^{2/3}$. The assumption here is that a nucleon at the surface of a nucleus interacts with fewer other nucleons than one in the interior of the nucleus and hence its binding energy is less. This surface energy term takes that into account and is therefore negative and is proportional to the surface area.\n", + "\n", + " * There is a Coulomb energy term $a_3\\frac{Z^2}{A^{1/3}}$. The electric repulsion between each pair of protons in a nucleus yields less binding. \n", + "\n", + " * There is an asymmetry term $a_4\\frac{(N-Z)^2}{A}$. This term is associated with the Pauli exclusion principle and reflects the fact that the proton-neutron interaction is more attractive on the average than the neutron-neutron and proton-proton interactions.\n", + "\n", + "We could also add a so-called pairing term, which is a correction term that\n", + "arises from the tendency of proton pairs and neutron pairs to\n", + "occur. An even number of particles is more stable than an odd number." + ] + }, + { + "cell_type": "markdown", + "id": "952f1a4e", + "metadata": { + "editable": true + }, + "source": [ + "### Organizing our data\n", + "\n", + "Let us start with reading and organizing our data. \n", + "We start with the compilation of masses and binding energies from 2016.\n", + "After having downloaded this file to our own computer, we are now ready to read the file and start structuring our data.\n", + "\n", + "We start with preparing folders for storing our calculations and the data file over masses and binding energies. We import also various modules that we will find useful in order to present various Machine Learning methods. Here we focus mainly on the functionality of **scikit-learn**." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "5a2348e9", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# Common imports\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import sklearn.linear_model as skl\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error\n", + "import os\n", + "\n", + "# Where to save the figures and data files\n", + "PROJECT_ROOT_DIR = \"Results\"\n", + "FIGURE_ID = \"Results/FigureFiles\"\n", + "DATA_ID = \"DataFiles/\"\n", + "\n", + "if not os.path.exists(PROJECT_ROOT_DIR):\n", + " os.mkdir(PROJECT_ROOT_DIR)\n", + "\n", + "if not os.path.exists(FIGURE_ID):\n", + " os.makedirs(FIGURE_ID)\n", + "\n", + "if not os.path.exists(DATA_ID):\n", + " os.makedirs(DATA_ID)\n", + "\n", + "def image_path(fig_id):\n", + " return os.path.join(FIGURE_ID, fig_id)\n", + "\n", + "def data_path(dat_id):\n", + " return os.path.join(DATA_ID, dat_id)\n", + "\n", + "def save_fig(fig_id):\n", + " plt.savefig(image_path(fig_id) + \".png\", format='png')\n", + "\n", + "infile = open(data_path(\"MassEval2016.dat\"),'r')" + ] + }, + { + "cell_type": "markdown", + "id": "8a4bbb48", + "metadata": { + "editable": true + }, + "source": [ + "Before we proceed, we define also a function for making our plots. You can obviously avoid this and simply set up various **matplotlib** commands every time you need them. You may however find it convenient to collect all such commands in one function and simply call this function." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "0460fb72", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "from pylab import plt, mpl\n", + "plt.style.use('seaborn')\n", + "mpl.rcParams['font.family'] = 'serif'\n", + "\n", + "def MakePlot(x,y, styles, labels, axlabels):\n", + " plt.figure(figsize=(10,6))\n", + " for i in range(len(x)):\n", + " plt.plot(x[i], y[i], styles[i], label = labels[i])\n", + " plt.xlabel(axlabels[0])\n", + " plt.ylabel(axlabels[1])\n", + " plt.legend(loc=0)" + ] + }, + { + "cell_type": "markdown", + "id": "ca0ecd37", + "metadata": { + "editable": true + }, + "source": [ + "Our next step is to read the data on experimental binding energies and\n", + "reorganize them as functions of the mass number $A$, the number of\n", + "protons $Z$ and neutrons $N$ using **pandas**. Before we do this it is\n", + "always useful (unless you have a binary file or other types of compressed\n", + "data) to actually open the file and simply take a look at it!\n", + "\n", + "In particular, the program that outputs the final nuclear masses is written in Fortran with a specific format. It means that we need to figure out the format and which columns contain the data we are interested in. Pandas comes with a function that reads formatted output. After having admired the file, we are now ready to start massaging it with **pandas**. The file begins with some basic format information." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "36bf7516", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "\"\"\" \n", + "This is taken from the data file of the mass 2016 evaluation. \n", + "All files are 3436 lines long with 124 character per line. \n", + " Headers are 39 lines long. \n", + " col 1 : Fortran character control: 1 = page feed 0 = line feed \n", + " format : a1,i3,i5,i5,i5,1x,a3,a4,1x,f13.5,f11.5,f11.3,f9.3,1x,a2,f11.3,f9.3,1x,i3,1x,f12.5,f11.5 \n", + " These formats are reflected in the pandas widths variable below, see the statement \n", + " widths=(1,3,5,5,5,1,3,4,1,13,11,11,9,1,2,11,9,1,3,1,12,11,1), \n", + " Pandas has also a variable header, with length 39 in this case. \n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "3c66ec96", + "metadata": { + "editable": true + }, + "source": [ + "The data we are interested in are in columns 2, 3, 4 and 11, giving us\n", + "the number of neutrons, protons, mass numbers and binding energies,\n", + "respectively. We add also for the sake of completeness the element name. The data are in fixed-width formatted lines and we will\n", + "covert them into the **pandas** DataFrame structure." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bedfdf26", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# Read the experimental data with Pandas\n", + "Masses = pd.read_fwf(infile, usecols=(2,3,4,6,11),\n", + " names=('N', 'Z', 'A', 'Element', 'Ebinding'),\n", + " widths=(1,3,5,5,5,1,3,4,1,13,11,11,9,1,2,11,9,1,3,1,12,11,1),\n", + " header=39,\n", + " index_col=False)\n", + "\n", + "# Extrapolated values are indicated by '#' in place of the decimal place, so\n", + "# the Ebinding column won't be numeric. Coerce to float and drop these entries.\n", + "Masses['Ebinding'] = pd.to_numeric(Masses['Ebinding'], errors='coerce')\n", + "Masses = Masses.dropna()\n", + "# Convert from keV to MeV.\n", + "Masses['Ebinding'] /= 1000\n", + "\n", + "# Group the DataFrame by nucleon number, A.\n", + "Masses = Masses.groupby('A')\n", + "# Find the rows of the grouped DataFrame with the maximum binding energy.\n", + "Masses = Masses.apply(lambda t: t[t.Ebinding==t.Ebinding.max()])" + ] + }, + { + "cell_type": "markdown", + "id": "c16f7a9c", + "metadata": { + "editable": true + }, + "source": [ + "We have now read in the data, grouped them according to the variables we are interested in. \n", + "We see how easy it is to reorganize the data using **pandas**. If we\n", + "were to do these operations in C/C++ or Fortran, we would have had to\n", + "write various functions/subroutines which perform the above\n", + "reorganizations for us. Having reorganized the data, we can now start\n", + "to make some simple fits using both the functionalities in **numpy** and\n", + "**Scikit-Learn** afterwards. \n", + "\n", + "Now we define five variables which contain\n", + "the number of nucleons $A$, the number of protons $Z$ and the number of neutrons $N$, the element name and finally the energies themselves." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "e4a0316f", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "A = Masses['A']\n", + "Z = Masses['Z']\n", + "N = Masses['N']\n", + "Element = Masses['Element']\n", + "Energies = Masses['Ebinding']\n", + "print(Masses)" + ] + }, + { + "cell_type": "markdown", + "id": "7a9d5603", + "metadata": { + "editable": true + }, + "source": [ + "The next step, and we will define this mathematically later, is to set up the so-called **design matrix**. We will throughout call this matrix $\\boldsymbol{X}$.\n", + "It has dimensionality $n\\times p$, where $n$ is the number of data points and $p$ are the so-called predictors. In our case here they are given by the number of polynomials in $A$ we wish to include in the fit." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "ed75b8f0", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# Now we set up the design matrix X\n", + "X = np.zeros((len(A),5))\n", + "X[:,0] = 1\n", + "X[:,1] = A\n", + "X[:,2] = A**(2.0/3.0)\n", + "X[:,3] = A**(-1.0/3.0)\n", + "X[:,4] = A**(-1.0)" + ] + }, + { + "cell_type": "markdown", + "id": "c159d19a", + "metadata": { + "editable": true + }, + "source": [ + "Note well that we have made life simple here. We perform a fit in\n", + "terms of the number of nucleons only. A more sophisticated fit can be\n", + "done by including an explicit dependence on the number of protons and\n", + "neutrons in the asymmetry and Coulomb terms. We leave this as an exercise to you the reader.\n", + "\n", + "With **Scikit-Learn** we are now ready to use linear regression and fit our data." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "4ca16e57", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "clf = skl.LinearRegression().fit(X, Energies)\n", + "fity = clf.predict(X)" + ] + }, + { + "cell_type": "markdown", + "id": "1e33c7a4", + "metadata": { + "editable": true + }, + "source": [ + "Pretty simple! \n", + "Now we can print measures of how our fit is doing, the coefficients from the fits and plot the final fit together with our data." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "38cf3567", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# The mean squared error \n", + "print(\"Mean squared error: %.2f\" % mean_squared_error(Energies, fity))\n", + "# Explained variance score: 1 is perfect prediction \n", + "print('Variance score: %.2f' % r2_score(Energies, fity))\n", + "# Mean absolute error \n", + "print('Mean absolute error: %.2f' % mean_absolute_error(Energies, fity))\n", + "\n", + "Masses['Eapprox'] = fity\n", + "# Generate a plot comparing the experimental with the fitted values values.\n", + "fig, ax = plt.subplots()\n", + "ax.set_xlabel(r'$A = N + Z$')\n", + "ax.set_ylabel(r'$E_\\mathrm{bind}\\,/\\mathrm{MeV}$')\n", + "ax.plot(Masses['A'], Masses['Ebinding'], alpha=0.7, lw=2,\n", + " label='Ame2016')\n", + "ax.plot(Masses['A'], Masses['Eapprox'], alpha=0.7, lw=2, c='m',\n", + " label='Fit')\n", + "ax.legend()\n", + "save_fig(\"Masses2016\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ed1f440c", + "metadata": { + "editable": true + }, + "source": [ + "As a teaser, let us now see how we can do this with decision trees using **Scikit-Learn**. Later we will switch to so-called **random forests**!" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "b3192533", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "\n", + "#Decision Tree Regression\n", + "from sklearn.tree import DecisionTreeRegressor\n", + "regr_1=DecisionTreeRegressor(max_depth=5)\n", + "regr_2=DecisionTreeRegressor(max_depth=7)\n", + "regr_3=DecisionTreeRegressor(max_depth=9)\n", + "regr_1.fit(X, Energies)\n", + "regr_2.fit(X, Energies)\n", + "regr_3.fit(X, Energies)\n", + "\n", + "\n", + "y_1 = regr_1.predict(X)\n", + "y_2 = regr_2.predict(X)\n", + "y_3=regr_3.predict(X)\n", + "Masses['Eapprox'] = y_3\n", + "# Plot the results\n", + "plt.figure()\n", + "plt.plot(A, Energies, color=\"blue\", label=\"Data\", linewidth=2)\n", + "plt.plot(A, y_1, color=\"red\", label=\"max_depth=5\", linewidth=2)\n", + "plt.plot(A, y_2, color=\"green\", label=\"max_depth=7\", linewidth=2)\n", + "plt.plot(A, y_3, color=\"m\", label=\"max_depth=9\", linewidth=2)\n", + "\n", + "plt.xlabel(\"$A$\")\n", + "plt.ylabel(\"$E$[MeV]\")\n", + "plt.title(\"Decision Tree Regression\")\n", + "plt.legend()\n", + "save_fig(\"Masses2016Trees\")\n", + "plt.show()\n", + "print(Masses)\n", + "print(np.mean( (Energies-y_1)**2))" + ] + }, + { + "cell_type": "markdown", + "id": "2c25359e", + "metadata": { + "editable": true + }, + "source": [ + "With a deeper and deeper tree level, we can almost reproduce every\n", + "single data point by increasing the max depth of the tree.\n", + "We can actually decide to make a decision tree which fits every single point.\n", + "As we will\n", + "see later, this has the benefit that we can really train a model which\n", + "traverses every single data point. However, the price we pay is that\n", + "we will easily overfit. That is, if we apply our model to unseen data,\n", + "we will most likely fail miserably in our attempt at making\n", + "predictions. As an exercise, try to make the tree level larger by adjusting the maximum depth variable. When printing out the predicition, you will note that the binding energy of every nucleus is accurately reproduced.\n", + "\n", + "The **seaborn** package allows us to visualize data in an efficient way. Note that we use **scikit-learn**'s multi-layer perceptron (or feed forward neural network) \n", + "functionality." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "388a648e", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "from sklearn.neural_network import MLPRegressor\n", + "from sklearn.metrics import accuracy_score\n", + "import seaborn as sns\n", + "\n", + "X_train = X\n", + "Y_train = Energies\n", + "n_hidden_neurons = 100\n", + "epochs = 100\n", + "# store models for later use\n", + "eta_vals = np.logspace(-5, 1, 7)\n", + "lmbd_vals = np.logspace(-5, 1, 7)\n", + "# store the models for later use\n", + "DNN_scikit = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)\n", + "train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", + "sns.set()\n", + "for i, eta in enumerate(eta_vals):\n", + " for j, lmbd in enumerate(lmbd_vals):\n", + " dnn = MLPRegressor(hidden_layer_sizes=(n_hidden_neurons), activation='logistic',\n", + " alpha=lmbd, learning_rate_init=eta, max_iter=epochs)\n", + " dnn.fit(X_train, Y_train)\n", + " DNN_scikit[i][j] = dnn\n", + " train_accuracy[i][j] = dnn.score(X_train, Y_train)\n", + "\n", + "fig, ax = plt.subplots(figsize = (10, 10))\n", + "sns.heatmap(train_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", + "ax.set_title(\"Training Accuracy\")\n", + "ax.set_ylabel(\"$\\eta$\")\n", + "ax.set_xlabel(\"$\\lambda$\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "a534ad4e", + "metadata": { + "editable": true + }, + "source": [ + "## Linear Regression, basic elements\n", + "\n", + "[Video of Lecture](https://www.uio.no/studier/emner/matnat/fys/FYS-STK4155/h20/forelesningsvideoer/LectureAug27.mp4?vrtx=view-as-webpage).\n", + "\n", + "Fitting a continuous function with linear parameterization in terms of the parameters $\\boldsymbol{\\beta}$.\n", + "* Method of choice for fitting a continuous function!\n", + "\n", + "* Gives an excellent introduction to central Machine Learning features with **understandable pedagogical** links to other methods like **Neural Networks**, **Support Vector Machines** etc\n", + "\n", + "* Analytical expression for the fitting parameters $\\boldsymbol{\\beta}$\n", + "\n", + "* Analytical expressions for statistical propertiers like mean values, variances, confidence intervals and more\n", + "\n", + "* Analytical relation with probabilistic interpretations \n", + "\n", + "* Easy to introduce basic concepts like bias-variance tradeoff, cross-validation, resampling and regularization techniques and many other ML topics\n", + "\n", + "* Easy to code! And links well with classification problems and logistic regression and neural networks\n", + "\n", + "* Allows for **easy** hands-on understanding of gradient descent methods\n", + "\n", + "* and many more features\n", + "\n", + "For more discussions of Ridge and Lasso regression, [Wessel van Wieringen's](https://arxiv.org/abs/1509.09169) article is highly recommended.\n", + "Similarly, [Mehta et al's article](https://arxiv.org/abs/1803.08823) is also recommended.\n", + "\n", + "Regression modeling deals with the description of the sampling distribution of a given random variable $y$ and how it varies as function of another variable or a set of such variables $\\boldsymbol{x} =[x_0, x_1,\\dots, x_{n-1}]^T$. \n", + "The first variable is called the **dependent**, the **outcome** or the **response** variable while the set of variables $\\boldsymbol{x}$ is called the independent variable, or the predictor variable or the explanatory variable. \n", + "\n", + "A regression model aims at finding a likelihood function $p(\\boldsymbol{y}\\vert \\boldsymbol{x})$, that is the conditional distribution for $\\boldsymbol{y}$ with a given $\\boldsymbol{x}$. The estimation of $p(\\boldsymbol{y}\\vert \\boldsymbol{x})$ is made using a data set with \n", + "* $n$ cases $i = 0, 1, 2, \\dots, n-1$ \n", + "\n", + "* Response (target, dependent or outcome) variable $y_i$ with $i = 0, 1, 2, \\dots, n-1$ \n", + "\n", + "* $p$ so-called explanatory (independent or predictor) variables $\\boldsymbol{x}_i=[x_{i0}, x_{i1}, \\dots, x_{ip-1}]$ with $i = 0, 1, 2, \\dots, n-1$ and explanatory variables running from $0$ to $p-1$. See below for more explicit examples. \n", + "\n", + " The goal of the regression analysis is to extract/exploit relationship between $\\boldsymbol{y}$ and $\\boldsymbol{x}$ in or to infer causal dependencies, approximations to the likelihood functions, functional relationships and to make predictions, making fits and many other things.\n", + "\n", + "Consider an experiment in which $p$ characteristics of $n$ samples are\n", + "measured. The data from this experiment, for various explanatory variables $p$ are normally represented by a matrix \n", + "$\\mathbf{X}$.\n", + "\n", + "The matrix $\\mathbf{X}$ is called the *design\n", + "matrix*. Additional information of the samples is available in the\n", + "form of $\\boldsymbol{y}$ (also as above). The variable $\\boldsymbol{y}$ is\n", + "generally referred to as the *response variable*. The aim of\n", + "regression analysis is to explain $\\boldsymbol{y}$ in terms of\n", + "$\\boldsymbol{X}$ through a functional relationship like $y_i =\n", + "f(\\mathbf{X}_{i,\\ast})$. When no prior knowledge on the form of\n", + "$f(\\cdot)$ is available, it is common to assume a linear relationship\n", + "between $\\boldsymbol{X}$ and $\\boldsymbol{y}$. This assumption gives rise to\n", + "the *linear regression model* where $\\boldsymbol{\\beta} = [\\beta_0, \\ldots,\n", + "\\beta_{p-1}]^{T}$ are the *regression parameters*. \n", + "\n", + "Linear regression gives us a set of analytical equations for the parameters $\\beta_j$.\n", + "\n", + "In order to understand the relation among the predictors $p$, the set of data $n$ and the target (outcome, output etc) $\\boldsymbol{y}$,\n", + "consider the model we discussed for describing nuclear binding energies. \n", + "\n", + "There we assumed that we could parametrize the data using a polynomial approximation based on the liquid drop model.\n", + "Assuming" + ] + }, + { + "cell_type": "markdown", + "id": "e88a6ca8", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "BE(A) = a_0+a_1A+a_2A^{2/3}+a_3A^{-1/3}+a_4A^{-1},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "25b363fc", + "metadata": { + "editable": true + }, + "source": [ + "we have five predictors, that is the intercept, the $A$ dependent term, the $A^{2/3}$ term and the $A^{-1/3}$ and $A^{-1}$ terms.\n", + "This gives $p=0,1,2,3,4$. Furthermore we have $n$ entries for each predictor. It means that our design matrix is a \n", + "$p\\times n$ matrix $\\boldsymbol{X}$.\n", + "\n", + "Here the predictors are based on a model we have made. A popular data set which is widely encountered in ML applications is the\n", + "so-called [credit card default data from Taiwan](https://www.sciencedirect.com/science/article/pii/S0957417407006719?via%3Dihub). The data set contains data on $n=30000$ credit card holders with predictors like gender, marital status, age, profession, education, etc. In total there are $24$ such predictors or attributes leading to a design matrix of dimensionality $24 \\times 30000$. This is however a classification problem and we will come back to it when we discuss Logistic Regression. \n", + "\n", + "Before we proceed let us study a case from linear algebra where we aim at fitting a set of data $\\boldsymbol{y}=[y_0,y_1,\\dots,y_{n-1}]$. We could think of these data as a result of an experiment or a complicated numerical experiment. These data are functions of a series of variables $\\boldsymbol{x}=[x_0,x_1,\\dots,x_{n-1}]$, that is $y_i = y(x_i)$ with $i=0,1,2,\\dots,n-1$. The variables $x_i$ could represent physical quantities like time, temperature, position etc. We assume that $y(x)$ is a smooth function. \n", + "\n", + "Since obtaining these data points may not be trivial, we want to use these data to fit a function which can allow us to make predictions for values of $y$ which are not in the present set. The perhaps simplest approach is to assume we can parametrize our function in terms of a polynomial of degree $n-1$ with $n$ points, that is" + ] + }, + { + "cell_type": "markdown", + "id": "ee870654", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y=y(x) \\rightarrow y(x_i)=\\tilde{y}_i+\\epsilon_i=\\sum_{j=0}^{n-1} \\beta_j x_i^j+\\epsilon_i,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "531162a4", + "metadata": { + "editable": true + }, + "source": [ + "where $\\epsilon_i$ is the error in our approximation. \n", + "\n", + "For every set of values $y_i,x_i$ we have thus the corresponding set of equations" + ] + }, + { + "cell_type": "markdown", + "id": "dab2ce7b", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\begin{align*}\n", + "y_0&=\\beta_0+\\beta_1x_0^1+\\beta_2x_0^2+\\dots+\\beta_{n-1}x_0^{n-1}+\\epsilon_0\\\\\n", + "y_1&=\\beta_0+\\beta_1x_1^1+\\beta_2x_1^2+\\dots+\\beta_{n-1}x_1^{n-1}+\\epsilon_1\\\\\n", + "y_2&=\\beta_0+\\beta_1x_2^1+\\beta_2x_2^2+\\dots+\\beta_{n-1}x_2^{n-1}+\\epsilon_2\\\\\n", + "\\dots & \\dots \\\\\n", + "y_{n-1}&=\\beta_0+\\beta_1x_{n-1}^1+\\beta_2x_{n-1}^2+\\dots+\\beta_{n-1}x_{n-1}^{n-1}+\\epsilon_{n-1}.\\\\\n", + "\\end{align*}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "fc16de28", + "metadata": { + "editable": true + }, + "source": [ + "Defining the vectors" + ] + }, + { + "cell_type": "markdown", + "id": "ea697f8f", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{y} = [y_0,y_1, y_2,\\dots, y_{n-1}]^T,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "a1662e73", + "metadata": { + "editable": true + }, + "source": [ + "and" + ] + }, + { + "cell_type": "markdown", + "id": "98882e41", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{\\beta} = [\\beta_0,\\beta_1, \\beta_2,\\dots, \\beta_{n-1}]^T,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "9a3e0d78", + "metadata": { + "editable": true + }, + "source": [ + "and" + ] + }, + { + "cell_type": "markdown", + "id": "57c6837d", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{\\epsilon} = [\\epsilon_0,\\epsilon_1, \\epsilon_2,\\dots, \\epsilon_{n-1}]^T,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "84a3c548", + "metadata": { + "editable": true + }, + "source": [ + "and the design matrix" + ] + }, + { + "cell_type": "markdown", + "id": "aa74b87a", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{X}=\n", + "\\begin{bmatrix} \n", + "1& x_{0}^1 &x_{0}^2& \\dots & \\dots &x_{0}^{n-1}\\\\\n", + "1& x_{1}^1 &x_{1}^2& \\dots & \\dots &x_{1}^{n-1}\\\\\n", + "1& x_{2}^1 &x_{2}^2& \\dots & \\dots &x_{2}^{n-1}\\\\ \n", + "\\dots& \\dots &\\dots& \\dots & \\dots &\\dots\\\\\n", + "1& x_{n-1}^1 &x_{n-1}^2& \\dots & \\dots &x_{n-1}^{n-1}\\\\\n", + "\\end{bmatrix}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "d847a903", + "metadata": { + "editable": true + }, + "source": [ + "we can rewrite our equations as" + ] + }, + { + "cell_type": "markdown", + "id": "4f3e6d28", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{y} = \\boldsymbol{X}\\boldsymbol{\\beta}+\\boldsymbol{\\epsilon}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "e3b50da6", + "metadata": { + "editable": true + }, + "source": [ + "The above design matrix is called a [Vandermonde matrix](https://en.wikipedia.org/wiki/Vandermonde_matrix).\n", + "\n", + "We are obviously not limited to the above polynomial expansions. We\n", + "could replace the various powers of $x$ with elements of Fourier\n", + "series or instead of $x_i^j$ we could have $\\cos{(j x_i)}$ or $\\sin{(j\n", + "x_i)}$, or time series or other orthogonal functions. For every set\n", + "of values $y_i,x_i$ we can then generalize the equations to" + ] + }, + { + "cell_type": "markdown", + "id": "81d56dbd", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\begin{align*}\n", + "y_0&=\\beta_0x_{00}+\\beta_1x_{01}+\\beta_2x_{02}+\\dots+\\beta_{n-1}x_{0n-1}+\\epsilon_0\\\\\n", + "y_1&=\\beta_0x_{10}+\\beta_1x_{11}+\\beta_2x_{12}+\\dots+\\beta_{n-1}x_{1n-1}+\\epsilon_1\\\\\n", + "y_2&=\\beta_0x_{20}+\\beta_1x_{21}+\\beta_2x_{22}+\\dots+\\beta_{n-1}x_{2n-1}+\\epsilon_2\\\\\n", + "\\dots & \\dots \\\\\n", + "y_{i}&=\\beta_0x_{i0}+\\beta_1x_{i1}+\\beta_2x_{i2}+\\dots+\\beta_{n-1}x_{in-1}+\\epsilon_i\\\\\n", + "\\dots & \\dots \\\\\n", + "y_{n-1}&=\\beta_0x_{n-1,0}+\\beta_1x_{n-1,2}+\\beta_2x_{n-1,2}+\\dots+\\beta_{n-1}x_{n-1,n-1}+\\epsilon_{n-1}.\\\\\n", + "\\end{align*}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "857628f9", + "metadata": { + "editable": true + }, + "source": [ + "**Note that we have $p=n$ here. The matrix is symmetric. This is generally not the case!**\n", + "\n", + "We redefine in turn the matrix $\\boldsymbol{X}$ as" + ] + }, + { + "cell_type": "markdown", + "id": "2d1a3940", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{X}=\n", + "\\begin{bmatrix} \n", + "x_{00}& x_{01} &x_{02}& \\dots & \\dots &x_{0,n-1}\\\\\n", + "x_{10}& x_{11} &x_{12}& \\dots & \\dots &x_{1,n-1}\\\\\n", + "x_{20}& x_{21} &x_{22}& \\dots & \\dots &x_{2,n-1}\\\\ \n", + "\\dots& \\dots &\\dots& \\dots & \\dots &\\dots\\\\\n", + "x_{n-1,0}& x_{n-1,1} &x_{n-1,2}& \\dots & \\dots &x_{n-1,n-1}\\\\\n", + "\\end{bmatrix}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "c42c31b4", + "metadata": { + "editable": true + }, + "source": [ + "and without loss of generality we rewrite again our equations as" + ] + }, + { + "cell_type": "markdown", + "id": "83b72ca8", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{y} = \\boldsymbol{X}\\boldsymbol{\\beta}+\\boldsymbol{\\epsilon}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "d346abd2", + "metadata": { + "editable": true + }, + "source": [ + "The left-hand side of this equation is kwown. Our error vector $\\boldsymbol{\\epsilon}$ and the parameter vector $\\boldsymbol{\\beta}$ are our unknow quantities. How can we obtain the optimal set of $\\beta_i$ values? \n", + "\n", + "We have defined the matrix $\\boldsymbol{X}$ via the equations" + ] + }, + { + "cell_type": "markdown", + "id": "5c9028d2", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\begin{align*}\n", + "y_0&=\\beta_0x_{00}+\\beta_1x_{01}+\\beta_2x_{02}+\\dots+\\beta_{n-1}x_{0n-1}+\\epsilon_0\\\\\n", + "y_1&=\\beta_0x_{10}+\\beta_1x_{11}+\\beta_2x_{12}+\\dots+\\beta_{n-1}x_{1n-1}+\\epsilon_1\\\\\n", + "y_2&=\\beta_0x_{20}+\\beta_1x_{21}+\\beta_2x_{22}+\\dots+\\beta_{n-1}x_{2n-1}+\\epsilon_1\\\\\n", + "\\dots & \\dots \\\\\n", + "y_{i}&=\\beta_0x_{i0}+\\beta_1x_{i1}+\\beta_2x_{i2}+\\dots+\\beta_{n-1}x_{in-1}+\\epsilon_1\\\\\n", + "\\dots & \\dots \\\\\n", + "y_{n-1}&=\\beta_0x_{n-1,0}+\\beta_1x_{n-1,2}+\\beta_2x_{n-1,2}+\\dots+\\beta_{n-1}x_{n-1,n-1}+\\epsilon_{n-1}.\\\\\n", + "\\end{align*}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "3333d187", + "metadata": { + "editable": true + }, + "source": [ + "As we noted above, we stayed with a system with the design matrix \n", + " $\\boldsymbol{X}\\in {\\mathbb{R}}^{n\\times n}$, that is we have $p=n$. For reasons to come later (algorithmic arguments) we will hereafter define \n", + "our matrix as $\\boldsymbol{X}\\in {\\mathbb{R}}^{n\\times p}$, with the predictors refering to the column numbers and the entries $n$ being the row elements.\n", + "\n", + "In our [introductory notes](https://compphysics.github.io/MachineLearning/doc/pub/How2ReadData/html/How2ReadData.html) we looked at the so-called [liquid drop model](https://en.wikipedia.org/wiki/Semi-empirical_mass_formula). Let us remind ourselves about what we did by looking at the code.\n", + "\n", + "We restate the parts of the code we are most interested in." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "353875c9", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# Common imports\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "from IPython.display import display\n", + "import os\n", + "\n", + "# Where to save the figures and data files\n", + "PROJECT_ROOT_DIR = \"Results\"\n", + "FIGURE_ID = \"Results/FigureFiles\"\n", + "DATA_ID = \"DataFiles/\"\n", + "\n", + "if not os.path.exists(PROJECT_ROOT_DIR):\n", + " os.mkdir(PROJECT_ROOT_DIR)\n", + "\n", + "if not os.path.exists(FIGURE_ID):\n", + " os.makedirs(FIGURE_ID)\n", + "\n", + "if not os.path.exists(DATA_ID):\n", + " os.makedirs(DATA_ID)\n", + "\n", + "def image_path(fig_id):\n", + " return os.path.join(FIGURE_ID, fig_id)\n", + "\n", + "def data_path(dat_id):\n", + " return os.path.join(DATA_ID, dat_id)\n", + "\n", + "def save_fig(fig_id):\n", + " plt.savefig(image_path(fig_id) + \".png\", format='png')\n", + "\n", + "infile = open(data_path(\"MassEval2016.dat\"),'r')\n", + "\n", + "\n", + "# Read the experimental data with Pandas\n", + "Masses = pd.read_fwf(infile, usecols=(2,3,4,6,11),\n", + " names=('N', 'Z', 'A', 'Element', 'Ebinding'),\n", + " widths=(1,3,5,5,5,1,3,4,1,13,11,11,9,1,2,11,9,1,3,1,12,11,1),\n", + " header=39,\n", + " index_col=False)\n", + "\n", + "# Extrapolated values are indicated by '#' in place of the decimal place, so\n", + "# the Ebinding column won't be numeric. Coerce to float and drop these entries.\n", + "Masses['Ebinding'] = pd.to_numeric(Masses['Ebinding'], errors='coerce')\n", + "Masses = Masses.dropna()\n", + "# Convert from keV to MeV.\n", + "Masses['Ebinding'] /= 1000\n", + "\n", + "# Group the DataFrame by nucleon number, A.\n", + "Masses = Masses.groupby('A')\n", + "# Find the rows of the grouped DataFrame with the maximum binding energy.\n", + "Masses = Masses.apply(lambda t: t[t.Ebinding==t.Ebinding.max()])\n", + "A = Masses['A']\n", + "Z = Masses['Z']\n", + "N = Masses['N']\n", + "Element = Masses['Element']\n", + "Energies = Masses['Ebinding']\n", + "\n", + "# Now we set up the design matrix X\n", + "X = np.zeros((len(A),5))\n", + "X[:,0] = 1\n", + "X[:,1] = A\n", + "X[:,2] = A**(2.0/3.0)\n", + "X[:,3] = A**(-1.0/3.0)\n", + "X[:,4] = A**(-1.0)\n", + "# Then nice printout using pandas\n", + "DesignMatrix = pd.DataFrame(X)\n", + "DesignMatrix.index = A\n", + "DesignMatrix.columns = ['1', 'A', 'A^(2/3)', 'A^(-1/3)', '1/A']\n", + "display(DesignMatrix)" + ] + }, + { + "cell_type": "markdown", + "id": "9b2ec4b2", + "metadata": { + "editable": true + }, + "source": [ + "With $\\boldsymbol{\\beta}\\in {\\mathbb{R}}^{p\\times 1}$, it means that we will hereafter write our equations for the approximation as" + ] + }, + { + "cell_type": "markdown", + "id": "31d92a8f", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{\\tilde{y}}= \\boldsymbol{X}\\boldsymbol{\\beta},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "34243326", + "metadata": { + "editable": true + }, + "source": [ + "throughout these lectures. \n", + "\n", + "With the above we use the design matrix to define the approximation $\\boldsymbol{\\tilde{y}}$ via the unknown quantity $\\boldsymbol{\\beta}$ as" + ] + }, + { + "cell_type": "markdown", + "id": "efb063d1", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{\\tilde{y}}= \\boldsymbol{X}\\boldsymbol{\\beta},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "89c551a4", + "metadata": { + "editable": true + }, + "source": [ + "and in order to find the optimal parameters $\\beta_i$ instead of solving the above linear algebra problem, we define a function which gives a measure of the spread between the values $y_i$ (which represent hopefully the exact values) and the parameterized values $\\tilde{y}_i$, namely" + ] + }, + { + "cell_type": "markdown", + "id": "7edbaa2d", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "C(\\boldsymbol{\\beta})=\\frac{1}{n}\\sum_{i=0}^{n-1}\\left(y_i-\\tilde{y}_i\\right)^2=\\frac{1}{n}\\left\\{\\left(\\boldsymbol{y}-\\boldsymbol{\\tilde{y}}\\right)^T\\left(\\boldsymbol{y}-\\boldsymbol{\\tilde{y}}\\right)\\right\\},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "ad91f91f", + "metadata": { + "editable": true + }, + "source": [ + "or using the matrix $\\boldsymbol{X}$ and in a more compact matrix-vector notation as" + ] + }, + { + "cell_type": "markdown", + "id": "d8e77f3b", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "C(\\boldsymbol{\\beta})=\\frac{1}{n}\\left\\{\\left(\\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right)^T\\left(\\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right)\\right\\}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "d1e41018", + "metadata": { + "editable": true + }, + "source": [ + "This function is one possible way to define the so-called cost function.\n", + "\n", + "It is also common to define\n", + "the function $C$ as" + ] + }, + { + "cell_type": "markdown", + "id": "998294a3", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "C(\\boldsymbol{\\beta})=\\frac{1}{2n}\\sum_{i=0}^{n-1}\\left(y_i-\\tilde{y}_i\\right)^2,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "12ad4cff", + "metadata": { + "editable": true + }, + "source": [ + "since when taking the first derivative with respect to the unknown parameters $\\beta$, the factor of $2$ cancels out. \n", + "\n", + "The function" + ] + }, + { + "cell_type": "markdown", + "id": "27b8d13f", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "C(\\boldsymbol{\\beta})=\\frac{1}{n}\\left\\{\\left(\\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right)^T\\left(\\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right)\\right\\},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "fba91a26", + "metadata": { + "editable": true + }, + "source": [ + "can be linked to the variance of the quantity $y_i$ if we interpret the latter as the mean value. \n", + "When linking (see the discussion below) with the maximum likelihood approach below, we will indeed interpret $y_i$ as a mean value" + ] + }, + { + "cell_type": "markdown", + "id": "a0aa0c5c", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y_{i}=\\langle y_i \\rangle = \\beta_0x_{i,0}+\\beta_1x_{i,1}+\\beta_2x_{i,2}+\\dots+\\beta_{n-1}x_{i,n-1}+\\epsilon_i,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "4697643d", + "metadata": { + "editable": true + }, + "source": [ + "where $\\langle y_i \\rangle$ is the mean value. Keep in mind also that\n", + "till now we have treated $y_i$ as the exact value. Normally, the\n", + "response (dependent or outcome) variable $y_i$ the outcome of a\n", + "numerical experiment or another type of experiment and is thus only an\n", + "approximation to the true value. It is then always accompanied by an\n", + "error estimate, often limited to a statistical error estimate given by\n", + "the standard deviation discussed earlier. In the discussion here we\n", + "will treat $y_i$ as our exact value for the response variable.\n", + "\n", + "In order to find the parameters $\\beta_i$ we will then minimize the spread of $C(\\boldsymbol{\\beta})$, that is we are going to solve the problem" + ] + }, + { + "cell_type": "markdown", + "id": "39f478a0", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "{\\displaystyle \\min_{\\boldsymbol{\\beta}\\in\n", + "{\\mathbb{R}}^{p}}}\\frac{1}{n}\\left\\{\\left(\\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right)^T\\left(\\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right)\\right\\}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "f0241afe", + "metadata": { + "editable": true + }, + "source": [ + "In practical terms it means we will require" + ] + }, + { + "cell_type": "markdown", + "id": "fce38de2", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial C(\\boldsymbol{\\beta})}{\\partial \\beta_j} = \\frac{\\partial }{\\partial \\beta_j}\\left[ \\frac{1}{n}\\sum_{i=0}^{n-1}\\left(y_i-\\beta_0x_{i,0}-\\beta_1x_{i,1}-\\beta_2x_{i,2}-\\dots-\\beta_{n-1}x_{i,n-1}\\right)^2\\right]=0,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "6c08544f", + "metadata": { + "editable": true + }, + "source": [ + "which results in" + ] + }, + { + "cell_type": "markdown", + "id": "04cb11f5", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial C(\\boldsymbol{\\beta})}{\\partial \\beta_j} = -\\frac{2}{n}\\left[ \\sum_{i=0}^{n-1}x_{ij}\\left(y_i-\\beta_0x_{i,0}-\\beta_1x_{i,1}-\\beta_2x_{i,2}-\\dots-\\beta_{n-1}x_{i,n-1}\\right)\\right]=0,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "80f58831", + "metadata": { + "editable": true + }, + "source": [ + "or in a matrix-vector form as" + ] + }, + { + "cell_type": "markdown", + "id": "d5d00d83", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial C(\\boldsymbol{\\beta})}{\\partial \\boldsymbol{\\beta}} = 0 = \\boldsymbol{X}^T\\left( \\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right).\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "6c41e17b", + "metadata": { + "editable": true + }, + "source": [ + "We can rewrite" + ] + }, + { + "cell_type": "markdown", + "id": "2a84da97", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial C(\\boldsymbol{\\beta})}{\\partial \\boldsymbol{\\beta}} = 0 = \\boldsymbol{X}^T\\left( \\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right),\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "bb72ee7e", + "metadata": { + "editable": true + }, + "source": [ + "as" + ] + }, + { + "cell_type": "markdown", + "id": "8de2c11f", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{X}^T\\boldsymbol{y} = \\boldsymbol{X}^T\\boldsymbol{X}\\boldsymbol{\\beta},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "0097a863", + "metadata": { + "editable": true + }, + "source": [ + "and if the matrix $\\boldsymbol{X}^T\\boldsymbol{X}$ is invertible we have the solution" + ] + }, + { + "cell_type": "markdown", + "id": "a12d4833", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{\\beta} =\\left(\\boldsymbol{X}^T\\boldsymbol{X}\\right)^{-1}\\boldsymbol{X}^T\\boldsymbol{y}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "c2959173", + "metadata": { + "editable": true + }, + "source": [ + "We note also that since our design matrix is defined as $\\boldsymbol{X}\\in\n", + "{\\mathbb{R}}^{n\\times p}$, the product $\\boldsymbol{X}^T\\boldsymbol{X} \\in\n", + "{\\mathbb{R}}^{p\\times p}$. In the above case we have that $p \\ll n$,\n", + "in our case $p=5$ meaning that we end up with inverting a small\n", + "$5\\times 5$ matrix. This is a rather common situation, in many cases we end up with low-dimensional\n", + "matrices to invert. The methods discussed here and for many other\n", + "supervised learning algorithms like classification with logistic\n", + "regression or support vector machines, exhibit dimensionalities which\n", + "allow for the usage of direct linear algebra methods such as **LU** decomposition or **Singular Value Decomposition** (SVD) for finding the inverse of the matrix\n", + "$\\boldsymbol{X}^T\\boldsymbol{X}$. \n", + "\n", + "**Small question**: Do you think the example we have at hand here (the nuclear binding energies) can lead to problems in inverting the matrix $\\boldsymbol{X}^T\\boldsymbol{X}$? What kind of problems can we expect? \n", + "\n", + "The following matrix and vector relation will be useful here and for the rest of the course. Vectors are always written as boldfaced lower case letters and \n", + "matrices as upper case boldfaced letters." + ] + }, + { + "cell_type": "markdown", + "id": "2052d76c", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial\\boldsymbol{b}^T\\boldsymbol{a}}{\\partial\\boldsymbol{a}}=\\boldsymbol{b},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "b52ba39b", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial\\boldsymbol{a}^T\\boldsymbol{A}\\boldsymbol{a}}{\\partial\\boldsymbol{a}}=(\\boldsymbol{A}+\\boldsymbol{A}^T)\\boldsymbol{a},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "0cc70939", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial tr(\\boldsymbol{B}\\boldsymbol{A})}{\\partial\\boldsymbol{A}}=\\boldsymbol{B}^T,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "793face0", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial\\log{\\vert\\boldsymbol{A}\\vert}}{\\partial \\boldsymbol{A}}=(\\boldsymbol{A}^{-1})^T.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "5f51c6e0", + "metadata": { + "editable": true + }, + "source": [ + "We can then compute the second derivative of the cost function, which in our case is the second derivative\n", + "of the means squared error. This leads to" + ] + }, + { + "cell_type": "markdown", + "id": "6675f933", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial^2 C(\\boldsymbol{\\beta})}{\\partial \\boldsymbol{\\beta}^T\\partial \\boldsymbol{\\beta}} =\\frac{2}{n}\\boldsymbol{X}^T\\boldsymbol{X}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "ebd22a84", + "metadata": { + "editable": true + }, + "source": [ + "This quantity defines was what is called the Hessian matrix (the second derivative of a function we want to optimize).\n", + "\n", + "The Hessian matrix plays an important role and is defined for the mean squared error as" + ] + }, + { + "cell_type": "markdown", + "id": "d1ba4497", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{H}=\\boldsymbol{X}^T\\boldsymbol{X}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "066d533a", + "metadata": { + "editable": true + }, + "source": [ + "The Hessian matrix for ordinary least squares is also proportional to\n", + "the covariance matrix. As we will see in the chapter on Ridge and Lasso regression, This means that we can use the Singular Value Decomposition of a matrix to find\n", + "the eigenvalues of the covariance matrix and the Hessian matrix in\n", + "terms of the singular values.\n", + "\n", + "The residuals $\\boldsymbol{\\epsilon}$ are in turn given by" + ] + }, + { + "cell_type": "markdown", + "id": "11166d2a", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{\\epsilon} = \\boldsymbol{y}-\\boldsymbol{\\tilde{y}} = \\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "f45efc00", + "metadata": { + "editable": true + }, + "source": [ + "and with" + ] + }, + { + "cell_type": "markdown", + "id": "d2421edc", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{X}^T\\left( \\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right)= 0,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "37c4dc8b", + "metadata": { + "editable": true + }, + "source": [ + "we have" + ] + }, + { + "cell_type": "markdown", + "id": "1827aadc", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{X}^T\\boldsymbol{\\epsilon}=\\boldsymbol{X}^T\\left( \\boldsymbol{y}-\\boldsymbol{X}\\boldsymbol{\\beta}\\right)= 0,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "6f377416", + "metadata": { + "editable": true + }, + "source": [ + "meaning that the solution for $\\boldsymbol{\\beta}$ is the one which minimizes the residuals. Later we will link this with the maximum likelihood approach.\n", + "\n", + "Let us now return to our nuclear binding energies and simply code the above equations. \n", + "\n", + "It is rather straightforward to implement the matrix inversion and obtain the parameters $\\boldsymbol{\\beta}$. After having defined the matrix $\\boldsymbol{X}$ we simply need to \n", + "write" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "56ad6a65", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# matrix inversion to find beta\n", + "beta = np.linalg.inv(X.T.dot(X)).dot(X.T).dot(Energies)\n", + "# and then make the prediction\n", + "ytilde = X @ beta" + ] + }, + { + "cell_type": "markdown", + "id": "f14523f7", + "metadata": { + "editable": true + }, + "source": [ + "Alternatively, you can use the least squares functionality in **Numpy** as" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b1e2a455", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "fit = np.linalg.lstsq(X, Energies, rcond =None)[0]\n", + "ytildenp = np.dot(fit,X.T)" + ] + }, + { + "cell_type": "markdown", + "id": "dd834990", + "metadata": { + "editable": true + }, + "source": [ + "And finally we plot our fit with and compare with data" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "d051a9f9", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "Masses['Eapprox'] = ytilde\n", + "# Generate a plot comparing the experimental with the fitted values values.\n", + "fig, ax = plt.subplots()\n", + "ax.set_xlabel(r'$A = N + Z$')\n", + "ax.set_ylabel(r'$E_\\mathrm{bind}\\,/\\mathrm{MeV}$')\n", + "ax.plot(Masses['A'], Masses['Ebinding'], alpha=0.7, lw=2,\n", + " label='Ame2016')\n", + "ax.plot(Masses['A'], Masses['Eapprox'], alpha=0.7, lw=2, c='m',\n", + " label='Fit')\n", + "ax.legend()\n", + "save_fig(\"Masses2016OLS\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "7528b95b", + "metadata": { + "editable": true + }, + "source": [ + "We can easily test our fit by computing the $R2$ score that we discussed in connection with the functionality of **Scikit-Learn** in the introductory slides.\n", + "Since we are not using **Scikit-Learn** here we can define our own $R2$ function as" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "f285746f", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "def R2(y_data, y_model):\n", + " return 1 - np.sum((y_data - y_model) ** 2) / np.sum((y_data - np.mean(y_data)) ** 2)" + ] + }, + { + "cell_type": "markdown", + "id": "d6cbf368", + "metadata": { + "editable": true + }, + "source": [ + "and we would be using it as" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "5d65a4fb", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "print(R2(Energies,ytilde))" + ] + }, + { + "cell_type": "markdown", + "id": "5008ed82", + "metadata": { + "editable": true + }, + "source": [ + "We can easily add our **MSE** score as" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2fe3c9b1", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "def MSE(y_data,y_model):\n", + " n = np.size(y_model)\n", + " return np.sum((y_data-y_model)**2)/n\n", + "\n", + "print(MSE(Energies,ytilde))" + ] + }, + { + "cell_type": "markdown", + "id": "9641b76a", + "metadata": { + "editable": true + }, + "source": [ + "and finally the relative error as" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "ec2c053f", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "def RelativeError(y_data,y_model):\n", + " return abs((y_data-y_model)/y_data)\n", + "print(RelativeError(Energies, ytilde))" + ] + }, + { + "cell_type": "markdown", + "id": "23107725", + "metadata": { + "editable": true + }, + "source": [ + "### The $\\chi^2$ function\n", + "\n", + "Normally, the response (dependent or outcome) variable $y_i$ is the\n", + "outcome of a numerical experiment or another type of experiment and is\n", + "thus only an approximation to the true value. It is then always\n", + "accompanied by an error estimate, often limited to a statistical error\n", + "estimate given by the standard deviation discussed earlier. In the\n", + "discussion here we will treat $y_i$ as our exact value for the\n", + "response variable.\n", + "\n", + "Introducing the standard deviation $\\sigma_i$ for each measurement\n", + "$y_i$, we define now the $\\chi^2$ function (omitting the $1/n$ term)\n", + "as" + ] + }, + { + "cell_type": "markdown", + "id": "e5e8f59d", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\chi^2(\\boldsymbol{\\beta})=\\frac{1}{n}\\sum_{i=0}^{n-1}\\frac{\\left(y_i-\\tilde{y}_i\\right)^2}{\\sigma_i^2}=\\frac{1}{n}\\left\\{\\left(\\boldsymbol{y}-\\boldsymbol{\\tilde{y}}\\right)^T\\frac{1}{\\boldsymbol{\\Sigma^2}}\\left(\\boldsymbol{y}-\\boldsymbol{\\tilde{y}}\\right)\\right\\},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "2c42caf5", + "metadata": { + "editable": true + }, + "source": [ + "where the matrix $\\boldsymbol{\\Sigma}$ is a diagonal matrix with $\\sigma_i$ as matrix elements. \n", + "\n", + "In order to find the parameters $\\beta_i$ we will then minimize the spread of $\\chi^2(\\boldsymbol{\\beta})$ by requiring" + ] + }, + { + "cell_type": "markdown", + "id": "47763df9", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial \\chi^2(\\boldsymbol{\\beta})}{\\partial \\beta_j} = \\frac{\\partial }{\\partial \\beta_j}\\left[ \\frac{1}{n}\\sum_{i=0}^{n-1}\\left(\\frac{y_i-\\beta_0x_{i,0}-\\beta_1x_{i,1}-\\beta_2x_{i,2}-\\dots-\\beta_{n-1}x_{i,n-1}}{\\sigma_i}\\right)^2\\right]=0,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "a0063426", + "metadata": { + "editable": true + }, + "source": [ + "which results in" + ] + }, + { + "cell_type": "markdown", + "id": "ae99ac64", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial \\chi^2(\\boldsymbol{\\beta})}{\\partial \\beta_j} = -\\frac{2}{n}\\left[ \\sum_{i=0}^{n-1}\\frac{x_{ij}}{\\sigma_i}\\left(\\frac{y_i-\\beta_0x_{i,0}-\\beta_1x_{i,1}-\\beta_2x_{i,2}-\\dots-\\beta_{n-1}x_{i,n-1}}{\\sigma_i}\\right)\\right]=0,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "dd87eb2b", + "metadata": { + "editable": true + }, + "source": [ + "or in a matrix-vector form as" + ] + }, + { + "cell_type": "markdown", + "id": "79471ec8", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial \\chi^2(\\boldsymbol{\\beta})}{\\partial \\boldsymbol{\\beta}} = 0 = \\boldsymbol{A}^T\\left( \\boldsymbol{b}-\\boldsymbol{A}\\boldsymbol{\\beta}\\right).\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "3c481fab", + "metadata": { + "editable": true + }, + "source": [ + "where we have defined the matrix $\\boldsymbol{A} =\\boldsymbol{X}/\\boldsymbol{\\Sigma}$ with matrix elements $a_{ij} = x_{ij}/\\sigma_i$ and the vector $\\boldsymbol{b}$ with elements $b_i = y_i/\\sigma_i$. \n", + "\n", + "We can rewrite" + ] + }, + { + "cell_type": "markdown", + "id": "efe01f34", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial \\chi^2(\\boldsymbol{\\beta})}{\\partial \\boldsymbol{\\beta}} = 0 = \\boldsymbol{A}^T\\left( \\boldsymbol{b}-\\boldsymbol{A}\\boldsymbol{\\beta}\\right),\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "76c0d9db", + "metadata": { + "editable": true + }, + "source": [ + "as" + ] + }, + { + "cell_type": "markdown", + "id": "eec993ea", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{A}^T\\boldsymbol{b} = \\boldsymbol{A}^T\\boldsymbol{A}\\boldsymbol{\\beta},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "69c23f76", + "metadata": { + "editable": true + }, + "source": [ + "and if the matrix $\\boldsymbol{A}^T\\boldsymbol{A}$ is invertible we have the solution" + ] + }, + { + "cell_type": "markdown", + "id": "9aece507", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{\\beta} =\\left(\\boldsymbol{A}^T\\boldsymbol{A}\\right)^{-1}\\boldsymbol{A}^T\\boldsymbol{b}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "b164740e", + "metadata": { + "editable": true + }, + "source": [ + "If we then introduce the matrix" + ] + }, + { + "cell_type": "markdown", + "id": "6b9d3eac", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\boldsymbol{H} = \\left(\\boldsymbol{A}^T\\boldsymbol{A}\\right)^{-1},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "72db4dda", + "metadata": { + "editable": true + }, + "source": [ + "we have then the following expression for the parameters $\\beta_j$ (the matrix elements of $\\boldsymbol{H}$ are $h_{ij}$)" + ] + }, + { + "cell_type": "markdown", + "id": "458e868e", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\beta_j = \\sum_{k=0}^{p-1}h_{jk}\\sum_{i=0}^{n-1}\\frac{y_i}{\\sigma_i}\\frac{x_{ik}}{\\sigma_i} = \\sum_{k=0}^{p-1}h_{jk}\\sum_{i=0}^{n-1}b_ia_{ik}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "14120da0", + "metadata": { + "editable": true + }, + "source": [ + "We state without proof the expression for the uncertainty in the parameters $\\beta_j$ as (we leave this as an exercise)" + ] + }, + { + "cell_type": "markdown", + "id": "de315ce1", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\sigma^2(\\beta_j) = \\sum_{i=0}^{n-1}\\sigma_i^2\\left( \\frac{\\partial \\beta_j}{\\partial y_i}\\right)^2,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "c4627c98", + "metadata": { + "editable": true + }, + "source": [ + "resulting in" + ] + }, + { + "cell_type": "markdown", + "id": "0a017823", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\sigma^2(\\beta_j) = \\left(\\sum_{k=0}^{p-1}h_{jk}\\sum_{i=0}^{n-1}a_{ik}\\right)\\left(\\sum_{l=0}^{p-1}h_{jl}\\sum_{m=0}^{n-1}a_{ml}\\right) = h_{jj}!\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "b27ceac7", + "metadata": { + "editable": true + }, + "source": [ + "The first step here is to approximate the function $y$ with a first-order polynomial, that is we write" + ] + }, + { + "cell_type": "markdown", + "id": "abdb9cfb", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "y=y(x) \\rightarrow y(x_i) \\approx \\beta_0+\\beta_1 x_i.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "6065ef18", + "metadata": { + "editable": true + }, + "source": [ + "By computing the derivatives of $\\chi^2$ with respect to $\\beta_0$ and $\\beta_1$ show that these are given by" + ] + }, + { + "cell_type": "markdown", + "id": "01b6fe26", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial \\chi^2(\\boldsymbol{\\beta})}{\\partial \\beta_0} = -2\\left[ \\frac{1}{n}\\sum_{i=0}^{n-1}\\left(\\frac{y_i-\\beta_0-\\beta_1x_{i}}{\\sigma_i^2}\\right)\\right]=0,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "414ff7bd", + "metadata": { + "editable": true + }, + "source": [ + "and" + ] + }, + { + "cell_type": "markdown", + "id": "6ed7bb4c", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\frac{\\partial \\chi^2(\\boldsymbol{\\beta})}{\\partial \\beta_1} = -\\frac{2}{n}\\left[ \\sum_{i=0}^{n-1}x_i\\left(\\frac{y_i-\\beta_0-\\beta_1x_{i}}{\\sigma_i^2}\\right)\\right]=0.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "993edfbb", + "metadata": { + "editable": true + }, + "source": [ + "For a linear fit (a first-order polynomial) we don't need to invert a matrix!! \n", + "Defining" + ] + }, + { + "cell_type": "markdown", + "id": "b5ce44a7", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\gamma = \\sum_{i=0}^{n-1}\\frac{1}{\\sigma_i^2},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "88bec43f", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\gamma_x = \\sum_{i=0}^{n-1}\\frac{x_{i}}{\\sigma_i^2},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "2a9e5919", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\gamma_y = \\sum_{i=0}^{n-1}\\left(\\frac{y_i}{\\sigma_i^2}\\right),\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "8b7c5212", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\gamma_{xx} = \\sum_{i=0}^{n-1}\\frac{x_ix_{i}}{\\sigma_i^2},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "96392911", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\gamma_{xy} = \\sum_{i=0}^{n-1}\\frac{y_ix_{i}}{\\sigma_i^2},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "66f56c20", + "metadata": { + "editable": true + }, + "source": [ + "we obtain" + ] + }, + { + "cell_type": "markdown", + "id": "d073d88a", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\beta_0 = \\frac{\\gamma_{xx}\\gamma_y-\\gamma_x\\gamma_y}{\\gamma\\gamma_{xx}-\\gamma_x^2},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "e10c263c", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\beta_1 = \\frac{\\gamma_{xy}\\gamma-\\gamma_x\\gamma_y}{\\gamma\\gamma_{xx}-\\gamma_x^2}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "73e6d72b", + "metadata": { + "editable": true + }, + "source": [ + "This approach (different linear and non-linear regression) suffers\n", + "often from both being underdetermined and overdetermined in the\n", + "unknown coefficients $\\beta_i$. A better approach is to use the\n", + "Singular Value Decomposition (SVD) method discussed below. Or using\n", + "Lasso and Ridge regression. See below." + ] + }, + { + "cell_type": "markdown", + "id": "f6b5761a", + "metadata": { + "editable": true + }, + "source": [ + "### Fitting an Equation of State for Dense Nuclear Matter\n", + "\n", + "Before we continue, let us introduce yet another example. We are going to fit the\n", + "nuclear equation of state using results from many-body calculations.\n", + "The equation of state we have made available here, as function of\n", + "density, has been derived using modern nucleon-nucleon potentials with\n", + "[the addition of three-body\n", + "forces](https://www.sciencedirect.com/science/article/pii/S0370157399001106). This\n", + "time the file is presented as a standard **csv** file.\n", + "\n", + "The beginning of the Python code here is similar to what you have seen\n", + "before, with the same initializations and declarations. We use also\n", + "**pandas** again, rather extensively in order to organize our data.\n", + "\n", + "The difference now is that we use **Scikit-Learn's** regression tools\n", + "instead of our own matrix inversion implementation." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "540f29fd", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# Common imports\n", + "import os\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.pyplot as plt\n", + "import sklearn.linear_model as skl\n", + "from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error\n", + "\n", + "# Where to save the figures and data files\n", + "PROJECT_ROOT_DIR = \"Results\"\n", + "FIGURE_ID = \"Results/FigureFiles\"\n", + "DATA_ID = \"DataFiles/\"\n", + "\n", + "if not os.path.exists(PROJECT_ROOT_DIR):\n", + " os.mkdir(PROJECT_ROOT_DIR)\n", + "\n", + "if not os.path.exists(FIGURE_ID):\n", + " os.makedirs(FIGURE_ID)\n", + "\n", + "if not os.path.exists(DATA_ID):\n", + " os.makedirs(DATA_ID)\n", + "\n", + "def image_path(fig_id):\n", + " return os.path.join(FIGURE_ID, fig_id)\n", + "\n", + "def data_path(dat_id):\n", + " return os.path.join(DATA_ID, dat_id)\n", + "\n", + "def save_fig(fig_id):\n", + " plt.savefig(image_path(fig_id) + \".png\", format='png')\n", + "\n", + "infile = open(data_path(\"EoS.csv\"),'r')\n", + "\n", + "# Read the EoS data as csv file and organize the data into two arrays with density and energies\n", + "EoS = pd.read_csv(infile, names=('Density', 'Energy'))\n", + "EoS['Energy'] = pd.to_numeric(EoS['Energy'], errors='coerce')\n", + "EoS = EoS.dropna()\n", + "Energies = EoS['Energy']\n", + "Density = EoS['Density']\n", + "# The design matrix now as function of various polytrops\n", + "X = np.zeros((len(Density),4))\n", + "X[:,3] = Density**(4.0/3.0)\n", + "X[:,2] = Density\n", + "X[:,1] = Density**(2.0/3.0)\n", + "X[:,0] = 1\n", + "\n", + "# We use now Scikit-Learn's linear regressor and ridge regressor\n", + "# OLS part\n", + "clf = skl.LinearRegression().fit(X, Energies)\n", + "ytilde = clf.predict(X)\n", + "EoS['Eols'] = ytilde\n", + "# The mean squared error \n", + "print(\"Mean squared error: %.2f\" % mean_squared_error(Energies, ytilde))\n", + "# Explained variance score: 1 is perfect prediction \n", + "print('Variance score: %.2f' % r2_score(Energies, ytilde))\n", + "# Mean absolute error \n", + "print('Mean absolute error: %.2f' % mean_absolute_error(Energies, ytilde))\n", + "print(clf.coef_, clf.intercept_)\n", + "\n", + "\n", + "fig, ax = plt.subplots()\n", + "ax.set_xlabel(r'$\\rho[\\mathrm{fm}^{-3}]$')\n", + "ax.set_ylabel(r'Energy per particle')\n", + "ax.plot(EoS['Density'], EoS['Energy'], alpha=0.7, lw=2,\n", + " label='Theoretical data')\n", + "ax.plot(EoS['Density'], EoS['Eols'], alpha=0.7, lw=2, c='m',\n", + " label='OLS')\n", + "ax.legend()\n", + "save_fig(\"EoSfitting\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "dced161c", + "metadata": { + "editable": true + }, + "source": [ + "The above simple polynomial in density $\\rho$ gives an excellent fit\n", + "to the data." + ] + }, + { + "cell_type": "markdown", + "id": "a8de86a4", + "metadata": { + "editable": true + }, + "source": [ + "## Splitting our Data in Training and Test data\n", + "\n", + "It is normal in essentially all Machine Learning studies to split the\n", + "data in a training set and a test set (sometimes also an additional\n", + "validation set). **Scikit-Learn** has an own function for this. There\n", + "is no explicit recipe for how much data should be included as training\n", + "data and say test data. An accepted rule of thumb is to use\n", + "approximately $2/3$ to $4/5$ of the data as training data. We will\n", + "postpone a discussion of this splitting to the end of these notes and\n", + "our discussion of the so-called **bias-variance** tradeoff. Here we\n", + "limit ourselves to repeat the above equation of state fitting example\n", + "but now splitting the data into a training set and a test set.\n", + "\n", + "Let us study some examples. The first code here takes a simple\n", + "one-dimensional second-order polynomial and we fit it to a\n", + "second-order polynomial. Depending on the strength of the added noise,\n", + "the various measures like the $R2$ score or the mean-squared error,\n", + "the fit becomes better or worse." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "9fc00294", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import os\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "\n", + "def R2(y_data, y_model):\n", + " return 1 - np.sum((y_data - y_model) ** 2) / np.sum((y_data - np.mean(y_data)) ** 2)\n", + "def MSE(y_data,y_model):\n", + " n = np.size(y_model)\n", + " return np.sum((y_data-y_model)**2)/n\n", + "\n", + "x = np.random.rand(100)\n", + "y = 2.0+5*x*x+0.1*np.random.randn(100)\n", + "\n", + "\n", + "# The design matrix now as function of a given polynomial\n", + "X = np.zeros((len(x),3))\n", + "X[:,0] = 1.0\n", + "X[:,1] = x\n", + "X[:,2] = x**2\n", + "# We split the data in test and training data\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)\n", + "# matrix inversion to find beta\n", + "beta = np.linalg.inv(X_train.T @ X_train) @ X_train.T @ y_train\n", + "print(beta)\n", + "# and then make the prediction\n", + "ytilde = X_train @ beta\n", + "print(\"Training R2\")\n", + "print(R2(y_train,ytilde))\n", + "print(\"Training MSE\")\n", + "print(MSE(y_train,ytilde))\n", + "ypredict = X_test @ beta\n", + "print(\"Test R2\")\n", + "print(R2(y_test,ypredict))\n", + "print(\"Test MSE\")\n", + "print(MSE(y_test,ypredict))" + ] + }, + { + "cell_type": "markdown", + "id": "230a73ae", + "metadata": { + "editable": true + }, + "source": [ + "Alternatively, you could write your own test-train splitting function as shown here." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "44159f62", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# equivalently in numpy\n", + "def train_test_split_numpy(inputs, labels, train_size, test_size):\n", + " n_inputs = len(inputs)\n", + " inputs_shuffled = inputs.copy()\n", + " labels_shuffled = labels.copy()\n", + "\n", + " np.random.shuffle(inputs_shuffled)\n", + " np.random.shuffle(labels_shuffled)\n", + "\n", + " train_end = int(n_inputs*train_size)\n", + " X_train, X_test = inputs_shuffled[:train_end], inputs_shuffled[train_end:]\n", + " Y_train, Y_test = labels_shuffled[:train_end], labels_shuffled[train_end:]\n", + "\n", + " return X_train, X_test, Y_train, Y_test" + ] + }, + { + "cell_type": "markdown", + "id": "f97b8ec0", + "metadata": { + "editable": true + }, + "source": [ + "But since **scikit-learn** has its own function for doing this and since\n", + "it interfaces easily with **tensorflow** and other libraries, we\n", + "normally recommend using the latter functionality.\n", + "\n", + "As another example, we apply the training and testing split to \n", + "to the above equation of state fitting example\n", + "but now splitting the data into a training set and a test set." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "b0a51b9e", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import os\n", + "import numpy as np\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "from sklearn.model_selection import train_test_split\n", + "# Where to save the figures and data files\n", + "PROJECT_ROOT_DIR = \"Results\"\n", + "FIGURE_ID = \"Results/FigureFiles\"\n", + "DATA_ID = \"DataFiles/\"\n", + "\n", + "if not os.path.exists(PROJECT_ROOT_DIR):\n", + " os.mkdir(PROJECT_ROOT_DIR)\n", + "\n", + "if not os.path.exists(FIGURE_ID):\n", + " os.makedirs(FIGURE_ID)\n", + "\n", + "if not os.path.exists(DATA_ID):\n", + " os.makedirs(DATA_ID)\n", + "\n", + "def image_path(fig_id):\n", + " return os.path.join(FIGURE_ID, fig_id)\n", + "\n", + "def data_path(dat_id):\n", + " return os.path.join(DATA_ID, dat_id)\n", + "\n", + "def save_fig(fig_id):\n", + " plt.savefig(image_path(fig_id) + \".png\", format='png')\n", + "\n", + "def R2(y_data, y_model):\n", + " return 1 - np.sum((y_data - y_model) ** 2) / np.sum((y_data - np.mean(y_data)) ** 2)\n", + "def MSE(y_data,y_model):\n", + " n = np.size(y_model)\n", + " return np.sum((y_data-y_model)**2)/n\n", + "\n", + "infile = open(data_path(\"EoS.csv\"),'r')\n", + "\n", + "# Read the EoS data as csv file and organized into two arrays with density and energies\n", + "EoS = pd.read_csv(infile, names=('Density', 'Energy'))\n", + "EoS['Energy'] = pd.to_numeric(EoS['Energy'], errors='coerce')\n", + "EoS = EoS.dropna()\n", + "Energies = EoS['Energy']\n", + "Density = EoS['Density']\n", + "# The design matrix now as function of various polytrops\n", + "X = np.zeros((len(Density),5))\n", + "X[:,0] = 1\n", + "X[:,1] = Density**(2.0/3.0)\n", + "X[:,2] = Density\n", + "X[:,3] = Density**(4.0/3.0)\n", + "X[:,4] = Density**(5.0/3.0)\n", + "# We split the data in test and training data\n", + "X_train, X_test, y_train, y_test = train_test_split(X, Energies, test_size=0.2)\n", + "# matrix inversion to find beta\n", + "beta = np.linalg.inv(X_train.T.dot(X_train)).dot(X_train.T).dot(y_train)\n", + "# and then make the prediction\n", + "ytilde = X_train @ beta\n", + "print(\"Training R2\")\n", + "print(R2(y_train,ytilde))\n", + "print(\"Training MSE\")\n", + "print(MSE(y_train,ytilde))\n", + "ypredict = X_test @ beta\n", + "print(\"Test R2\")\n", + "print(R2(y_test,ypredict))\n", + "print(\"Test MSE\")\n", + "print(MSE(y_test,ypredict))" + ] + }, + { + "cell_type": "markdown", + "id": "1770afec", + "metadata": { + "editable": true + }, + "source": [ + "## The Boston housing data example\n", + "\n", + "The Boston housing \n", + "data set was originally a part of UCI Machine Learning Repository\n", + "and has been removed now. The data set is now included in **Scikit-Learn**'s \n", + "library. There are 506 samples and 13 feature (predictor) variables\n", + "in this data set. The objective is to predict the value of prices of\n", + "the house using the features (predictors) listed here.\n", + "\n", + "The features/predictors are\n", + "1. CRIM: Per capita crime rate by town\n", + "\n", + "2. ZN: Proportion of residential land zoned for lots over 25000 square feet\n", + "\n", + "3. INDUS: Proportion of non-retail business acres per town\n", + "\n", + "4. CHAS: Charles River dummy variable (= 1 if tract bounds river; 0 otherwise)\n", + "\n", + "5. NOX: Nitric oxide concentration (parts per 10 million)\n", + "\n", + "6. RM: Average number of rooms per dwelling\n", + "\n", + "7. AGE: Proportion of owner-occupied units built prior to 1940\n", + "\n", + "8. DIS: Weighted distances to five Boston employment centers\n", + "\n", + "9. RAD: Index of accessibility to radial highways\n", + "\n", + "10. TAX: Full-value property tax rate per USD10000\n", + "\n", + "11. B: $1000(Bk - 0.63)^2$, where $Bk$ is the proportion of [people of African American descent] by town\n", + "\n", + "12. LSTAT: Percentage of lower status of the population\n", + "\n", + "13. MEDV: Median value of owner-occupied homes in USD 1000s" + ] + }, + { + "cell_type": "markdown", + "id": "5aff6cc0", + "metadata": { + "editable": true + }, + "source": [ + "## Housing data, the code\n", + "We start by importing the libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "d38176fb", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt \n", + "\n", + "import pandas as pd \n", + "import seaborn as sns" + ] + }, + { + "cell_type": "markdown", + "id": "5261036b", + "metadata": { + "editable": true + }, + "source": [ + "and load the Boston Housing DataSet from **Scikit-Learn**" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "8620e357", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "from sklearn.datasets import load_boston\n", + "\n", + "boston_dataset = load_boston()\n", + "\n", + "# boston_dataset is a dictionary\n", + "# let's check what it contains\n", + "boston_dataset.keys()" + ] + }, + { + "cell_type": "markdown", + "id": "3efe2d46", + "metadata": { + "editable": true + }, + "source": [ + "Then we invoke Pandas" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "df229a55", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "boston = pd.DataFrame(boston_dataset.data, columns=boston_dataset.feature_names)\n", + "boston.head()\n", + "boston['MEDV'] = boston_dataset.target" + ] + }, + { + "cell_type": "markdown", + "id": "88f2726b", + "metadata": { + "editable": true + }, + "source": [ + "and preprocess the data" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "2cce6e9d", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# check for missing values in all the columns\n", + "boston.isnull().sum()" + ] + }, + { + "cell_type": "markdown", + "id": "d57071f7", + "metadata": { + "editable": true + }, + "source": [ + "We can then visualize the data" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "27a64892", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# set the size of the figure\n", + "sns.set(rc={'figure.figsize':(11.7,8.27)})\n", + "\n", + "# plot a histogram showing the distribution of the target values\n", + "sns.distplot(boston['MEDV'], bins=30)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "371598fb", + "metadata": { + "editable": true + }, + "source": [ + "It is now useful to look at the correlation matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "487c9079", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# compute the pair wise correlation for all columns \n", + "correlation_matrix = boston.corr().round(2)\n", + "# use the heatmap function from seaborn to plot the correlation matrix\n", + "# annot = True to print the values inside the square\n", + "sns.heatmap(data=correlation_matrix, annot=True)" + ] + }, + { + "cell_type": "markdown", + "id": "c5046b22", + "metadata": { + "editable": true + }, + "source": [ + "From the above coorelation plot we can see that **MEDV** is strongly correlated to **LSTAT** and **RM**. We see also that **RAD** and **TAX** are stronly correlated, but we don't include this in our features together to avoid multi-colinearity" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "15eb6709", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "plt.figure(figsize=(20, 5))\n", + "\n", + "features = ['LSTAT', 'RM']\n", + "target = boston['MEDV']\n", + "\n", + "for i, col in enumerate(features):\n", + " plt.subplot(1, len(features) , i+1)\n", + " x = boston[col]\n", + " y = target\n", + " plt.scatter(x, y, marker='o')\n", + " plt.title(col)\n", + " plt.xlabel(col)\n", + " plt.ylabel('MEDV')" + ] + }, + { + "cell_type": "markdown", + "id": "2887ab0e", + "metadata": { + "editable": true + }, + "source": [ + "Now we start training our model" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "c4d0a58c", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "X = pd.DataFrame(np.c_[boston['LSTAT'], boston['RM']], columns = ['LSTAT','RM'])\n", + "Y = boston['MEDV']" + ] + }, + { + "cell_type": "markdown", + "id": "e46427c4", + "metadata": { + "editable": true + }, + "source": [ + "We split the data into training and test sets" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "9624c327", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "from sklearn.model_selection import train_test_split\n", + "\n", + "# splits the training and test data set in 80% : 20%\n", + "# assign random_state to any value.This ensures consistency.\n", + "X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.2, random_state=5)\n", + "print(X_train.shape)\n", + "print(X_test.shape)\n", + "print(Y_train.shape)\n", + "print(Y_test.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "ae726472", + "metadata": { + "editable": true + }, + "source": [ + "Then we use the linear regression functionality from **Scikit-Learn**" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "94a499cc", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "from sklearn.linear_model import LinearRegression\n", + "from sklearn.metrics import mean_squared_error, r2_score\n", + "\n", + "lin_model = LinearRegression()\n", + "lin_model.fit(X_train, Y_train)\n", + "\n", + "# model evaluation for training set\n", + "\n", + "y_train_predict = lin_model.predict(X_train)\n", + "rmse = (np.sqrt(mean_squared_error(Y_train, y_train_predict)))\n", + "r2 = r2_score(Y_train, y_train_predict)\n", + "\n", + "print(\"The model performance for training set\")\n", + "print(\"--------------------------------------\")\n", + "print('RMSE is {}'.format(rmse))\n", + "print('R2 score is {}'.format(r2))\n", + "print(\"\\n\")\n", + "\n", + "# model evaluation for testing set\n", + "\n", + "y_test_predict = lin_model.predict(X_test)\n", + "# root mean square error of the model\n", + "rmse = (np.sqrt(mean_squared_error(Y_test, y_test_predict)))\n", + "\n", + "# r-squared score of the model\n", + "r2 = r2_score(Y_test, y_test_predict)\n", + "\n", + "print(\"The model performance for testing set\")\n", + "print(\"--------------------------------------\")\n", + "print('RMSE is {}'.format(rmse))\n", + "print('R2 score is {}'.format(r2))" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "8e2f7cf0", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# plotting the y_test vs y_pred\n", + "# ideally should have been a straight line\n", + "plt.scatter(Y_test, y_test_predict)\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "17e6e280", + "metadata": { + "editable": true + }, + "source": [ + "## Reducing the number of degrees of freedom, overarching view\n", + "\n", + "Many Machine Learning problems involve thousands or even millions of\n", + "features for each training instance. Not only does this make training\n", + "extremely slow, it can also make it much harder to find a good\n", + "solution, as we will see. This problem is often referred to as the\n", + "curse of dimensionality. Fortunately, in real-world problems, it is\n", + "often possible to reduce the number of features considerably, turning\n", + "an intractable problem into a tractable one.\n", + "\n", + "Later we will discuss some of the most popular dimensionality reduction\n", + "techniques: the principal component analysis (PCA), Kernel PCA, and\n", + "Locally Linear Embedding (LLE). \n", + "\n", + "Principal component analysis and its various variants deal with the\n", + "problem of fitting a low-dimensional [affine\n", + "subspace](https://en.wikipedia.org/wiki/Affine_space) to a set of of\n", + "data points in a high-dimensional space. With its family of methods it\n", + "is one of the most used tools in data modeling, compression and\n", + "visualization.\n", + "\n", + "Before we proceed however, we will discuss how to preprocess our\n", + "data. Till now and in connection with our previous examples we have\n", + "not met so many cases where we are too sensitive to the scaling of our\n", + "data. Normally the data may need a rescaling and/or may be sensitive\n", + "to extreme values. Scaling the data renders our inputs much more\n", + "suitable for the algorithms we want to employ.\n", + "\n", + "For data sets gathered for real world applications, it is rather normal that\n", + "different features have very different units and\n", + "numerical scales. For example, a data set detailing health habits may include\n", + "features such as **age** in the range $0-80$, and **caloric intake** of order $2000$.\n", + "Many machine learning methods sensitive to the scales of the features and may perform poorly if they\n", + "are very different scales. Therefore, it is typical to scale\n", + "the features in a way to avoid such outlier values.\n", + "\n", + "**Scikit-Learn** has several functions which allow us to rescale the\n", + "data, normally resulting in much better results in terms of various\n", + "accuracy scores. The **StandardScaler** function in **Scikit-Learn**\n", + "ensures that for each feature/predictor we study the mean value is\n", + "zero and the variance is one (every column in the design/feature\n", + "matrix). This scaling has the drawback that it does not ensure that\n", + "we have a particular maximum or minimum in our data set. Another\n", + "function included in **Scikit-Learn** is the **MinMaxScaler** which\n", + "ensures that all features are exactly between $0$ and $1$. The\n", + "\n", + "The **Normalizer** scales each data\n", + "point such that the feature vector has a euclidean length of one. In other words, it\n", + "projects a data point on the circle (or sphere in the case of higher dimensions) with a\n", + "radius of 1. This means every data point is scaled by a different number (by the\n", + "inverse of it’s length).\n", + "This normalization is often used when only the direction (or angle) of the data matters,\n", + "not the length of the feature vector.\n", + "\n", + "The **RobustScaler** works similarly to the StandardScaler in that it\n", + "ensures statistical properties for each feature that guarantee that\n", + "they are on the same scale. However, the RobustScaler uses the median\n", + "and quartiles, instead of mean and variance. This makes the\n", + "RobustScaler ignore data points that are very different from the rest\n", + "(like measurement errors). These odd data points are also called\n", + "outliers, and might often lead to trouble for other scaling\n", + "techniques.\n", + "\n", + "Many features are often scaled using standardization to improve\n", + "performance. In **Scikit-Learn** this is given by the **StandardScaler**\n", + "function as discussed above. It is easy however to write your own.\n", + "Mathematically, this involves subtracting the mean and divide by the\n", + "standard deviation over the data set, for each feature:" + ] + }, + { + "cell_type": "markdown", + "id": "fbdb7902", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "x_j^{(i)} \\rightarrow \\frac{x_j^{(i)} - \\overline{x}_j}{\\sigma(x_j)},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "27bfecb1", + "metadata": { + "editable": true + }, + "source": [ + "where $\\overline{x}_j$ and $\\sigma(x_j)$ are the mean and standard\n", + "deviation, respectively, of the feature $x_j$. This ensures that each\n", + "feature has zero mean and unit standard deviation. For data sets\n", + "where we do not have the standard deviation or don't wish to calculate\n", + "it, it is then common to simply set it to one.\n", + "\n", + "Let us consider the following vanilla example where we use both\n", + "**Scikit-Learn** and write our own function as well. We produce a\n", + "simple test design matrix with random numbers. Each column could then\n", + "represent a specific feature whose mean value is subracted." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "00f32aef", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import sklearn.linear_model as skl\n", + "from sklearn.metrics import mean_squared_error\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.preprocessing import MinMaxScaler, StandardScaler, Normalizer\n", + "import numpy as np\n", + "import pandas as pd\n", + "from IPython.display import display\n", + "np.random.seed(100)\n", + "# setting up a 10 x 5 matrix\n", + "rows = 10\n", + "cols = 5\n", + "X = np.random.randn(rows,cols)\n", + "XPandas = pd.DataFrame(X)\n", + "display(XPandas)\n", + "print(XPandas.mean())\n", + "print(XPandas.std())\n", + "XPandas = (XPandas -XPandas.mean())\n", + "display(XPandas)\n", + "# This option does not include the standard deviation\n", + "scaler = StandardScaler(with_std=False)\n", + "scaler.fit(X)\n", + "Xscaled = scaler.transform(X)\n", + "display(XPandas-Xscaled)" + ] + }, + { + "cell_type": "markdown", + "id": "dcd1b0a1", + "metadata": { + "editable": true + }, + "source": [ + "Small exercise: perform the standard scaling by including the standard deviation and compare with what Scikit-Learn gives.\n", + "\n", + "Another commonly used scaling method is min-max scaling. This is very\n", + "useful for when we want the features to lie in a certain interval. To\n", + "scale the feature $x_j$ to the interval $[a, b]$, we can apply the\n", + "transformation" + ] + }, + { + "cell_type": "markdown", + "id": "784d2d63", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "x_j^{(i)} \\rightarrow (b-a)\\frac{x_j^{(i)} - \\min(x_j)}{\\max(x_j) - \\min(x_j)} - a\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "0ed3861c", + "metadata": { + "editable": true + }, + "source": [ + "where $\\min(x_j)$ and $\\max(x_j)$ return the minimum and maximum value of $x_j$ over the data set, respectively." + ] + }, + { + "cell_type": "markdown", + "id": "9898dc9e", + "metadata": { + "editable": true + }, + "source": [ + "## Testing the Means Squared Error as function of Complexity\n", + "\n", + "Before we proceed with a more detailed analysis of the so-called\n", + "Bias-Variance tradeoff, we present here an example of the relation\n", + "between model complexity and the mean squared error for the triaining\n", + "data and the test data.\n", + "\n", + "The results here tell us clearly that for the data not included in the\n", + "training, there is an optimal model as function of the complexity of\n", + "ourmodel (here in terms of the polynomial degree of the model).\n", + "\n", + "The results here will vary as function of model complexity and the amount od data used for training. \n", + "\n", + "Our data is defined by $x\\in [-3,3]$ with a total of for example $100$ data points." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "2c42e978", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from sklearn.linear_model import LinearRegression, Ridge, Lasso\n", + "from sklearn.preprocessing import PolynomialFeatures\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.pipeline import make_pipeline\n", + "\n", + "\n", + "np.random.seed(2018)\n", + "n = 100\n", + "maxdegree = 14\n", + "# Make data set.\n", + "x = np.linspace(-3, 3, n).reshape(-1, 1)\n", + "y = np.exp(-x**2) + 1.5 * np.exp(-(x-2)**2)+ np.random.normal(0, 0.1, x.shape)\n", + "TestError = np.zeros(maxdegree)\n", + "TrainError = np.zeros(maxdegree)\n", + "polydegree = np.zeros(maxdegree)\n", + "x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2)\n", + "\n", + "\n", + "for degree in range(maxdegree):\n", + " model = make_pipeline(PolynomialFeatures(degree=degree), LinearRegression(fit_intercept=False))\n", + " clf = model.fit(x_train,y_train)\n", + " y_fit = clf.predict(x_train)\n", + " y_pred = clf.predict(x_test) \n", + " polydegree[degree] = degree\n", + " TestError[degree] = np.mean( np.mean((y_test - y_pred)**2) )\n", + " TrainError[degree] = np.mean( np.mean((y_train - y_fit)**2) )\n", + "\n", + "plt.plot(polydegree, TestError, label='Test Error')\n", + "plt.plot(polydegree, TrainError, label='Train Error')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "1c436a1f", + "metadata": { + "editable": true + }, + "source": [ + "## Exercises" + ] + }, + { + "cell_type": "markdown", + "id": "cd791377", + "metadata": { + "editable": true + }, + "source": [ + "### Exercise: Setting up various Python environments\n", + "\n", + "The first exercise here is of a mere technical art. We want you to have \n", + "* git as a version control software and to establish a user account on a provider like GitHub. Other providers like GitLab etc are equally fine. You can also use the University of Oslo [GitHub facilities](https://www.uio.no/tjenester/it/maskin/filer/versjonskontroll/github.html). \n", + "\n", + "* Install various Python packages\n", + "\n", + "We will make extensive use of Python as programming language and its\n", + "myriad of available libraries. You will find\n", + "IPython/Jupyter notebooks invaluable in your work. You can run **R**\n", + "codes in the Jupyter/IPython notebooks, with the immediate benefit of\n", + "visualizing your data. You can also use compiled languages like C++,\n", + "Rust, Fortran etc if you prefer. The focus in these lectures will be\n", + "on Python.\n", + "\n", + "If you have Python installed (we recommend Python3) and you feel\n", + "pretty familiar with installing different packages, we recommend that\n", + "you install the following Python packages via **pip** as \n", + "\n", + "1. pip install numpy scipy matplotlib ipython scikit-learn sympy pandas pillow \n", + "\n", + "For **Tensorflow**, we recommend following the instructions in the text of \n", + "[Aurelien Geron, Hands‑On Machine Learning with Scikit‑Learn and TensorFlow, O'Reilly](http://shop.oreilly.com/product/0636920052289.do)\n", + "\n", + "We will come back to **tensorflow** later. \n", + "\n", + "For Python3, replace **pip** with **pip3**.\n", + "\n", + "For OSX users we recommend, after having installed Xcode, to\n", + "install **brew**. Brew allows for a seamless installation of additional\n", + "software via for example \n", + "\n", + "1. brew install python3\n", + "\n", + "For Linux users, with its variety of distributions like for example the widely popular Ubuntu distribution,\n", + "you can use **pip** as well and simply install Python as \n", + "\n", + "1. sudo apt-get install python3 (or python for Python2.7)\n", + "\n", + "If you don't want to perform these operations separately and venture\n", + "into the hassle of exploring how to set up dependencies and paths, we\n", + "recommend two widely used distrubutions which set up all relevant\n", + "dependencies for Python, namely \n", + "\n", + "* [Anaconda](https://docs.anaconda.com/), \n", + "\n", + "which is an open source\n", + "distribution of the Python and R programming languages for large-scale\n", + "data processing, predictive analytics, and scientific computing, that\n", + "aims to simplify package management and deployment. Package versions\n", + "are managed by the package management system **conda**. \n", + "\n", + "* [Enthought canopy](https://www.enthought.com/product/canopy/) \n", + "\n", + "is a Python\n", + "distribution for scientific and analytic computing distribution and\n", + "analysis environment, available for free and under a commercial\n", + "license.\n", + "\n", + "We recommend using **Anaconda** if you are not too familiar with setting paths in a terminal environment." + ] + }, + { + "cell_type": "markdown", + "id": "8b7fa338", + "metadata": { + "editable": true + }, + "source": [ + "### Exercise: making your own data and exploring scikit-learn\n", + "\n", + "We will generate our own dataset for a function $y(x)$ where $x \\in [0,1]$ and defined by random numbers computed with the uniform distribution. The function $y$ is a quadratic polynomial in $x$ with added stochastic noise according to the normal distribution $\\cal {N}(0,1)$.\n", + "The following simple Python instructions define our $x$ and $y$ values (with 100 data points)." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "97f48774", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "x = np.random.rand(100,1)\n", + "y = 2.0+5*x*x+0.1*np.random.randn(100,1)" + ] + }, + { + "cell_type": "markdown", + "id": "dbb4f5bd", + "metadata": { + "editable": true + }, + "source": [ + "1. Write your own code (following the examples under the [regression notes](https://compphysics.github.io/MachineLearning/doc/LectureNotes/_build/html/chapter1.html)) for computing the parametrization of the data set fitting a second-order polynomial. \n", + "\n", + "2. Use thereafter **scikit-learn** (see again the examples in the regression slides) and compare with your own code. \n", + "\n", + "3. Using scikit-learn, compute also the mean square error, a risk metric corresponding to the expected value of the squared (quadratic) error defined as" + ] + }, + { + "cell_type": "markdown", + "id": "5ff18ab8", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "MSE(\\boldsymbol{y},\\boldsymbol{\\tilde{y}}) = \\frac{1}{n}\n", + "\\sum_{i=0}^{n-1}(y_i-\\tilde{y}_i)^2,\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "dc726278", + "metadata": { + "editable": true + }, + "source": [ + "and the $R^2$ score function.\n", + "If $\\tilde{\\boldsymbol{y}}_i$ is the predicted value of the $i-th$ sample and $y_i$ is the corresponding true value, then the score $R^2$ is defined as" + ] + }, + { + "cell_type": "markdown", + "id": "57cfd0a3", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "R^2(\\boldsymbol{y}, \\tilde{\\boldsymbol{y}}) = 1 - \\frac{\\sum_{i=0}^{n - 1} (y_i - \\tilde{y}_i)^2}{\\sum_{i=0}^{n - 1} (y_i - \\bar{y})^2},\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "261fcb06", + "metadata": { + "editable": true + }, + "source": [ + "where we have defined the mean value of $\\boldsymbol{y}$ as" + ] + }, + { + "cell_type": "markdown", + "id": "e6459396", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "\\bar{y} = \\frac{1}{n} \\sum_{i=0}^{n - 1} y_i.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "a5f2c612", + "metadata": { + "editable": true + }, + "source": [ + "You can use the functionality included in scikit-learn. If you feel for it, you can use your own program and define functions which compute the above two functions. \n", + "Discuss the meaning of these results. Try also to vary the coefficient in front of the added stochastic noise term and discuss the quality of the fits." + ] + }, + { + "cell_type": "markdown", + "id": "17f65e28", + "metadata": { + "editable": true + }, + "source": [ + "### Exercise: Normalizing our data\n", + "\n", + "A much used approach before starting to train the data is to preprocess our\n", + "data. Normally the data may need a rescaling and/or may be sensitive\n", + "to extreme values. Scaling the data renders our inputs much more\n", + "suitable for the algorithms we want to employ.\n", + "\n", + "**Scikit-Learn** has several functions which allow us to rescale the\n", + "data, normally resulting in much better results in terms of various\n", + "accuracy scores. The **StandardScaler** function in **Scikit-Learn**\n", + "ensures that for each feature/predictor we study the mean value is\n", + "zero and the variance is one (every column in the design/feature\n", + "matrix). This scaling has the drawback that it does not ensure that\n", + "we have a particular maximum or minimum in our data set. Another\n", + "function included in **Scikit-Learn** is the **MinMaxScaler** which\n", + "ensures that all features are exactly between $0$ and $1$. The\n", + "\n", + "The **Normalizer** scales each data\n", + "point such that the feature vector has a euclidean length of one. In other words, it\n", + "projects a data point on the circle (or sphere in the case of higher dimensions) with a\n", + "radius of 1. This means every data point is scaled by a different number (by the\n", + "inverse of it’s length).\n", + "This normalization is often used when only the direction (or angle) of the data matters,\n", + "not the length of the feature vector.\n", + "\n", + "The **RobustScaler** works similarly to the StandardScaler in that it\n", + "ensures statistical properties for each feature that guarantee that\n", + "they are on the same scale. However, the RobustScaler uses the median\n", + "and quartiles, instead of mean and variance. This makes the\n", + "RobustScaler ignore data points that are very different from the rest\n", + "(like measurement errors). These odd data points are also called\n", + "outliers, and might often lead to trouble for other scaling\n", + "techniques.\n", + "\n", + "It also common to split the data in a **training** set and a **testing** set. A typical split is to use $80\\%$ of the data for training and the rest\n", + "for testing. This can be done as follows with our design matrix $\\boldsymbol{X}$ and data $\\boldsymbol{y}$ (remember to import **scikit-learn**)" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "id": "f6c610d2", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "# split in training and test data\n", + "# X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2)" + ] + }, + { + "cell_type": "markdown", + "id": "6b55a45d", + "metadata": { + "editable": true + }, + "source": [ + "Then we can use the standard scaler to scale our data as" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "id": "0a60e58b", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "scaler = StandardScaler()\n", + "scaler.fit(X_train)\n", + "X_train_scaled = scaler.transform(X_train)\n", + "X_test_scaled = scaler.transform(X_test)" + ] + }, + { + "cell_type": "markdown", + "id": "d1061df4", + "metadata": { + "editable": true + }, + "source": [ + "In this exercise we want you to to compute the MSE for the training\n", + "data and the test data as function of the complexity of a polynomial,\n", + "that is the degree of a given polynomial. We want you also to compute the $R2$ score as function of the complexity of the model for both training data and test data. You should also run the calculation with and without scaling. \n", + "\n", + "One of \n", + "the aims is to reproduce Figure 2.11 of [Hastie et al](https://github.com/CompPhysics/MLErasmus/blob/master/doc/Textbooks/elementsstat.pdf).\n", + "\n", + "Our data is defined by $x\\in [-3,3]$ with a total of for example $100$ data points." + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "id": "d708e09a", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "np.random.seed()\n", + "n = 100\n", + "maxdegree = 14\n", + "# Make data set.\n", + "x = np.linspace(-3, 3, n).reshape(-1, 1)\n", + "y = np.exp(-x**2) + 1.5 * np.exp(-(x-2)**2)+ np.random.normal(0, 0.1, x.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "c31441b4", + "metadata": { + "editable": true + }, + "source": [ + "where $y$ is the function we want to fit with a given polynomial.\n", + "\n", + "Write a first code which sets up a design matrix $X$ defined by a\n", + "fifth-order polynomial. Scale your data and split it in training and\n", + "test data.\n", + "\n", + "Perform an ordinary least squares and compute the means squared error\n", + "and the $R2$ factor for the training data and the test data, with and\n", + "without scaling.\n", + "\n", + "Add now a model which allows you to make polynomials up to degree\n", + "$15$. Perform a standard OLS fitting of the training data and compute\n", + "the MSE and $R2$ for the training and test data and plot both test and\n", + "training data MSE and $R2$ as functions of the polynomial\n", + "degree. Compare what you see with Figure 2.11 of Hastie et al. Comment\n", + "your results. For which polynomial degree do you find an optimal MSE\n", + "(smallest value)?" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/BookChapters/chapter10.do.txt b/doc/BookChapters/chapter10.do.txt index 1eec16a34..72e24e9c3 100644 --- a/doc/BookChapters/chapter10.do.txt +++ b/doc/BookChapters/chapter10.do.txt @@ -1009,10 +1009,7 @@ Program Interfaces (APIs), and see how we use them to build our graph. Then we will build (effectively) the same graph in Keras, to see just how simple solving a machine learning problem can be. -To install tensorflow on Unix/Linux systems, use pip as -!bc pycod -pip3 install tensorflow -!ec +To install tensorflow on Unix/Linux systems, use pip as _pip3 install tensorflow_ and/or if you use _anaconda_, just write (or install from the graphical user interface) (current release of CPU-only TensorFlow) !bc pycod diff --git a/doc/BookChapters/chapter10.ipynb b/doc/BookChapters/chapter10.ipynb deleted file mode 100644 index cc65cfe33..000000000 --- a/doc/BookChapters/chapter10.ipynb +++ /dev/null @@ -1,2047 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Building a Feed Forward Neural Network\n", - "\n", - "We are now gong to develop an example based on the MNIST data\n", - "base. This is a classification problem and we need to use our\n", - "cross-entropy function we discussed in connection with logistic\n", - "regression. The cross-entropy defines our cost function for the\n", - "classificaton problems with neural networks.\n", - "\n", - "In binary classification with two classes $(0, 1)$ we define the\n", - "logistic/sigmoid function as the probability that a particular input\n", - "is in class $0$ or $1$. This is possible because the logistic\n", - "function takes any input from the real numbers and inputs a number\n", - "between 0 and 1, and can therefore be interpreted as a probability. It\n", - "also has other nice properties, such as a derivative that is simple to\n", - "calculate.\n", - "\n", - "For an input $\\boldsymbol{a}$ from the hidden layer, the probability that the input $\\boldsymbol{x}$\n", - "is in class 0 or 1 is just. We let $\\theta$ represent the unknown weights and biases to be adjusted by our equations). The variable $x$\n", - "represents our activation values $z$. We have" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "P(y = 0 \\mid \\hat{x}, \\hat{\\theta}) = \\frac{1}{1 + \\exp{(- \\hat{x}})} ,\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "P(y = 1 \\mid \\hat{x}, \\hat{\\theta}) = 1 - P(y = 0 \\mid \\hat{x}, \\hat{\\theta}) ,\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "where $y \\in \\{0, 1\\}$ and $\\hat{\\theta}$ represents the weights and biases\n", - "of our network.\n", - "\n", - "\n", - "\n", - "## Defining the cost function\n", - "\n", - "Our cost function is given as (see the Logistic regression lectures)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "\\mathcal{C}(\\hat{\\theta}) = - \\ln P(\\mathcal{D} \\mid \\hat{\\theta}) = - \\sum_{i=1}^n\n", - "y_i \\ln[P(y_i = 0)] + (1 - y_i) \\ln [1 - P(y_i = 0)] = \\sum_{i=1}^n \\mathcal{L}_i(\\hat{\\theta}) .\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This last equality means that we can interpret our *cost* function as a sum over the *loss* function\n", - "for each point in the dataset $\\mathcal{L}_i(\\hat{\\theta})$. \n", - "The negative sign is just so that we can think about our algorithm as minimizing a positive number, rather\n", - "than maximizing a negative number. \n", - "\n", - "In *multiclass* classification it is common to treat each integer label as a so called *one-hot* vector: \n", - "\n", - "$y = 5 \\quad \\rightarrow \\quad \\hat{y} = (0, 0, 0, 0, 0, 1, 0, 0, 0, 0) ,$ and\n", - "\n", - "\n", - "$y = 1 \\quad \\rightarrow \\quad \\hat{y} = (0, 1, 0, 0, 0, 0, 0, 0, 0, 0) ,$ \n", - "\n", - "\n", - "i.e. a binary bit string of length $C$, where $C = 10$ is the number of classes in the MNIST dataset (numbers from $0$ to $9$).. \n", - "\n", - "If $\\hat{x}_i$ is the $i$-th input (image), $y_{ic}$ refers to the $c$-th component of the $i$-th\n", - "output vector $\\hat{y}_i$. \n", - "The probability of $\\hat{x}_i$ being in class $c$ will be given by the softmax function:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "P(y_{ic} = 1 \\mid \\hat{x}_i, \\hat{\\theta}) = \\frac{\\exp{((\\hat{a}_i^{hidden})^T \\hat{w}_c)}}\n", - "{\\sum_{c'=0}^{C-1} \\exp{((\\hat{a}_i^{hidden})^T \\hat{w}_{c'})}} ,\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "which reduces to the logistic function in the binary case. \n", - "The likelihood of this $C$-class classifier\n", - "is now given as:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "P(\\mathcal{D} \\mid \\hat{\\theta}) = \\prod_{i=1}^n \\prod_{c=0}^{C-1} [P(y_{ic} = 1)]^{y_{ic}} .\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Again we take the negative log-likelihood to define our cost function:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "\\mathcal{C}(\\hat{\\theta}) = - \\log{P(\\mathcal{D} \\mid \\hat{\\theta})}.\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "See the logistic regression lectures for a full definition of the cost function.\n", - "\n", - "The back propagation equations need now only a small change, namely the definition of a new cost function. We are thus ready to use the same equations as before!\n", - "\n", - "\n", - "### Example: binary classification problem\n", - "\n", - "As an example of the above, relevant for project 2 as well, let us consider a binary class. As discussed in our logistic regression lectures, we defined a cost function in terms of the parameters $\\beta$ as" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "\\mathcal{C}(\\hat{\\beta}) = - \\sum_{i=1}^n \\left(y_i\\log{p(y_i \\vert x_i,\\hat{\\beta})}+(1-y_i)\\log{1-p(y_i \\vert x_i,\\hat{\\beta})}\\right),\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "where we had defined the logistic (sigmoid) function" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "p(y_i =1\\vert x_i,\\hat{\\beta})=\\frac{\\exp{(\\beta_0+\\beta_1 x_i)}}{1+\\exp{(\\beta_0+\\beta_1 x_i)}},\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "p(y_i =0\\vert x_i,\\hat{\\beta})=1-p(y_i =1\\vert x_i,\\hat{\\beta}).\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The parameters $\\hat{\\beta}$ were defined using a minimization method like gradient descent or Newton-Raphson's method. \n", - "\n", - "Now we replace $x_i$ with the activation $z_i^l$ for a given layer $l$ and the outputs as $y_i=a_i^l=f(z_i^l)$, with $z_i^l$ now being a function of the weights $w_{ij}^l$ and biases $b_i^l$. \n", - "We have then" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "a_i^l = y_i = \\frac{\\exp{(z_i^l)}}{1+\\exp{(z_i^l)}},\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "with" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "z_i^l = \\sum_{j}w_{ij}^l a_j^{l-1}+b_i^l,\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "where the superscript $l-1$ indicates that these are the outputs from layer $l-1$.\n", - "Our cost function at the final layer $l=L$ is now" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "\\mathcal{C}(\\hat{W}) = - \\sum_{i=1}^n \\left(t_i\\log{a_i^L}+(1-t_i)\\log{(1-a_i^L)}\\right),\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "where we have defined the targets $t_i$. The derivatives of the cost function with respect to the output $a_i^L$ are then easily calculated and we get" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "\\frac{\\partial \\mathcal{C}(\\hat{W})}{\\partial a_i^L} = \\frac{a_i^L-t_i}{a_i^L(1-a_i^L)}.\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In case we use another activation function than the logistic one, we need to evaluate other derivatives. \n", - "\n", - "\n", - "\n", - "### The Softmax function\n", - "\n", - "In case we employ the more general case given by the Softmax equation, we need to evaluate the derivative of the activation function with respect to the activation $z_i^l$, that is we need" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "\\frac{\\partial f(z_i^l)}{\\partial w_{jk}^l} =\n", - "\\frac{\\partial f(z_i^l)}{\\partial z_j^l} \\frac{\\partial z_j^l}{\\partial w_{jk}^l}= \\frac{\\partial f(z_i^l)}{\\partial z_j^l}a_k^{l-1}.\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the Softmax function we have" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "f(z_i^l) = \\frac{\\exp{(z_i^l)}}{\\sum_{m=1}^K\\exp{(z_m^l)}}.\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Its derivative with respect to $z_j^l$ gives" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "\\frac{\\partial f(z_i^l)}{\\partial z_j^l}= f(z_i^l)\\left(\\delta_{ij}-f(z_j^l)\\right),\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "which in case of the simply binary model reduces to having $i=j$. \n", - "\n", - "\n", - "## Developing a code for doing neural networks with back propagation\n", - "\n", - "\n", - "One can identify a set of key steps when using neural networks to solve supervised learning problems: \n", - "\n", - "1. Collect and pre-process data \n", - "\n", - "2. Define model and architecture \n", - "\n", - "3. Choose cost function and optimizer \n", - "\n", - "4. Train the model \n", - "\n", - "5. Evaluate model performance on test data \n", - "\n", - "6. Adjust hyperparameters (if necessary, network architecture)\n", - "\n", - "### Collect and pre-process data\n", - "\n", - "Here we will be using the MNIST dataset, which is readily available through the **scikit-learn**\n", - "package. You may also find it for example [here](http://yann.lecun.com/exdb/mnist/). \n", - "The *MNIST* (Modified National Institute of Standards and Technology) database is a large database\n", - "of handwritten digits that is commonly used for training various image processing systems. \n", - "The MNIST dataset consists of 70 000 images of size $28\\times 28$ pixels, each labeled from 0 to 9. \n", - "The scikit-learn dataset we will use consists of a selection of 1797 images of size $8\\times 8$ collected and processed from this database. \n", - "\n", - "To feed data into a feed-forward neural network we need to represent\n", - "the inputs as a design/feature matrix $X = (n_{inputs}, n_{features})$. Each\n", - "row represents an *input*, in this case a handwritten digit, and\n", - "each column represents a *feature*, in this case a pixel. The\n", - "correct answers, also known as *labels* or *targets* are\n", - "represented as a 1D array of integers \n", - "$Y = (n_{inputs}) = (5, 3, 1, 8,...)$.\n", - "\n", - "As an example, say we want to build a neural network using supervised learning to predict Body-Mass Index (BMI) from\n", - "measurements of height (in m) \n", - "and weight (in kg). If we have measurements of 5 people the design/feature matrix could be for example: \n", - "\n", - "$$ X = \\begin{bmatrix}\n", - "1.85 & 81\\\\\n", - "1.71 & 65\\\\\n", - "1.95 & 103\\\\\n", - "1.55 & 42\\\\\n", - "1.63 & 56\n", - "\\end{bmatrix} ,$$ \n", - "\n", - "and the targets would be: \n", - "\n", - "$$ Y = (23.7, 22.2, 27.1, 17.5, 21.1) $$ \n", - "\n", - "Since each input image is a 2D matrix, we need to flatten the image\n", - "(i.e. \"unravel\" the 2D matrix into a 1D array) to turn the data into a\n", - "design/feature matrix. This means we lose all spatial information in the\n", - "image, such as locality and translational invariance. More complicated\n", - "architectures such as Convolutional Neural Networks can take advantage\n", - "of such information, and are most commonly applied when analyzing\n", - "images." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "# import necessary packages\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "from sklearn import datasets\n", - "\n", - "\n", - "# ensure the same random numbers appear every time\n", - "np.random.seed(0)\n", - "\n", - "# display images in notebook\n", - "%matplotlib inline\n", - "plt.rcParams['figure.figsize'] = (12,12)\n", - "\n", - "\n", - "# download MNIST dataset\n", - "digits = datasets.load_digits()\n", - "\n", - "# define inputs and labels\n", - "inputs = digits.images\n", - "labels = digits.target\n", - "\n", - "print(\"inputs = (n_inputs, pixel_width, pixel_height) = \" + str(inputs.shape))\n", - "print(\"labels = (n_inputs) = \" + str(labels.shape))\n", - "\n", - "\n", - "# flatten the image\n", - "# the value -1 means dimension is inferred from the remaining dimensions: 8x8 = 64\n", - "n_inputs = len(inputs)\n", - "inputs = inputs.reshape(n_inputs, -1)\n", - "print(\"X = (n_inputs, n_features) = \" + str(inputs.shape))\n", - "\n", - "\n", - "# choose some random images to display\n", - "indices = np.arange(n_inputs)\n", - "random_indices = np.random.choice(indices, size=5)\n", - "\n", - "for i, image in enumerate(digits.images[random_indices]):\n", - " plt.subplot(1, 5, i+1)\n", - " plt.axis('off')\n", - " plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')\n", - " plt.title(\"Label: %d\" % digits.target[random_indices[i]])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Train and test datasets\n", - "\n", - "Performing analysis before partitioning the dataset is a major error, that can lead to incorrect conclusions. \n", - "\n", - "We will reserve $80 \\%$ of our dataset for training and $20 \\%$ for testing. \n", - "\n", - "It is important that the train and test datasets are drawn randomly from our dataset, to ensure\n", - "no bias in the sampling. \n", - "Say you are taking measurements of weather data to predict the weather in the coming 5 days.\n", - "You don't want to train your model on measurements taken from the hours 00.00 to 12.00, and then test it on data\n", - "collected from 12.00 to 24.00." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "from sklearn.model_selection import train_test_split\n", - "\n", - "# one-liner from scikit-learn library\n", - "train_size = 0.8\n", - "test_size = 1 - train_size\n", - "X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,\n", - " test_size=test_size)\n", - "\n", - "# equivalently in numpy\n", - "def train_test_split_numpy(inputs, labels, train_size, test_size):\n", - " n_inputs = len(inputs)\n", - " inputs_shuffled = inputs.copy()\n", - " labels_shuffled = labels.copy()\n", - " \n", - " np.random.shuffle(inputs_shuffled)\n", - " np.random.shuffle(labels_shuffled)\n", - " \n", - " train_end = int(n_inputs*train_size)\n", - " X_train, X_test = inputs_shuffled[:train_end], inputs_shuffled[train_end:]\n", - " Y_train, Y_test = labels_shuffled[:train_end], labels_shuffled[train_end:]\n", - " \n", - " return X_train, X_test, Y_train, Y_test\n", - "\n", - "#X_train, X_test, Y_train, Y_test = train_test_split_numpy(inputs, labels, train_size, test_size)\n", - "\n", - "print(\"Number of training images: \" + str(len(X_train)))\n", - "print(\"Number of test images: \" + str(len(X_test)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define model and architecture\n", - "\n", - "Our simple feed-forward neural network will consist of an *input* layer, a single *hidden* layer and an *output* layer. The activation $y$ of each neuron is a weighted sum of inputs, passed through an activation function. In case of the simple perceptron model we have \n", - "\n", - "$$ z = \\sum_{i=1}^n w_i a_i ,$$\n", - "\n", - "$$ y = f(z) ,$$\n", - "\n", - "where $f$ is the activation function, $a_i$ represents input from neuron $i$ in the preceding layer\n", - "and $w_i$ is the weight to input $i$. \n", - "The activation of the neurons in the input layer is just the features (e.g. a pixel value). \n", - "\n", - "The simplest activation function for a neuron is the *Heaviside* function:\n", - "\n", - "$$ f(z) = \n", - "\\begin{cases}\n", - "1, & z > 0\\\\\n", - "0, & \\text{otherwise}\n", - "\\end{cases}\n", - "$$\n", - "\n", - "A feed-forward neural network with this activation is known as a *perceptron*. \n", - "For a binary classifier (i.e. two classes, 0 or 1, dog or not-dog) we can also use this in our output layer. \n", - "This activation can be generalized to $k$ classes (using e.g. the *one-against-all* strategy), \n", - "and we call these architectures *multiclass perceptrons*. \n", - "\n", - "However, it is now common to use the terms Single Layer Perceptron (SLP) (1 hidden layer) and \n", - "Multilayer Perceptron (MLP) (2 or more hidden layers) to refer to feed-forward neural networks with any activation function. \n", - "\n", - "Typical choices for activation functions include the sigmoid function, hyperbolic tangent, and Rectified Linear Unit (ReLU). \n", - "We will be using the sigmoid function $\\sigma(x)$: \n", - "\n", - "$$ f(x) = \\sigma(x) = \\frac{1}{1 + e^{-x}} ,$$\n", - "\n", - "which is inspired by probability theory (see logistic regression) and was most commonly used until about 2011. See the discussion below concerning other activation functions.\n", - "\n", - "### Layers\n", - "\n", - "* Input \n", - "\n", - "Since each input image has 8x8 = 64 pixels or features, we have an input layer of 64 neurons. \n", - "\n", - "* Hidden layer\n", - "\n", - "We will use 50 neurons in the hidden layer receiving input from the neurons in the input layer. \n", - "Since each neuron in the hidden layer is connected to the 64 inputs we have 64x50 = 3200 weights to the hidden layer. \n", - "\n", - "* Output\n", - "\n", - "If we were building a binary classifier, it would be sufficient with a single neuron in the output layer,\n", - "which could output 0 or 1 according to the Heaviside function. This would be an example of a *hard* classifier, meaning it outputs the class of the input directly. However, if we are dealing with noisy data it is often beneficial to use a *soft* classifier, which outputs the probability of being in class 0 or 1. \n", - "\n", - "For a soft binary classifier, we could use a single neuron and interpret the output as either being the probability of being in class 0 or the probability of being in class 1. Alternatively we could use 2 neurons, and interpret each neuron as the probability of being in each class. \n", - "\n", - "Since we are doing multiclass classification, with 10 categories, it is natural to use 10 neurons in the output layer. We number the neurons $j = 0,1,...,9$. The activation of each output neuron $j$ will be according to the *softmax* function: \n", - "\n", - "$$ P(\\text{class $j$} \\mid \\text{input $\\hat{a}$}) = \\frac{\\exp{(\\hat{a}^T \\hat{w}_j)}}\n", - "{\\sum_{c=0}^{9} \\exp{(\\hat{a}^T \\hat{w}_c)}} ,$$ \n", - "\n", - "i.e. each neuron $j$ outputs the probability of being in class $j$ given an input from the hidden layer $\\hat{a}$, with $\\hat{w}_j$ the weights of neuron $j$ to the inputs. \n", - "The denominator is a normalization factor to ensure the outputs (probabilities) sum up to 1. \n", - "The exponent is just the weighted sum of inputs as before: \n", - "\n", - "$$ z_j = \\sum_{i=1}^n w_ {ij} a_i+b_j.$$ \n", - "\n", - "Since each neuron in the output layer is connected to the 50 inputs from the hidden layer we have 50x10 = 500\n", - "weights to the output layer.\n", - "\n", - "\n", - "Typically weights are initialized with small values distributed around zero, drawn from a uniform\n", - "or normal distribution. Setting all weights to zero means all neurons give the same output, making the network useless. \n", - "\n", - "Adding a bias value to the weighted sum of inputs allows the neural network to represent a greater range\n", - "of values. Without it, any input with the value 0 will be mapped to zero (before being passed through the activation). The bias unit has an output of 1, and a weight to each neuron $j$, $b_j$: \n", - "\n", - "$$ z_j = \\sum_{i=1}^n w_ {ij} a_i + b_j.$$ \n", - "\n", - "The bias weights $\\hat{b}$ are often initialized to zero, but a small value like $0.01$ ensures all neurons have some output which can be backpropagated in the first training cycle." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# building our neural network\n", - "\n", - "n_inputs, n_features = X_train.shape\n", - "n_hidden_neurons = 50\n", - "n_categories = 10\n", - "\n", - "# we make the weights normally distributed using numpy.random.randn\n", - "\n", - "# weights and bias in the hidden layer\n", - "hidden_weights = np.random.randn(n_features, n_hidden_neurons)\n", - "hidden_bias = np.zeros(n_hidden_neurons) + 0.01\n", - "\n", - "# weights and bias in the output layer\n", - "output_weights = np.random.randn(n_hidden_neurons, n_categories)\n", - "output_bias = np.zeros(n_categories) + 0.01" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Feed-forward pass\n", - "\n", - "Denote $F$ the number of features, $H$ the number of hidden neurons and $C$ the number of categories. \n", - "For each input image we calculate a weighted sum of input features (pixel values) to each neuron $j$ in the hidden layer $l$: \n", - "\n", - "$$ z_{j}^{l} = \\sum_{i=1}^{F} w_{ij}^{l} x_i + b_{j}^{l},$$\n", - "\n", - "this is then passed through our activation function \n", - "\n", - "$$ a_{j}^{l} = f(z_{j}^{l}) .$$ \n", - "\n", - "We calculate a weighted sum of inputs (activations in the hidden layer) to each neuron $j$ in the output layer: \n", - "\n", - "$$ z_{j}^{L} = \\sum_{i=1}^{H} w_{ij}^{L} a_{i}^{l} + b_{j}^{L}.$$ \n", - "\n", - "Finally we calculate the output of neuron $j$ in the output layer using the softmax function: \n", - "\n", - "$$ a_{j}^{L} = \\frac{\\exp{(z_j^{L})}}\n", - "{\\sum_{c=0}^{C-1} \\exp{(z_c^{L})}} .$$ \n", - "\n", - "\n", - "Since our data has the dimensions $X = (n_{inputs}, n_{features})$ and our weights to the hidden\n", - "layer have the dimensions \n", - "$W_{hidden} = (n_{features}, n_{hidden})$,\n", - "we can easily feed the network all our training data in one go by taking the matrix product \n", - "\n", - "$$ X W^{h} = (n_{inputs}, n_{hidden}),$$ \n", - "\n", - "and obtain a matrix that holds the weighted sum of inputs to the hidden layer\n", - "for each input image and each hidden neuron. \n", - "We also add the bias to obtain a matrix of weighted sums to the hidden layer $Z^{h}$: \n", - "\n", - "$$ \\hat{z}^{l} = \\hat{X} \\hat{W}^{l} + \\hat{b}^{l} ,$$\n", - "\n", - "meaning the same bias (1D array with size equal number of hidden neurons) is added to each input image. \n", - "This is then passed through the activation: \n", - "\n", - "$$ \\hat{a}^{l} = f(\\hat{z}^l) .$$ \n", - "\n", - "This is fed to the output layer: \n", - "\n", - "$$ \\hat{z}^{L} = \\hat{a}^{L} \\hat{W}^{L} + \\hat{b}^{L} .$$\n", - "\n", - "Finally we receive our output values for each image and each category by passing it through the softmax function: \n", - "\n", - "$$ output = softmax (\\hat{z}^{L}) = (n_{inputs}, n_{categories}) .$$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# setup the feed-forward pass, subscript h = hidden layer\n", - "\n", - "def sigmoid(x):\n", - " return 1/(1 + np.exp(-x))\n", - "\n", - "def feed_forward(X):\n", - " # weighted sum of inputs to the hidden layer\n", - " z_h = np.matmul(X, hidden_weights) + hidden_bias\n", - " # activation in the hidden layer\n", - " a_h = sigmoid(z_h)\n", - " \n", - " # weighted sum of inputs to the output layer\n", - " z_o = np.matmul(a_h, output_weights) + output_bias\n", - " # softmax output\n", - " # axis 0 holds each input and axis 1 the probabilities of each category\n", - " exp_term = np.exp(z_o)\n", - " probabilities = exp_term / np.sum(exp_term, axis=1, keepdims=True)\n", - " \n", - " return probabilities\n", - "\n", - "probabilities = feed_forward(X_train)\n", - "print(\"probabilities = (n_inputs, n_categories) = \" + str(probabilities.shape))\n", - "print(\"probability that image 0 is in category 0,1,2,...,9 = \\n\" + str(probabilities[0]))\n", - "print(\"probabilities sum up to: \" + str(probabilities[0].sum()))\n", - "print()\n", - "\n", - "# we obtain a prediction by taking the class with the highest likelihood\n", - "def predict(X):\n", - " probabilities = feed_forward(X)\n", - " return np.argmax(probabilities, axis=1)\n", - "\n", - "predictions = predict(X_train)\n", - "print(\"predictions = (n_inputs) = \" + str(predictions.shape))\n", - "print(\"prediction for image 0: \" + str(predictions[0]))\n", - "print(\"correct label for image 0: \" + str(Y_train[0]))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Choose cost function and optimizer\n", - "\n", - "To measure how well our neural network is doing we need to introduce a cost function. \n", - "We will call the function that gives the error of a single sample output the *loss* function, and the function\n", - "that gives the total error of our network across all samples the *cost* function.\n", - "A typical choice for multiclass classification is the *cross-entropy* loss, also known as the negative log likelihood. \n", - "\n", - "In *multiclass* classification it is common to treat each integer label as a so called *one-hot* vector: \n", - "\n", - "$$ y = 5 \\quad \\rightarrow \\quad \\hat{y} = (0, 0, 0, 0, 0, 1, 0, 0, 0, 0) ,$$ \n", - "\n", - "\n", - "$$ y = 1 \\quad \\rightarrow \\quad \\hat{y} = (0, 1, 0, 0, 0, 0, 0, 0, 0, 0) ,$$ \n", - "\n", - "\n", - "i.e. a binary bit string of length $C$, where $C = 10$ is the number of classes in the MNIST dataset. \n", - "\n", - "Let $y_{ic}$ denote the $c$-th component of the $i$-th one-hot vector. \n", - "We define the cost function $\\mathcal{C}$ as a sum over the cross-entropy loss for each point $\\hat{x}_i$ in the dataset.\n", - "\n", - "In the one-hot representation only one of the terms in the loss function is non-zero, namely the\n", - "probability of the correct category $c'$ \n", - "(i.e. the category $c'$ such that $y_{ic'} = 1$). This means that the cross entropy loss only punishes you for how wrong\n", - "you got the correct label. The probability of category $c$ is given by the softmax function. The vector $\\hat{\\theta}$ represents the parameters of our network, i.e. all the weights and biases. \n", - "\n", - "\n", - "\n", - "### Optimizing the cost function\n", - "\n", - "The network is trained by finding the weights and biases that minimize the cost function. One of the most widely used classes of methods is *gradient descent* and its generalizations. The idea behind gradient descent\n", - "is simply to adjust the weights in the direction where the gradient of the cost function is large and negative. This ensures we flow toward a *local* minimum of the cost function. \n", - "Each parameter $\\theta$ is iteratively adjusted according to the rule \n", - "\n", - "$$ \\theta_{i+1} = \\theta_i - \\eta \\nabla \\mathcal{C}(\\theta_i) ,$$\n", - "\n", - "where $\\eta$ is known as the *learning rate*, which controls how big a step we take towards the minimum. \n", - "This update can be repeated for any number of iterations, or until we are satisfied with the result. \n", - "\n", - "A simple and effective improvement is a variant called *Batch Gradient Descent*. \n", - "Instead of calculating the gradient on the whole dataset, we calculate an approximation of the gradient\n", - "on a subset of the data called a *minibatch*. \n", - "If there are $N$ data points and we have a minibatch size of $M$, the total number of batches\n", - "is $N/M$. \n", - "We denote each minibatch $B_k$, with $k = 1, 2,...,N/M$. The gradient then becomes: \n", - "\n", - "$$ \\nabla \\mathcal{C}(\\theta) = \\frac{1}{N} \\sum_{i=1}^N \\nabla \\mathcal{L}_i(\\theta) \\quad \\rightarrow \\quad\n", - "\\frac{1}{M} \\sum_{i \\in B_k} \\nabla \\mathcal{L}_i(\\theta) ,$$\n", - "\n", - "i.e. instead of averaging the loss over the entire dataset, we average over a minibatch. \n", - "\n", - "This has two important benefits: \n", - "1. Introducing stochasticity decreases the chance that the algorithm becomes stuck in a local minima. \n", - "\n", - "2. It significantly speeds up the calculation, since we do not have to use the entire dataset to calculate the gradient. \n", - "\n", - "The various optmization methods, with codes and algorithms, are discussed in our lectures on [Gradient descent approaches](https://compphysics.github.io/MachineLearning/doc/pub/Splines/html/Splines-bs.html).\n", - "\n", - "\n", - "### Regularization\n", - "\n", - "It is common to add an extra term to the cost function, proportional\n", - "to the size of the weights. This is equivalent to constraining the\n", - "size of the weights, so that they do not grow out of control.\n", - "Constraining the size of the weights means that the weights cannot\n", - "grow arbitrarily large to fit the training data, and in this way\n", - "reduces *overfitting*.\n", - "\n", - "We will measure the size of the weights using the so called *L2-norm*, meaning our cost function becomes: \n", - "\n", - "$$ \\mathcal{C}(\\theta) = \\frac{1}{N} \\sum_{i=1}^N \\mathcal{L}_i(\\theta) \\quad \\rightarrow \\quad\n", - "\\frac{1}{N} \\sum_{i=1}^N \\mathcal{L}_i(\\theta) + \\lambda \\lvert \\lvert \\hat{w} \\rvert \\rvert_2^2 \n", - "= \\frac{1}{N} \\sum_{i=1}^N \\mathcal{L}(\\theta) + \\lambda \\sum_{ij} w_{ij}^2,$$ \n", - "\n", - "i.e. we sum up all the weights squared. The factor $\\lambda$ is known as a regularization parameter.\n", - "\n", - "\n", - "In order to train the model, we need to calculate the derivative of\n", - "the cost function with respect to every bias and weight in the\n", - "network. In total our network has $(64 + 1)\\times 50=3250$ weights in\n", - "the hidden layer and $(50 + 1)\\times 10=510$ weights to the output\n", - "layer ($+1$ for the bias), and the gradient must be calculated for\n", - "every parameter. We use the *backpropagation* algorithm discussed\n", - "above. This is a clever use of the chain rule that allows us to\n", - "calculate the gradient efficently. \n", - "\n", - "\n", - "### Matrix multiplication\n", - "\n", - "To more efficently train our network these equations are implemented using matrix operations. \n", - "The error in the output layer is calculated simply as, with $\\hat{t}$ being our targets, \n", - "\n", - "$$ \\delta_L = \\hat{t} - \\hat{y} = (n_{inputs}, n_{categories}) .$$ \n", - "\n", - "The gradient for the output weights is calculated as \n", - "\n", - "$$ \\nabla W_{L} = \\hat{a}^T \\delta_L = (n_{hidden}, n_{categories}) ,$$\n", - "\n", - "where $\\hat{a} = (n_{inputs}, n_{hidden})$. This simply means that we are summing up the gradients for each input. \n", - "Since we are going backwards we have to transpose the activation matrix. \n", - "\n", - "The gradient with respect to the output bias is then \n", - "\n", - "$$ \\nabla \\hat{b}_{L} = \\sum_{i=1}^{n_{inputs}} \\delta_L = (n_{categories}) .$$ \n", - "\n", - "The error in the hidden layer is \n", - "\n", - "$$ \\Delta_h = \\delta_L W_{L}^T \\circ f'(z_{h}) = \\delta_L W_{L}^T \\circ a_{h} \\circ (1 - a_{h}) = (n_{inputs}, n_{hidden}) ,$$ \n", - "\n", - "where $f'(a_{h})$ is the derivative of the activation in the hidden layer. The matrix products mean\n", - "that we are summing up the products for each neuron in the output layer. The symbol $\\circ$ denotes\n", - "the *Hadamard product*, meaning element-wise multiplication. \n", - "\n", - "This again gives us the gradients in the hidden layer: \n", - "\n", - "$$ \\nabla W_{h} = X^T \\delta_h = (n_{features}, n_{hidden}) ,$$ \n", - "\n", - "$$ \\nabla b_{h} = \\sum_{i=1}^{n_{inputs}} \\delta_h = (n_{hidden}) .$$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# to categorical turns our integer vector into a onehot representation\n", - "from sklearn.metrics import accuracy_score\n", - "\n", - "# one-hot in numpy\n", - "def to_categorical_numpy(integer_vector):\n", - " n_inputs = len(integer_vector)\n", - " n_categories = np.max(integer_vector) + 1\n", - " onehot_vector = np.zeros((n_inputs, n_categories))\n", - " onehot_vector[range(n_inputs), integer_vector] = 1\n", - " \n", - " return onehot_vector\n", - "\n", - "#Y_train_onehot, Y_test_onehot = to_categorical(Y_train), to_categorical(Y_test)\n", - "Y_train_onehot, Y_test_onehot = to_categorical_numpy(Y_train), to_categorical_numpy(Y_test)\n", - "\n", - "def feed_forward_train(X):\n", - " # weighted sum of inputs to the hidden layer\n", - " z_h = np.matmul(X, hidden_weights) + hidden_bias\n", - " # activation in the hidden layer\n", - " a_h = sigmoid(z_h)\n", - " \n", - " # weighted sum of inputs to the output layer\n", - " z_o = np.matmul(a_h, output_weights) + output_bias\n", - " # softmax output\n", - " # axis 0 holds each input and axis 1 the probabilities of each category\n", - " exp_term = np.exp(z_o)\n", - " probabilities = exp_term / np.sum(exp_term, axis=1, keepdims=True)\n", - " \n", - " # for backpropagation need activations in hidden and output layers\n", - " return a_h, probabilities\n", - "\n", - "def backpropagation(X, Y):\n", - " a_h, probabilities = feed_forward_train(X)\n", - " \n", - " # error in the output layer\n", - " error_output = probabilities - Y\n", - " # error in the hidden layer\n", - " error_hidden = np.matmul(error_output, output_weights.T) * a_h * (1 - a_h)\n", - " \n", - " # gradients for the output layer\n", - " output_weights_gradient = np.matmul(a_h.T, error_output)\n", - " output_bias_gradient = np.sum(error_output, axis=0)\n", - " \n", - " # gradient for the hidden layer\n", - " hidden_weights_gradient = np.matmul(X.T, error_hidden)\n", - " hidden_bias_gradient = np.sum(error_hidden, axis=0)\n", - "\n", - " return output_weights_gradient, output_bias_gradient, hidden_weights_gradient, hidden_bias_gradient\n", - "\n", - "print(\"Old accuracy on training data: \" + str(accuracy_score(predict(X_train), Y_train)))\n", - "\n", - "eta = 0.01\n", - "lmbd = 0.01\n", - "for i in range(1000):\n", - " # calculate gradients\n", - " dWo, dBo, dWh, dBh = backpropagation(X_train, Y_train_onehot)\n", - " \n", - " # regularization term gradients\n", - " dWo += lmbd * output_weights\n", - " dWh += lmbd * hidden_weights\n", - " \n", - " # update weights and biases\n", - " output_weights -= eta * dWo\n", - " output_bias -= eta * dBo\n", - " hidden_weights -= eta * dWh\n", - " hidden_bias -= eta * dBh\n", - "\n", - "print(\"New accuracy on training data: \" + str(accuracy_score(predict(X_train), Y_train)))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Improving performance\n", - "\n", - "As we can see the network does not seem to be learning at all. It seems to be just guessing the label for each image. \n", - "In order to obtain a network that does something useful, we will have to do a bit more work. \n", - "\n", - "The choice of *hyperparameters* such as learning rate and regularization parameter is hugely influential for the performance of the network. Typically a *grid-search* is performed, wherein we test different hyperparameters separated by orders of magnitude. For example we could test the learning rates $\\eta = 10^{-6}, 10^{-5},...,10^{-1}$ with different regularization parameters $\\lambda = 10^{-6},...,10^{-0}$. \n", - "\n", - "Next, we haven't implemented minibatching yet, which introduces stochasticity and is though to act as an important regularizer on the weights. We call a feed-forward + backward pass with a minibatch an *iteration*, and a full training period\n", - "going through the entire dataset ($n/M$ batches) an *epoch*.\n", - "\n", - "If this does not improve network performance, you may want to consider altering the network architecture, adding more neurons or hidden layers. \n", - "Andrew Ng goes through some of these considerations in this [video](https://youtu.be/F1ka6a13S9I). You can find a summary of the video [here](https://kevinzakka.github.io/2016/09/26/applying-deep-learning/). \n", - "\n", - "\n", - "It is very natural to think of the network as an object, with specific instances of the network\n", - "being realizations of this object with different hyperparameters. An implementation using Python classes provides a clean structure and interface, and the full implementation of our neural network is given below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "class NeuralNetwork:\n", - " def __init__(\n", - " self,\n", - " X_data,\n", - " Y_data,\n", - " n_hidden_neurons=50,\n", - " n_categories=10,\n", - " epochs=10,\n", - " batch_size=100,\n", - " eta=0.1,\n", - " lmbd=0.0):\n", - "\n", - " self.X_data_full = X_data\n", - " self.Y_data_full = Y_data\n", - "\n", - " self.n_inputs = X_data.shape[0]\n", - " self.n_features = X_data.shape[1]\n", - " self.n_hidden_neurons = n_hidden_neurons\n", - " self.n_categories = n_categories\n", - "\n", - " self.epochs = epochs\n", - " self.batch_size = batch_size\n", - " self.iterations = self.n_inputs // self.batch_size\n", - " self.eta = eta\n", - " self.lmbd = lmbd\n", - "\n", - " self.create_biases_and_weights()\n", - "\n", - " def create_biases_and_weights(self):\n", - " self.hidden_weights = np.random.randn(self.n_features, self.n_hidden_neurons)\n", - " self.hidden_bias = np.zeros(self.n_hidden_neurons) + 0.01\n", - "\n", - " self.output_weights = np.random.randn(self.n_hidden_neurons, self.n_categories)\n", - " self.output_bias = np.zeros(self.n_categories) + 0.01\n", - "\n", - " def feed_forward(self):\n", - " # feed-forward for training\n", - " self.z_h = np.matmul(self.X_data, self.hidden_weights) + self.hidden_bias\n", - " self.a_h = sigmoid(self.z_h)\n", - "\n", - " self.z_o = np.matmul(self.a_h, self.output_weights) + self.output_bias\n", - "\n", - " exp_term = np.exp(self.z_o)\n", - " self.probabilities = exp_term / np.sum(exp_term, axis=1, keepdims=True)\n", - "\n", - " def feed_forward_out(self, X):\n", - " # feed-forward for output\n", - " z_h = np.matmul(X, self.hidden_weights) + self.hidden_bias\n", - " a_h = sigmoid(z_h)\n", - "\n", - " z_o = np.matmul(a_h, self.output_weights) + self.output_bias\n", - " \n", - " exp_term = np.exp(z_o)\n", - " probabilities = exp_term / np.sum(exp_term, axis=1, keepdims=True)\n", - " return probabilities\n", - "\n", - " def backpropagation(self):\n", - " error_output = self.probabilities - self.Y_data\n", - " error_hidden = np.matmul(error_output, self.output_weights.T) * self.a_h * (1 - self.a_h)\n", - "\n", - " self.output_weights_gradient = np.matmul(self.a_h.T, error_output)\n", - " self.output_bias_gradient = np.sum(error_output, axis=0)\n", - "\n", - " self.hidden_weights_gradient = np.matmul(self.X_data.T, error_hidden)\n", - " self.hidden_bias_gradient = np.sum(error_hidden, axis=0)\n", - "\n", - " if self.lmbd > 0.0:\n", - " self.output_weights_gradient += self.lmbd * self.output_weights\n", - " self.hidden_weights_gradient += self.lmbd * self.hidden_weights\n", - "\n", - " self.output_weights -= self.eta * self.output_weights_gradient\n", - " self.output_bias -= self.eta * self.output_bias_gradient\n", - " self.hidden_weights -= self.eta * self.hidden_weights_gradient\n", - " self.hidden_bias -= self.eta * self.hidden_bias_gradient\n", - "\n", - " def predict(self, X):\n", - " probabilities = self.feed_forward_out(X)\n", - " return np.argmax(probabilities, axis=1)\n", - "\n", - " def predict_probabilities(self, X):\n", - " probabilities = self.feed_forward_out(X)\n", - " return probabilities\n", - "\n", - " def train(self):\n", - " data_indices = np.arange(self.n_inputs)\n", - "\n", - " for i in range(self.epochs):\n", - " for j in range(self.iterations):\n", - " # pick datapoints with replacement\n", - " chosen_datapoints = np.random.choice(\n", - " data_indices, size=self.batch_size, replace=False\n", - " )\n", - "\n", - " # minibatch training data\n", - " self.X_data = self.X_data_full[chosen_datapoints]\n", - " self.Y_data = self.Y_data_full[chosen_datapoints]\n", - "\n", - " self.feed_forward()\n", - " self.backpropagation()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Evaluate model performance on test data\n", - "\n", - "To measure the performance of our network we evaluate how well it does it data it has never seen before, i.e. the test data. \n", - "We measure the performance of the network using the *accuracy* score. \n", - "The accuracy is as you would expect just the number of images correctly labeled divided by the total number of images. A perfect classifier will have an accuracy score of $1$. \n", - "\n", - "$$ \\text{Accuracy} = \\frac{\\sum_{i=1}^n I(\\hat{y}_i = y_i)}{n} ,$$ \n", - "\n", - "where $I$ is the indicator function, $1$ if $\\hat{y}_i = y_i$ and $0$ otherwise." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "epochs = 100\n", - "batch_size = 100\n", - "\n", - "dnn = NeuralNetwork(X_train, Y_train_onehot, eta=eta, lmbd=lmbd, epochs=epochs, batch_size=batch_size,\n", - " n_hidden_neurons=n_hidden_neurons, n_categories=n_categories)\n", - "dnn.train()\n", - "test_predict = dnn.predict(X_test)\n", - "\n", - "# accuracy score from scikit library\n", - "print(\"Accuracy score on test set: \", accuracy_score(Y_test, test_predict))\n", - "\n", - "# equivalent in numpy\n", - "def accuracy_score_numpy(Y_test, Y_pred):\n", - " return np.sum(Y_test == Y_pred) / len(Y_test)\n", - "\n", - "#print(\"Accuracy score on test set: \", accuracy_score_numpy(Y_test, test_predict))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Adjust hyperparameters\n", - "\n", - "We now perform a grid search to find the optimal hyperparameters for the network. \n", - "Note that we are only using 1 layer with 50 neurons, and human performance is estimated to be around $98\\%$ ($2\\%$ error rate)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "eta_vals = np.logspace(-5, 1, 7)\n", - "lmbd_vals = np.logspace(-5, 1, 7)\n", - "# store the models for later use\n", - "DNN_numpy = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)\n", - "\n", - "# grid search\n", - "for i, eta in enumerate(eta_vals):\n", - " for j, lmbd in enumerate(lmbd_vals):\n", - " dnn = NeuralNetwork(X_train, Y_train_onehot, eta=eta, lmbd=lmbd, epochs=epochs, batch_size=batch_size,\n", - " n_hidden_neurons=n_hidden_neurons, n_categories=n_categories)\n", - " dnn.train()\n", - " \n", - " DNN_numpy[i][j] = dnn\n", - " \n", - " test_predict = dnn.predict(X_test)\n", - " \n", - " print(\"Learning rate = \", eta)\n", - " print(\"Lambda = \", lmbd)\n", - " print(\"Accuracy score on test set: \", accuracy_score(Y_test, test_predict))\n", - " print()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# visual representation of grid search\n", - "# uses seaborn heatmap, you can also do this with matplotlib imshow\n", - "import seaborn as sns\n", - "\n", - "sns.set()\n", - "\n", - "train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", - "test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", - "\n", - "for i in range(len(eta_vals)):\n", - " for j in range(len(lmbd_vals)):\n", - " dnn = DNN_numpy[i][j]\n", - " \n", - " train_pred = dnn.predict(X_train) \n", - " test_pred = dnn.predict(X_test)\n", - "\n", - " train_accuracy[i][j] = accuracy_score(Y_train, train_pred)\n", - " test_accuracy[i][j] = accuracy_score(Y_test, test_pred)\n", - "\n", - " \n", - "fig, ax = plt.subplots(figsize = (10, 10))\n", - "sns.heatmap(train_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", - "ax.set_title(\"Training Accuracy\")\n", - "ax.set_ylabel(\"$\\eta$\")\n", - "ax.set_xlabel(\"$\\lambda$\")\n", - "plt.show()\n", - "\n", - "fig, ax = plt.subplots(figsize = (10, 10))\n", - "sns.heatmap(test_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", - "ax.set_title(\"Test Accuracy\")\n", - "ax.set_ylabel(\"$\\eta$\")\n", - "ax.set_xlabel(\"$\\lambda$\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## scikit-learn implementation\n", - "\n", - "**scikit-learn** focuses more\n", - "on traditional machine learning methods, such as regression,\n", - "clustering, decision trees, etc. As such, it has only two types of\n", - "neural networks: Multi Layer Perceptron outputting continuous values,\n", - "*MPLRegressor*, and Multi Layer Perceptron outputting labels,\n", - "*MLPClassifier*. We will see how simple it is to use these classes.\n", - "\n", - "**scikit-learn** implements a few improvements from our neural network,\n", - "such as early stopping, a varying learning rate, different\n", - "optimization methods, etc. We would therefore expect a better\n", - "performance overall." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "from sklearn.neural_network import MLPClassifier\n", - "# store models for later use\n", - "DNN_scikit = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)\n", - "\n", - "for i, eta in enumerate(eta_vals):\n", - " for j, lmbd in enumerate(lmbd_vals):\n", - " dnn = MLPClassifier(hidden_layer_sizes=(n_hidden_neurons), activation='logistic',\n", - " alpha=lmbd, learning_rate_init=eta, max_iter=epochs)\n", - " dnn.fit(X_train, Y_train)\n", - " \n", - " DNN_scikit[i][j] = dnn\n", - " \n", - " print(\"Learning rate = \", eta)\n", - " print(\"Lambda = \", lmbd)\n", - " print(\"Accuracy score on test set: \", dnn.score(X_test, Y_test))\n", - " print()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# optional\n", - "# visual representation of grid search\n", - "# uses seaborn heatmap, could probably do this in matplotlib\n", - "import seaborn as sns\n", - "\n", - "sns.set()\n", - "\n", - "train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", - "test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", - "\n", - "for i in range(len(eta_vals)):\n", - " for j in range(len(lmbd_vals)):\n", - " dnn = DNN_scikit[i][j]\n", - " \n", - " train_pred = dnn.predict(X_train) \n", - " test_pred = dnn.predict(X_test)\n", - "\n", - " train_accuracy[i][j] = accuracy_score(Y_train, train_pred)\n", - " test_accuracy[i][j] = accuracy_score(Y_test, test_pred)\n", - "\n", - " \n", - "fig, ax = plt.subplots(figsize = (10, 10))\n", - "sns.heatmap(train_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", - "ax.set_title(\"Training Accuracy\")\n", - "ax.set_ylabel(\"$\\eta$\")\n", - "ax.set_xlabel(\"$\\lambda$\")\n", - "plt.show()\n", - "\n", - "fig, ax = plt.subplots(figsize = (10, 10))\n", - "sns.heatmap(test_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", - "ax.set_title(\"Test Accuracy\")\n", - "ax.set_ylabel(\"$\\eta$\")\n", - "ax.set_xlabel(\"$\\lambda$\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Building neural networks in Tensorflow and Keras\n", - "\n", - "Now we want to build on the experience gained from our neural network implementation in NumPy and scikit-learn\n", - "and use it to construct a neural network in Tensorflow. Once we have constructed a neural network in NumPy\n", - "and Tensorflow, building one in Keras is really quite trivial, though the performance may suffer. \n", - "\n", - "In our previous example we used only one hidden layer, and in this we will use two. From this it should be quite\n", - "clear how to build one using an arbitrary number of hidden layers, using data structures such as Python lists or\n", - "NumPy arrays.\n", - "\n", - "\n", - "Tensorflow is an open source library machine learning library\n", - "developed by the Google Brain team for internal use. It was released\n", - "under the Apache 2.0 open source license in November 9, 2015.\n", - "\n", - "Tensorflow is a computational framework that allows you to construct\n", - "machine learning models at different levels of abstraction, from\n", - "high-level, object-oriented APIs like Keras, down to the C++ kernels\n", - "that Tensorflow is built upon. The higher levels of abstraction are\n", - "simpler to use, but less flexible, and our choice of implementation\n", - "should reflect the problems we are trying to solve.\n", - "\n", - "[Tensorflow uses](https://www.tensorflow.org/guide/graphs) so-called graphs to represent your computation\n", - "in terms of the dependencies between individual operations, such that you first build a Tensorflow *graph*\n", - "to represent your model, and then create a Tensorflow *session* to run the graph.\n", - "\n", - "In this guide we will analyze the same data as we did in our NumPy and\n", - "scikit-learn tutorial, gathered from the MNIST database of images. We\n", - "will give an introduction to the lower level Python Application\n", - "Program Interfaces (APIs), and see how we use them to build our graph.\n", - "Then we will build (effectively) the same graph in Keras, to see just\n", - "how simple solving a machine learning problem can be.\n", - "\n", - "To install tensorflow on Unix/Linux systems, use pip as" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "pip3 install tensorflow" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "and/or if you use **anaconda**, just write (or install from the graphical user interface)\n", - "(current release of CPU-only TensorFlow)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "conda create -n tf tensorflow\n", - "conda activate tf" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To install the current release of GPU TensorFlow" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "conda create -n tf-gpu tensorflow-gpu\n", - "conda activate tf-gpu" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Keras is a high level [neural network](https://en.wikipedia.org/wiki/Application_programming_interface)\n", - "that supports Tensorflow, CTNK and Theano as backends. \n", - "If you have Anaconda installed you may run the following command" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "conda install keras" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can look up the [instructions here](https://keras.io/) for more information.\n", - "\n", - "We will to a large extent use **keras** in this course. \n", - "\n", - "\n", - "Let us look again at the MINST data set." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# import necessary packages\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import tensorflow as tf\n", - "from sklearn import datasets\n", - "\n", - "\n", - "# ensure the same random numbers appear every time\n", - "np.random.seed(0)\n", - "\n", - "# display images in notebook\n", - "%matplotlib inline\n", - "plt.rcParams['figure.figsize'] = (12,12)\n", - "\n", - "\n", - "# download MNIST dataset\n", - "digits = datasets.load_digits()\n", - "\n", - "# define inputs and labels\n", - "inputs = digits.images\n", - "labels = digits.target\n", - "\n", - "print(\"inputs = (n_inputs, pixel_width, pixel_height) = \" + str(inputs.shape))\n", - "print(\"labels = (n_inputs) = \" + str(labels.shape))\n", - "\n", - "\n", - "# flatten the image\n", - "# the value -1 means dimension is inferred from the remaining dimensions: 8x8 = 64\n", - "n_inputs = len(inputs)\n", - "inputs = inputs.reshape(n_inputs, -1)\n", - "print(\"X = (n_inputs, n_features) = \" + str(inputs.shape))\n", - "\n", - "\n", - "# choose some random images to display\n", - "indices = np.arange(n_inputs)\n", - "random_indices = np.random.choice(indices, size=5)\n", - "\n", - "for i, image in enumerate(digits.images[random_indices]):\n", - " plt.subplot(1, 5, i+1)\n", - " plt.axis('off')\n", - " plt.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')\n", - " plt.title(\"Label: %d\" % digits.target[random_indices[i]])\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "from tensorflow.keras.layers import Input\n", - "from tensorflow.keras.models import Sequential #This allows appending layers to existing models\n", - "from tensorflow.keras.layers import Dense #This allows defining the characteristics of a particular layer\n", - "from tensorflow.keras import optimizers #This allows using whichever optimiser we want (sgd,adam,RMSprop)\n", - "from tensorflow.keras import regularizers #This allows using whichever regularizer we want (l1,l2,l1_l2)\n", - "from tensorflow.keras.utils import to_categorical #This allows using categorical cross entropy as the cost function\n", - "\n", - "from sklearn.model_selection import train_test_split\n", - "\n", - "# one-hot representation of labels\n", - "labels = to_categorical(labels)\n", - "\n", - "# split into train and test data\n", - "train_size = 0.8\n", - "test_size = 1 - train_size\n", - "X_train, X_test, Y_train, Y_test = train_test_split(inputs, labels, train_size=train_size,\n", - " test_size=test_size)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "\n", - "epochs = 100\n", - "batch_size = 100\n", - "n_neurons_layer1 = 100\n", - "n_neurons_layer2 = 50\n", - "n_categories = 10\n", - "eta_vals = np.logspace(-5, 1, 7)\n", - "lmbd_vals = np.logspace(-5, 1, 7)\n", - "def create_neural_network_keras(n_neurons_layer1, n_neurons_layer2, n_categories, eta, lmbd):\n", - " model = Sequential()\n", - " model.add(Dense(n_neurons_layer1, activation='sigmoid', kernel_regularizer=regularizers.l2(lmbd)))\n", - " model.add(Dense(n_neurons_layer2, activation='sigmoid', kernel_regularizer=regularizers.l2(lmbd)))\n", - " model.add(Dense(n_categories, activation='softmax'))\n", - " \n", - " sgd = optimizers.SGD(lr=eta)\n", - " model.compile(loss='categorical_crossentropy', optimizer=sgd, metrics=['accuracy'])\n", - " \n", - " return model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "DNN_keras = np.zeros((len(eta_vals), len(lmbd_vals)), dtype=object)\n", - " \n", - "for i, eta in enumerate(eta_vals):\n", - " for j, lmbd in enumerate(lmbd_vals):\n", - " DNN = create_neural_network_keras(n_neurons_layer1, n_neurons_layer2, n_categories,\n", - " eta=eta, lmbd=lmbd)\n", - " DNN.fit(X_train, Y_train, epochs=epochs, batch_size=batch_size, verbose=0)\n", - " scores = DNN.evaluate(X_test, Y_test)\n", - " \n", - " DNN_keras[i][j] = DNN\n", - " \n", - " print(\"Learning rate = \", eta)\n", - " print(\"Lambda = \", lmbd)\n", - " print(\"Test accuracy: %.3f\" % scores[1])\n", - " print()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "# optional\n", - "# visual representation of grid search\n", - "# uses seaborn heatmap, could probably do this in matplotlib\n", - "import seaborn as sns\n", - "\n", - "sns.set()\n", - "\n", - "train_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", - "test_accuracy = np.zeros((len(eta_vals), len(lmbd_vals)))\n", - "\n", - "for i in range(len(eta_vals)):\n", - " for j in range(len(lmbd_vals)):\n", - " DNN = DNN_keras[i][j]\n", - "\n", - " train_accuracy[i][j] = DNN.evaluate(X_train, Y_train)[1]\n", - " test_accuracy[i][j] = DNN.evaluate(X_test, Y_test)[1]\n", - "\n", - " \n", - "fig, ax = plt.subplots(figsize = (10, 10))\n", - "sns.heatmap(train_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", - "ax.set_title(\"Training Accuracy\")\n", - "ax.set_ylabel(\"$\\eta$\")\n", - "ax.set_xlabel(\"$\\lambda$\")\n", - "plt.show()\n", - "\n", - "fig, ax = plt.subplots(figsize = (10, 10))\n", - "sns.heatmap(test_accuracy, annot=True, ax=ax, cmap=\"viridis\")\n", - "ax.set_title(\"Test Accuracy\")\n", - "ax.set_ylabel(\"$\\eta$\")\n", - "ax.set_xlabel(\"$\\lambda$\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## The Breast Cancer Data, now with Keras" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "\n", - "import tensorflow as tf\n", - "from tensorflow.keras.layers import Input\n", - "from tensorflow.keras.models import Sequential #This allows appending layers to existing models\n", - "from tensorflow.keras.layers import Dense #This allows defining the characteristics of a particular layer\n", - "from tensorflow.keras import optimizers #This allows using whichever optimiser we want (sgd,adam,RMSprop)\n", - "from tensorflow.keras import regularizers #This allows using whichever regularizer we want (l1,l2,l1_l2)\n", - "from tensorflow.keras.utils import to_categorical #This allows using categorical cross entropy as the cost function\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "from sklearn.model_selection import train_test_split as splitter\n", - "from sklearn.datasets import load_breast_cancer\n", - "import pickle\n", - "import os \n", - "\n", - "\n", - "\"\"\"Load breast cancer dataset\"\"\"\n", - "\n", - "np.random.seed(0) #create same seed for random number every time\n", - "\n", - "cancer=load_breast_cancer() #Download breast cancer dataset\n", - "\n", - "inputs=cancer.data #Feature matrix of 569 rows (samples) and 30 columns (parameters)\n", - "outputs=cancer.target #Label array of 569 rows (0 for benign and 1 for malignant)\n", - "labels=cancer.feature_names[0:30]\n", - "\n", - "print('The content of the breast cancer dataset is:') #Print information about the datasets\n", - "print(labels)\n", - "print('-------------------------')\n", - "print(\"inputs = \" + str(inputs.shape))\n", - "print(\"outputs = \" + str(outputs.shape))\n", - "print(\"labels = \"+ str(labels.shape))\n", - "\n", - "x=inputs #Reassign the Feature and Label matrices to other variables\n", - "y=outputs\n", - "\n", - "#%% \n", - "\n", - "# Visualisation of dataset (for correlation analysis)\n", - "\n", - "plt.figure()\n", - "plt.scatter(x[:,0],x[:,2],s=40,c=y,cmap=plt.cm.Spectral)\n", - "plt.xlabel('Mean radius',fontweight='bold')\n", - "plt.ylabel('Mean perimeter',fontweight='bold')\n", - "plt.show()\n", - "\n", - "plt.figure()\n", - "plt.scatter(x[:,5],x[:,6],s=40,c=y, cmap=plt.cm.Spectral)\n", - "plt.xlabel('Mean compactness',fontweight='bold')\n", - "plt.ylabel('Mean concavity',fontweight='bold')\n", - "plt.show()\n", - "\n", - "\n", - "plt.figure()\n", - "plt.scatter(x[:,0],x[:,1],s=40,c=y,cmap=plt.cm.Spectral)\n", - "plt.xlabel('Mean radius',fontweight='bold')\n", - "plt.ylabel('Mean texture',fontweight='bold')\n", - "plt.show()\n", - "\n", - "plt.figure()\n", - "plt.scatter(x[:,2],x[:,1],s=40,c=y,cmap=plt.cm.Spectral)\n", - "plt.xlabel('Mean perimeter',fontweight='bold')\n", - "plt.ylabel('Mean compactness',fontweight='bold')\n", - "plt.show()\n", - "\n", - "\n", - "# Generate training and testing datasets\n", - "\n", - "#Select features relevant to classification (texture,perimeter,compactness and symmetery) \n", - "#and add to input matrix\n", - "\n", - "temp1=np.reshape(x[:,1],(len(x[:,1]),1))\n", - "temp2=np.reshape(x[:,2],(len(x[:,2]),1))\n", - "X=np.hstack((temp1,temp2)) \n", - "temp=np.reshape(x[:,5],(len(x[:,5]),1))\n", - "X=np.hstack((X,temp)) \n", - "temp=np.reshape(x[:,8],(len(x[:,8]),1))\n", - "X=np.hstack((X,temp)) \n", - "\n", - "X_train,X_test,y_train,y_test=splitter(X,y,test_size=0.1) #Split datasets into training and testing\n", - "\n", - "y_train=to_categorical(y_train) #Convert labels to categorical when using categorical cross entropy\n", - "y_test=to_categorical(y_test)\n", - "\n", - "del temp1,temp2,temp\n", - "\n", - "# %%\n", - "\n", - "# Define tunable parameters\"\n", - "\n", - "eta=np.logspace(-3,-1,3) #Define vector of learning rates (parameter to SGD optimiser)\n", - "lamda=0.01 #Define hyperparameter\n", - "n_layers=2 #Define number of hidden layers in the model\n", - "n_neuron=np.logspace(0,3,4,dtype=int) #Define number of neurons per layer\n", - "epochs=100 #Number of reiterations over the input data\n", - "batch_size=100 #Number of samples per gradient update\n", - "\n", - "# %%\n", - "\n", - "\"\"\"Define function to return Deep Neural Network model\"\"\"\n", - "\n", - "def NN_model(inputsize,n_layers,n_neuron,eta,lamda):\n", - " model=Sequential() \n", - " for i in range(n_layers): #Run loop to add hidden layers to the model\n", - " if (i==0): #First layer requires input dimensions\n", - " model.add(Dense(n_neuron,activation='relu',kernel_regularizer=regularizers.l2(lamda),input_dim=inputsize))\n", - " else: #Subsequent layers are capable of automatic shape inferencing\n", - " model.add(Dense(n_neuron,activation='relu',kernel_regularizer=regularizers.l2(lamda)))\n", - " model.add(Dense(2,activation='softmax')) #2 outputs - ordered and disordered (softmax for prob)\n", - " sgd=optimizers.SGD(lr=eta)\n", - " model.compile(loss='categorical_crossentropy',optimizer=sgd,metrics=['accuracy'])\n", - " return model\n", - "\n", - " \n", - "Train_accuracy=np.zeros((len(n_neuron),len(eta))) #Define matrices to store accuracy scores as a function\n", - "Test_accuracy=np.zeros((len(n_neuron),len(eta))) #of learning rate and number of hidden neurons for \n", - "\n", - "for i in range(len(n_neuron)): #run loops over hidden neurons and learning rates to calculate \n", - " for j in range(len(eta)): #accuracy scores \n", - " DNN_model=NN_model(X_train.shape[1],n_layers,n_neuron[i],eta[j],lamda)\n", - " DNN_model.fit(X_train,y_train,epochs=epochs,batch_size=batch_size,verbose=1)\n", - " Train_accuracy[i,j]=DNN_model.evaluate(X_train,y_train)[1]\n", - " Test_accuracy[i,j]=DNN_model.evaluate(X_test,y_test)[1]\n", - " \n", - "\n", - "def plot_data(x,y,data,title=None):\n", - "\n", - " # plot results\n", - " fontsize=16\n", - "\n", - "\n", - " fig = plt.figure()\n", - " ax = fig.add_subplot(111)\n", - " cax = ax.matshow(data, interpolation='nearest', vmin=0, vmax=1)\n", - " \n", - " cbar=fig.colorbar(cax)\n", - " cbar.ax.set_ylabel('accuracy (%)',rotation=90,fontsize=fontsize)\n", - " cbar.set_ticks([0,.2,.4,0.6,0.8,1.0])\n", - " cbar.set_ticklabels(['0%','20%','40%','60%','80%','100%'])\n", - "\n", - " # put text on matrix elements\n", - " for i, x_val in enumerate(np.arange(len(x))):\n", - " for j, y_val in enumerate(np.arange(len(y))):\n", - " c = \"${0:.1f}\\\\%$\".format( 100*data[j,i]) \n", - " ax.text(x_val, y_val, c, va='center', ha='center')\n", - "\n", - " # convert axis vaues to to string labels\n", - " x=[str(i) for i in x]\n", - " y=[str(i) for i in y]\n", - "\n", - "\n", - " ax.set_xticklabels(['']+x)\n", - " ax.set_yticklabels(['']+y)\n", - "\n", - " ax.set_xlabel('$\\\\mathrm{learning\\\\ rate}$',fontsize=fontsize)\n", - " ax.set_ylabel('$\\\\mathrm{hidden\\\\ neurons}$',fontsize=fontsize)\n", - " if title is not None:\n", - " ax.set_title(title)\n", - "\n", - " plt.tight_layout()\n", - "\n", - " plt.show()\n", - " \n", - "plot_data(eta,n_neuron,Train_accuracy, 'training')\n", - "plot_data(eta,n_neuron,Test_accuracy, 'testing')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Fine-tuning neural network hyperparameters\n", - "\n", - "The flexibility of neural networks is also one of their main\n", - "drawbacks: there are many hyperparameters to tweak. Not only can you\n", - "use any imaginable network topology (how neurons/nodes are interconnected),\n", - "but even in a simple FFNN you can change the number of layers, the\n", - "number of neurons per layer, the type of activation function to use in\n", - "each layer, the weight initialization logic, the stochastic gradient optmized and much more. How do you\n", - "know what combination of hyperparameters is the best for your task?\n", - "\n", - "* You can use grid search with cross-validation to find the right hyperparameters.\n", - "\n", - "However,since there are many hyperparameters to tune, and since\n", - "training a neural network on a large dataset takes a lot of time, you\n", - "will only be able to explore a tiny part of the hyperparameter space.\n", - "\n", - "\n", - "* You can use randomized search.\n", - "\n", - "* Or use tools like [Oscar](http://oscar.calldesk.ai/), which implements more complex algorithms to help you find a good set of hyperparameters quickly. \n", - "\n", - "For many problems you can start with just one or two hidden layers and it will work just fine.\n", - "For the MNIST data set you ca easily get a high accuracy using just one hidden layer with a\n", - "few hundred neurons.\n", - "You can reach for this data set above 98% accuracy using two hidden layers with the same total amount of\n", - "neurons, in roughly the same amount of training time. \n", - "\n", - "For more complex problems, you can gradually\n", - "ramp up the number of hidden layers, until you start overfitting the training set. Very complex tasks, such\n", - "as large image classification or speech recognition, typically require networks with dozens of layers\n", - "and they need a huge amount\n", - "of training data. However, you will rarely have to train such networks from scratch: it is much more\n", - "common to reuse parts of a pretrained state-of-the-art network that performs a similar task.\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "## Which activation function should I use?\n", - "\n", - "The Back propagation algorithm we derived above works by going from\n", - "the output layer to the input layer, propagating the error gradient on\n", - "the way. Once the algorithm has computed the gradient of the cost\n", - "function with regards to each parameter in the network, it uses these\n", - "gradients to update each parameter with a Gradient Descent (GD) step.\n", - "\n", - "\n", - "Unfortunately for us, the gradients often get smaller and smaller as the\n", - "algorithm progresses down to the first hidden layers. As a result, the\n", - "GD update leaves the lower layer connection weights\n", - "virtually unchanged, and training never converges to a good\n", - "solution. This is known in the literature as \n", - "**the vanishing gradients problem**. \n", - "\n", - "In other cases, the opposite can happen, namely the the gradients can grow bigger and\n", - "bigger. The result is that many of the layers get large updates of the \n", - "weights the\n", - "algorithm diverges. This is the **exploding gradients problem**, which is\n", - "mostly encountered in recurrent neural networks. More generally, deep\n", - "neural networks suffer from unstable gradients, different layers may\n", - "learn at widely different speeds\n", - "\n", - "\n", - "\n", - "\n", - "Although this unfortunate behavior has been empirically observed for\n", - "quite a while (it was one of the reasons why deep neural networks were\n", - "mostly abandoned for a long time), it is only around 2010 that\n", - "significant progress was made in understanding it.\n", - "\n", - "A paper titled [Understanding the Difficulty of Training Deep\n", - "Feedforward Neural Networks by Xavier Glorot and Yoshua Bengio](http://proceedings.mlr.press/v9/glorot10a.html) found that\n", - "the problems with the popular logistic\n", - "sigmoid activation function and the weight initialization technique\n", - "that was most popular at the time, namely random initialization using\n", - "a normal distribution with a mean of 0 and a standard deviation of\n", - "1. \n", - "\n", - "They showed that with this activation function and this\n", - "initialization scheme, the variance of the outputs of each layer is\n", - "much greater than the variance of its inputs. Going forward in the\n", - "network, the variance keeps increasing after each layer until the\n", - "activation function saturates at the top layers. This is actually made\n", - "worse by the fact that the logistic function has a mean of 0.5, not 0\n", - "(the hyperbolic tangent function has a mean of 0 and behaves slightly\n", - "better than the logistic function in deep networks).\n", - "\n", - "\n", - "\n", - "Looking at the logistic activation function, when inputs become large\n", - "(negative or positive), the function saturates at 0 or 1, with a\n", - "derivative extremely close to 0. Thus when backpropagation kicks in,\n", - "it has virtually no gradient to propagate back through the network,\n", - "and what little gradient exists keeps getting diluted as\n", - "backpropagation progresses down through the top layers, so there is\n", - "really nothing left for the lower layers.\n", - "\n", - "In their paper, Glorot and Bengio propose a way to significantly\n", - "alleviate this problem. We need the signal to flow properly in both\n", - "directions: in the forward direction when making predictions, and in\n", - "the reverse direction when backpropagating gradients. We don’t want\n", - "the signal to die out, nor do we want it to explode and saturate. For\n", - "the signal to flow properly, the authors argue that we need the\n", - "variance of the outputs of each layer to be equal to the variance of\n", - "its inputs, and we also need the gradients to have equal variance\n", - "before and after flowing through a layer in the reverse direction.\n", - "\n", - "\n", - "\n", - "One of the insights in the 2010 paper by Glorot and Bengio was that\n", - "the vanishing/exploding gradients problems were in part due to a poor\n", - "choice of activation function. Until then most people had assumed that\n", - "if Nature had chosen to use roughly sigmoid activation functions in\n", - "biological neurons, they must be an excellent choice. But it turns out\n", - "that other activation functions behave much better in deep neural\n", - "networks, in particular the ReLU activation function, mostly because\n", - "it does not saturate for positive values (and also because it is quite\n", - "fast to compute).\n", - "\n", - "\n", - "## The RELU function family\n", - "\n", - "The ReLU activation function suffers from a problem known as the dying\n", - "ReLUs: during training, some neurons effectively die, meaning they\n", - "stop outputting anything other than 0.\n", - "\n", - "In some cases, you may find that half of your network’s neurons are\n", - "dead, especially if you used a large learning rate. During training,\n", - "if a neuron’s weights get updated such that the weighted sum of the\n", - "neuron’s inputs is negative, it will start outputting 0. When this\n", - "happen, the neuron is unlikely to come back to life since the gradient\n", - "of the ReLU function is 0 when its input is negative.\n", - "\n", - "To solve this problem, nowadays practitioners use a variant of the ReLU\n", - "function, such as the leaky ReLU discussed above or the so-called\n", - "exponential linear unit (ELU) function" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "$$\n", - "ELU(z) = \\left\\{\\begin{array}{cc} \\alpha\\left( \\exp{(z)}-1\\right) & z < 0,\\\\ z & z \\ge 0.\\end{array}\\right.\n", - "$$" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "In general it seems that the ELU activation function is better than\n", - "the leaky ReLU function (and its variants), which is better than\n", - "ReLU. ReLU performs better than $\\tanh$ which in turn performs better\n", - "than the logistic function. \n", - "\n", - "If runtime\n", - "performance is an issue, then you may opt for the leaky ReLU function over the \n", - "ELU function If you don’t\n", - "want to tweak yet another hyperparameter, you may just use the default\n", - "$\\alpha$ of $0.01$ for the leaky ReLU, and $1$ for ELU. If you have\n", - "spare time and computing power, you can use cross-validation or\n", - "bootstrap to evaluate other activation functions.\n", - "\n", - "\n", - "\n", - "In most cases you can use the ReLU activation function in the hidden layers (or one of its variants).\n", - "\n", - "It is a bit faster to compute than other activation functions, and the gradient descent optimization does in general not get stuck.\n", - "\n", - "**For the output layer:**\n", - "\n", - "* For classification the softmax activation function is generally a good choice for classification tasks (when the classes are mutually exclusive).\n", - "\n", - "* For regression tasks, you can simply use no activation function at all.\n", - "\n", - "## Batch Normalization\n", - "\n", - "Batch Normalization\n", - "aims to address the vanishing/exploding gradients problems, and more generally the problem that the\n", - "distribution of each layer’s inputs changes during training, as the parameters of the previous layers change.\n", - "\n", - "The technique consists of adding an operation in the model just before the activation function of each\n", - "layer, simply zero-centering and normalizing the inputs, then scaling and shifting the result using two new\n", - "parameters per layer (one for scaling, the other for shifting). In other words, this operation lets the model\n", - "learn the optimal scale and mean of the inputs for each layer.\n", - "In order to zero-center and normalize the inputs, the algorithm needs to estimate the inputs’ mean and\n", - "standard deviation. It does so by evaluating the mean and standard deviation of the inputs over the current\n", - "mini-batch, from this the name batch normalization.\n", - "\n", - "## Dropout\n", - "\n", - "It is a fairly simple algorithm: at every training step, every neuron (including the input neurons but\n", - "excluding the output neurons) has a probability $p$ of being temporarily dropped out, meaning it will be\n", - "entirely ignored during this training step, but it may be active during the next step.\n", - "\n", - "The\n", - "hyperparameter $p$ is called the dropout rate, and it is typically set to 50%. After training, the neurons are not dropped anymore.\n", - " It is viewed as one of the most popular regularization techniques.\n", - "\n", - "## Gradient Clipping\n", - "\n", - "A popular technique to lessen the exploding gradients problem is to simply clip the gradients during\n", - "backpropagation so that they never exceed some threshold (this is mostly useful for recurrent neural\n", - "networks).\n", - "\n", - "This technique is called Gradient Clipping.\n", - "\n", - "In general however, Batch\n", - "Normalization is preferred.\n", - "\n", - "\n", - "## A top-down perspective on Neural networks\n", - "\n", - "\n", - "The first thing we would like to do is divide the data into two or three\n", - "parts. A training set, a validation or dev (development) set, and a\n", - "test set. The test set is the data on which we want to make\n", - "predictions. The dev set is a subset of the training data we use to\n", - "check how well we are doing out-of-sample, after training the model on\n", - "the training dataset. We use the validation error as a proxy for the\n", - "test error in order to make tweaks to our model. It is crucial that we\n", - "do not use any of the test data to train the algorithm. This is a\n", - "cardinal sin in ML. Then:\n", - "\n", - "\n", - "* Estimate optimal error rate\n", - "\n", - "* Minimize underfitting (bias) on training data set.\n", - "\n", - "* Make sure you are not overfitting.\n", - "\n", - "If the validation and test sets are drawn from the same distributions,\n", - "then a good performance on the validation set should lead to similarly\n", - "good performance on the test set. \n", - "\n", - "However, sometimes\n", - "the training data and test data differ in subtle ways because, for\n", - "example, they are collected using slightly different methods, or\n", - "because it is cheaper to collect data in one way versus another. In\n", - "this case, there can be a mismatch between the training and test\n", - "data. This can lead to the neural network overfitting these small\n", - "differences between the test and training sets, and a poor performance\n", - "on the test set despite having a good performance on the validation\n", - "set. To rectify this, Andrew Ng suggests making two validation or dev\n", - "sets, one constructed from the training data and one constructed from\n", - "the test data. The difference between the performance of the algorithm\n", - "on these two validation sets quantifies the train-test mismatch. This\n", - "can serve as another important diagnostic when using DNNs for\n", - "supervised learning.\n", - "\n", - "\n", - "## Limitations of supervised learning with deep networks\n", - "\n", - "Like all statistical methods, supervised learning using neural\n", - "networks has important limitations. This is especially important when\n", - "one seeks to apply these methods, especially to physics problems. Like\n", - "all tools, DNNs are not a universal solution. Often, the same or\n", - "better performance on a task can be achieved by using a few\n", - "hand-engineered features (or even a collection of random\n", - "features). \n", - "\n", - "Here we list some of the important limitations of supervised neural network based models. \n", - "\n", - "\n", - "\n", - "* **Need labeled data**. All supervised learning methods, DNNs for supervised learning require labeled data. Often, labeled data is harder to acquire than unlabeled data (e.g. one must pay for human experts to label images).\n", - "\n", - "* **Supervised neural networks are extremely data intensive.** DNNs are data hungry. They perform best when data is plentiful. This is doubly so for supervised methods where the data must also be labeled. The utility of DNNs is extremely limited if data is hard to acquire or the datasets are small (hundreds to a few thousand samples). In this case, the performance of other methods that utilize hand-engineered features can exceed that of DNNs.\n", - "\n", - "* **Homogeneous data.** Almost all DNNs deal with homogeneous data of one type. It is very hard to design architectures that mix and match data types (i.e. some continuous variables, some discrete variables, some time series). In applications beyond images, video, and language, this is often what is required. In contrast, ensemble models like random forests or gradient-boosted trees have no difficulty handling mixed data types.\n", - "\n", - "* **Many problems are not about prediction.** In natural science we are often interested in learning something about the underlying distribution that generates the data. In this case, it is often difficult to cast these ideas in a supervised learning setting. While the problems are related, it is possible to make good predictions with a *wrong* model. The model might or might not be useful for understanding the underlying science.\n", - "\n", - "Some of these remarks are particular to DNNs, others are shared by all supervised learning methods. This motivates the use of unsupervised methods which in part circumvent these problems." - ] - } - ], - "metadata": {}, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/doc/BookChapters/chapter11.ipynb b/doc/BookChapters/chapter11.ipynb index c03e4a368..00bf35796 100644 --- a/doc/BookChapters/chapter11.ipynb +++ b/doc/BookChapters/chapter11.ipynb @@ -2,7 +2,21 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "id": "7660a7d5", + "metadata": { + "editable": true + }, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "7174820b", + "metadata": { + "editable": true + }, "source": [ "# Solving Differential Equations with Deep Learning\n", "\n", @@ -10,7 +24,6 @@ "approximate any function at a single hidden layer along with one input\n", "and output layer to any given precision. \n", "\n", - "\n", "An ordinary differential equation (ODE) is an equation involving functions having one variable.\n", "\n", "In general, an ordinary differential equation looks like" @@ -18,7 +31,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ac838a12", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -32,7 +48,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ccee4286", + "metadata": { + "editable": true + }, "source": [ "where $g(x)$ is the function to find, and $g^{(n)}(x)$ is the $n$-th derivative of $g(x)$.\n", "\n", @@ -42,14 +61,15 @@ "Along with ([1](#ode)), some additional conditions of the function $g(x)$ are typically given\n", "for the solution to be unique.\n", "\n", - "\n", - "\n", "Let the trial solution $g_t(x)$ be" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "39d7dabd", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -64,7 +84,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "74f33170", + "metadata": { + "editable": true + }, "source": [ "where $h_1(x)$ is a function that makes $g_t(x)$ satisfy a given set\n", "of conditions, $N(x,P)$ a neural network with weights and biases\n", @@ -77,11 +100,8 @@ "\n", "But what about the network $N(x,P)$?\n", "\n", - "\n", "As described previously, an optimization method could be used to minimize the parameters of a neural network, that being its weights and biases, through backward propagation.\n", "\n", - "\n", - "\n", "For the minimization to be defined, we need to have a cost function at hand to minimize.\n", "\n", "It is given that $f\\left(x, \\, g(x), \\, g'(x), \\, g''(x), \\, \\dots \\, , \\, g^{(n)}(x)\\right)$ should be equal to zero in ([1](#ode)).\n", @@ -92,7 +112,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "2565ab16", + "metadata": { + "editable": true + }, "source": [ "$$\n", "C\\left(x, P\\right) = \\big(f\\left(x, \\, g(x), \\, g'(x), \\, g''(x), \\, \\dots \\, , \\, g^{(n)}(x)\\right)\\big)^2\n", @@ -101,7 +124,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "83d4ee25", + "metadata": { + "editable": true + }, "source": [ "If $N$ inputs are given as a vector $\\boldsymbol{x}$ with elements $x_i$ for $i = 1,\\dots,N$,\n", "the cost function becomes" @@ -109,7 +135,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "a7a3a709", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -123,20 +152,28 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "d3ca8026", + "metadata": { + "editable": true + }, "source": [ "The neural net should then find the parameters $P$ that minimizes the cost function in\n", "([3](#cost)) for a set of $N$ training samples $x_i$.\n", "\n", - "\n", - "\n", "To perform the minimization using gradient descent, the gradient of $C\\left(\\boldsymbol{x}, P\\right)$ is needed.\n", "It might happen so that finding an analytical expression of the gradient of $C(\\boldsymbol{x}, P)$ from ([3](#cost)) gets too messy, depending on which cost function one desires to use.\n", "\n", "Luckily, there exists libraries that makes the job for us through automatic differentiation.\n", - "Automatic differentiation is a method of finding the derivatives numerically with very high precision.\n", - "\n", - "\n", + "Automatic differentiation is a method of finding the derivatives numerically with very high precision." + ] + }, + { + "cell_type": "markdown", + "id": "9bde4bd4", + "metadata": { + "editable": true + }, + "source": [ "### Example: Exponential decay\n", "\n", "An exponential decay of a quantity $g(x)$ is described by the equation" @@ -144,7 +181,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "e74337c3", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -158,7 +198,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "273549a4", + "metadata": { + "editable": true + }, "source": [ "with $g(0) = g_0$ for some chosen initial value $g_0$.\n", "\n", @@ -167,7 +210,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "db3c6623", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -182,18 +228,22 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "8547e3c8", + "metadata": { + "editable": true + }, "source": [ "Having an analytical solution at hand, it is possible to use it to compare how well a neural network finds a solution of ([4](#solve_expdec)).\n", "\n", - "\n", - "\n", "The program will use a neural network to solve" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "48341fc6", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -207,19 +257,24 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "d25b1e02", + "metadata": { + "editable": true + }, "source": [ "where $g(0) = g_0$ with $\\gamma$ and $g_0$ being some chosen values.\n", "\n", "In this example, $\\gamma = 2$ and $g_0 = 10$.\n", "\n", - "\n", "To begin with, a trial solution $g_t(t)$ must be chosen. A general trial solution for ordinary differential equations could be" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "7c5dd91e", + "metadata": { + "editable": true + }, "source": [ "$$\n", "g_t(x, P) = h_1(x) + h_2(x, N(x, P))\n", @@ -228,12 +283,13 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "24223062", + "metadata": { + "editable": true + }, "source": [ "with $h_1(x)$ ensuring that $g_t(x)$ satisfies some conditions and $h_2(x,N(x, P))$ an expression involving $x$ and the output from the neural network $N(x,P)$ with $P $ being the collection of the weights and biases for each layer. For now, it is assumed that the network consists of one input layer, one hidden layer, and one output layer.\n", "\n", - "\n", - "\n", "In this network, there are no weights and bias at the input layer, so $P = \\{ P_{\\text{hidden}}, P_{\\text{output}} \\}$.\n", "If there are $N_{\\text{hidden} }$ neurons in the hidden layer, then $P_{\\text{hidden}}$ is a $N_{\\text{hidden} } \\times (1 + N_{\\text{input}})$ matrix, given that there are $N_{\\text{input}}$ neurons in the input layer.\n", "\n", @@ -247,7 +303,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ebf04383", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -261,7 +320,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ea4a0013", + "metadata": { + "editable": true + }, "source": [ "### Reformulating the problem\n", "\n", @@ -277,7 +339,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "2351b84f", + "metadata": { + "editable": true + }, "source": [ "$$\n", "g_t(x, P) = g_0 + x \\cdot N(x, P)\n", @@ -286,14 +351,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "f6cc00e4", + "metadata": { + "editable": true + }, "source": [ "has been chosen such that it already solves the condition $g(0) = g_0$. What remains, is to find $P$ such that" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "84e066a1", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -307,11 +378,13 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "feff9ab3", + "metadata": { + "editable": true + }, "source": [ "is fulfilled as *best as possible*.\n", "\n", - "\n", "The left hand side and right hand side of ([8](#nnmin)) must be computed separately, and then the neural network must choose weights and biases, contained in $P$, such that the sides are equal as best as possible.\n", "This means that the absolute or squared difference between the sides must be as close to zero, ideally equal to zero.\n", "In this case, the difference squared shows to be an appropriate measurement of how erroneous the trial solution is with respect to $P$ of the neural network.\n", @@ -321,7 +394,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "0c6f0e79", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\min_{P}\\Big\\{ \\big(g_t'(x, P) - ( -\\gamma g_t(x, P) \\big)^2 \\Big\\}\n", @@ -330,7 +406,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "9302a1dd", + "metadata": { + "editable": true + }, "source": [ "(the notation $\\min_{P}\\{ f(x, P) \\}$ means that we desire to find $P$ that yields the minimum of $f(x, P)$)\n", "\n", @@ -339,7 +418,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "f7f204bb", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\min_{P_{\\text{hidden} }, \\ P_{\\text{output} }}\\Big\\{ \\big(g_t'(x, \\{ P_{\\text{hidden} }, P_{\\text{output} }\\}) - ( -\\gamma g_t(x, \\{ P_{\\text{hidden} }, P_{\\text{output} }\\}) \\big)^2 \\Big\\}\n", @@ -348,18 +430,22 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "8d61d75f", + "metadata": { + "editable": true + }, "source": [ "for an input value $x$.\n", "\n", - "\n", - "\n", "If the neural network evaluates $g_t(x, P)$ at more values for $x$, say $N$ values $x_i$ for $i = 1, \\dots, N$, then the *total* error to minimize becomes" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "10d3aec9", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -373,14 +459,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "5e296388", + "metadata": { + "editable": true + }, "source": [ "Letting $\\boldsymbol{x}$ be a vector with elements $x_i$ and $C(\\boldsymbol{x}, P) = \\frac{1}{N} \\sum_i \\big(g_t'(x_i, P) - ( -\\gamma g_t(x_i, P) \\big)^2$ denote the cost function, the minimization problem that our network must solve, becomes" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "fe010d79", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\min_{P} C(\\boldsymbol{x}, P)\n", @@ -389,7 +481,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "add715f9", + "metadata": { + "editable": true + }, "source": [ "In terms of $P_{\\text{hidden} }$ and $P_{\\text{output} }$, this could also be expressed as\n", "\n", @@ -397,20 +492,21 @@ "\\min_{P_{\\text{hidden} }, \\ P_{\\text{output} }} C(\\boldsymbol{x}, \\{P_{\\text{hidden} }, P_{\\text{output} }\\})\n", "$$\n", "\n", - "\n", "For simplicity, it is assumed that the input is an array $\\boldsymbol{x} = (x_1, \\dots, x_N)$ with $N$ elements. It is at these points the neural network should find $P$ such that it fulfills ([9](#min)).\n", "\n", "First, the neural network must feed forward the inputs.\n", "This means that $\\boldsymbol{x}s$ must be passed through an input layer, a hidden layer and a output layer. The input layer in this case, does not need to process the data any further.\n", "The input layer will consist of $N_{\\text{input} }$ neurons, passing its element to each neuron in the hidden layer. The number of neurons in the hidden layer will be $N_{\\text{hidden} }$.\n", "\n", - "\n", "For the $i$-th in the hidden layer with weight $w_i^{\\text{hidden} }$ and bias $b_i^{\\text{hidden} }$, the weighting from the $j$-th neuron at the input layer is:" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "cb9b22eb", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -429,14 +525,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "49f3c493", + "metadata": { + "editable": true + }, "source": [ "The result after weighting the inputs at the $i$-th hidden neuron can be written as a vector:" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "a352bd61", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -456,7 +558,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "e5636ad7", + "metadata": { + "editable": true + }, "source": [ "The vector $\\boldsymbol{p}_{i, \\text{hidden}}^T$ constitutes each row in $P_{\\text{hidden} }$, which contains the weights for the neural network to minimize according to ([9](#min)).\n", "\n", @@ -467,7 +572,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "68bb8b9c", + "metadata": { + "editable": true + }, "source": [ "$$\n", "f(z) = \\frac{1}{1 + \\exp{(-z)}}\n", @@ -476,7 +584,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ff6e6536", + "metadata": { + "editable": true + }, "source": [ "It is possible to use other activations functions for the hidden layer also.\n", "\n", @@ -494,14 +605,15 @@ "and biases $b_i^{\\text{output}}$. In this case,\n", "it is assumes that the number of neurons in the output layer is one.\n", "\n", - "\n", - "\n", "The procedure of weighting the output neuron $j$ in the hidden layer to the $i$-th neuron in the output layer is similar as for the hidden layer described previously." ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "6fb4c55b", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -519,14 +631,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "09c31d8d", + "metadata": { + "editable": true + }, "source": [ "Expressing $z_{1,j}^{\\text{output}}$ as a vector gives the following way of weighting the inputs from the hidden layer:" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "f81fe361", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\boldsymbol{z}_{1}^{\\text{output}} =\n", @@ -542,11 +660,13 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "c309a4ce", + "metadata": { + "editable": true + }, "source": [ "In this case we seek a continuous range of values since we are approximating a function. This means that after computing $\\boldsymbol{z}_{1}^{\\text{output}}$ the neural network has finished its feed forward step, and $\\boldsymbol{z}_{1}^{\\text{output}}$ is the final output of the network.\n", "\n", - "\n", "The next step is to decide how the parameters should be changed such that they minimize the cost function.\n", "\n", "The chosen cost function for this problem is" @@ -554,7 +674,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ea47ae29", + "metadata": { + "editable": true + }, "source": [ "$$\n", "C(\\boldsymbol{x}, P) = \\frac{1}{N} \\sum_i \\big(g_t'(x_i, P) - ( -\\gamma g_t(x_i, P) \\big)^2\n", @@ -563,12 +686,23 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "9dd83767", + "metadata": { + "editable": true + }, "source": [ "In order to minimize the cost function, an optimization method must be chosen.\n", "\n", - "Here, gradient descent with a constant step size has been chosen.\n", - "\n", + "Here, gradient descent with a constant step size has been chosen." + ] + }, + { + "cell_type": "markdown", + "id": "531a7b4f", + "metadata": { + "editable": true + }, + "source": [ "### Gradient descent\n", "\n", "The idea of the gradient descent algorithm is to update parameters in\n", @@ -581,7 +715,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "e10c204a", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\boldsymbol{\\omega}_{\\text{new} } = \\boldsymbol{\\omega} - \\lambda \\nabla_{\\boldsymbol{\\omega}} C(\\boldsymbol{x}, \\boldsymbol{\\omega})\n", @@ -590,7 +727,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "9f4fa48e", + "metadata": { + "editable": true + }, "source": [ "for a number of iterations or until $ \\big|\\big| \\boldsymbol{\\omega}_{\\text{new} } - \\boldsymbol{\\omega} \\big|\\big|$ becomes smaller than some given tolerance.\n", "\n", @@ -609,7 +749,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "4652fc79", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -621,14 +764,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "41ae0626", + "metadata": { + "editable": true + }, "source": [ "### The code for solving the ODE" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, + "id": "af7c165f", "metadata": { "collapsed": false, "editable": true @@ -785,7 +932,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "875a0c0b", + "metadata": { + "editable": true + }, "source": [ "## The network with one input layer, specified number of hidden layers, and one output layer\n", "\n", @@ -796,7 +946,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, + "id": "00684827", "metadata": { "collapsed": false, "editable": true @@ -965,7 +1116,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "5420c490", + "metadata": { + "editable": true + }, "source": [ "### Example: Population growth\n", "\n", @@ -975,7 +1129,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "1436ed3c", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -989,7 +1146,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "a590bdb6", + "metadata": { + "editable": true + }, "source": [ "where $g(t)$ is the population density at time $t$, $\\alpha > 0$ the growth rate and $A > 0$ is the maximum population number in the environment.\n", "Also, at $t = 0$ the population has the size $g(0) = g_0$, where $g_0$ is some chosen constant.\n", @@ -999,15 +1159,16 @@ "using a library like TensorFlow is recommended.\n", "Here, we stay with a more simple approach and implement for comparison, the simple forward Euler method.\n", "\n", - "\n", - "\n", "Here, we will model a population $g(t)$ in an environment having carrying capacity $A$.\n", "The population follows the model" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "48d788d6", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -1021,13 +1182,15 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "169c6c25", + "metadata": { + "editable": true + }, "source": [ "where $g(0) = g_0$.\n", "\n", "In this example, we let $\\alpha = 2$, $A = 1$, and $g_0 = 1.2$.\n", "\n", - "\n", "We will get a slightly different trial solution, as the boundary conditions are different\n", "compared to the case for exponential decay.\n", "\n", @@ -1045,14 +1208,13 @@ "g(t) = \\frac{Ag_0}{g_0 + (A - g_0)\\exp(-\\alpha A t)}\n", "$$\n", "\n", - "\n", - "\n", "The network will be the similar as for the exponential decay example, but with some small modifications for our problem." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, + "id": "6d2e33bf", "metadata": { "collapsed": false, "editable": true @@ -1226,7 +1388,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "e31ec549", + "metadata": { + "editable": true + }, "source": [ "## Using forward Euler to solve the ODE\n", "\n", @@ -1243,7 +1408,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "2e9ee105", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -1255,7 +1423,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "e876da16", + "metadata": { + "editable": true + }, "source": [ "along with the condition that $g(0) = g_0$.\n", "\n", @@ -1266,7 +1437,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "b79a3def", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -1279,14 +1453,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "9d99cb4e", + "metadata": { + "editable": true + }, "source": [ "Now, if $g_i = g(t_i)$ then" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "3b9dcc24", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -1305,7 +1485,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "65ce688e", + "metadata": { + "editable": true + }, "source": [ "for $i \\geq 1$ and $g_0 = g(t_0) = g(0) = g_0$.\n", "\n", @@ -1315,7 +1498,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, + "id": "d5497948", "metadata": { "collapsed": false, "editable": true @@ -1391,7 +1575,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "fa8f0bb4", + "metadata": { + "editable": true + }, "source": [ "## Solving the one dimensional Poisson equation\n", "\n", @@ -1400,7 +1587,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "f765b0ba", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -1414,7 +1604,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "bd63b92e", + "metadata": { + "editable": true + }, "source": [ "where $f(x)$ is a given function for $x \\in (0,1)$.\n", "\n", @@ -1423,7 +1616,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "c0a7face", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{align*}\n", @@ -1435,19 +1631,24 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "f71c3cb9", + "metadata": { + "editable": true + }, "source": [ "This equation can be solved numerically using programs where e.g Autograd and TensorFlow are used.\n", "The results from the networks can then be compared to the analytical solution.\n", "In addition, it could be interesting to see how a typical method for numerically solving second order ODEs compares to the neural networks.\n", "\n", - "\n", "Here, the function $g(x)$ to solve for follows the equation" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "35aa6a37", + "metadata": { + "editable": true + }, "source": [ "$$\n", "-g''(x) = f(x),\\qquad x \\in (0,1)\n", @@ -1456,14 +1657,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "f8b35111", + "metadata": { + "editable": true + }, "source": [ "where $f(x)$ is a given function, along with the chosen conditions" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "33813514", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -1477,7 +1684,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "c70aadd4", + "metadata": { + "editable": true + }, "source": [ "In this example, we consider the case when $f(x) = (3x + x^2)\\exp(x)$.\n", "\n", @@ -1486,7 +1696,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "d5719dee", + "metadata": { + "editable": true + }, "source": [ "$$\n", "g_t(x) = x \\cdot (1-x) \\cdot N(P,x)\n", @@ -1495,14 +1708,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "a683041b", + "metadata": { + "editable": true + }, "source": [ "The analytical solution for this problem is" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "672cdaff", + "metadata": { + "editable": true + }, "source": [ "$$\n", "g(x) = x(1 - x)\\exp(x)\n", @@ -1511,7 +1730,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, + "id": "66e6bebb", "metadata": { "collapsed": false, "editable": true @@ -1672,7 +1892,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "d425cba5", + "metadata": { + "editable": true + }, "source": [ "### Comparing with a numerical scheme\n", "\n", @@ -1691,7 +1914,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "a2efa110", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -1705,14 +1931,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "5011ee25", + "metadata": { + "editable": true + }, "source": [ "If $x_i = i \\Delta x = x_{i-1} + \\Delta x$ and $g_i = g(x_i)$ for $i = 1,\\dots N_x - 2$ with $N_x$ being the number of values for $x$, ([15](#approx)) becomes" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "705ee300", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -1724,14 +1956,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "b390796d", + "metadata": { + "editable": true + }, "source": [ "Since we know from our problem that" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "19c9ece4", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -1743,7 +1981,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "6ade1a7a", + "metadata": { + "editable": true + }, "source": [ "along with the conditions $g(0) = g(1) = 0$,\n", "the following scheme can be used to find an approximate solution for $g(x)$ numerically:" @@ -1751,7 +1992,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "78b16d02", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -1768,7 +2012,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "f2bfcca4", + "metadata": { + "editable": true + }, "source": [ "for $i = 1, \\dots, N_x - 2$ where $g_0 = g_{N_x - 1} = 0$ and $f(x_i) = (3x_i + x_i^2)\\exp(x_i)$, which is given for our specific problem.\n", "\n", @@ -1777,7 +2024,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "a6191528", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{aligned}\n", @@ -1811,17 +2061,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "aefef707", + "metadata": { + "editable": true + }, "source": [ "which makes it possible to solve for the vector $\\boldsymbol{g}$.\n", "\n", - "\n", "We can then compare the result from this numerical scheme with the output from our network using Autograd:" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, + "id": "471664cd", "metadata": { "collapsed": false, "editable": true @@ -2022,7 +2275,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "6f02e86d", + "metadata": { + "editable": true + }, "source": [ "## Partial Differential Equations\n", "\n", @@ -2036,7 +2292,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ad5c63e8", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -2050,10 +2309,21 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "c7b98797", + "metadata": { + "editable": true + }, + "source": [ + "where $f$ is an expression involving all kinds of possible mixed derivatives of $g(x_1,\\dots,x_N)$ up to an order $n$. In order for the solution to be unique, some additional conditions must also be given." + ] + }, + { + "cell_type": "markdown", + "id": "52f9394e", + "metadata": { + "editable": true + }, "source": [ - "where $f$ is an expression involving all kinds of possible mixed derivatives of $g(x_1,\\dots,x_N)$ up to an order $n$. In order for the solution to be unique, some additional conditions must also be given.\n", - "\n", "### Type of problem\n", "\n", "The problem our network must solve for, is similar to the ODE case.\n", @@ -2064,7 +2334,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "4d489854", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{align*}\n", @@ -2075,15 +2348,24 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "82311538", + "metadata": { + "editable": true + }, "source": [ "where $h_1(x_1,\\dots,x_N)$ is a function that ensures $g_t(x_1,\\dots,x_N)$ satisfies some given conditions.\n", "The neural network $N(x_1,\\dots,x_N,P)$ has weights and biases described by $P$ and $h_2(x_1,\\dots,x_N,N(x_1,\\dots,x_N,P))$ is an expression using the output from the neural network in some way.\n", "\n", - "The role of the function $h_2(x_1,\\dots,x_N,N(x_1,\\dots,x_N,P))$, is to ensure that the output of $N(x_1,\\dots,x_N,P)$ is zero when $g_t(x_1,\\dots,x_N)$ is evaluated at the values of $x_1,\\dots,x_N$ where the given conditions must be satisfied. The function $h_1(x_1,\\dots,x_N)$ should alone make $g_t(x_1,\\dots,x_N)$ satisfy the conditions.\n", - "\n", - "\n", - "\n", + "The role of the function $h_2(x_1,\\dots,x_N,N(x_1,\\dots,x_N,P))$, is to ensure that the output of $N(x_1,\\dots,x_N,P)$ is zero when $g_t(x_1,\\dots,x_N)$ is evaluated at the values of $x_1,\\dots,x_N$ where the given conditions must be satisfied. The function $h_1(x_1,\\dots,x_N)$ should alone make $g_t(x_1,\\dots,x_N)$ satisfy the conditions." + ] + }, + { + "cell_type": "markdown", + "id": "9d134db4", + "metadata": { + "editable": true + }, + "source": [ "### Network requirements\n", "\n", "The network tries then the minimize the cost function following the\n", @@ -2099,7 +2381,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "5a463498", + "metadata": { + "editable": true + }, "source": [ "$$\n", "C\\left(x_1, \\dots, x_N, P\\right) = \\left( f\\left(x_1, \\, \\dots \\, , x_N, \\frac{\\partial g(x_1,\\dots,x_N) }{\\partial x_1}, \\dots , \\frac{\\partial g(x_1,\\dots,x_N) }{\\partial x_N}, \\frac{\\partial g(x_1,\\dots,x_N) }{\\partial x_1\\partial x_2}, \\, \\dots \\, , \\frac{\\partial^n g(x_1,\\dots,x_N) }{\\partial x_N^n} \\right) \\right)^2\n", @@ -2108,14 +2393,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "c40d8997", + "metadata": { + "editable": true + }, "source": [ "If we let $\\boldsymbol{x} = \\big( x_1, \\dots, x_N \\big)$ be an array containing the values for $x_1, \\dots, x_N$ respectively, the cost function can be reformulated into the following:" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "cc033de6", + "metadata": { + "editable": true + }, "source": [ "$$\n", "C\\left(\\boldsymbol{x}, P\\right) = f\\left( \\left( \\boldsymbol{x}, \\frac{\\partial g(\\boldsymbol{x}) }{\\partial x_1}, \\dots , \\frac{\\partial g(\\boldsymbol{x}) }{\\partial x_N}, \\frac{\\partial g(\\boldsymbol{x}) }{\\partial x_1\\partial x_2}, \\, \\dots \\, , \\frac{\\partial^n g(\\boldsymbol{x}) }{\\partial x_N^n} \\right) \\right)^2\n", @@ -2124,14 +2415,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ae18b4f4", + "metadata": { + "editable": true + }, "source": [ "If we also have $M$ different sets of values for $x_1, \\dots, x_N$, that is $\\boldsymbol{x}_i = \\big(x_1^{(i)}, \\dots, x_N^{(i)}\\big)$ for $i = 1,\\dots,M$ being the rows in matrix $X$, the cost function can be generalized into" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "44ca21bd", + "metadata": { + "editable": true + }, "source": [ "$$\n", "C\\left(X, P \\right) = \\sum_{i=1}^M f\\left( \\left( \\boldsymbol{x}_i, \\frac{\\partial g(\\boldsymbol{x}_i) }{\\partial x_1}, \\dots , \\frac{\\partial g(\\boldsymbol{x}_i) }{\\partial x_N}, \\frac{\\partial g(\\boldsymbol{x}_i) }{\\partial x_1\\partial x_2}, \\, \\dots \\, , \\frac{\\partial^n g(\\boldsymbol{x}_i) }{\\partial x_N^n} \\right) \\right)^2.\n", @@ -2140,7 +2437,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "fe94451e", + "metadata": { + "editable": true + }, "source": [ "## Example: The diffusion equation\n", "\n", @@ -2149,7 +2449,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "30a42273", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\frac{\\partial g(x,t)}{\\partial t} = \\frac{\\partial^2 g(x,t)}{\\partial x^2}\n", @@ -2158,14 +2461,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "fd4077e7", + "metadata": { + "editable": true + }, "source": [ "where a possible choice of conditions are" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "dbbf2e4b", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{align*}\n", @@ -2178,18 +2487,22 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "83653db9", + "metadata": { + "editable": true + }, "source": [ "with $u(x)$ being some given function.\n", "\n", - "\n", - "\n", "For this case, we want to find $g(x,t)$ such that" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "d2c2ef4f", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -2203,14 +2516,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "8fb27bdb", + "metadata": { + "editable": true + }, "source": [ "and" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "dc9297ab", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{align*}\n", @@ -2223,7 +2542,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "ab90784f", + "metadata": { + "editable": true + }, "source": [ "with $u(x) = \\sin(\\pi x)$.\n", "\n", @@ -2231,9 +2553,6 @@ "The deep neural network will follow the same structure as discussed in the examples solving the ODEs.\n", "First, we will look into how Autograd could be used in a network tailored to solve for bivariate functions.\n", "\n", - "\n", - "\n", - "\n", "The only change to do here, is to extend our network such that\n", "functions of multiple parameters are correctly handled. In this case\n", "we have two variables in our function to solve for, that is time $t$\n", @@ -2245,7 +2564,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, + "id": "b07e858c", "metadata": { "collapsed": false, "editable": true @@ -2300,7 +2620,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "9a167ec1", + "metadata": { + "editable": true + }, "source": [ "The cost function must then iterate through the given arrays\n", "containing values for $x$ and $t$, defines a point $(x,t)$ the deep\n", @@ -2322,8 +2645,6 @@ "$$\n", "since $(0) = u(1) = 0$ and $u(x) = \\sin(\\pi x)$.\n", "\n", - "\n", - "\n", "The Jacobian is used because the program must find the derivative of\n", "the trial solution with respect to $x$ and $t$.\n", "\n", @@ -2345,7 +2666,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, + "id": "921e5969", "metadata": { "collapsed": false, "editable": true @@ -2392,7 +2714,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "0bc00b69", + "metadata": { + "editable": true + }, "source": [ "### Setting up the network using Autograd; The full program\n", "\n", @@ -2408,14 +2733,14 @@ "Be aware, though, that it is fairly slow for the parameters used.\n", "A better result is possible, but requires more iterations, and thus longer time to complete.\n", "\n", - "\n", "Indeed, the program below is not optimal in its implementation, but rather serves as an example on how to implement and use a neural network to solve a PDE.\n", "Using TensorFlow results in a much better execution time. Try it!" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, + "id": "20734418", "metadata": { "collapsed": false, "editable": true @@ -2649,7 +2974,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "143f1c80", + "metadata": { + "editable": true + }, "source": [ "## Solving the wave equation with Neural Networks\n", "\n", @@ -2658,7 +2986,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "190bd4f2", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\frac{\\partial^2 g(x,t)}{\\partial t^2} = c^2\\frac{\\partial^2 g(x,t)}{\\partial x^2}\n", @@ -2667,7 +2998,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "9719cfc5", + "metadata": { + "editable": true + }, "source": [ "with $c$ being the specified wave speed.\n", "\n", @@ -2676,7 +3010,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "f69ec7fd", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\begin{align*}\n", @@ -2690,17 +3027,22 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "6ce8c5a8", + "metadata": { + "editable": true + }, "source": [ "where $\\frac{\\partial g(x,t)}{\\partial t} \\Big |_{t = 0}$ means the derivative of $g(x,t)$ with respect to $t$ is evaluated at $t = 0$, and $u(x)$ and $v(x)$ being given functions.\n", "\n", - "\n", "The wave equation to solve for, is" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "4be700d7", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -2714,7 +3056,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "993f93ba", + "metadata": { + "editable": true + }, "source": [ "where $c$ is the given wave speed.\n", "The chosen conditions for this equation are" @@ -2722,7 +3067,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "2cb2a80f", + "metadata": { + "editable": true + }, "source": [ "\n", "
\n", @@ -2739,12 +3087,13 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "3f1dcfd4", + "metadata": { + "editable": true + }, "source": [ "In this example, let $c = 1$ and $u(x) = \\sin(\\pi x)$ and $v(x) = -\\pi\\sin(\\pi x)$.\n", "\n", - "\n", - "\n", "Setting up the network is done in similar matter as for the example of solving the diffusion equation.\n", "The only things we have to change, is the trial solution such that it satisfies the conditions from ([20](#condwave)) and the cost function.\n", "\n", @@ -2762,7 +3111,6 @@ "\n", "Note that this trial solution satisfies the conditions only if $u(0) = v(0) = u(1) = v(1) = 0$, which is the case in this example.\n", "\n", - "\n", "The analytical solution for our specific problem, is\n", "\n", "$$\n", @@ -2772,7 +3120,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, + "id": "230a9aef", "metadata": { "collapsed": false, "editable": true @@ -3003,7 +3352,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "a23bd19a", + "metadata": { + "editable": true + }, "source": [ "## Resources on differential equations and deep learning\n", "\n", @@ -3019,5 +3371,5 @@ ], "metadata": {}, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 } diff --git a/doc/BookChapters/clustering.do.txt b/doc/BookChapters/clustering.do.txt new file mode 100644 index 000000000..79205c639 --- /dev/null +++ b/doc/BookChapters/clustering.do.txt @@ -0,0 +1,416 @@ +======= Clustering and Unsupervised Learning ======= + +In general terms cluster analysis, or clustering, is the task of grouping a +data-set into different distinct categories based on some measure of equality of +the data. This measure is often referred to as a _metric_ or _similarity +measure_ in the literature (note: sometimes we deal with a _dissimilarity +measure_ instead). Usually, these metrics are formulated as some kind of +distance function between points in a high-dimensional space. + +The simplest, and also the most +common is the _Euclidean distance_. + + +The simplest of all clustering algorithms is the _k-means algorithm_ +, sometimes also referred to as *Lloyds algorithm*. It is the simplest and also +the most common. From its simplicity it obtains both strengths and weaknesses. +These will be discussed in more detail later. The $k$-means algorithm is a +_centroid based_ clustering algorithm. + + +Assume, we are given $n$ data points and we wish to split the data into $K < n$ +different categories, or clusters. We label each cluster by an integer + +!bt +\[ k\in\{1, \cdots, K \}. +\] +!et + +In the basic k-means algorithm each point is assigned to only +one cluster $k$, and these assignments are *non-injective* i.e. many-to-one. We +can think of these mappings as an encoder $k = C(i)$, which assigns the $i$-th +data-point $\bf x_i$ to the $k$-th cluster. + +$k$-means algorithm in words: +o We start with guesses / random initializations of our $k$ cluster centers/centroids +o For each centroid the points that are most similar are identified +o Then we move / replace each centroid with a coordinate average of all the points that were assigned to that centroid. +o Iterate 2-3 until the centroids no longer move (to some tolerance) + + + +We assume we have $n$ data-points +!bt +\begin{equation}\label{eq:kmeanspoints} + \bm{x_i} = \{x_{i, 1}, \cdots, x_{i, p}\}\in\mathbb{R}^p. +\end{equation} +!et +which we wish to group into $K < n$ clusters. For our dissimilarity measure we +use the *squared Euclidean distance* +!bt +\begin{equation}\label{eq:squaredeuclidean} + d(\bm{x_i}, \bm{x_i'}) = \sum_{j=1}^p(x_{ij} - x_{i'j})^2 + = ||\bm{x_i} - \bm{x_{i'}}||^2 +\end{equation} +!et + + + +We define the so called *within-cluster point scatter* which gives us a +measure of how close each data point assigned to the same cluster tends to be to +the all the others. +!bt +\begin{equation}\label{eq:withincluster} + W(C) = \frac{1}{2}\sum_{k=1}^K\sum_{C(i)=k} + \sum_{C(i')=k}d(\bm{x_i}, \bm{x_{i'}}) = + \sum_{k=1}^KN_k\sum_{C(i)=k}||\bm{x_i} - \bm{\overline{x_k}}||^2 +\end{equation} +!et +where $\bm{\overline{x_k}}$ is the mean vector associated with the $k$-th +cluster, and $N_k = \sum_{i=1}^nI(C(i) = k)$, where the $I()$ notation is +similar to the Kronecker delta (*Commonly used in statistics, it just means that +when $i = k$ we have the encoder $C(i)$*). In other words, the within-cluster +scatter measures the compactness of each cluster with respect to the data points +assigned to each cluster. This is the quantity that the $k$-means algorithm aims +to minimize. We refer to this quantity $W(C)$ as the within cluster scatter +because of its relation to the *total scatter*. + + + +We have +!bt +\begin{equation}\label{eq:totalscatter} + T = W(C) + B(C) = \frac{1}{2}\sum_{i=1}^n + \sum_{i'=1}^nd(\bm{x_i}, \bm{x_{i'}}) + = \frac{1}{2}\sum_{k=1}^K\sum_{C(i)=k} + \Big(\sum_{C(i') = k}d(\bm{x_i}, \bm{x_{i'}}) + + \sum_{C(i')\neq k}d(\bm{x_i}, \bm{x_{i'}})\Big). +\end{equation} +!et + +This is a quantity that is conserved throughout the $k$-means algorithm. It can +be thought of as the total amount of information in the data, and it is composed +of the aforementioned within-cluster scatter and the *between-cluster scatter* +$B(C)$. In methods such as principle component analysis the total scatter is not +conserved. + + +Given a cluster mean $\bm{m_k}$ we define the _total cluster variance_ +!bt +\begin{equation}\label{eq:totalclustervariance} + \min_{C, \{\bm{m_k}\}_1^K}\sum_{k=1}^KN_k\sum||\bm{x_i} - \bm{m_k}||^2 +\end{equation} +!et +Now we have all the pieces necessary to formally revisit the $k$-means algorithm. + + + + +The $k$-means clustering algorithm goes as follows + +o For a given cluster assignment $C$, and $k$ cluster means $\left\{m_1, \cdots, m_k\right\}$. We minimize the total cluster variance with respect to the cluster means $\{m_k\}$ yielding the means of the currently assigned clusters. +o Given a current set of $k$ means $\{m_k\}$ the total cluster variance is minimized by assigning each observation to the closest (current) cluster mean. That is $$C(i) = \underset{1\leq k\leq K}{\mathrm{argmin}} ||\bm{x_i} - \bm{m_k}||^2$$ +o Steps 1 and 2 are repeated until the assignments do not change. + + + +===== Codes and Approaches ===== + + +o Before we start we specify a number $k$ which is the number of clusters we want to try to separate our data into. +o We initially choose $k$ random data points in our data as our initial centroids, *or means* (this is where the name comes from). +o Assign each data point to their closest centroid, based on the squared Euclidean distance. +o For each of the $k$ cluster we update the centroid by calculating new mean values for all the data points in the cluster. +o Iteratively minimize the within cluster scatter by performing steps (3, 4) until the new assignments stop changing (can be to some tolerance) or until a maximum number of iterations have passed. + + + +Let us now program the most basic version of the algorithm using nothing but +Python with numpy arrays. This code is kept intentionally simple to gradually +progress our understanding. There is no vectorization of any kind, and even most +helper functions are not utilized. + +We need first a dataset to do our cluster analysis on. In our case +this is a plain *vanilla* data set using random numbers using a +Gaussian distribution. + +!bc pycod +import time +import numpy as np +import tensorflow as tf +from matplotlib import image +import matplotlib.pyplot as plt +from sklearn.cluster import KMeans +from IPython.display import display + +np.random.seed(2021) + +!ec + +Next we define functions, for ease of use later, to generate Gaussians and to +set up our toy data set. +!bc pycod +def gaussian_points(dim=2, n_points=1000, mean_vector=np.array([0, 0]), + sample_variance=1): + """ + Very simple custom function to generate gaussian distributed point clusters + with variable dimension, number of points, means in each direction + (must match dim) and sample variance. + + Inputs: + dim (int) + n_points (int) + mean_vector (np.array) (where index 0 is x, index 1 is y etc.) + sample_variance (float) + + Returns: + data (np.array): with dimensions (dim x n_points) + """ + + mean_matrix = np.zeros(dim) + mean_vector + covariance_matrix = np.eye(dim) * sample_variance + data = np.random.multivariate_normal(mean_matrix, covariance_matrix, + n_points) + return data + + + +def generate_simple_clustering_dataset(dim=2, n_points=1000, plotting=True, + return_data=True): + """ + Toy model to illustrate k-means clustering + """ + + data1 = gaussian_points(mean_vector=np.array([5, 5])) + data2 = gaussian_points() + data3 = gaussian_points(mean_vector=np.array([1, 4.5])) + data4 = gaussian_points(mean_vector=np.array([5, 1])) + data = np.concatenate((data1, data2, data3, data4), axis=0) + + if plotting: + fig, ax = plt.subplots() + ax.scatter(data[:, 0], data[:, 1], alpha=0.2) + ax.set_title('Toy Model Dataset') + plt.show() + + + if return_data: + return data + + +data = generate_simple_clustering_dataset() +!ec + + + +With the above dataset we start +implementing the $k$-means algorithm. + +!bc pycod + +n_samples, dimensions = data.shape +n_clusters = 4 + +# we randomly initialize our centroids +np.random.seed(2021) +centroids = data[np.random.choice(n_samples, n_clusters, replace=False), :] +distances = np.zeros((n_samples, n_clusters)) + +# first we need to calculate the distance to each centroid from our data +for k in range(n_clusters): + for n in range(n_samples): + dist = 0 + for d in range(dimensions): + dist += np.abs(data[n, d] - centroids[k, d])**2 + distances[n, k] = dist + +# we initialize an array to keep track of to which cluster each point belongs +# the way we set it up here the index tracks which point and the value which +# cluster the point belongs to +cluster_labels = np.zeros(n_samples, dtype='int') + +# next we loop through our samples and for every point assign it to the cluster +# to which it has the smallest distance to +for n in range(n_samples): + # tracking variables (all of this is basically just an argmin) + smallest = 1e10 + smallest_row_index = 1e10 + for k in range(n_clusters): + if distances[n, k] < smallest: + smallest = distances[n, k] + smallest_row_index = k + + cluster_labels[n] = smallest_row_index + +!ec + + +!bc pycod +fig = plt.figure() +ax = fig.add_subplot() +unique_cluster_labels = np.unique(cluster_labels) +for i in unique_cluster_labels: + ax.scatter(data[cluster_labels == i, 0], + data[cluster_labels == i, 1], + label = i, + alpha = 0.2) + ax.scatter(centroids[:, 0], centroids[:, 1], c='black') + +ax.set_title("First Grouping of Points to Centroids") + +plt.show() +!ec + +So what do we have so far? We have 'picked' $k$ centroids at random from our +data points. There are other ways of more intelligently choosing their +initializations, however for our purposes randomly is fine. Then we have +initialized an array 'distances' which holds the information of the distance, +*or dissimilarity*, of every point to of our centroids. Finally, we have +initialized an array 'cluster_labels' which according to our distances array +holds the information of to which centroid every point is assigned. This was the +first pass of our algorithm. Essentially, all we need to do now is repeat the +distance and assignment steps above until we have reached a desired convergence +or a maximum amount of iterations. + + +!bc pycod + +max_iterations = 100 +tolerance = 1e-8 + +for iteration in range(max_iterations): + prev_centroids = centroids.copy() + for k in range(n_clusters): + # this array will be used to update our centroid positions + vector_mean = np.zeros(dimensions) + mean_divisor = 0 + for n in range(n_samples): + if cluster_labels[n] == k: + vector_mean += data[n, :] + mean_divisor += 1 + + # update according to the k means + centroids[k, :] = vector_mean / mean_divisor + + # we find the dissimilarity + for k in range(n_clusters): + for n in range(n_samples): + dist = 0 + for d in range(dimensions): + dist += np.abs(data[n, d] - centroids[k, d])**2 + distances[n, k] = dist + + # assign each point + for n in range(n_samples): + smallest = 1e10 + smallest_row_index = 1e10 + for k in range(n_clusters): + if distances[n, k] < smallest: + smallest = distances[n, k] + smallest_row_index = k + + cluster_labels[n] = smallest_row_index + + # convergence criteria + centroid_difference = np.sum(np.abs(centroids - prev_centroids)) + if centroid_difference < tolerance: + print(f'Converged at iteration {iteration}') + break + + elif iteration == max_iterations: + print(f'Did not converge in {max_iterations} iterations') +!ec + + +We now have a simple , un-optimized $k$-means +clustering implementation. Lets plot the final result + +!bc pycod +fig = plt.figure() +ax = fig.add_subplot() +unique_cluster_labels = np.unique(cluster_labels) +for i in unique_cluster_labels: + ax.scatter(data[cluster_labels == i, 0], + data[cluster_labels == i, 1], + label = i, + alpha = 0.2) + ax.scatter(centroids[:, 0], centroids[:, 1], c='black') + +ax.set_title("Final Result of K-means Clustering") + +plt.show() +!ec + +!bc pycod +def naive_kmeans(data, n_clusters=4, max_iterations=100, tolerance=1e-8): + start_time = time.time() + + n_samples, dimensions = data.shape + n_clusters = 4 + #np.random.seed(2021) + centroids = data[np.random.choice(n_samples, n_clusters, replace=False), :] + distances = np.zeros((n_samples, n_clusters)) + + for k in range(n_clusters): + for n in range(n_samples): + dist = 0 + for d in range(dimensions): + dist += np.abs(data[n, d] - centroids[k, d])**2 + distances[n, k] = dist + + cluster_labels = np.zeros(n_samples, dtype='int') + + for n in range(n_samples): + smallest = 1e10 + smallest_row_index = 1e10 + for k in range(n_clusters): + if distances[n, k] < smallest: + smallest = distances[n, k] + smallest_row_index = k + + cluster_labels[n] = smallest_row_index + + for iteration in range(max_iterations): + prev_centroids = centroids.copy() + for k in range(n_clusters): + vector_mean = np.zeros(dimensions) + mean_divisor = 0 + for n in range(n_samples): + if cluster_labels[n] == k: + vector_mean += data[n, :] + mean_divisor += 1 + + centroids[k, :] = vector_mean / mean_divisor + + for k in range(n_clusters): + for n in range(n_samples): + dist = 0 + for d in range(dimensions): + dist += np.abs(data[n, d] - centroids[k, d])**2 + distances[n, k] = dist + + for n in range(n_samples): + smallest = 1e10 + smallest_row_index = 1e10 + for k in range(n_clusters): + if distances[n, k] < smallest: + smallest = distances[n, k] + smallest_row_index = k + + cluster_labels[n] = smallest_row_index + + centroid_difference = np.sum(np.abs(centroids - prev_centroids)) + if centroid_difference < tolerance: + print(f'Converged at iteration {iteration}') + print(f'Runtime: {time.time() - start_time} seconds') + + return cluster_labels, centroids + + print(f'Did not converge in {max_iterations} iterations') + print(f'Runtime: {time.time() - start_time} seconds') + + return cluster_labels, centroids + +!ec + + diff --git a/doc/LectureNotes/chapter10.ipynb b/doc/LectureNotes/chapter10.ipynb index cc65cfe33..fb9797ca3 100644 --- a/doc/LectureNotes/chapter10.ipynb +++ b/doc/LectureNotes/chapter10.ipynb @@ -2,7 +2,21 @@ "cells": [ { "cell_type": "markdown", - "metadata": {}, + "id": "7e3af66b", + "metadata": { + "editable": true + }, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "64b4469b", + "metadata": { + "editable": true + }, "source": [ "# Building a Feed Forward Neural Network\n", "\n", @@ -27,7 +41,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "a27e39fb", + "metadata": { + "editable": true + }, "source": [ "$$\n", "P(y = 0 \\mid \\hat{x}, \\hat{\\theta}) = \\frac{1}{1 + \\exp{(- \\hat{x}})} ,\n", @@ -36,14 +53,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "6332f4a5", + "metadata": { + "editable": true + }, "source": [ "and" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "5d2a5fe5", + "metadata": { + "editable": true + }, "source": [ "$$\n", "P(y = 1 \\mid \\hat{x}, \\hat{\\theta}) = 1 - P(y = 0 \\mid \\hat{x}, \\hat{\\theta}) ,\n", @@ -52,13 +75,22 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "62bf3ae6", + "metadata": { + "editable": true + }, "source": [ "where $y \\in \\{0, 1\\}$ and $\\hat{\\theta}$ represents the weights and biases\n", - "of our network.\n", - "\n", - "\n", - "\n", + "of our network." + ] + }, + { + "cell_type": "markdown", + "id": "5fdb1150", + "metadata": { + "editable": true + }, + "source": [ "## Defining the cost function\n", "\n", "Our cost function is given as (see the Logistic regression lectures)" @@ -66,7 +98,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "64cfe441", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\mathcal{C}(\\hat{\\theta}) = - \\ln P(\\mathcal{D} \\mid \\hat{\\theta}) = - \\sum_{i=1}^n\n", @@ -76,7 +111,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "dbd78c70", + "metadata": { + "editable": true + }, "source": [ "This last equality means that we can interpret our *cost* function as a sum over the *loss* function\n", "for each point in the dataset $\\mathcal{L}_i(\\hat{\\theta})$. \n", @@ -87,10 +125,8 @@ "\n", "$y = 5 \\quad \\rightarrow \\quad \\hat{y} = (0, 0, 0, 0, 0, 1, 0, 0, 0, 0) ,$ and\n", "\n", - "\n", "$y = 1 \\quad \\rightarrow \\quad \\hat{y} = (0, 1, 0, 0, 0, 0, 0, 0, 0, 0) ,$ \n", "\n", - "\n", "i.e. a binary bit string of length $C$, where $C = 10$ is the number of classes in the MNIST dataset (numbers from $0$ to $9$).. \n", "\n", "If $\\hat{x}_i$ is the $i$-th input (image), $y_{ic}$ refers to the $c$-th component of the $i$-th\n", @@ -100,7 +136,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "67834a87", + "metadata": { + "editable": true + }, "source": [ "$$\n", "P(y_{ic} = 1 \\mid \\hat{x}_i, \\hat{\\theta}) = \\frac{\\exp{((\\hat{a}_i^{hidden})^T \\hat{w}_c)}}\n", @@ -110,7 +149,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "0521db23", + "metadata": { + "editable": true + }, "source": [ "which reduces to the logistic function in the binary case. \n", "The likelihood of this $C$-class classifier\n", @@ -119,7 +161,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "12a92031", + "metadata": { + "editable": true + }, "source": [ "$$\n", "P(\\mathcal{D} \\mid \\hat{\\theta}) = \\prod_{i=1}^n \\prod_{c=0}^{C-1} [P(y_{ic} = 1)]^{y_{ic}} .\n", @@ -128,14 +173,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "e03ffaa9", + "metadata": { + "editable": true + }, "source": [ "Again we take the negative log-likelihood to define our cost function:" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "cfab4fa3", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\mathcal{C}(\\hat{\\theta}) = - \\log{P(\\mathcal{D} \\mid \\hat{\\theta})}.\n", @@ -144,13 +195,23 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "864840d8", + "metadata": { + "editable": true + }, "source": [ "See the logistic regression lectures for a full definition of the cost function.\n", "\n", - "The back propagation equations need now only a small change, namely the definition of a new cost function. We are thus ready to use the same equations as before!\n", - "\n", - "\n", + "The back propagation equations need now only a small change, namely the definition of a new cost function. We are thus ready to use the same equations as before!" + ] + }, + { + "cell_type": "markdown", + "id": "496bdba3", + "metadata": { + "editable": true + }, + "source": [ "### Example: binary classification problem\n", "\n", "As an example of the above, relevant for project 2 as well, let us consider a binary class. As discussed in our logistic regression lectures, we defined a cost function in terms of the parameters $\\beta$ as" @@ -158,7 +219,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "8646c18f", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\mathcal{C}(\\hat{\\beta}) = - \\sum_{i=1}^n \\left(y_i\\log{p(y_i \\vert x_i,\\hat{\\beta})}+(1-y_i)\\log{1-p(y_i \\vert x_i,\\hat{\\beta})}\\right),\n", @@ -167,14 +231,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "2dbfda71", + "metadata": { + "editable": true + }, "source": [ "where we had defined the logistic (sigmoid) function" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "5cd0a375", + "metadata": { + "editable": true + }, "source": [ "$$\n", "p(y_i =1\\vert x_i,\\hat{\\beta})=\\frac{\\exp{(\\beta_0+\\beta_1 x_i)}}{1+\\exp{(\\beta_0+\\beta_1 x_i)}},\n", @@ -183,14 +253,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "020de545", + "metadata": { + "editable": true + }, "source": [ "and" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "2efde2f5", + "metadata": { + "editable": true + }, "source": [ "$$\n", "p(y_i =0\\vert x_i,\\hat{\\beta})=1-p(y_i =1\\vert x_i,\\hat{\\beta}).\n", @@ -199,7 +275,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "bf9a596d", + "metadata": { + "editable": true + }, "source": [ "The parameters $\\hat{\\beta}$ were defined using a minimization method like gradient descent or Newton-Raphson's method. \n", "\n", @@ -209,7 +288,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "0b392cc5", + "metadata": { + "editable": true + }, "source": [ "$$\n", "a_i^l = y_i = \\frac{\\exp{(z_i^l)}}{1+\\exp{(z_i^l)}},\n", @@ -218,14 +300,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "5169434f", + "metadata": { + "editable": true + }, "source": [ "with" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "dcc8ed0a", + "metadata": { + "editable": true + }, "source": [ "$$\n", "z_i^l = \\sum_{j}w_{ij}^l a_j^{l-1}+b_i^l,\n", @@ -234,7 +322,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "4010628a", + "metadata": { + "editable": true + }, "source": [ "where the superscript $l-1$ indicates that these are the outputs from layer $l-1$.\n", "Our cost function at the final layer $l=L$ is now" @@ -242,7 +333,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "7bf89431", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\mathcal{C}(\\hat{W}) = - \\sum_{i=1}^n \\left(t_i\\log{a_i^L}+(1-t_i)\\log{(1-a_i^L)}\\right),\n", @@ -251,14 +345,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "79bb56ee", + "metadata": { + "editable": true + }, "source": [ "where we have defined the targets $t_i$. The derivatives of the cost function with respect to the output $a_i^L$ are then easily calculated and we get" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "a27a06b5", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\frac{\\partial \\mathcal{C}(\\hat{W})}{\\partial a_i^L} = \\frac{a_i^L-t_i}{a_i^L(1-a_i^L)}.\n", @@ -267,12 +367,21 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "cd6acfaf", + "metadata": { + "editable": true + }, + "source": [ + "In case we use another activation function than the logistic one, we need to evaluate other derivatives." + ] + }, + { + "cell_type": "markdown", + "id": "b9e59acd", + "metadata": { + "editable": true + }, "source": [ - "In case we use another activation function than the logistic one, we need to evaluate other derivatives. \n", - "\n", - "\n", - "\n", "### The Softmax function\n", "\n", "In case we employ the more general case given by the Softmax equation, we need to evaluate the derivative of the activation function with respect to the activation $z_i^l$, that is we need" @@ -280,7 +389,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "eceaf7eb", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\frac{\\partial f(z_i^l)}{\\partial w_{jk}^l} =\n", @@ -290,14 +402,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "bbc77522", + "metadata": { + "editable": true + }, "source": [ "For the Softmax function we have" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "88d4c18b", + "metadata": { + "editable": true + }, "source": [ "$$\n", "f(z_i^l) = \\frac{\\exp{(z_i^l)}}{\\sum_{m=1}^K\\exp{(z_m^l)}}.\n", @@ -306,14 +424,20 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "b47f4092", + "metadata": { + "editable": true + }, "source": [ "Its derivative with respect to $z_j^l$ gives" ] }, { "cell_type": "markdown", - "metadata": {}, + "id": "65ec4336", + "metadata": { + "editable": true + }, "source": [ "$$\n", "\\frac{\\partial f(z_i^l)}{\\partial z_j^l}= f(z_i^l)\\left(\\delta_{ij}-f(z_j^l)\\right),\n", @@ -322,14 +446,23 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "38f54e19", + "metadata": { + "editable": true + }, + "source": [ + "which in case of the simply binary model reduces to having $i=j$." + ] + }, + { + "cell_type": "markdown", + "id": "c0d8bc5e", + "metadata": { + "editable": true + }, "source": [ - "which in case of the simply binary model reduces to having $i=j$. \n", - "\n", - "\n", "## Developing a code for doing neural networks with back propagation\n", "\n", - "\n", "One can identify a set of key steps when using neural networks to solve supervised learning problems: \n", "\n", "1. Collect and pre-process data \n", @@ -342,8 +475,16 @@ "\n", "5. Evaluate model performance on test data \n", "\n", - "6. Adjust hyperparameters (if necessary, network architecture)\n", - "\n", + "6. Adjust hyperparameters (if necessary, network architecture)" + ] + }, + { + "cell_type": "markdown", + "id": "e3810222", + "metadata": { + "editable": true + }, + "source": [ "### Collect and pre-process data\n", "\n", "Here we will be using the MNIST dataset, which is readily available through the **scikit-learn**\n", @@ -388,7 +529,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, + "id": "5e7205ac", "metadata": { "collapsed": false, "editable": true @@ -443,7 +585,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "78721970", + "metadata": { + "editable": true + }, "source": [ "### Train and test datasets\n", "\n", @@ -460,7 +605,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, + "id": "dcc066a0", "metadata": { "collapsed": false, "editable": true @@ -498,7 +644,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "95cf3f87", + "metadata": { + "editable": true + }, "source": [ "### Define model and architecture\n", "\n", @@ -534,8 +683,16 @@ "\n", "$$ f(x) = \\sigma(x) = \\frac{1}{1 + e^{-x}} ,$$\n", "\n", - "which is inspired by probability theory (see logistic regression) and was most commonly used until about 2011. See the discussion below concerning other activation functions.\n", - "\n", + "which is inspired by probability theory (see logistic regression) and was most commonly used until about 2011. See the discussion below concerning other activation functions." + ] + }, + { + "cell_type": "markdown", + "id": "25c0cbe6", + "metadata": { + "editable": true + }, + "source": [ "### Layers\n", "\n", "* Input \n", @@ -568,7 +725,6 @@ "Since each neuron in the output layer is connected to the 50 inputs from the hidden layer we have 50x10 = 500\n", "weights to the output layer.\n", "\n", - "\n", "Typically weights are initialized with small values distributed around zero, drawn from a uniform\n", "or normal distribution. Setting all weights to zero means all neurons give the same output, making the network useless. \n", "\n", @@ -582,7 +738,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, + "id": "9f415ea2", "metadata": { "collapsed": false, "editable": true @@ -608,7 +765,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "afbff46b", + "metadata": { + "editable": true + }, "source": [ "### Feed-forward pass\n", "\n", @@ -630,7 +790,6 @@ "$$ a_{j}^{L} = \\frac{\\exp{(z_j^{L})}}\n", "{\\sum_{c=0}^{C-1} \\exp{(z_c^{L})}} .$$ \n", "\n", - "\n", "Since our data has the dimensions $X = (n_{inputs}, n_{features})$ and our weights to the hidden\n", "layer have the dimensions \n", "$W_{hidden} = (n_{features}, n_{hidden})$,\n", @@ -660,7 +819,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, + "id": "e05b2147", "metadata": { "collapsed": false, "editable": true @@ -706,7 +866,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "6c8100ba", + "metadata": { + "editable": true + }, "source": [ "### Choose cost function and optimizer\n", "\n", @@ -719,10 +882,8 @@ "\n", "$$ y = 5 \\quad \\rightarrow \\quad \\hat{y} = (0, 0, 0, 0, 0, 1, 0, 0, 0, 0) ,$$ \n", "\n", - "\n", "$$ y = 1 \\quad \\rightarrow \\quad \\hat{y} = (0, 1, 0, 0, 0, 0, 0, 0, 0, 0) ,$$ \n", "\n", - "\n", "i.e. a binary bit string of length $C$, where $C = 10$ is the number of classes in the MNIST dataset. \n", "\n", "Let $y_{ic}$ denote the $c$-th component of the $i$-th one-hot vector. \n", @@ -731,10 +892,16 @@ "In the one-hot representation only one of the terms in the loss function is non-zero, namely the\n", "probability of the correct category $c'$ \n", "(i.e. the category $c'$ such that $y_{ic'} = 1$). This means that the cross entropy loss only punishes you for how wrong\n", - "you got the correct label. The probability of category $c$ is given by the softmax function. The vector $\\hat{\\theta}$ represents the parameters of our network, i.e. all the weights and biases. \n", - "\n", - "\n", - "\n", + "you got the correct label. The probability of category $c$ is given by the softmax function. The vector $\\hat{\\theta}$ represents the parameters of our network, i.e. all the weights and biases." + ] + }, + { + "cell_type": "markdown", + "id": "720d19fd", + "metadata": { + "editable": true + }, + "source": [ "### Optimizing the cost function\n", "\n", "The network is trained by finding the weights and biases that minimize the cost function. One of the most widely used classes of methods is *gradient descent* and its generalizations. The idea behind gradient descent\n", @@ -763,9 +930,16 @@ "\n", "2. It significantly speeds up the calculation, since we do not have to use the entire dataset to calculate the gradient. \n", "\n", - "The various optmization methods, with codes and algorithms, are discussed in our lectures on [Gradient descent approaches](https://compphysics.github.io/MachineLearning/doc/pub/Splines/html/Splines-bs.html).\n", - "\n", - "\n", + "The various optmization methods, with codes and algorithms, are discussed in our lectures on [Gradient descent approaches](https://compphysics.github.io/MachineLearning/doc/pub/Splines/html/Splines-bs.html)." + ] + }, + { + "cell_type": "markdown", + "id": "d0b1f84a", + "metadata": { + "editable": true + }, + "source": [ "### Regularization\n", "\n", "It is common to add an extra term to the cost function, proportional\n", @@ -783,7 +957,6 @@ "\n", "i.e. we sum up all the weights squared. The factor $\\lambda$ is known as a regularization parameter.\n", "\n", - "\n", "In order to train the model, we need to calculate the derivative of\n", "the cost function with respect to every bias and weight in the\n", "network. In total our network has $(64 + 1)\\times 50=3250$ weights in\n", @@ -791,9 +964,16 @@ "layer ($+1$ for the bias), and the gradient must be calculated for\n", "every parameter. We use the *backpropagation* algorithm discussed\n", "above. This is a clever use of the chain rule that allows us to\n", - "calculate the gradient efficently. \n", - "\n", - "\n", + "calculate the gradient efficently." + ] + }, + { + "cell_type": "markdown", + "id": "96417f62", + "metadata": { + "editable": true + }, + "source": [ "### Matrix multiplication\n", "\n", "To more efficently train our network these equations are implemented using matrix operations. \n", @@ -829,7 +1009,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, + "id": "c692ab5f", "metadata": { "collapsed": false, "editable": true @@ -908,7 +1089,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "9d0ad486", + "metadata": { + "editable": true + }, "source": [ "## Improving performance\n", "\n", @@ -923,14 +1107,14 @@ "If this does not improve network performance, you may want to consider altering the network architecture, adding more neurons or hidden layers. \n", "Andrew Ng goes through some of these considerations in this [video](https://youtu.be/F1ka6a13S9I). You can find a summary of the video [here](https://kevinzakka.github.io/2016/09/26/applying-deep-learning/). \n", "\n", - "\n", "It is very natural to think of the network as an object, with specific instances of the network\n", "being realizations of this object with different hyperparameters. An implementation using Python classes provides a clean structure and interface, and the full implementation of our neural network is given below." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, + "id": "84088dcb", "metadata": { "collapsed": false, "editable": true @@ -1040,7 +1224,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "61dae78a", + "metadata": { + "editable": true + }, "source": [ "## Evaluate model performance on test data\n", "\n", @@ -1055,7 +1242,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, + "id": "999a01b6", "metadata": { "collapsed": false, "editable": true @@ -1082,7 +1270,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "12b8be76", + "metadata": { + "editable": true + }, "source": [ "## Adjust hyperparameters\n", "\n", @@ -1092,7 +1283,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, + "id": "f022ba3c", "metadata": { "collapsed": false, "editable": true @@ -1123,14 +1315,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "118ee392", + "metadata": { + "editable": true + }, "source": [ "## Visualization" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, + "id": "01cd7236", "metadata": { "collapsed": false, "editable": true @@ -1174,7 +1370,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "1cef52ca", + "metadata": { + "editable": true + }, "source": [ "## scikit-learn implementation\n", "\n", @@ -1193,7 +1392,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, + "id": "08650740", "metadata": { "collapsed": false, "editable": true @@ -1220,14 +1420,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "9a5819b0", + "metadata": { + "editable": true + }, "source": [ "## Visualization" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, + "id": "c76390e1", "metadata": { "collapsed": false, "editable": true @@ -1272,7 +1476,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "6b973f8c", + "metadata": { + "editable": true + }, "source": [ "## Building neural networks in Tensorflow and Keras\n", "\n", @@ -1284,7 +1491,6 @@ "clear how to build one using an arbitrary number of hidden layers, using data structures such as Python lists or\n", "NumPy arrays.\n", "\n", - "\n", "Tensorflow is an open source library machine learning library\n", "developed by the Google Brain team for internal use. It was released\n", "under the Apache 2.0 open source license in November 9, 2015.\n", @@ -1307,32 +1513,15 @@ "Then we will build (effectively) the same graph in Keras, to see just\n", "how simple solving a machine learning problem can be.\n", "\n", - "To install tensorflow on Unix/Linux systems, use pip as" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false, - "editable": true - }, - "outputs": [], - "source": [ - "pip3 install tensorflow" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ + "To install tensorflow on Unix/Linux systems, use pip as **pip3 install tensorflow**\n", "and/or if you use **anaconda**, just write (or install from the graphical user interface)\n", "(current release of CPU-only TensorFlow)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, + "id": "cb71108d", "metadata": { "collapsed": false, "editable": true @@ -1345,14 +1534,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "8cf89ee7", + "metadata": { + "editable": true + }, "source": [ "To install the current release of GPU TensorFlow" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, + "id": "ace3d31e", "metadata": { "collapsed": false, "editable": true @@ -1365,7 +1558,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "f5d7454d", + "metadata": { + "editable": true + }, "source": [ "Keras is a high level [neural network](https://en.wikipedia.org/wiki/Application_programming_interface)\n", "that supports Tensorflow, CTNK and Theano as backends. \n", @@ -1374,7 +1570,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, + "id": "024068e3", "metadata": { "collapsed": false, "editable": true @@ -1386,19 +1583,22 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "e36b74d0", + "metadata": { + "editable": true + }, "source": [ "You can look up the [instructions here](https://keras.io/) for more information.\n", "\n", "We will to a large extent use **keras** in this course. \n", "\n", - "\n", "Let us look again at the MINST data set." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, + "id": "42dbd743", "metadata": { "collapsed": false, "editable": true @@ -1452,7 +1652,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, + "id": "eba6a34d", "metadata": { "collapsed": false, "editable": true @@ -1480,7 +1681,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, + "id": "da80e8b6", "metadata": { "collapsed": false, "editable": true @@ -1509,7 +1711,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, + "id": "87273539", "metadata": { "collapsed": false, "editable": true @@ -1535,7 +1738,8 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, + "id": "8cd653a0", "metadata": { "collapsed": false, "editable": true @@ -1577,14 +1781,18 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "31009a5d", + "metadata": { + "editable": true + }, "source": [ "## The Breast Cancer Data, now with Keras" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, + "id": "991bf288", "metadata": { "collapsed": false, "editable": true @@ -1761,7 +1969,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "d197746e", + "metadata": { + "editable": true + }, "source": [ "## Fine-tuning neural network hyperparameters\n", "\n", @@ -1779,7 +1990,6 @@ "training a neural network on a large dataset takes a lot of time, you\n", "will only be able to explore a tiny part of the hyperparameter space.\n", "\n", - "\n", "* You can use randomized search.\n", "\n", "* Or use tools like [Oscar](http://oscar.calldesk.ai/), which implements more complex algorithms to help you find a good set of hyperparameters quickly. \n", @@ -1795,12 +2005,16 @@ "as large image classification or speech recognition, typically require networks with dozens of layers\n", "and they need a huge amount\n", "of training data. However, you will rarely have to train such networks from scratch: it is much more\n", - "common to reuse parts of a pretrained state-of-the-art network that performs a similar task.\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "common to reuse parts of a pretrained state-of-the-art network that performs a similar task." + ] + }, + { + "cell_type": "markdown", + "id": "3614fe93", + "metadata": { + "editable": true + }, + "source": [ "## Which activation function should I use?\n", "\n", "The Back propagation algorithm we derived above works by going from\n", @@ -1809,7 +2023,6 @@ "function with regards to each parameter in the network, it uses these\n", "gradients to update each parameter with a Gradient Descent (GD) step.\n", "\n", - "\n", "Unfortunately for us, the gradients often get smaller and smaller as the\n", "algorithm progresses down to the first hidden layers. As a result, the\n", "GD update leaves the lower layer connection weights\n", @@ -1825,9 +2038,6 @@ "neural networks suffer from unstable gradients, different layers may\n", "learn at widely different speeds\n", "\n", - "\n", - "\n", - "\n", "Although this unfortunate behavior has been empirically observed for\n", "quite a while (it was one of the reasons why deep neural networks were\n", "mostly abandoned for a long time), it is only around 2010 that\n", @@ -1850,8 +2060,6 @@ "(the hyperbolic tangent function has a mean of 0 and behaves slightly\n", "better than the logistic function in deep networks).\n", "\n", - "\n", - "\n", "Looking at the logistic activation function, when inputs become large\n", "(negative or positive), the function saturates at 0 or 1, with a\n", "derivative extremely close to 0. Thus when backpropagation kicks in,\n", @@ -1870,8 +2078,6 @@ "its inputs, and we also need the gradients to have equal variance\n", "before and after flowing through a layer in the reverse direction.\n", "\n", - "\n", - "\n", "One of the insights in the 2010 paper by Glorot and Bengio was that\n", "the vanishing/exploding gradients problems were in part due to a poor\n", "choice of activation function. Until then most people had assumed that\n", @@ -1880,9 +2086,16 @@ "that other activation functions behave much better in deep neural\n", "networks, in particular the ReLU activation function, mostly because\n", "it does not saturate for positive values (and also because it is quite\n", - "fast to compute).\n", - "\n", - "\n", + "fast to compute)." + ] + }, + { + "cell_type": "markdown", + "id": "0816093d", + "metadata": { + "editable": true + }, + "source": [ "## The RELU function family\n", "\n", "The ReLU activation function suffers from a problem known as the dying\n", @@ -1903,7 +2116,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "d979c7db", + "metadata": { + "editable": true + }, "source": [ "$$\n", "ELU(z) = \\left\\{\\begin{array}{cc} \\alpha\\left( \\exp{(z)}-1\\right) & z < 0,\\\\ z & z \\ge 0.\\end{array}\\right.\n", @@ -1912,7 +2128,10 @@ }, { "cell_type": "markdown", - "metadata": {}, + "id": "eadc7692", + "metadata": { + "editable": true + }, "source": [ "In general it seems that the ELU activation function is better than\n", "the leaky ReLU function (and its variants), which is better than\n", @@ -1927,8 +2146,6 @@ "spare time and computing power, you can use cross-validation or\n", "bootstrap to evaluate other activation functions.\n", "\n", - "\n", - "\n", "In most cases you can use the ReLU activation function in the hidden layers (or one of its variants).\n", "\n", "It is a bit faster to compute than other activation functions, and the gradient descent optimization does in general not get stuck.\n", @@ -1937,8 +2154,16 @@ "\n", "* For classification the softmax activation function is generally a good choice for classification tasks (when the classes are mutually exclusive).\n", "\n", - "* For regression tasks, you can simply use no activation function at all.\n", - "\n", + "* For regression tasks, you can simply use no activation function at all." + ] + }, + { + "cell_type": "markdown", + "id": "a78e9091", + "metadata": { + "editable": true + }, + "source": [ "## Batch Normalization\n", "\n", "Batch Normalization\n", @@ -1951,8 +2176,16 @@ "learn the optimal scale and mean of the inputs for each layer.\n", "In order to zero-center and normalize the inputs, the algorithm needs to estimate the inputs’ mean and\n", "standard deviation. It does so by evaluating the mean and standard deviation of the inputs over the current\n", - "mini-batch, from this the name batch normalization.\n", - "\n", + "mini-batch, from this the name batch normalization." + ] + }, + { + "cell_type": "markdown", + "id": "04e309c6", + "metadata": { + "editable": true + }, + "source": [ "## Dropout\n", "\n", "It is a fairly simple algorithm: at every training step, every neuron (including the input neurons but\n", @@ -1961,8 +2194,16 @@ "\n", "The\n", "hyperparameter $p$ is called the dropout rate, and it is typically set to 50%. After training, the neurons are not dropped anymore.\n", - " It is viewed as one of the most popular regularization techniques.\n", - "\n", + " It is viewed as one of the most popular regularization techniques." + ] + }, + { + "cell_type": "markdown", + "id": "f9eb2036", + "metadata": { + "editable": true + }, + "source": [ "## Gradient Clipping\n", "\n", "A popular technique to lessen the exploding gradients problem is to simply clip the gradients during\n", @@ -1972,12 +2213,18 @@ "This technique is called Gradient Clipping.\n", "\n", "In general however, Batch\n", - "Normalization is preferred.\n", - "\n", - "\n", + "Normalization is preferred." + ] + }, + { + "cell_type": "markdown", + "id": "95301882", + "metadata": { + "editable": true + }, + "source": [ "## A top-down perspective on Neural networks\n", "\n", - "\n", "The first thing we would like to do is divide the data into two or three\n", "parts. A training set, a validation or dev (development) set, and a\n", "test set. The test set is the data on which we want to make\n", @@ -1988,7 +2235,6 @@ "do not use any of the test data to train the algorithm. This is a\n", "cardinal sin in ML. Then:\n", "\n", - "\n", "* Estimate optimal error rate\n", "\n", "* Minimize underfitting (bias) on training data set.\n", @@ -2012,9 +2258,16 @@ "the test data. The difference between the performance of the algorithm\n", "on these two validation sets quantifies the train-test mismatch. This\n", "can serve as another important diagnostic when using DNNs for\n", - "supervised learning.\n", - "\n", - "\n", + "supervised learning." + ] + }, + { + "cell_type": "markdown", + "id": "e1da8c6e", + "metadata": { + "editable": true + }, + "source": [ "## Limitations of supervised learning with deep networks\n", "\n", "Like all statistical methods, supervised learning using neural\n", @@ -2027,8 +2280,6 @@ "\n", "Here we list some of the important limitations of supervised neural network based models. \n", "\n", - "\n", - "\n", "* **Need labeled data**. All supervised learning methods, DNNs for supervised learning require labeled data. Often, labeled data is harder to acquire than unlabeled data (e.g. one must pay for human experts to label images).\n", "\n", "* **Supervised neural networks are extremely data intensive.** DNNs are data hungry. They perform best when data is plentiful. This is doubly so for supervised methods where the data must also be labeled. The utility of DNNs is extremely limited if data is hard to acquire or the datasets are small (hundreds to a few thousand samples). In this case, the performance of other methods that utilize hand-engineered features can exceed that of DNNs.\n", @@ -2043,5 +2294,5 @@ ], "metadata": {}, "nbformat": 4, - "nbformat_minor": 4 + "nbformat_minor": 5 } diff --git a/doc/LectureNotes/clustering.ipynb b/doc/LectureNotes/clustering.ipynb new file mode 100644 index 000000000..84bc644b4 --- /dev/null +++ b/doc/LectureNotes/clustering.ipynb @@ -0,0 +1,649 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c3edcae5", + "metadata": { + "editable": true + }, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "4102577e", + "metadata": { + "editable": true + }, + "source": [ + "# Clustering and Unsupervised Learning\n", + "\n", + "In general terms cluster analysis, or clustering, is the task of grouping a\n", + "data-set into different distinct categories based on some measure of equality of\n", + "the data. This measure is often referred to as a **metric** or **similarity\n", + "measure** in the literature (note: sometimes we deal with a **dissimilarity\n", + "measure** instead). Usually, these metrics are formulated as some kind of\n", + "distance function between points in a high-dimensional space.\n", + "\n", + "The simplest, and also the most\n", + "common is the **Euclidean distance**.\n", + "\n", + "The simplest of all clustering algorithms is the **k-means algorithm**\n", + ", sometimes also referred to as *Lloyds algorithm*. It is the simplest and also\n", + "the most common. From its simplicity it obtains both strengths and weaknesses.\n", + "These will be discussed in more detail later. The $k$-means algorithm is a\n", + "**centroid based** clustering algorithm.\n", + "\n", + "Assume, we are given $n$ data points and we wish to split the data into $K < n$\n", + "different categories, or clusters. We label each cluster by an integer" + ] + }, + { + "cell_type": "markdown", + "id": "0deb3255", + "metadata": { + "editable": true + }, + "source": [ + "$$\n", + "k\\in\\{1, \\cdots, K \\}.\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "cfd8fe00", + "metadata": { + "editable": true + }, + "source": [ + "In the basic k-means algorithm each point is assigned to only\n", + "one cluster $k$, and these assignments are *non-injective* i.e. many-to-one. We\n", + "can think of these mappings as an encoder $k = C(i)$, which assigns the $i$-th\n", + "data-point $\\bf x_i$ to the $k$-th cluster.\n", + "\n", + "$k$-means algorithm in words:\n", + "1. We start with guesses / random initializations of our $k$ cluster centers/centroids\n", + "\n", + "2. For each centroid the points that are most similar are identified\n", + "\n", + "3. Then we move / replace each centroid with a coordinate average of all the points that were assigned to that centroid.\n", + "\n", + "4. Iterate 2-3 until the centroids no longer move (to some tolerance)\n", + "\n", + "We assume we have $n$ data-points" + ] + }, + { + "cell_type": "markdown", + "id": "a29b7459", + "metadata": { + "editable": true + }, + "source": [ + "\n", + "
\n", + "\n", + "$$\n", + "\\begin{equation}\\label{eq:kmeanspoints} \\tag{1}\n", + " \\boldsymbol{x_i} = \\{x_{i, 1}, \\cdots, x_{i, p}\\}\\in\\mathbb{R}^p.\n", + "\\end{equation}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "98f9e37b", + "metadata": { + "editable": true + }, + "source": [ + "which we wish to group into $K < n$ clusters. For our dissimilarity measure we\n", + "use the *squared Euclidean distance*" + ] + }, + { + "cell_type": "markdown", + "id": "d3c32572", + "metadata": { + "editable": true + }, + "source": [ + "\n", + "
\n", + "\n", + "$$\n", + "\\begin{equation}\\label{eq:squaredeuclidean} \\tag{2}\n", + " d(\\boldsymbol{x_i}, \\boldsymbol{x_i'}) = \\sum_{j=1}^p(x_{ij} - x_{i'j})^2\n", + " = ||\\boldsymbol{x_i} - \\boldsymbol{x_{i'}}||^2\n", + "\\end{equation}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "29d24648", + "metadata": { + "editable": true + }, + "source": [ + "We define the so called *within-cluster point scatter* which gives us a\n", + "measure of how close each data point assigned to the same cluster tends to be to\n", + "the all the others." + ] + }, + { + "cell_type": "markdown", + "id": "fce5c797", + "metadata": { + "editable": true + }, + "source": [ + "\n", + "
\n", + "\n", + "$$\n", + "\\begin{equation}\\label{eq:withincluster} \\tag{3}\n", + " W(C) = \\frac{1}{2}\\sum_{k=1}^K\\sum_{C(i)=k}\n", + " \\sum_{C(i')=k}d(\\boldsymbol{x_i}, \\boldsymbol{x_{i'}}) =\n", + " \\sum_{k=1}^KN_k\\sum_{C(i)=k}||\\boldsymbol{x_i} - \\boldsymbol{\\overline{x_k}}||^2\n", + "\\end{equation}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "674a26b7", + "metadata": { + "editable": true + }, + "source": [ + "where $\\boldsymbol{\\overline{x_k}}$ is the mean vector associated with the $k$-th\n", + "cluster, and $N_k = \\sum_{i=1}^nI(C(i) = k)$, where the $I()$ notation is\n", + "similar to the Kronecker delta (*Commonly used in statistics, it just means that\n", + "when $i = k$ we have the encoder $C(i)$*). In other words, the within-cluster\n", + "scatter measures the compactness of each cluster with respect to the data points\n", + "assigned to each cluster. This is the quantity that the $k$-means algorithm aims\n", + "to minimize. We refer to this quantity $W(C)$ as the within cluster scatter\n", + "because of its relation to the *total scatter*.\n", + "\n", + "We have" + ] + }, + { + "cell_type": "markdown", + "id": "f200e7ff", + "metadata": { + "editable": true + }, + "source": [ + "\n", + "
\n", + "\n", + "$$\n", + "\\begin{equation}\\label{eq:totalscatter} \\tag{4}\n", + " T = W(C) + B(C) = \\frac{1}{2}\\sum_{i=1}^n\n", + " \\sum_{i'=1}^nd(\\boldsymbol{x_i}, \\boldsymbol{x_{i'}})\n", + " = \\frac{1}{2}\\sum_{k=1}^K\\sum_{C(i)=k}\n", + " \\Big(\\sum_{C(i') = k}d(\\boldsymbol{x_i}, \\boldsymbol{x_{i'}})\n", + " + \\sum_{C(i')\\neq k}d(\\boldsymbol{x_i}, \\boldsymbol{x_{i'}})\\Big).\n", + "\\end{equation}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "5471a94d", + "metadata": { + "editable": true + }, + "source": [ + "This is a quantity that is conserved throughout the $k$-means algorithm. It can\n", + "be thought of as the total amount of information in the data, and it is composed\n", + "of the aforementioned within-cluster scatter and the *between-cluster scatter*\n", + "$B(C)$. In methods such as principle component analysis the total scatter is not\n", + "conserved.\n", + "\n", + "Given a cluster mean $\\boldsymbol{m_k}$ we define the **total cluster variance**" + ] + }, + { + "cell_type": "markdown", + "id": "299a99ce", + "metadata": { + "editable": true + }, + "source": [ + "\n", + "
\n", + "\n", + "$$\n", + "\\begin{equation}\\label{eq:totalclustervariance} \\tag{5}\n", + " \\min_{C, \\{\\boldsymbol{m_k}\\}_1^K}\\sum_{k=1}^KN_k\\sum||\\boldsymbol{x_i} - \\boldsymbol{m_k}||^2\n", + "\\end{equation}\n", + "$$" + ] + }, + { + "cell_type": "markdown", + "id": "bdfa54ee", + "metadata": { + "editable": true + }, + "source": [ + "Now we have all the pieces necessary to formally revisit the $k$-means algorithm.\n", + "\n", + "The $k$-means clustering algorithm goes as follows \n", + "\n", + "1. For a given cluster assignment $C$, and $k$ cluster means $\\left\\{m_1, \\cdots, m_k\\right\\}$. We minimize the total cluster variance with respect to the cluster means $\\{m_k\\}$ yielding the means of the currently assigned clusters.\n", + "\n", + "2. Given a current set of $k$ means $\\{m_k\\}$ the total cluster variance is minimized by assigning each observation to the closest (current) cluster mean. That is $$C(i) = \\underset{1\\leq k\\leq K}{\\mathrm{argmin}} ||\\boldsymbol{x_i} - \\boldsymbol{m_k}||^2$$\n", + "\n", + "3. Steps 1 and 2 are repeated until the assignments do not change." + ] + }, + { + "cell_type": "markdown", + "id": "f5def86c", + "metadata": { + "editable": true + }, + "source": [ + "## Codes and Approaches\n", + "\n", + "1. Before we start we specify a number $k$ which is the number of clusters we want to try to separate our data into.\n", + "\n", + "2. We initially choose $k$ random data points in our data as our initial centroids, *or means* (this is where the name comes from).\n", + "\n", + "3. Assign each data point to their closest centroid, based on the squared Euclidean distance.\n", + "\n", + "4. For each of the $k$ cluster we update the centroid by calculating new mean values for all the data points in the cluster.\n", + "\n", + "5. Iteratively minimize the within cluster scatter by performing steps (3, 4) until the new assignments stop changing (can be to some tolerance) or until a maximum number of iterations have passed.\n", + "\n", + "Let us now program the most basic version of the algorithm using nothing but\n", + "Python with numpy arrays. This code is kept intentionally simple to gradually\n", + "progress our understanding. There is no vectorization of any kind, and even most\n", + "helper functions are not utilized.\n", + "\n", + "We need first a dataset to do our cluster analysis on. In our case\n", + "this is a plain *vanilla* data set using random numbers using a\n", + "Gaussian distribution." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b0260188", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "\n", + "import time\n", + "import numpy as np\n", + "import tensorflow as tf\n", + "from matplotlib import image\n", + "import matplotlib.pyplot as plt\n", + "from sklearn.cluster import KMeans\n", + "from IPython.display import display\n", + "\n", + "np.random.seed(2021)" + ] + }, + { + "cell_type": "markdown", + "id": "fe680e35", + "metadata": { + "editable": true + }, + "source": [ + "Next we define functions, for ease of use later, to generate Gaussians and to\n", + "set up our toy data set." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9db2bbce", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "def gaussian_points(dim=2, n_points=1000, mean_vector=np.array([0, 0]),\n", + " sample_variance=1):\n", + " \"\"\"\n", + " Very simple custom function to generate gaussian distributed point clusters\n", + " with variable dimension, number of points, means in each direction\n", + " (must match dim) and sample variance.\n", + "\n", + " Inputs:\n", + " dim (int)\n", + " n_points (int)\n", + " mean_vector (np.array) (where index 0 is x, index 1 is y etc.)\n", + " sample_variance (float)\n", + "\n", + " Returns:\n", + " data (np.array): with dimensions (dim x n_points)\n", + " \"\"\"\n", + "\n", + " mean_matrix = np.zeros(dim) + mean_vector\n", + " covariance_matrix = np.eye(dim) * sample_variance\n", + " data = np.random.multivariate_normal(mean_matrix, covariance_matrix,\n", + " n_points)\n", + " return data\n", + "\n", + "\n", + "\n", + "def generate_simple_clustering_dataset(dim=2, n_points=1000, plotting=True,\n", + " return_data=True):\n", + " \"\"\"\n", + " Toy model to illustrate k-means clustering\n", + " \"\"\"\n", + "\n", + " data1 = gaussian_points(mean_vector=np.array([5, 5]))\n", + " data2 = gaussian_points()\n", + " data3 = gaussian_points(mean_vector=np.array([1, 4.5]))\n", + " data4 = gaussian_points(mean_vector=np.array([5, 1]))\n", + " data = np.concatenate((data1, data2, data3, data4), axis=0)\n", + "\n", + " if plotting:\n", + " fig, ax = plt.subplots()\n", + " ax.scatter(data[:, 0], data[:, 1], alpha=0.2)\n", + " ax.set_title('Toy Model Dataset')\n", + " plt.show()\n", + "\n", + "\n", + " if return_data:\n", + " return data\n", + "\n", + "\n", + "data = generate_simple_clustering_dataset()" + ] + }, + { + "cell_type": "markdown", + "id": "c0bb8c76", + "metadata": { + "editable": true + }, + "source": [ + "With the above dataset we start\n", + "implementing the $k$-means algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "29a75065", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "\n", + "n_samples, dimensions = data.shape\n", + "n_clusters = 4\n", + "\n", + "# we randomly initialize our centroids\n", + "np.random.seed(2021)\n", + "centroids = data[np.random.choice(n_samples, n_clusters, replace=False), :]\n", + "distances = np.zeros((n_samples, n_clusters))\n", + "\n", + "# first we need to calculate the distance to each centroid from our data\n", + "for k in range(n_clusters):\n", + " for n in range(n_samples):\n", + " dist = 0\n", + " for d in range(dimensions):\n", + " dist += np.abs(data[n, d] - centroids[k, d])**2\n", + " distances[n, k] = dist\n", + "\n", + "# we initialize an array to keep track of to which cluster each point belongs\n", + "# the way we set it up here the index tracks which point and the value which\n", + "# cluster the point belongs to\n", + "cluster_labels = np.zeros(n_samples, dtype='int')\n", + "\n", + "# next we loop through our samples and for every point assign it to the cluster\n", + "# to which it has the smallest distance to\n", + "for n in range(n_samples):\n", + " # tracking variables (all of this is basically just an argmin)\n", + " smallest = 1e10\n", + " smallest_row_index = 1e10\n", + " for k in range(n_clusters):\n", + " if distances[n, k] < smallest:\n", + " smallest = distances[n, k]\n", + " smallest_row_index = k\n", + "\n", + " cluster_labels[n] = smallest_row_index" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9fae7fc9", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "fig = plt.figure()\n", + "ax = fig.add_subplot()\n", + "unique_cluster_labels = np.unique(cluster_labels)\n", + "for i in unique_cluster_labels:\n", + " ax.scatter(data[cluster_labels == i, 0],\n", + " data[cluster_labels == i, 1],\n", + " label = i,\n", + " alpha = 0.2)\n", + " ax.scatter(centroids[:, 0], centroids[:, 1], c='black')\n", + "\n", + "ax.set_title(\"First Grouping of Points to Centroids\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "90d2a873", + "metadata": { + "editable": true + }, + "source": [ + "So what do we have so far? We have 'picked' $k$ centroids at random from our\n", + "data points. There are other ways of more intelligently choosing their\n", + "initializations, however for our purposes randomly is fine. Then we have\n", + "initialized an array 'distances' which holds the information of the distance,\n", + "*or dissimilarity*, of every point to of our centroids. Finally, we have\n", + "initialized an array 'cluster_labels' which according to our distances array\n", + "holds the information of to which centroid every point is assigned. This was the\n", + "first pass of our algorithm. Essentially, all we need to do now is repeat the\n", + "distance and assignment steps above until we have reached a desired convergence\n", + "or a maximum amount of iterations." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "378c29fc", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "\n", + "max_iterations = 100\n", + "tolerance = 1e-8\n", + "\n", + "for iteration in range(max_iterations):\n", + " prev_centroids = centroids.copy()\n", + " for k in range(n_clusters):\n", + " # this array will be used to update our centroid positions\n", + " vector_mean = np.zeros(dimensions)\n", + " mean_divisor = 0\n", + " for n in range(n_samples):\n", + " if cluster_labels[n] == k:\n", + " vector_mean += data[n, :]\n", + " mean_divisor += 1\n", + "\n", + " # update according to the k means\n", + " centroids[k, :] = vector_mean / mean_divisor\n", + "\n", + " # we find the dissimilarity\n", + " for k in range(n_clusters):\n", + " for n in range(n_samples):\n", + " dist = 0\n", + " for d in range(dimensions):\n", + " dist += np.abs(data[n, d] - centroids[k, d])**2\n", + " distances[n, k] = dist\n", + "\n", + " # assign each point\n", + " for n in range(n_samples):\n", + " smallest = 1e10\n", + " smallest_row_index = 1e10\n", + " for k in range(n_clusters):\n", + " if distances[n, k] < smallest:\n", + " smallest = distances[n, k]\n", + " smallest_row_index = k\n", + "\n", + " cluster_labels[n] = smallest_row_index\n", + "\n", + " # convergence criteria\n", + " centroid_difference = np.sum(np.abs(centroids - prev_centroids))\n", + " if centroid_difference < tolerance:\n", + " print(f'Converged at iteration {iteration}')\n", + " break\n", + "\n", + " elif iteration == max_iterations:\n", + " print(f'Did not converge in {max_iterations} iterations')" + ] + }, + { + "cell_type": "markdown", + "id": "545a6742", + "metadata": { + "editable": true + }, + "source": [ + "We now have a simple , un-optimized $k$-means\n", + "clustering implementation. Lets plot the final result" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d9d3973b", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "fig = plt.figure()\n", + "ax = fig.add_subplot()\n", + "unique_cluster_labels = np.unique(cluster_labels)\n", + "for i in unique_cluster_labels:\n", + " ax.scatter(data[cluster_labels == i, 0],\n", + " data[cluster_labels == i, 1],\n", + " label = i,\n", + " alpha = 0.2)\n", + " ax.scatter(centroids[:, 0], centroids[:, 1], c='black')\n", + "\n", + "ax.set_title(\"Final Result of K-means Clustering\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ee6a145f", + "metadata": { + "collapsed": false, + "editable": true + }, + "outputs": [], + "source": [ + "def naive_kmeans(data, n_clusters=4, max_iterations=100, tolerance=1e-8):\n", + " start_time = time.time()\n", + "\n", + " n_samples, dimensions = data.shape\n", + " n_clusters = 4\n", + " #np.random.seed(2021)\n", + " centroids = data[np.random.choice(n_samples, n_clusters, replace=False), :]\n", + " distances = np.zeros((n_samples, n_clusters))\n", + "\n", + " for k in range(n_clusters):\n", + " for n in range(n_samples):\n", + " dist = 0\n", + " for d in range(dimensions):\n", + " dist += np.abs(data[n, d] - centroids[k, d])**2\n", + " distances[n, k] = dist\n", + "\n", + " cluster_labels = np.zeros(n_samples, dtype='int')\n", + "\n", + " for n in range(n_samples):\n", + " smallest = 1e10\n", + " smallest_row_index = 1e10\n", + " for k in range(n_clusters):\n", + " if distances[n, k] < smallest:\n", + " smallest = distances[n, k]\n", + " smallest_row_index = k\n", + "\n", + " cluster_labels[n] = smallest_row_index\n", + "\n", + " for iteration in range(max_iterations):\n", + " prev_centroids = centroids.copy()\n", + " for k in range(n_clusters):\n", + " vector_mean = np.zeros(dimensions)\n", + " mean_divisor = 0\n", + " for n in range(n_samples):\n", + " if cluster_labels[n] == k:\n", + " vector_mean += data[n, :]\n", + " mean_divisor += 1\n", + "\n", + " centroids[k, :] = vector_mean / mean_divisor\n", + "\n", + " for k in range(n_clusters):\n", + " for n in range(n_samples):\n", + " dist = 0\n", + " for d in range(dimensions):\n", + " dist += np.abs(data[n, d] - centroids[k, d])**2\n", + " distances[n, k] = dist\n", + "\n", + " for n in range(n_samples):\n", + " smallest = 1e10\n", + " smallest_row_index = 1e10\n", + " for k in range(n_clusters):\n", + " if distances[n, k] < smallest:\n", + " smallest = distances[n, k]\n", + " smallest_row_index = k\n", + "\n", + " cluster_labels[n] = smallest_row_index\n", + "\n", + " centroid_difference = np.sum(np.abs(centroids - prev_centroids))\n", + " if centroid_difference < tolerance:\n", + " print(f'Converged at iteration {iteration}')\n", + " print(f'Runtime: {time.time() - start_time} seconds')\n", + "\n", + " return cluster_labels, centroids\n", + "\n", + " print(f'Did not converge in {max_iterations} iterations')\n", + " print(f'Runtime: {time.time() - start_time} seconds')\n", + "\n", + " return cluster_labels, centroids" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/doc/pub/week47/html/._week47-bs000.html b/doc/pub/week47/html/._week47-bs000.html index fce5ce0a5..26fd1bd4f 100644 --- a/doc/pub/week47/html/._week47-bs000.html +++ b/doc/pub/week47/html/._week47-bs000.html @@ -369,7 +369,7 @@ MathJax.Hub.Config({
-

Nov 26, 2021

+

Dec 8, 2021


diff --git a/doc/pub/week47/html/week47-bs.html b/doc/pub/week47/html/week47-bs.html index fce5ce0a5..26fd1bd4f 100644 --- a/doc/pub/week47/html/week47-bs.html +++ b/doc/pub/week47/html/week47-bs.html @@ -369,7 +369,7 @@ MathJax.Hub.Config({
-

Nov 26, 2021

+

Dec 8, 2021


diff --git a/doc/pub/week47/html/week47-reveal.html b/doc/pub/week47/html/week47-reveal.html index ac7dfec07..cea74efe4 100644 --- a/doc/pub/week47/html/week47-reveal.html +++ b/doc/pub/week47/html/week47-reveal.html @@ -184,7 +184,7 @@ MathJax.Hub.Config({
-

Nov 26, 2021

+

Dec 8, 2021


diff --git a/doc/pub/week47/html/week47-solarized.html b/doc/pub/week47/html/week47-solarized.html index 144662353..755047bbb 100644 --- a/doc/pub/week47/html/week47-solarized.html +++ b/doc/pub/week47/html/week47-solarized.html @@ -293,7 +293,7 @@ MathJax.Hub.Config({
-

Nov 26, 2021

+

Dec 8, 2021


diff --git a/doc/pub/week47/html/week47.html b/doc/pub/week47/html/week47.html index 099f4a4a5..e63c91701 100644 --- a/doc/pub/week47/html/week47.html +++ b/doc/pub/week47/html/week47.html @@ -370,7 +370,7 @@ MathJax.Hub.Config({
-

Nov 26, 2021

+

Dec 8, 2021


diff --git a/doc/pub/week47/ipynb/ipynb-week47-src.tar.gz b/doc/pub/week47/ipynb/ipynb-week47-src.tar.gz index 87fc5f5a9..41bdfa652 100644 Binary files a/doc/pub/week47/ipynb/ipynb-week47-src.tar.gz and b/doc/pub/week47/ipynb/ipynb-week47-src.tar.gz differ diff --git a/doc/pub/week47/ipynb/week47.ipynb b/doc/pub/week47/ipynb/week47.ipynb index 4588f6b1e..b8bac110f 100644 --- a/doc/pub/week47/ipynb/week47.ipynb +++ b/doc/pub/week47/ipynb/week47.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "df84b934", + "id": "943101c2", "metadata": { "editable": true }, @@ -14,7 +14,7 @@ }, { "cell_type": "markdown", - "id": "578607ec", + "id": "ec785b7b", "metadata": { "editable": true }, @@ -22,14 +22,14 @@ "# Week 47: Support Vector Machines and Summary of Course\n", "**Morten Hjorth-Jensen**, Department of Physics, University of Oslo and Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University\n", "\n", - "Date: **Nov 26, 2021**\n", + "Date: **Dec 8, 2021**\n", "\n", "Copyright 1999-2021, Morten Hjorth-Jensen. Released under CC Attribution-NonCommercial 4.0 license" ] }, { "cell_type": "markdown", - "id": "21b4b4bd", + "id": "cf19a5a9", "metadata": { "editable": true }, @@ -56,7 +56,7 @@ }, { "cell_type": "markdown", - "id": "f5583444", + "id": "36b7ea14", "metadata": { "editable": true }, @@ -90,7 +90,7 @@ }, { "cell_type": "markdown", - "id": "d2dd9fbe", + "id": "4609623d", "metadata": { "editable": true }, @@ -112,7 +112,7 @@ { "cell_type": "code", "execution_count": 1, - "id": "9a316c54", + "id": "c08c55c2", "metadata": { "collapsed": false, "editable": true @@ -191,7 +191,7 @@ }, { "cell_type": "markdown", - "id": "f4d162ab", + "id": "8beb4f10", "metadata": { "editable": true }, @@ -211,7 +211,7 @@ }, { "cell_type": "markdown", - "id": "b20123e7", + "id": "1d41d558", "metadata": { "editable": true }, @@ -223,7 +223,7 @@ }, { "cell_type": "markdown", - "id": "5385d102", + "id": "4caf0a0b", "metadata": { "editable": true }, @@ -236,7 +236,7 @@ }, { "cell_type": "markdown", - "id": "e7881507", + "id": "5a39f815", "metadata": { "editable": true }, @@ -248,7 +248,7 @@ }, { "cell_type": "markdown", - "id": "0a787a1e", + "id": "3c1174b2", "metadata": { "editable": true }, @@ -258,7 +258,7 @@ }, { "cell_type": "markdown", - "id": "e4a2a10c", + "id": "9e91a009", "metadata": { "editable": true }, @@ -271,7 +271,7 @@ }, { "cell_type": "markdown", - "id": "3273cfdb", + "id": "6d2ce326", "metadata": { "editable": true }, @@ -283,7 +283,7 @@ }, { "cell_type": "markdown", - "id": "6f8a8155", + "id": "402bec00", "metadata": { "editable": true }, @@ -295,7 +295,7 @@ }, { "cell_type": "markdown", - "id": "d393f477", + "id": "e38c6274", "metadata": { "editable": true }, @@ -307,7 +307,7 @@ }, { "cell_type": "markdown", - "id": "ff3cb997", + "id": "19a1d64e", "metadata": { "editable": true }, @@ -317,7 +317,7 @@ }, { "cell_type": "markdown", - "id": "142ebc7c", + "id": "e0319f8a", "metadata": { "editable": true }, @@ -329,7 +329,7 @@ }, { "cell_type": "markdown", - "id": "cd4762a5", + "id": "6fc49f68", "metadata": { "editable": true }, @@ -340,7 +340,7 @@ }, { "cell_type": "markdown", - "id": "d8cde2d3", + "id": "6a06e38e", "metadata": { "editable": true }, @@ -352,7 +352,7 @@ }, { "cell_type": "markdown", - "id": "e9731869", + "id": "7e6681ba", "metadata": { "editable": true }, @@ -365,7 +365,7 @@ }, { "cell_type": "markdown", - "id": "2d56766d", + "id": "b8e5f262", "metadata": { "editable": true }, @@ -377,7 +377,7 @@ }, { "cell_type": "markdown", - "id": "18d648d1", + "id": "bb1bd6a8", "metadata": { "editable": true }, @@ -387,7 +387,7 @@ }, { "cell_type": "markdown", - "id": "23b0ed28", + "id": "c9138fdb", "metadata": { "editable": true }, @@ -416,7 +416,7 @@ }, { "cell_type": "markdown", - "id": "b397779e", + "id": "18edafca", "metadata": { "editable": true }, @@ -428,7 +428,7 @@ }, { "cell_type": "markdown", - "id": "9f3cd0e8", + "id": "a323750c", "metadata": { "editable": true }, @@ -440,7 +440,7 @@ }, { "cell_type": "markdown", - "id": "370d8862", + "id": "1cad5f1a", "metadata": { "editable": true }, @@ -455,7 +455,7 @@ }, { "cell_type": "markdown", - "id": "cb933371", + "id": "e3b5b137", "metadata": { "editable": true }, @@ -467,7 +467,7 @@ }, { "cell_type": "markdown", - "id": "53447235", + "id": "f45aa3f5", "metadata": { "editable": true }, @@ -481,7 +481,7 @@ }, { "cell_type": "markdown", - "id": "296b389b", + "id": "513ef95d", "metadata": { "editable": true }, @@ -493,7 +493,7 @@ }, { "cell_type": "markdown", - "id": "51a33a2a", + "id": "393b8ebb", "metadata": { "editable": true }, @@ -503,7 +503,7 @@ }, { "cell_type": "markdown", - "id": "6577d3e5", + "id": "09a7082e", "metadata": { "editable": true }, @@ -515,7 +515,7 @@ }, { "cell_type": "markdown", - "id": "8aa02a87", + "id": "5caa8350", "metadata": { "editable": true }, @@ -525,7 +525,7 @@ }, { "cell_type": "markdown", - "id": "3e536e15", + "id": "f68a28f8", "metadata": { "editable": true }, @@ -537,7 +537,7 @@ }, { "cell_type": "markdown", - "id": "09611d15", + "id": "4ef7b60e", "metadata": { "editable": true }, @@ -549,7 +549,7 @@ }, { "cell_type": "markdown", - "id": "a603b6a2", + "id": "49032b0e", "metadata": { "editable": true }, @@ -561,7 +561,7 @@ }, { "cell_type": "markdown", - "id": "0a88243d", + "id": "f6f6c304", "metadata": { "editable": true }, @@ -571,7 +571,7 @@ }, { "cell_type": "markdown", - "id": "c86a1f8f", + "id": "17870f82", "metadata": { "editable": true }, @@ -583,7 +583,7 @@ }, { "cell_type": "markdown", - "id": "a81be92f", + "id": "b7a7e3fb", "metadata": { "editable": true }, @@ -593,7 +593,7 @@ }, { "cell_type": "markdown", - "id": "c0ac192a", + "id": "34bf6b7f", "metadata": { "editable": true }, @@ -616,7 +616,7 @@ }, { "cell_type": "markdown", - "id": "b7d4b67c", + "id": "e7e827c6", "metadata": { "editable": true }, @@ -632,7 +632,7 @@ }, { "cell_type": "markdown", - "id": "ca6c7ef1", + "id": "3dfccf3a", "metadata": { "editable": true }, @@ -644,7 +644,7 @@ }, { "cell_type": "markdown", - "id": "a7b65d5d", + "id": "563fb245", "metadata": { "editable": true }, @@ -656,7 +656,7 @@ }, { "cell_type": "markdown", - "id": "d6f1f76e", + "id": "8e097ac8", "metadata": { "editable": true }, @@ -668,7 +668,7 @@ }, { "cell_type": "markdown", - "id": "d68d2d3c", + "id": "8e361eec", "metadata": { "editable": true }, @@ -678,7 +678,7 @@ }, { "cell_type": "markdown", - "id": "e5e35ef7", + "id": "bbfb3d02", "metadata": { "editable": true }, @@ -690,7 +690,7 @@ }, { "cell_type": "markdown", - "id": "2fe8af72", + "id": "afa2fc37", "metadata": { "editable": true }, @@ -701,7 +701,7 @@ }, { "cell_type": "markdown", - "id": "4428e0d8", + "id": "d33cb851", "metadata": { "editable": true }, @@ -713,7 +713,7 @@ }, { "cell_type": "markdown", - "id": "73eaa8f2", + "id": "5d629f82", "metadata": { "editable": true }, @@ -726,7 +726,7 @@ }, { "cell_type": "markdown", - "id": "8f785489", + "id": "71276e92", "metadata": { "editable": true }, @@ -739,7 +739,7 @@ }, { "cell_type": "markdown", - "id": "ef2627a4", + "id": "18899c96", "metadata": { "editable": true }, @@ -751,7 +751,7 @@ }, { "cell_type": "markdown", - "id": "ff934695", + "id": "e4f883d6", "metadata": { "editable": true }, @@ -761,7 +761,7 @@ }, { "cell_type": "markdown", - "id": "6b051fc7", + "id": "99118120", "metadata": { "editable": true }, @@ -773,7 +773,7 @@ }, { "cell_type": "markdown", - "id": "4c3a8c7b", + "id": "d80a980c", "metadata": { "editable": true }, @@ -783,7 +783,7 @@ }, { "cell_type": "markdown", - "id": "708991d1", + "id": "098d8505", "metadata": { "editable": true }, @@ -795,7 +795,7 @@ }, { "cell_type": "markdown", - "id": "97e77e20", + "id": "2f5895b5", "metadata": { "editable": true }, @@ -812,7 +812,7 @@ }, { "cell_type": "markdown", - "id": "fc5b5921", + "id": "c894de27", "metadata": { "editable": true }, @@ -824,7 +824,7 @@ }, { "cell_type": "markdown", - "id": "caa64e0f", + "id": "3c85050e", "metadata": { "editable": true }, @@ -834,7 +834,7 @@ }, { "cell_type": "markdown", - "id": "18ff815c", + "id": "6200f6e3", "metadata": { "editable": true }, @@ -846,7 +846,7 @@ }, { "cell_type": "markdown", - "id": "91e0d118", + "id": "a1b33335", "metadata": { "editable": true }, @@ -856,7 +856,7 @@ }, { "cell_type": "markdown", - "id": "2f7a09d2", + "id": "8c7dd2eb", "metadata": { "editable": true }, @@ -868,7 +868,7 @@ }, { "cell_type": "markdown", - "id": "e4ed3d8c", + "id": "a8299c76", "metadata": { "editable": true }, @@ -881,7 +881,7 @@ }, { "cell_type": "markdown", - "id": "8b87bb16", + "id": "57902133", "metadata": { "editable": true }, @@ -893,7 +893,7 @@ }, { "cell_type": "markdown", - "id": "10c85872", + "id": "466781fe", "metadata": { "editable": true }, @@ -905,7 +905,7 @@ }, { "cell_type": "markdown", - "id": "9820d1e4", + "id": "ca1d6840", "metadata": { "editable": true }, @@ -915,7 +915,7 @@ }, { "cell_type": "markdown", - "id": "1eda1a63", + "id": "47e5ed5f", "metadata": { "editable": true }, @@ -929,7 +929,7 @@ }, { "cell_type": "markdown", - "id": "2434e2c9", + "id": "5bbc0036", "metadata": { "editable": true }, @@ -939,7 +939,7 @@ }, { "cell_type": "markdown", - "id": "a008a614", + "id": "f429d439", "metadata": { "editable": true }, @@ -951,7 +951,7 @@ }, { "cell_type": "markdown", - "id": "9e99f265", + "id": "a97cfc09", "metadata": { "editable": true }, @@ -961,7 +961,7 @@ }, { "cell_type": "markdown", - "id": "c1a4c18d", + "id": "44dfe9bd", "metadata": { "editable": true }, @@ -973,7 +973,7 @@ }, { "cell_type": "markdown", - "id": "454866b5", + "id": "47f4a430", "metadata": { "editable": true }, @@ -983,7 +983,7 @@ }, { "cell_type": "markdown", - "id": "f2b4f1c6", + "id": "46cfddbd", "metadata": { "editable": true }, @@ -995,7 +995,7 @@ }, { "cell_type": "markdown", - "id": "dad3112f", + "id": "26e63eef", "metadata": { "editable": true }, @@ -1009,7 +1009,7 @@ }, { "cell_type": "markdown", - "id": "a5a47668", + "id": "e6451db2", "metadata": { "editable": true }, @@ -1021,7 +1021,7 @@ }, { "cell_type": "markdown", - "id": "b7fbe66e", + "id": "62e26cf2", "metadata": { "editable": true }, @@ -1033,7 +1033,7 @@ }, { "cell_type": "markdown", - "id": "afa10b99", + "id": "7ac73f1d", "metadata": { "editable": true }, @@ -1045,7 +1045,7 @@ }, { "cell_type": "markdown", - "id": "e68045b4", + "id": "11541201", "metadata": { "editable": true }, @@ -1057,7 +1057,7 @@ }, { "cell_type": "markdown", - "id": "ea1afb2d", + "id": "4c938b6a", "metadata": { "editable": true }, @@ -1069,7 +1069,7 @@ }, { "cell_type": "markdown", - "id": "d8995ba6", + "id": "73f6333a", "metadata": { "editable": true }, @@ -1079,7 +1079,7 @@ }, { "cell_type": "markdown", - "id": "8856aaf0", + "id": "caf3592f", "metadata": { "editable": true }, @@ -1091,7 +1091,7 @@ }, { "cell_type": "markdown", - "id": "016aa7e8", + "id": "23aa37b5", "metadata": { "editable": true }, @@ -1101,7 +1101,7 @@ }, { "cell_type": "markdown", - "id": "63f06777", + "id": "79a1f89b", "metadata": { "editable": true }, @@ -1113,7 +1113,7 @@ }, { "cell_type": "markdown", - "id": "a358b1f8", + "id": "80dc70aa", "metadata": { "editable": true }, @@ -1124,7 +1124,7 @@ }, { "cell_type": "markdown", - "id": "4a907443", + "id": "7df9eb53", "metadata": { "editable": true }, @@ -1136,7 +1136,7 @@ }, { "cell_type": "markdown", - "id": "525337e8", + "id": "417c81c7", "metadata": { "editable": true }, @@ -1150,7 +1150,7 @@ }, { "cell_type": "markdown", - "id": "49537051", + "id": "00bf53e7", "metadata": { "editable": true }, @@ -1162,7 +1162,7 @@ }, { "cell_type": "markdown", - "id": "aa2e5219", + "id": "0252a890", "metadata": { "editable": true }, @@ -1174,7 +1174,7 @@ }, { "cell_type": "markdown", - "id": "6fd8275a", + "id": "c59c02c5", "metadata": { "editable": true }, @@ -1184,7 +1184,7 @@ }, { "cell_type": "markdown", - "id": "a985c2a8", + "id": "709fffb4", "metadata": { "editable": true }, @@ -1201,7 +1201,7 @@ }, { "cell_type": "markdown", - "id": "e0e3581e", + "id": "70e65085", "metadata": { "editable": true }, @@ -1212,7 +1212,7 @@ }, { "cell_type": "markdown", - "id": "07735771", + "id": "91392d42", "metadata": { "editable": true }, @@ -1225,7 +1225,7 @@ }, { "cell_type": "markdown", - "id": "81ee43ba", + "id": "c2006e76", "metadata": { "editable": true }, @@ -1237,7 +1237,7 @@ }, { "cell_type": "markdown", - "id": "62c29544", + "id": "33dae99c", "metadata": { "editable": true }, @@ -1247,7 +1247,7 @@ }, { "cell_type": "markdown", - "id": "a0b083da", + "id": "0c4a6891", "metadata": { "editable": true }, @@ -1259,7 +1259,7 @@ }, { "cell_type": "markdown", - "id": "d013b78f", + "id": "636ae746", "metadata": { "editable": true }, @@ -1269,7 +1269,7 @@ }, { "cell_type": "markdown", - "id": "9574456b", + "id": "dac567de", "metadata": { "editable": true }, @@ -1281,7 +1281,7 @@ }, { "cell_type": "markdown", - "id": "1465d057", + "id": "bbaf33dd", "metadata": { "editable": true }, @@ -1291,7 +1291,7 @@ }, { "cell_type": "markdown", - "id": "2e49b3b9", + "id": "c92c076f", "metadata": { "editable": true }, @@ -1303,7 +1303,7 @@ }, { "cell_type": "markdown", - "id": "ce771b14", + "id": "5606cbaa", "metadata": { "editable": true }, @@ -1313,7 +1313,7 @@ }, { "cell_type": "markdown", - "id": "202f087c", + "id": "ee24a14d", "metadata": { "editable": true }, @@ -1325,7 +1325,7 @@ }, { "cell_type": "markdown", - "id": "03b9d620", + "id": "ae35eea0", "metadata": { "editable": true }, @@ -1335,7 +1335,7 @@ }, { "cell_type": "markdown", - "id": "27c25ddf", + "id": "2eb892aa", "metadata": { "editable": true }, @@ -1357,7 +1357,7 @@ }, { "cell_type": "markdown", - "id": "89cda311", + "id": "59a7d72d", "metadata": { "editable": true }, @@ -1369,7 +1369,7 @@ }, { "cell_type": "markdown", - "id": "e05e0bbb", + "id": "39a7d3b5", "metadata": { "editable": true }, @@ -1379,7 +1379,7 @@ }, { "cell_type": "markdown", - "id": "1cb40820", + "id": "cbb2560f", "metadata": { "editable": true }, @@ -1391,7 +1391,7 @@ }, { "cell_type": "markdown", - "id": "0d5dc73d", + "id": "8155f88b", "metadata": { "editable": true }, @@ -1407,7 +1407,7 @@ }, { "cell_type": "markdown", - "id": "8e03a332", + "id": "8cbe545f", "metadata": { "editable": true }, @@ -1419,7 +1419,7 @@ }, { "cell_type": "markdown", - "id": "970b9d90", + "id": "6a6d1b53", "metadata": { "editable": true }, @@ -1431,7 +1431,7 @@ }, { "cell_type": "markdown", - "id": "f5105d82", + "id": "2439f37b", "metadata": { "editable": true }, @@ -1441,7 +1441,7 @@ }, { "cell_type": "markdown", - "id": "058450f7", + "id": "1ea9bb1b", "metadata": { "editable": true }, @@ -1453,7 +1453,7 @@ }, { "cell_type": "markdown", - "id": "f06fb620", + "id": "2cabc791", "metadata": { "editable": true }, @@ -1465,7 +1465,7 @@ }, { "cell_type": "markdown", - "id": "27baa80c", + "id": "2572863e", "metadata": { "editable": true }, @@ -1477,7 +1477,7 @@ }, { "cell_type": "markdown", - "id": "947dd0e5", + "id": "48d79578", "metadata": { "editable": true }, @@ -1487,7 +1487,7 @@ }, { "cell_type": "markdown", - "id": "b42e5666", + "id": "b25b5608", "metadata": { "editable": true }, @@ -1499,7 +1499,7 @@ }, { "cell_type": "markdown", - "id": "2b934e6b", + "id": "a62a3ffa", "metadata": { "editable": true }, @@ -1509,7 +1509,7 @@ }, { "cell_type": "markdown", - "id": "0030e7c8", + "id": "00c70054", "metadata": { "editable": true }, @@ -1521,7 +1521,7 @@ }, { "cell_type": "markdown", - "id": "af8e719d", + "id": "262bc0dd", "metadata": { "editable": true }, @@ -1531,7 +1531,7 @@ }, { "cell_type": "markdown", - "id": "089eaf56", + "id": "fca5250d", "metadata": { "editable": true }, @@ -1543,7 +1543,7 @@ }, { "cell_type": "markdown", - "id": "0db973a0", + "id": "100c573a", "metadata": { "editable": true }, @@ -1554,7 +1554,7 @@ }, { "cell_type": "markdown", - "id": "6c2c7799", + "id": "d8361c06", "metadata": { "editable": true }, @@ -1566,7 +1566,7 @@ }, { "cell_type": "markdown", - "id": "f7fa83a1", + "id": "b7b9fe83", "metadata": { "editable": true }, @@ -1578,7 +1578,7 @@ }, { "cell_type": "markdown", - "id": "1f85412d", + "id": "b1db3f0a", "metadata": { "editable": true }, @@ -1588,7 +1588,7 @@ }, { "cell_type": "markdown", - "id": "fa3072f1", + "id": "ac9cf5e4", "metadata": { "editable": true }, @@ -1600,7 +1600,7 @@ }, { "cell_type": "markdown", - "id": "0d868fc8", + "id": "dce93fad", "metadata": { "editable": true }, @@ -1627,7 +1627,7 @@ { "cell_type": "code", "execution_count": 2, - "id": "7698c577", + "id": "c9fcde48", "metadata": { "collapsed": false, "editable": true @@ -1684,7 +1684,7 @@ }, { "cell_type": "markdown", - "id": "5aeb713a", + "id": "8ba953ec", "metadata": { "editable": true }, @@ -1696,7 +1696,7 @@ }, { "cell_type": "markdown", - "id": "e20b01f5", + "id": "08684a90", "metadata": { "editable": true }, @@ -1708,7 +1708,7 @@ }, { "cell_type": "markdown", - "id": "0683b2ac", + "id": "ebae3712", "metadata": { "editable": true }, @@ -1718,7 +1718,7 @@ }, { "cell_type": "markdown", - "id": "f9109302", + "id": "4dbe23e2", "metadata": { "editable": true }, @@ -1730,7 +1730,7 @@ }, { "cell_type": "markdown", - "id": "2225601a", + "id": "4f99a406", "metadata": { "editable": true }, @@ -1740,7 +1740,7 @@ }, { "cell_type": "markdown", - "id": "b8070c6c", + "id": "c08cc2a4", "metadata": { "editable": true }, @@ -1752,7 +1752,7 @@ }, { "cell_type": "markdown", - "id": "9226cc98", + "id": "0d476f28", "metadata": { "editable": true }, @@ -1763,7 +1763,7 @@ }, { "cell_type": "markdown", - "id": "b9974440", + "id": "4533e33e", "metadata": { "editable": true }, @@ -1775,7 +1775,7 @@ }, { "cell_type": "markdown", - "id": "6ccc7e82", + "id": "f476e653", "metadata": { "editable": true }, @@ -1785,7 +1785,7 @@ }, { "cell_type": "markdown", - "id": "07329c18", + "id": "1774de01", "metadata": { "editable": true }, @@ -1797,7 +1797,7 @@ }, { "cell_type": "markdown", - "id": "12e1983f", + "id": "710edbb9", "metadata": { "editable": true }, @@ -1815,7 +1815,7 @@ }, { "cell_type": "markdown", - "id": "3ed51388", + "id": "d160c968", "metadata": { "editable": true }, @@ -1826,7 +1826,7 @@ }, { "cell_type": "markdown", - "id": "4020d53e", + "id": "2bc12199", "metadata": { "editable": true }, @@ -1838,7 +1838,7 @@ }, { "cell_type": "markdown", - "id": "60dce47f", + "id": "ecdfdb09", "metadata": { "editable": true }, @@ -1848,7 +1848,7 @@ }, { "cell_type": "markdown", - "id": "7adc514d", + "id": "d0f3a32f", "metadata": { "editable": true }, @@ -1865,7 +1865,7 @@ }, { "cell_type": "markdown", - "id": "24894d2e", + "id": "5d927724", "metadata": { "editable": true }, @@ -1879,7 +1879,7 @@ }, { "cell_type": "markdown", - "id": "b3020c80", + "id": "7592bee6", "metadata": { "editable": true }, @@ -1894,7 +1894,7 @@ }, { "cell_type": "markdown", - "id": "5ae59d13", + "id": "ccf7915f", "metadata": { "editable": true }, @@ -1906,7 +1906,7 @@ }, { "cell_type": "markdown", - "id": "5dfde84d", + "id": "96a678bd", "metadata": { "editable": true }, @@ -1934,7 +1934,7 @@ }, { "cell_type": "markdown", - "id": "9c92f6bc", + "id": "704725b3", "metadata": { "editable": true }, @@ -1946,7 +1946,7 @@ }, { "cell_type": "markdown", - "id": "303691f2", + "id": "13108de8", "metadata": { "editable": true }, @@ -1961,7 +1961,7 @@ }, { "cell_type": "markdown", - "id": "5e223d93", + "id": "0b6387a3", "metadata": { "editable": true }, @@ -1972,7 +1972,7 @@ { "cell_type": "code", "execution_count": 3, - "id": "9f7e8138", + "id": "65105f8d", "metadata": { "collapsed": false, "editable": true @@ -2171,7 +2171,7 @@ }, { "cell_type": "markdown", - "id": "75ae76b9", + "id": "8ad06c44", "metadata": { "editable": true }, @@ -2183,7 +2183,7 @@ }, { "cell_type": "markdown", - "id": "16ba6e80", + "id": "fad554a8", "metadata": { "editable": true }, @@ -2198,7 +2198,7 @@ }, { "cell_type": "markdown", - "id": "588523da", + "id": "248f4828", "metadata": { "editable": true }, @@ -2215,7 +2215,7 @@ }, { "cell_type": "markdown", - "id": "a39a446b", + "id": "67498572", "metadata": { "editable": true }, @@ -2236,7 +2236,7 @@ { "cell_type": "code", "execution_count": 4, - "id": "6d86fa85", + "id": "73f314f3", "metadata": { "collapsed": false, "editable": true @@ -2249,7 +2249,7 @@ }, { "cell_type": "markdown", - "id": "49496f74", + "id": "96b2a34b", "metadata": { "editable": true }, @@ -2259,7 +2259,7 @@ }, { "cell_type": "markdown", - "id": "63af3df7", + "id": "76f0c138", "metadata": { "editable": true }, @@ -2271,7 +2271,7 @@ }, { "cell_type": "markdown", - "id": "48690f75", + "id": "673f54c3", "metadata": { "editable": true }, @@ -2286,7 +2286,7 @@ }, { "cell_type": "markdown", - "id": "7b5c295a", + "id": "383e0e90", "metadata": { "editable": true }, @@ -2296,7 +2296,7 @@ }, { "cell_type": "markdown", - "id": "e230f41c", + "id": "bd879cdc", "metadata": { "editable": true }, @@ -2315,7 +2315,7 @@ }, { "cell_type": "markdown", - "id": "2cde8fb2", + "id": "3d52e4dd", "metadata": { "editable": true }, @@ -2325,7 +2325,7 @@ }, { "cell_type": "markdown", - "id": "49740d95", + "id": "db364c9d", "metadata": { "editable": true }, @@ -2337,7 +2337,7 @@ }, { "cell_type": "markdown", - "id": "fe5febd9", + "id": "4511250d", "metadata": { "editable": true }, @@ -2347,7 +2347,7 @@ }, { "cell_type": "markdown", - "id": "00db9342", + "id": "406094a2", "metadata": { "editable": true }, @@ -2359,7 +2359,7 @@ }, { "cell_type": "markdown", - "id": "5372de8f", + "id": "1c21f68c", "metadata": { "editable": true }, @@ -2369,7 +2369,7 @@ }, { "cell_type": "markdown", - "id": "ca9a12a7", + "id": "6505ef46", "metadata": { "editable": true }, @@ -2381,7 +2381,7 @@ }, { "cell_type": "markdown", - "id": "365d32e4", + "id": "5daf9b0e", "metadata": { "editable": true }, @@ -2392,7 +2392,7 @@ }, { "cell_type": "markdown", - "id": "7e37abcd", + "id": "f6e0bce7", "metadata": { "editable": true }, @@ -2404,7 +2404,7 @@ }, { "cell_type": "markdown", - "id": "f58777eb", + "id": "8baa2388", "metadata": { "editable": true }, @@ -2416,7 +2416,7 @@ { "cell_type": "code", "execution_count": 5, - "id": "dae156a5", + "id": "be13940b", "metadata": { "collapsed": false, "editable": true @@ -2440,7 +2440,7 @@ }, { "cell_type": "markdown", - "id": "57ec65e3", + "id": "69991635", "metadata": { "editable": true }, @@ -2452,7 +2452,7 @@ }, { "cell_type": "markdown", - "id": "4c9142c0", + "id": "285592d1", "metadata": { "editable": true }, @@ -2462,7 +2462,7 @@ }, { "cell_type": "markdown", - "id": "42707fad", + "id": "7bbbf2aa", "metadata": { "editable": true }, @@ -2477,7 +2477,7 @@ }, { "cell_type": "markdown", - "id": "9aa5ca8f", + "id": "803ece95", "metadata": { "editable": true }, @@ -2493,7 +2493,7 @@ }, { "cell_type": "markdown", - "id": "8578364c", + "id": "2ee672a8", "metadata": { "editable": true }, @@ -2518,7 +2518,7 @@ }, { "cell_type": "markdown", - "id": "10072571", + "id": "c5630fcb", "metadata": { "editable": true }, @@ -2547,7 +2547,7 @@ }, { "cell_type": "markdown", - "id": "6a6ab78e", + "id": "a6934263", "metadata": { "editable": true }, @@ -2563,7 +2563,7 @@ }, { "cell_type": "markdown", - "id": "cf96a874", + "id": "c783b79f", "metadata": { "editable": true }, @@ -2588,7 +2588,7 @@ }, { "cell_type": "markdown", - "id": "9813ea96", + "id": "62cbc914", "metadata": { "editable": true }, @@ -2635,7 +2635,7 @@ }, { "cell_type": "markdown", - "id": "c17e7226", + "id": "786d3f57", "metadata": { "editable": true }, @@ -2671,7 +2671,7 @@ }, { "cell_type": "markdown", - "id": "5963ec7d", + "id": "9d21f180", "metadata": { "editable": true }, @@ -2694,7 +2694,7 @@ }, { "cell_type": "markdown", - "id": "290942e2", + "id": "683a16f5", "metadata": { "editable": true }, @@ -2717,7 +2717,7 @@ }, { "cell_type": "markdown", - "id": "c29eb43e", + "id": "ea575b67", "metadata": { "editable": true }, @@ -2737,7 +2737,7 @@ }, { "cell_type": "markdown", - "id": "a0f18da2", + "id": "131f42dc", "metadata": { "editable": true }, @@ -2753,7 +2753,7 @@ }, { "cell_type": "markdown", - "id": "b1798858", + "id": "f7c2ef52", "metadata": { "editable": true }, @@ -2785,7 +2785,7 @@ }, { "cell_type": "markdown", - "id": "bfa66a96", + "id": "a025a38b", "metadata": { "editable": true }, @@ -2807,7 +2807,7 @@ }, { "cell_type": "markdown", - "id": "f92d9280", + "id": "04fe559d", "metadata": { "editable": true }, @@ -2832,7 +2832,7 @@ }, { "cell_type": "markdown", - "id": "f7c1d15c", + "id": "a5e5029b", "metadata": { "editable": true }, @@ -2850,7 +2850,7 @@ }, { "cell_type": "markdown", - "id": "d568418c", + "id": "ad6b3462", "metadata": { "editable": true }, @@ -2878,7 +2878,7 @@ }, { "cell_type": "markdown", - "id": "6d90707d", + "id": "6d0c1bad", "metadata": { "editable": true }, @@ -2892,7 +2892,7 @@ }, { "cell_type": "markdown", - "id": "8397543b", + "id": "df28b5b1", "metadata": { "editable": true }, @@ -2918,7 +2918,7 @@ }, { "cell_type": "markdown", - "id": "0fa12026", + "id": "ff6a5bfc", "metadata": { "editable": true }, @@ -2947,7 +2947,7 @@ }, { "cell_type": "markdown", - "id": "d5e1e5fa", + "id": "024ab619", "metadata": { "editable": true }, @@ -2964,7 +2964,7 @@ }, { "cell_type": "markdown", - "id": "ea134ac0", + "id": "36270b44", "metadata": { "editable": true }, @@ -2986,7 +2986,7 @@ }, { "cell_type": "markdown", - "id": "999ec22f", + "id": "8fcb5515", "metadata": { "editable": true }, @@ -3004,7 +3004,7 @@ }, { "cell_type": "markdown", - "id": "d6cd9560", + "id": "b7b59ced", "metadata": { "editable": true }, @@ -3027,7 +3027,7 @@ }, { "cell_type": "markdown", - "id": "7a96abf3", + "id": "17e8af3e", "metadata": { "editable": true }, @@ -3046,7 +3046,7 @@ }, { "cell_type": "markdown", - "id": "f7a2cc3a", + "id": "d983f858", "metadata": { "editable": true }, @@ -3062,7 +3062,7 @@ }, { "cell_type": "markdown", - "id": "3c9a815e", + "id": "c790ca41", "metadata": { "editable": true }, @@ -3077,7 +3077,7 @@ }, { "cell_type": "markdown", - "id": "59d8e075", + "id": "22a42bc5", "metadata": { "editable": true }, @@ -3101,7 +3101,7 @@ }, { "cell_type": "markdown", - "id": "10cf3f14", + "id": "f225781c", "metadata": { "editable": true }, @@ -3113,7 +3113,7 @@ }, { "cell_type": "markdown", - "id": "f2d4229a", + "id": "062a9528", "metadata": { "editable": true }, @@ -3131,7 +3131,7 @@ }, { "cell_type": "markdown", - "id": "67d3614f", + "id": "e24462bc", "metadata": { "editable": true }, @@ -3141,7 +3141,7 @@ }, { "cell_type": "markdown", - "id": "5b03eb62", + "id": "fead8cb4", "metadata": { "editable": true }, @@ -3159,7 +3159,7 @@ }, { "cell_type": "markdown", - "id": "3d57cbec", + "id": "f8c0fe16", "metadata": { "editable": true }, @@ -3169,7 +3169,7 @@ }, { "cell_type": "markdown", - "id": "3e6a1572", + "id": "e8a5f59f", "metadata": { "editable": true }, @@ -3188,7 +3188,7 @@ }, { "cell_type": "markdown", - "id": "eed132fb", + "id": "c356a7cf", "metadata": { "editable": true }, @@ -3200,7 +3200,7 @@ }, { "cell_type": "markdown", - "id": "d71d6279", + "id": "c2105201", "metadata": { "editable": true }, @@ -3214,7 +3214,7 @@ }, { "cell_type": "markdown", - "id": "2ecb2916", + "id": "770c68fa", "metadata": { "editable": true }, @@ -3229,7 +3229,7 @@ }, { "cell_type": "markdown", - "id": "99c9d943", + "id": "b7d70c3a", "metadata": { "editable": true }, @@ -3247,7 +3247,7 @@ }, { "cell_type": "markdown", - "id": "74af5323", + "id": "51a35a4e", "metadata": { "editable": true }, @@ -3261,7 +3261,7 @@ }, { "cell_type": "markdown", - "id": "3c848b46", + "id": "2bd3dadc", "metadata": { "editable": true }, @@ -3279,7 +3279,7 @@ }, { "cell_type": "markdown", - "id": "e3d757fe", + "id": "80d1dcdf", "metadata": { "editable": true }, @@ -3303,7 +3303,7 @@ }, { "cell_type": "markdown", - "id": "906985ab", + "id": "9e3bc860", "metadata": { "editable": true }, @@ -3341,7 +3341,7 @@ }, { "cell_type": "markdown", - "id": "d839b4a0", + "id": "ab5a3613", "metadata": { "editable": true }, @@ -3364,7 +3364,7 @@ }, { "cell_type": "markdown", - "id": "e4436e7f", + "id": "15b7af3b", "metadata": { "editable": true }, @@ -3400,7 +3400,7 @@ }, { "cell_type": "markdown", - "id": "ffed5986", + "id": "fbcb0899", "metadata": { "editable": true }, @@ -3421,7 +3421,7 @@ }, { "cell_type": "markdown", - "id": "fbc3b5ca", + "id": "9faeb724", "metadata": { "editable": true }, @@ -3443,7 +3443,7 @@ }, { "cell_type": "markdown", - "id": "bf4aa2e8", + "id": "596db357", "metadata": { "editable": true }, @@ -3463,7 +3463,7 @@ }, { "cell_type": "markdown", - "id": "a2cde7ea", + "id": "aa8dc92c", "metadata": { "editable": true }, @@ -3478,7 +3478,7 @@ }, { "cell_type": "markdown", - "id": "dcc50686", + "id": "184cc913", "metadata": { "editable": true }, @@ -3496,7 +3496,7 @@ }, { "cell_type": "markdown", - "id": "a3a0d110", + "id": "d4ed4daa", "metadata": { "editable": true }, @@ -3526,7 +3526,7 @@ }, { "cell_type": "markdown", - "id": "1c378697", + "id": "afd5f121", "metadata": { "editable": true }, @@ -3555,7 +3555,7 @@ }, { "cell_type": "markdown", - "id": "b3545977", + "id": "f5c5204c", "metadata": { "editable": true }, @@ -3586,7 +3586,7 @@ }, { "cell_type": "markdown", - "id": "b171a6e3", + "id": "cbed1a59", "metadata": { "editable": true }, @@ -3609,7 +3609,7 @@ }, { "cell_type": "markdown", - "id": "287512bb", + "id": "68d1cdef", "metadata": { "editable": true }, @@ -3627,7 +3627,7 @@ }, { "cell_type": "markdown", - "id": "efd4d2c5", + "id": "d7153a64", "metadata": { "editable": true }, @@ -3650,7 +3650,7 @@ }, { "cell_type": "markdown", - "id": "9469c962", + "id": "62f30c4c", "metadata": { "editable": true }, @@ -3671,7 +3671,7 @@ }, { "cell_type": "markdown", - "id": "d2b04db5", + "id": "7009b504", "metadata": { "editable": true }, @@ -3687,7 +3687,7 @@ }, { "cell_type": "markdown", - "id": "607b48b7", + "id": "4afdacda", "metadata": { "editable": true },