{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Math for Machine Learning\n", "## Linear Algebra\n", "### Kousuke Ariga (kouariga@gmail.com, @kouariga)\n", "[v1] January 17, 2018 \n", "[v2] January 18, 2019 \n", "[v3] April 10, 2019\n", "\n", "The latest version is available [here](https://github.com/kouariga/math-for-ml)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook is meant to be a quick refresher of linear algebra and a brief introduction of NumPy (Python package for scientific computing), and it is by no means a through review. I assume that you have a prior experience of learning linear algebra such as taking an introductory course a while ago. The goal is to go over some of the important properties of matrices and showcase some of the NumPy methods through practical examples. We consider linear regression and three different solutions: algebraic, analytic, and geometric. I heavily cite and highly recommend Kolter's review notes on linear algebra [2]." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Contents\n", "1. Introduction \n", " 1.1 Boston house prices dataset \n", " 1.2 Linear regression model\n", "2. Matrices and vectors \n", " 2.1 Matrix \n", " 2.2 Vector\n", "3. Basic operations on matrices and vectors \n", " 3.1 Matrix addition \n", " 3.2 Scalar matrix multiplication \n", " 3.3 Matrix vector multiplication \n", " 3.4 Matrix matrix multiplication\n", "4. Properties of matrices \n", " 4.1 Linear independence \n", " 4.2 Rank \n", " 4.3 Inverse \n", " 4.4 Transpose \n", " 4.5 Span and range \n", " 4.6 Norm \n", " 4.7 Gradient \n", " 4.8 Matrix calculus \n", " 4.9 Dot products \n", " 4.10 Projections \n", " 4.11 Orthogonality\n", "5. Discussion and conclusions\n", "\n", "Appendix \n", " A. Linear algebra visualized \n", " B. Transpose and 1-dimensional arrays in NumPy \n", " C. Outer products in NumPy\n", "\n", "Reference" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Introduction\n", "\n", "Why is linear algebra important in machine learning? Machine learning methods often involves a large amount of data, and linear algebra provides a clever way to analyze and manipulate them. To make the argument concrete, let's take a look at a sample dataset." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.1 Boston house prices dataset" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import time\n", "\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "from sklearn.datasets import load_boston\n", "from IPython.display import Image, IFrame\n", "\n", "np.set_printoptions(suppress=True, linewidth=120, precision=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can load boston dataset from sklearn package, which is a very popular and easy to use machine learning package of Python. It implements many kinds of machine learning algorithms and utility functions. The loaded dataset has the following attributes." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "boston = load_boston()\n", "print(boston.__dir__())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "scrolled": true }, "outputs": [], "source": [ "print(boston.DESCR)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The data and target values are stored in arrays of type numpy.ndarray. In the data array, each row corresponds to a sample, a Boston suburb or town in this example, and each column corresponds to a feature that is described above. Note that numpy.ndarray is not just multi-dimensional array (or list in Python). It implements many useful numeric methods and indexing feature. Refer to the [ndarray document](https://docs.scipy.org/doc/numpy-1.15.0/reference/generated/numpy.ndarray.html) and [indexing document](https://docs.scipy.org/doc/numpy-1.13.0/reference/arrays.indexing.html) for the details. Here, I show the first 10 samples, each of which consists of 13 feature values, and some of their statistics by slicing the data array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(boston.feature_names)\n", "print(boston.data[:10])\n", "\n", "print('\\nmean')\n", "print(boston.data[:10].mean(axis=0))\n", "\n", "print('\\nvariance')\n", "print(boston.data[:10].var(axis=0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The target values are the following. Our task here is to predict the target value, or the \"median value of owner-occupied homes in $1000's\" in a Boston town, given its feature values such as \"per capita crime rate by town\" and \"average number of rooms per dwelling.\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print('MEDV')\n", "print(boston.target[:10])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 1.2 Linear regression\n", "\n", "Linear regression is one of the simplest statistical models. It assumes that the target variable$y$is explained by a weighted sum of feature values$x_1, x_2, \\dots, x_n$. In an equation,\n", "\n", "$$y_{MEDV} = w_{CRIM}x_{CRIM} + w_{ZN}x_{ZN} + \\dots + w_{LSTAT}x_{LSTAT} + b$$\n", "\n", "where$b$is a bias term. Intuitively,$w_*x_*$terms define the relative up/down from the standard target value. This standard value is what the bias term accounts for. You may wonder if the relationship is really that simple.\n", "\n", "> \"Essentially, all models are wrong, but some are useful.\"\n", ">\n", "> George Box, 1987" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Assuming that the linear regression model is valid and we know all the weights and bias, we can estimate a median house price of a Boston town from its feature values. The bad news is that we don't know the weights... The good news is that we have training samples (a set of features and target pair)! We want to find a set of weights such that the equation holds for the training samples. To this end, we can solve systems of equations.\n", "\n", "\\begin{cases}\n", "y_{MEDV}^{(1)} &= w_{CRIM}x_{CRIM}^{(1)} + w_{ZN}x_{ZN}^{(1)} + \\dots + w_{LSTAT}x_{LSTAT}^{(1)} + b \\\\\n", "y_{MEDV}^{(2)} &= w_{CRIM}x_{CRIM}^{(2)} + w_{ZN}x_{ZN}^{(2)} + \\dots + w_{LSTAT}x_{LSTAT}^{(2)} + b \\\\\n", " &\\, \\vdots \\\\\n", "y_{MEDV}^{(n)} &= w_{CRIM}x_{CRIM}^{(n)} + w_{ZN}x_{ZN}^{(n)} + \\dots + w_{LSTAT}x_{LSTAT}^{(n)} + b\n", "\\end{cases}\n", "\n", "Great, we can solve it, ...or can we (more on this later)? Let's rewrite the equations with a better notation.\n", "\n", "$$\n", "\\begin{bmatrix}\n", "y_{MEDV}^{(1)} \\\\\n", "y_{MEDV}^{(2)} \\\\\n", "\\vdots \\\\\n", "y_{MEDV}^{(n)}\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "x_{CRIM}^{(1)} & x_{ZN}^{(1)} & \\dots & x_{LSTAT}^{(1)} & 1 \\\\\n", "x_{CRIM}^{(2)} & x_{ZN}^{(2)} & \\dots & x_{LSTAT}^{(2)} & 1 \\\\\n", "\\vdots & \\vdots & \\ddots & \\vdots & \\vdots \\\\\n", "x_{CRIM}^{(n)} & x_{ZN}^{(n)} & \\dots & x_{LSTAT}^{(n)} & 1\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "w_{CRIM} \\\\\n", "w_{ZN} \\\\\n", "\\vdots \\\\\n", "w_{LSTAT} \\\\\n", "b\n", "\\end{bmatrix}\n", "$$\n", "\n", "More simply,\n", "\n", "$$\n", "\\begin{bmatrix}\n", "y^{(1)} \\\\\n", "y^{(2)} \\\\\n", "\\vdots \\\\\n", "y^{(n)}\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "\\boldsymbol{x}^{(1)} \\\\\n", "\\boldsymbol{x}^{(2)} \\\\\n", "\\vdots \\\\\n", "\\boldsymbol{x}^{(n)}\n", "\\end{bmatrix}\n", "\\boldsymbol{w}\n", "$$\n", "\n", "or even...\n", "\n", "$$\\boldsymbol{y} = X\\boldsymbol{w}$$\n", "\n", "Yes, this is beautiful. This notation is used in linear algebra, and it is a very powerful tool given to us to tackle machine learing problems. The objective here is to find a set of weights$\\boldsymbol{w}$that solves this equation. We call this process to learn from data." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Matrices and vectors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.1 Matrix\n", "\n", "$$A=\n", "\\begin{bmatrix}\n", " 0 & 1 & 2 \\\\\n", " 3 & 4 & 5\n", "\\end{bmatrix} \\quad A \\in \\mathbb{R}^{2\\times3}$$\n", "\n", "A matrix is a rectangular array of numbers. The dimension of matrix is number of rows by number of columns.$A_{ij}$is the$i, j$entry of$A$, which is in the$i$th row and the$j$th column." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = np.array(np.arange(0, 6)).reshape((2, 3))\n", "\n", "print(A)\n", "print(A.shape)\n", "\n", "for i in range(A.shape[0]):\n", " for j in range(A.shape[1]):\n", " print(\"{},{} entry: {}\".format(i, j, A[i, j]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 2.2 Vector\n", "\n", "$$\\boldsymbol{y} = \n", "\\begin{bmatrix}\n", " 0 \\\\\n", " 2 \\\\\n", " 4 \\\\\n", " 6\n", "\\end{bmatrix}\n", "\\quad \\boldsymbol{y} \\in \\mathbb{R}^4$$\n", "\n", "A vector is a$n\\times 1$matrix. Here$\\boldsymbol{y}$is said to be a 4-dimensional vector because it has 4 elements in it.$\\boldsymbol{y}_i$denotes the$i$th element of$\\boldsymbol{y}$." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "y = np.array(2*np.arange(0, 4))\n", "print(y)\n", "print(y.shape)\n", "\n", "for i in range(y.shape[0]):\n", " print(\"{} element: {}\".format(i, y[i]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Basic operations on matrices and vectors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3. 1 Matrix addition\n", "\n", "$$\n", "\\begin{bmatrix}\n", "1 & 0 \\\\\n", "2 & 5 \\\\\n", "3 & 1\n", "\\end{bmatrix}\n", "+ \\begin{bmatrix}\n", "4 & 0.5 \\\\\n", "2 & 5 \\\\\n", "0 & 1\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "5 & 0.5 \\\\\n", "4 & 10 \\\\\n", "3 & 2\n", "\\end{bmatrix}\n", "$$\n", "\n", "The shapes have to be the same." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = np.array([[1, 0],\n", " [2, 5],\n", " [3, 1]])\n", "B = np.array([[4, 0.5],\n", " [2, 5],\n", " [0, 1]])\n", "assert A.shape == B.shape\n", "print(A + B)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.2 Scalar matrix multiplication" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "3\\begin{bmatrix}\n", "1 & 0 \\\\\n", "2 & 5 \\\\\n", "3 & 1\n", "\\end{bmatrix}\n", "=\n", "\\begin{bmatrix}\n", "3 & 0 \\\\\n", "6 & 15 \\\\\n", "9 & 3\n", "\\end{bmatrix}\n", "$$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = np.array([[1, 0],\n", " [2, 5],\n", " [3, 1]])\n", "print(3*A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.3 Matrix vector multiplication" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$A\\boldsymbol{x} = \\boldsymbol{y}$$\n", "\n", "$A: m \\times n$matrix (m rows, n columns)\n", "\n", "$\\boldsymbol{x}: n \\times 1$matrix (n-dimensional vector)\n", "\n", "$\\boldsymbol{y}: m \\times 1$matrix (m-dimensional vector) \n", "\n", "To get$y_i$, multiply$A$'s ith row with vector$x$element-wise, then add them up." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$\n", "\\begin{bmatrix}\n", "1 & 2 & 1 & 5 \\\\\n", "0 & 3 & 0 & 4 \\\\\n", "-1 & -2 & 0 & 0\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "1 \\\\\n", "3 \\\\\n", "2 \\\\\n", "1\n", "\\end{bmatrix}\n", "= \\,?\n", "$$\n", "\n", "Hint:$\\mathbb{R}^{3\\times4} \\times \\mathbb{R}^{4\\times1} = \\mathbb{R}^{3\\times1}$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = np.array([[1, 2, 1, 5],\n", " [0, 3, 0, 4],\n", " [-1, -2, 0, 0]])\n", "x = np.array([[1],\n", " [3],\n", " [2],\n", " [1]])\n", "\n", "assert x.shape[0] == A.shape[1]\n", "y = np.dot(A, x)\n", "y = A.dot(x) # another way to calculate the dot product\n", "\n", "print(y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 3.4 Matrix matrix multiplication" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$$AB = C$$\n", "\n", "$A := l \\times m$matrix (l rows, m columns matrix)\n", "\n", "$B := m \\times n$matrix (m rows, n columns matrix)\n", "\n", "$C := l \\times n$matrix (l rows, n columns matrix)\n", "\n", "$$\n", "\\begin{bmatrix}\n", "1 & 4 \\\\\n", "5 & 3 \\\\\n", "2 & 6\n", "\\end{bmatrix}\n", "\\begin{bmatrix}\n", "1 & 8 & 7 & 4 \\\\\n", "5 & 6 & 2 & 3\n", "\\end{bmatrix}\n", "= \\,?\n", "$$\n", "\n", "Hint:$\\mathbb{R}^{3\\times2} \\times \\mathbb{R}^{2\\times4} = \\mathbb{R}^{3\\times4}$\n", "\n", "Note$AB$and$BA$are not the same, i.e. matrix multiplication is NOT commutative. Actually, the latter is not necessarily defined. Check dimension." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = np.array([[1, 4],\n", " [5, 3],\n", " [2, 6]])\n", "B = np.array([[1, 8, 7, 4],\n", " [5, 6, 2, 3]])\n", "print(A)\n", "print(B)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(A.dot(B))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "try:\n", " print(B.dot(A))\n", "except ValueError as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Properties of matrices\n", "\n", "Is linear algebra all about saving papers? Definitely NO! Do you remember terminologies such as *linear independence*, *rank*, *span*, etc that you learned in the linear algebra course? Did you get the ideas? Being able to calculate them is important, but understanding the concept is more (at least as) important for the purpose of this course (we can use computers for calculation after all). Let's review the properties of matrices while solving the linear regression of Boston house price. The goal is to solve the following equation.\n", "\n", "$$\\boldsymbol{y} = X\\boldsymbol{w}$$\n", "\n", "where$X\\in \\mathbb{R}^{m\\times n}$and$m>n$.$m$is greater than$n$because there are more samples than the number of features (remember rows are samples). In other words,$X$is a vertically long matrix." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.1 Linear independence\n", "\n", "Here, let's assume that all the features (columns of$X$) are linearly independent.\n", "\n", "> A set of vectors$\\{\\boldsymbol{x}_1, \\boldsymbol{x}_2, \\dots, \\boldsymbol{x}_n\\}\\in\\mathbb{R}^m$is said to be (linearly) independent if no vector can be represented as a linear combination of the remaining vectors. [2]\n", "\n", "Otherwise, it is linearly dependent. For example, if we have temperature in Fahrenheit and in Celsius as two different features, the latter is represented in terms of the first as\n", "\n", "$$F = \\frac{9}{5}C + 32.$$\n", "\n", "Such features are linearly dependent. For another example, if we have categorical features like gender, we could have two columns one for male and the other for female. For male samples we have ones in the male column and zeros in the female column, and the opposite for female samples. Did you notice that we have a linear dependence here because these features can be represented in the form\n", "\n", "$$F = - M + 1.$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.2 Rank\n", "\n", "For a matrix$A \\in \\mathbb{R}^{m\\times n}$where$m>n$, if its columns are linearly independent, it is said to be full rank. Formally,\n", "\n", "> The column rank of a matrix$A \\in \\mathbb{R}^{m\\times n}$is the size of the largest subset of columns of$A$that constitute a linearly independent set. With some abuse of terminology, this is often referred to simply as the number of linearly independent columns of$A$. In the same way, the row rank is the largest number of rows of$A$that constitute a linearly independent set. \n", ">\n", "> For any matrix$A \\in \\mathbb{R}^{m\\times n}$, it turns out that the column rank of$A$is equal to the row\n", "rank of$A$(though we will not prove this), and so both quantities are referred to collectively as the rank of$A$, denoted as$rank(A)$.\n", ">\n", "> For$A \\in \\mathbb{R}^{m\\times n}$,$rank(A) \\leq \\min(m, n)$. If$rank(A) = \\min(m, n)$, then$A$is said to be full rank. [2]\n", "\n", "Therefore, the first statement holds." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.3 Inverse\n", "> The inverse of a square matrix$A\\in\\mathbb{R}^{n\\times n}$is denoted$A^{-1}$, and is the unique matrix such that\n", ">\n", "> $$A^{-1}A=I=AA^{-1}.$$\n", ">\n", "> Note that not all matrices have inverses. Non-square matrices, for example, do not have inverses by definition.\n", "> However, for some square matrices$A$, it may still be the case that$A^{-1}$may not exist.\n", "> In particular, we say that$A$is invertible or non-singular if$A^{-1}$exists and non-invertible or singular otherwise.\n", "> In order for a square matrix$A$to have an inverse$A^{-1}$, then$A$must be full rank. [2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, again, let's assume that the columns of$X$are linearly independent, i.e.$X$is full rank. Here's our first attempt to solve the equation for$\\boldsymbol{w}.\n", "\n", "#### The first approach (wrong)\n", "\n", "\n", "\\begin{align*}\n", " \\boldsymbol{y} &= X\\boldsymbol{w} \\\\\n", " \\boldsymbol{w} &= X^{-1}\\boldsymbol{y}\n", "\\end{align*}\n", "\n", "\n", "Can we do this?\n", "\n", "Remember that ourX$is a vertically long matrix i.e. non-square, and cannot be inverted by definition.\n", "\n", "Therefore, we can't do$\\boldsymbol{w} = X^{-1}\\boldsymbol{y}$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.4 Transpose\n", "\n", "> By convention, an n-dimensional vector is often thought of as a matrix with n rows and 1 column, known as a column vector. If we want to explicitly represent a row vector — a matrix with 1 row and n columns — we typically write$\\boldsymbol{x}^T$. (2)\n", "\n", "$$\\boldsymbol{y}\n", "= \\begin{bmatrix}\n", "0 \\\\\n", "1 \\\\\n", "2 \\\\\n", "3\n", "\\end{bmatrix}\n", "\\quad\\boldsymbol{y}^T\n", "= \\begin{bmatrix}\n", "0 & 1 & 2 & 3\n", "\\end{bmatrix}\n", "$$\n", "\n", "The transpose can be generalized to matrices.\n", "\n", "> The transpose of a matrix results from \"flipping\" the rows and columns. Given a matrix$A\\in\\mathbb{R}^{m\\times n}$, its transpose, written$A^T\\in\\mathbb{R}^{n\\times m}$, is the$n\\times m$matrix whose entries are given by\n", ">\n", "> $$(A^T)_{ij} = A_{ji}.$$ (2)\n", "\n", "$$A\n", "= \\begin{bmatrix}\n", "1 & 2 \\\\\n", "3 & 4 \\\\\n", "5 & 6 \n", "\\end{bmatrix}\n", "\\quad A^T\n", "= \\begin{bmatrix}\n", "1 & 3 & 5 \\\\\n", "2 & 4 & 6\n", "\\end{bmatrix}$$\n", "\n", "What is the dimension of$A^TA$?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A = np.array([[1, 2],\n", " [3, 4],\n", " [5, 6]])\n", "print(A)\n", "print(np.dot(A.T, A))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "$A^TA$is always a square matrix ($\\mathbb{R}^{n\\times m}\\mathbb{R}^{m\\times n} = \\mathbb{R}^{n\\times n}$). If$A$is full rank,$A^TAis also invertible." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### The second approach (algebraic)\n", "\n", "\n", "\\begin{align*}\n", " \\boldsymbol{y} &= X\\boldsymbol{w} \\\\\n", " X^{T}\\boldsymbol{y} &= X^{T}X\\boldsymbol{w} \\\\\n", " \\boldsymbol{w} &= {(X^{T}X)}^{-1}X^{T}\\boldsymbol{y} \n", "\\end{align*}\n", "\n", "\n", "Note that the second line multiplies both sides by the transpose ofX$from the left. Then, we can solve for$\\boldsymbol{w}$because$X^TX$is invertible.\n", "\n", "This is a valid algebraic approach." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Why don't we get the intuition behind the solution. Consider the linear system\n", "\n", "$$\\boldsymbol{y} = X\\boldsymbol{w}$$\n", "\n", "where\n", "\n", "$$\\boldsymbol{y} = \\begin{bmatrix}\n", "1 \\\\\n", "2 \\\\\n", "2\n", "\\end{bmatrix}\n", "\\quad\n", "X = \\begin{bmatrix}\n", "1 & 1 \\\\\n", "1 & 2 \\\\\n", "1 & 3\n", "\\end{bmatrix}\n", "\\quad\n", "\\boldsymbol{w} = \\begin{bmatrix}\n", "w_{1} \\\\\n", "w_{2}\n", "\\end{bmatrix}.$$\n", "\n", "This equation is saying that$\\boldsymbol{y}$is a linear combination of column vectors of$X$.\n", "\n", "$$\\begin{bmatrix}\n", "1 \\\\\n", "2 \\\\\n", "2\n", "\\end{bmatrix}\n", "= w_1\\begin{bmatrix}\n", "1 \\\\\n", "1 \\\\\n", "1\n", "\\end{bmatrix}\n", "+ w_2\\begin{bmatrix}\n", "1 \\\\\n", "2 \\\\\n", "3\n", "\\end{bmatrix}$$\n", "\n", "However, there are no such weights. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.5 Span\n", "With linear algebra's terminology,$\\boldsymbol{y}$doesn't lie in the column space of$X$, or the space that column vectors of$X$spans. Formally, \n", "\n", "> The span of a set of vectors$\\{\\boldsymbol{x}_1, \\boldsymbol{x}_2, \\dots, \\boldsymbol{x}_n\\}$is the set of all vectors that can be expressed as a linear combination of$\\{\\boldsymbol{x}_1, \\boldsymbol{x}_2, \\dots, \\boldsymbol{x}_n\\}$. That is,\n", ">\n", "> $$\\text{span}(\\{\\boldsymbol{x}_1, \\dots, \\boldsymbol{x}_n\\}) = \\Big\\{\\boldsymbol{v}:\\boldsymbol{v}=\\sum_{i=1}^{n}\\alpha_{i}\\boldsymbol{x}_{i}, \\alpha_{i}\\in\\mathbb{R}\\Big\\}.$$ (2)\n", "\n", "Especially, when$\\boldsymbol{x}$'s are the columns of a matrix$X$, their span is said to be the range or the column space of$X$and denoted$\\mathcal{R}(X)$.\n", "\n", "Back to the equation, although the target vector$\\boldsymbol{y}$is 3-dimensional, there are only two column vectors that span the space, i.e. the range of$X$is just a 2-dimensional plane. Therefore, there certainly exists 3-dimensional vectors that don't lie on this plane, like the$\\boldsymbol{y}$above. Visually, it looks something like below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Image('../images/linear-algebra/4.5.png', height=300, width=400)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But we want to represent$\\boldsymbol{y}$in terms of$\\boldsymbol{x}_i$'s. The best we can do is to find a vector that lies in the range of$X$, but is also as close to$\\boldsymbol{y}$as possible." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.6 Norm\n", "\n", "This objective can be formulated by using norm by saying to find$\\boldsymbol{w}$that minimizes$||y - X\\boldsymbol{w}||_2$. \n", "\n", "> A norm of a vector$\\Vert x\\Vert$is informally a measure of the “length” of the vector. For example, we have the commonly-used Euclidean or$\\ell_2$norm,\n", ">\n", "> $$\\Vert\\boldsymbol{x}\\Vert_2 = \\sqrt{\\sum_{i=1}^nx_i^2}.$$\n", ">\n", "> Note that$\\Vert \\boldsymbol{x}\\Vert_2^2 = \\boldsymbol{x}^T\\boldsymbol{x}$. (2)\n", "\n", "If you take the norm of difference of vectors, it is a measure of distance between them. There are several types of norms, but another popular one is$\\ell_1$norm. Given a vector$\\boldsymbol{x}\\in\\mathbb{R}^n$,\n", "\n", "$${\\Vert\\boldsymbol{x}\\Vert}_1 = \\sum_{i=0}^{n}|x_i|$$\n", "\n", "Let's use$\\ell_2$norm as a measure of distance for now. For convinience, we can minimize the square of$\\ell_2$norm without loss of generality. To find weights that minimizes$\\Vert\\boldsymbol{y} - X\\boldsymbol{w}\\Vert_2^2$, we can take its derivative with respect to$\\boldsymbol{w}$and set to zero. Easy, right?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.7 Gradient\n", "\n", "To this end, the notion of gradient, which is a natural extension of partial derivatives to a vector setting, comes in handy.\n", "\n", "> Suppose that$f:\\mathbb{R}^{m\\times n} \\rightarrow \\mathbb{R}$is a function that takes as input a matrix$A$of size$m\\times n$and\n", "returns a real value. Then the gradient of$f$(with respect to$A\\in\\mathbb{R}^{m\\times n}$) is the matrix of partial derivatives, defined as:\n", ">\n", ">\n", ">$$\n", "\\nabla_{A}f(A)\\in\\mathbb{R}^{m\\times n}\n", "=\n", "\\begin{bmatrix}\n", "\\frac{\\partial f(A)}{\\partial A_{11}} & \\frac{\\partial f(A)}{\\partial A_{12}} & \\dots & \\frac{\\partial f(A)}{\\partial A_{1n}} \\\\\n", "\\frac{\\partial f(A)}{\\partial A_{21}} & \\frac{\\partial f(A)}{\\partial A_{22}} & \\dots & \\frac{\\partial f(A)}{\\partial A_{2n}} \\\\\n", "\\vdots & \\vdots & \\ddots & \\vdots \\\\\n", "\\frac{\\partial f(A)}{\\partial A_{m1}} & \\frac{\\partial f(A)}{\\partial A_{m2}} & \\dots & \\frac{\\partial f(A)}{\\partial A_{mn}}\n", "\\end{bmatrix}\n", "$$\n", ">\n", "> i.e., an$m\\times n$matrix with\n", ">\n", "> $${(\\nabla_{A}f(A))}_{ij} = \\frac{\\partial f(A)}{\\partial A_{ij}}.$$\n", ">\n", "> Note that the size of$\\nabla_{A}f(A)$is always the same as the size of$A$. So if, in particular,$A$is just a vector$x\\in\\mathbb{R}^n$,\n", ">\n", "> $$\\nabla_{x}f(x)=\\begin{bmatrix}\n", "\\frac{\\partial f(x)}{\\partial x_{1}} \\\\\n", "\\frac{\\partial f(x)}{\\partial x_{2}} \\\\\n", "\\vdots \\\\\n", "\\frac{\\partial f(x)}{\\partial x_{mx}}\n", "\\end{bmatrix}$$ (2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**The third approach (analytical)**\n", "\n", "Let$RSS(\\boldsymbol{w})$denote$\\Vert y - X\\boldsymbol{w}\\Vert_2^2, meaning Resisual Sum of Squares.\n", "\n", "\n", "\\begin{align*}\n", "RSS(\\boldsymbol{w}) &= \\Vert\\boldsymbol{y} - X\\boldsymbol{w}\\Vert_2^2 \\\\\n", "&= {\\Bigg(\\sqrt{\\sum_{i}(y_i - \\boldsymbol{x}_i\\boldsymbol{w})^2}\\Bigg)}^2 \\\\\n", "&= \\sum_{i}{\\big(y_i - \\boldsymbol{x}_i\\boldsymbol{w}\\big)}^2\n", "\\end{align*}\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Take the gradient ofRSS(\\boldsymbol{w})$with respect to$\\boldsymbol{w}.\n", "\n", "\n", "\\begin{align*}\n", "\\frac{\\partial}{\\partial w_k}RSS(\\boldsymbol{w})\n", "&= \\frac{\\partial}{\\partial w_k}\\sum_{i}{\\big(y_i-\\boldsymbol{x}_{i}\\boldsymbol{w}\\big)}^2 \\\\\n", "&= \\frac{\\partial}{\\partial w_k}\\sum_{i}{\\big(y_i-\\sum_{j}x_{ij}w_j\\big)}^2 \\\\\n", "&= \\sum_{i}{\\frac{\\partial}{\\partial w_k}\\big(y_i-\\sum_{j}x_{ij}w_j\\big)}^2 \\\\\n", "&= \\sum_{i}{2\\big(y_i-\\sum_{j}x_{ij}w_j\\big)\\frac{\\partial}{\\partial w_k}\\big(y_i-\\sum_{j}x_{ij}w_j\\big)} \\\\\n", "&= -2\\sum_{i}{(y_i-\\boldsymbol{x}_{i}\\boldsymbol{w})x_{ik}} \\\\\n", "&= -2\\boldsymbol{x}_{:k}^T(\\boldsymbol{y}-X\\boldsymbol{w})\n", "\\end{align*}\n", "\n", "\n", "Note that\\boldsymbol{x}_{:k}$denotes k-th column vector of$X$. Also,${x}_{:k}^T$is a row vector and$(\\boldsymbol{y}-X\\boldsymbol{w})is a column vector, so the last line calculates the dot product. This result lead us to the following gradient expression.\n", "\n", "$$\\nabla_w RSS(\\boldsymbol{w}) = -2X^T(\\boldsymbol{y} - X\\boldsymbol{w}).$$" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Setting the gradient to a zero vector.\n", "\n", "\n", "\\begin{align*}\n", "\\nabla_w RSS(\\boldsymbol{w}) &= \\boldsymbol{0} \\\\\n", "-2X^T(\\boldsymbol{y} - X\\boldsymbol{w}) &= \\boldsymbol{0} \\\\\n", "X^T\\boldsymbol{y} &= X^TX\\boldsymbol{w}\n", "\\end{align*}\n", "\n", "\n", "$$\\boldsymbol{w} = {(X^TX)}^{-1}X^T\\boldsymbol{y}$$\n", "\n", "We reached to the same solution as the algebraic approach." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.8 Matrix calculus\n", "\n", "Don't worry, you don't have to calculate this all the time. The *gradient* is a special case of matrix calculus, where we take the derivative of a scalar function with respect to a vector. Similarly, there are cases we take the derivative of vector with respect to vector and derivative of scalar function with respect to matrix, etc. Fortunatelly, matrix/vector calculus can be done by natural analogies of multivariable calculus, and here are some formulas that we can use.\n", "\n", "\\begin{array}{c c}\n", "f & \\frac{\\partial f}{\\partial x} \\\\\n", "\\hline\n", "A\\boldsymbol{x} & A^T \\\\\n", "\\boldsymbol{x}^TA & A \\\\\n", "\\boldsymbol{x}^T\\boldsymbol{x} & 2\\boldsymbol{x} \\\\\n", "\\boldsymbol{x}^TA\\boldsymbol{x} & A\\boldsymbol{x}+A^T\\boldsymbol{x} \\\\\n", "\\end{array}\n", "\n", "Therefore,\n", "\n", "\n", "\\begin{align*}\n", "\\frac{\\partial}{\\partial\\boldsymbol{w}}RSS(\\boldsymbol{w})\n", "&= \\frac{\\partial}{\\partial\\boldsymbol{w}}\\Vert\\boldsymbol{y} - X\\boldsymbol{w}\\Vert_2^2 \\\\\n", "&= \\frac{\\partial}{\\partial\\boldsymbol{w}}{(\\boldsymbol{y} - X\\boldsymbol{w})}^T(\\boldsymbol{y} - X\\boldsymbol{w}) \\\\\n", "&= 2(\\boldsymbol{y} - X\\boldsymbol{w})\\frac{\\partial}{\\partial\\boldsymbol{w}}(\\boldsymbol{y} - X\\boldsymbol{w}) \\\\\n", "&= -2X^T(\\boldsymbol{y} - X\\boldsymbol{w}) \\\\\n", "\\end{align*}\n", "\n", "\n", "The rest is the same." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.9 Dot products\n", "\n", "I will introduce yet another approach to the linear regression, which is geometric.\n", "\n", "It's kind of silly to introduce dot product (also known as inner product) now since we have been using it extensively, but I wanted to make sure that we have the same picture in our mind. The dot product of two vectors can be obtained by taking their correxponding elements, multiply them, and add them together. For example, given\n", "\n", "$$\n", "\\boldsymbol{u}\n", "=\n", "\\begin{bmatrix}\n", "u_1 \\\\\n", "u_2\n", "\\end{bmatrix}\n", "\\quad\n", "\\boldsymbol{v}\n", "=\n", "\\begin{bmatrix}\n", "v_1 \\\\\n", "v_2\n", "\\end{bmatrix}\n", ",\n", "$$\n", "\n", "$$\n", "\\boldsymbol{u}\\cdot\\boldsymbol{v}\n", "=\n", "\\begin{bmatrix}\n", "u_1 \\\\\n", "u_2\n", "\\end{bmatrix}\n", "\\cdot\n", "\\begin{bmatrix}\n", "v_1 \\\\\n", "v_2\n", "\\end{bmatrix}\n", "=\n", "u_1v_1+u_2v_2\n", "$$\n", "\n", "which is in general case where\\mathbf{u},\\mathbf{v}\\in\\mathbb{R}^{n}$,\n", "\n", "$$\\sum_{i=1}^{n}u_{i}v_{i}.$$\n", "\n", "So, dot product seems to be an operator that maps vectors to a scalar value, but how can we interpret it?\n", "\n", "> To tackle this question I'm going to present an alternative (but equivalent!) way to define the dot product: given vectors$\\boldsymbol{u}$and$\\boldsymbol{v}$, let$\\thetabe the angle between them, and define their dot product to be:\n", "> \n", "> $$\\boldsymbol{u}\\cdot\\boldsymbol{v}=\\Vert\\boldsymbol{u}\\Vert\\Vert\\boldsymbol{v}\\Vert \\text{cos}\\theta.$$ (2)\n", "\n", "Note that the dot product is represented in terms of the magnitude of the vectors and the angle between them. This is the basis of our geometric intuition. If you want to learn why these definitions are equivalent (the duality of dot products), check out [this episode](https://www.youtube.com/watch?v=LyGKycYT2v0&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab&index=9) in [Essense of linear algebra](https://www.youtube.com/watch?v=kjBOesZCoqc&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab) series [3].\n", "\n", "Consider the following vector pairs." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Image('../images/linear-algebra/4.9.png')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Recall that\n", "\n", "$$\\text{cos}0=1\\quad\\text{cos}\\frac{\\pi}{4}=\\frac{1}{\\sqrt{2}}\\quad\\text{cos}\\frac{\\pi}{2}=0\\quad\\text{cos}\\pi=-1.$$\n", "\n", "Therefore, the dot products of the given vector pairs are, respectively,\n", "\n", "$$\\Vert\\boldsymbol{v}\\Vert\\Vert\\boldsymbol{u}\\Vert\\quad\\frac{1}{\\sqrt{2}}\\Vert\\boldsymbol{v}\\Vert\\Vert\\boldsymbol{u}\\Vert\\quad 0\\quad -\\Vert\\boldsymbol{v}\\Vert\\Vert\\boldsymbol{u}\\Vert.$$\n", "\n", "Notice that the dot product is maximized when the two vectors are perfectly aligned (\\text{argmax}_\\theta(\\text{cos}\\theta)=0$) and gets smaller as their discrepancy grows. In this sense, dot product works as a measure of the similarity between vectors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.10 Projections\n", "\n", "Now, let me introduce projection. I assume you have learned them in geometry or physics. Basically, we want to give a name to the following component." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Image('../images/linear-algebra/4.10.png', height=300, width=400)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The formal definition is the following.\n", "\n", "> The projection of a vector$y\\in\\mathbb{R}^m$onto the span of$\\{\\boldsymbol{x}_1, \\dots, \\boldsymbol{x}_n\\}$(here we assume$\\boldsymbol{x}_i\\in\\mathbb{R}^m$) is the vector$\\boldsymbol{v}\\in\\text{span}(\\boldsymbol{x}_1,\\dots,\\boldsymbol{x}_n\\})$, such that$\\boldsymbol{v}$is as close as possible to$\\boldsymbol{y}$, as measured by the Euclidean norm$\\Vert \\boldsymbol{v}-\\boldsymbol{y}\\Vert_2$. We denote the projection as$Proj(\\boldsymbol{y};\\{\\boldsymbol{x}_1,\\dots,\\boldsymbol{x}_n\\})$and can define it formally as,\n", ">\n", "> $$\\text{Proj}(\\boldsymbol{y}; \\{\\boldsymbol{x}_1,\\dots,\\boldsymbol{x}_n\\})=\\text{argmin}_{v\\in\\text{span}(\\{\\boldsymbol{x}_1,\\dots,\\boldsymbol{x}_n\\})}\\Vert\\boldsymbol{y}-\\boldsymbol{v}\\Vert_2.$$ (2)\n", "\n", "When we project onto the span of single vector, like the above image, the projection is calculated by\n", "\n", "$$\\text{Proj}(\\boldsymbol{v}, \\boldsymbol{u}) =\\Vert\\boldsymbol{v}\\Vert\\text{cos}\\theta\\frac{\\boldsymbol{u}}{\\Vert\\boldsymbol{u}\\Vert}.$$\n", "\n", "Notice that projection is a vector, i.e. it has a direction. The$\\Vert\\boldsymbol{v}\\Vert\\text{cos}\\theta$part defines the length of projection and the$\\frac{\\boldsymbol{u}}{\\Vert\\boldsymbol{u}\\Vert}$part defines its direction. Using the definition of dot products,\n", "\n", "$$\\text{Proj}(\\boldsymbol{v}, \\boldsymbol{u}) =\\Vert\\boldsymbol{v}\\Vert\\text{cos}\\theta\\frac{\\boldsymbol{u}}{\\Vert\\boldsymbol{u}\\Vert}\n", "=\\frac{\\boldsymbol{u}\\cdot\\boldsymbol{v}}{\\Vert\\boldsymbol{u}\\Vert}\\frac{\\boldsymbol{u}}{\\Vert\\boldsymbol{u}\\Vert}$$\n", "\n", "If$\\boldsymbol{u}$is a unit vector,$\\Vert\\boldsymbol{u}\\Vert=1$, therefore,\n", "$$\\text{Proj}(\\boldsymbol{v}, \\boldsymbol{u})=(\\boldsymbol{u}\\cdot\\boldsymbol{v})\\boldsymbol{u}.$$\n", "\n", "> It turns out that this is a very useful construction.\n", "> For example, projections give us a way to make orthogonal things.\n", "> By the nature of “projecting” vectors, if we connect the endpoints of$\\boldsymbol{v}$with its projection$\\text{Proj}(\\boldsymbol{v},\\boldsymbol{u})$, we get a vector orthogonal to our reference direction$\\boldsymbol{u}$.\n", "> In other words, the vector$\\boldsymbol{v}-\\text{Proj}(\\boldsymbol{v},\\boldsymbol{u})$is orthogonal to$\\boldsymbol{u}$. [1]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### 4.11 Orthogonality\n", "\n", "When we say things are *orthogonal*, it basically means they are perpendicular. But the notion is more general and can be extended to higher dimensional spaces. The formal definition is the following.\n", "\n", "> Two vectors$\\boldsymbol{x},\\boldsymbol{y}\\in\\mathbb{R}^n$are orthogonal if$\\boldsymbol{x}^T\\boldsymbol{y} = 0$. A vector \n", "$\\boldsymbol{x}\\in\\mathbb{R}^n$is normalized if\n", "$\\Vert x\\Vert_2=1$. A square matrix$U\\in\\mathbb{R}^{n\\times n}$is orthogonal (note the different meanings when\n", "talking about vectors versus matrices) if all its columns are orthogonal to each other and are normalized (the columns are then referred to as being orthonormal). [2]\n", "\n", "Note that if a vector$\\boldsymbol{v}\\in\\mathbb{R}^{n}$is in the span of orthogonal vectors$\\boldsymbol{u}_1, \\boldsymbol{u}_2, \\dots, \\boldsymbol{u}_n$, then$\\boldsymbol{v}$can be represented as the sum of projections onto$\\boldsymbol{u}_i$s, that is\n", "\n", "$$\\boldsymbol{v}=\\sum_{i=1}^{n}\\text{Proj}(\\boldsymbol{v},\\boldsymbol{u}_i).$$\n", "\n", "Essentially, what this means is that you can reconstruct a vector from its projections as visualized below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Image('../images/linear-algebra/4.11.1.png')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you can have as many linearly independent projections as the dimension of the target vector, you can perfectly reconstruct the original vector. What if we can't? Notice the similarity of this question and linear regression. The problem of linear regression is that$\\boldsymbol{y}$is not in the range of$X$.\n", " \n", "The geometric approach to the linear regression model is to get the projection of$\\boldsymbol{y}$onto the range of$X$like the image below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Image('../images/linear-algebra/4.11.2.png', height=300, width=400)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The component of$\\boldsymbol{y}$which is orthogonal to the range of$X$is the difference between$\\boldsymbol{y}$and$X\\boldsymbol{w}$.\n", "\n", "$$\\boldsymbol{y}-X\\boldsymbol{w}$$\n", "\n", "We know this vector is orthogonal to any vector on the plane including all the column vectors of$X. It means that their dot product is zero. Let's formulate the problem.\n", "\n", "\n", "\\begin{align*}\n", "\\forall\\boldsymbol{x}\\in\\mathcal{R}(X)\\quad &\n", "\\boldsymbol{x} \\perp (\\boldsymbol{y} - X\\boldsymbol{w}) \\\\\n", "\\forall\\boldsymbol{x}\\in\\mathcal{R}(X)\\quad &\n", "\\boldsymbol{x}\\cdot(\\boldsymbol{y} - X\\boldsymbol{w})=0\n", "\\end{align*}\n", "\n", "\n", "\n", "\\begin{align*}\n", "X^T(\\boldsymbol{y} - X\\boldsymbol{w}) &= \\boldsymbol{0} \\\\\n", "X^T\\boldsymbol{y} - X^T X\\boldsymbol{w} &= \\boldsymbol{0} \\\\\n", "\\end{align*}\n", "\n", "\n", "$$\\boldsymbol{w} = {(X^T X)}^{-1}X^T\\boldsymbol{y}$$\n", "\n", "This solution by geometric approach is exactly the same as the algebraic and analytical approach." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Discussion and conclusions\n", "\n", "We reviewed the basic operations and properties of linear algebra through the example of linear regression model. On the way, we have shown that the solution for the linear regression is\\boldsymbol{w}={(X^T X)}^{-1}X^T\\boldsymbol{y}\\;$by three different approaches: algebraic, analytical, and geometric.\n", "\n", "Finally, by using Python we get the following solution." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = boston.data\n", "y = boston.target\n", "w = np.linalg.inv(np.dot(X.T, X)).dot(np.dot(X.T, y))\n", "print(w)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can test the accuracy of the linear regression estiamtor with these weights by using a new dataset called testing dataset, which is out of scope of this review. Unfortunately, however, this solution is not practical. First, np.linalg.inv should not be used to solve systems of linear equations with matrices. It's more efficient and stable to use np.linalg.solve. Please refer to the Appendix D for more details. Second, even with np.linalg.solve, solving linear systems in closed form is prohibitively expensive ($\\mathcal{O}(n^3)$) for non-trivial problems. For problems of the scale that we handle in machine learning, iterative methods, like gradient descent and its variants, should be used. To learn more about linear algebra in general, please refer to the Appendix A." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Appendix\n", "\n", "### A. Linear algebra visualized\n", "\n", "Are you curious why matrices have to be full rank to be invertible? What it means to multiply a vector by a matrix and to multiply matrices by their inverse? Watch these videos. These are some selections from a short course, [Essence of linear algebra](https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab) [3]. Grant Sanderson (the author) visualized major concepts of linear algebra very very very well.\n", "\n", "- [Linear transformations and matrices | Essence of linear algebra, chapter 3](https://www.youtube.com/watch?v=kYB8IZa5AuE)\n", "- [Inverse matrices, column space and null space | Essence of linear algebra, chapter 7](https://www.youtube.com/watch?v=uQhTuRlWMxw)\n", "- [Dot products and duality | Essence of linear algebra, chapter 9\n", "](https://www.youtube.com/watch?v=LyGKycYT2v0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### B. Transpose and 1-dimensional arrays in NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You might be wondering why vectors are printed horizontally in NumPy. When 1d arrays are used, NumPy does not distinguish column vectors and row vectors unlike mathematical expressions. To see this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = np.array(np.arange(0, 3))\n", "at = a.transpose()\n", "print(a, at)\n", "print(a.shape, at.shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, they are just 1d arrays. To create a column vector, you need to explicitly create an$n \\times 1$matrix, i.e. 2d array." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = np.array([[i] for i in range(3)])\n", "at = a.T\n", "\n", "print('column vector')\n", "print(a)\n", "print(a.shape)\n", "\n", "print('\\ntranspose')\n", "print(at)\n", "print(at.shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that in order to access the$i$th element of a column vector$\\boldsymbol{y}$, we need to use y[i][0], not y[i]. Also, it's relatively tricky to index arrays with (n, 1) array as opposed to (n,) array. These subtle differences often cause bugs. Therefore, although we use column vectors in mathematical expression, I recommend to use (n,) array in code unless you really need (n, 1) array. In case you want to collapse dimensions, you can use ravel() method. When you want to turn (n,) array to (n, 1) array, there are several ways as shown below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a = np.array([[i] for i in range(3)])\n", "print('2d')\n", "print(a)\n", "\n", "a = a.ravel()\n", "print('\\n1d')\n", "print(a)\n", "\n", "b = np.atleast_2d(a).T\n", "c = a[:, np.newaxis]\n", "print('\\n2d')\n", "print(b)\n", "print(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Read the [indexing document](https://docs.scipy.org/doc/numpy-1.13.0/reference/arrays.indexing.html) to learn more. It's powerful!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### C. Outer products in NumPy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The outer product$\\boldsymbol{u}\\otimes\\boldsymbol{v}$is defined as the vector multiplication$\\boldsymbol{u}\\boldsymbol{v}^{T}$, where$\\boldsymbol{u}\\in\\mathbb{R}^{m}$and$\\boldsymbol{v}\\in\\mathbb{R}^{n}$are column vectors. Note that$\\boldsymbol{v}^{T}$is a row vector. For example, if$m=3$and$n=2$, then\n", "\n", "$$\\boldsymbol{u}\\otimes\\boldsymbol{v}=\\boldsymbol{u}\\boldsymbol{v}^{\\mathrm{T}}=\n", "\\begin{bmatrix}\n", "u_{1} \\\\\n", "u_{2} \\\\\n", "u_{3}\n", "\\end{bmatrix}\n", "\\begin{bmatrix}v_{1} & v_{2}\\end{bmatrix}=\n", "\\begin{bmatrix}\n", "u_{1}v_{1}&u_{1}v_{2} \\\\\n", "u_{2}v_{1}&u_{2}v_{2} \\\\\n", "u_{3}v_{1}&u_{3}v_{2}\n", "\\end{bmatrix}.$$\n", "\n", "Or generally,$\\boldsymbol{u}\\otimes\\boldsymbol{v}$is a$m\\times n$matrix whose entries are given by,\n", "\n", "$$(\\boldsymbol{u}\\otimes\\boldsymbol{v})_{ij}=u_{i}v_{j}.$$\n", "\n", "To calculate this, you might want to do something like the following." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "u = np.array([1, 2, 3])\n", "v = np.array([2, 2])\n", "try:\n", " print(np.dot(u, v.T))\n", "except ValueError as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I told you, it doesn't work. Why? because the u and v in the above code are not column vectors. They are just arrays so that you cannot transpose them. There are basically two ways to handle this.\n", "\n", "1. Explicitly add another dimension to$u$and$v$.\n", "2. Leave them as 1d arrays and use outer method provided by NumPy." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Option 1 \n", "u = np.array([1, 2, 3])[:, np.newaxis]\n", "v = np.array([2, 2])[:, np.newaxis]\n", "print(u.shape, v.T.shape)\n", "print(np.dot(u, v.T))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Option 2\n", "u = np.array([1, 2, 3])\n", "v = np.array([2, 2])\n", "print(u.shape, v.shape)\n", "print(np.outer(u, v))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You know which I recommend:)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### D. np.linalg.inv, np.linalg.solve, and np.linalg.lstsq" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This (https://stackoverflow.com/questions/31256252/why-does-numpy-linalg-solve-offer-more-precise-matrix-inversions-than-numpy-li) StackOverflow answer describes the problem well. In short, np.linalg.inv involves steps for solving for$A^{-1}$that is not necessary to solve the systems of linear equations. These extra steps make the solution not only inefficient but also numerically unstable if$A\$ is ill-conditioned matrix. \n", "\n", "At this point, you could also use np.linalg.lstsq. This solver uses SVD under the hood where np.linalg.solve uses LU decompoition. The implication of such difference is that np.linalg.solve is cheaper, but it can only handle square and full rank matrix, while np.linalg.lstsq can deal with over/under determined cases." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "X = boston.data\n", "y = boston.target\n", "\n", "# np.linalg.inv\n", "start = time.time()\n", "inv_w = np.linalg.inv(np.dot(X.T, X)).dot(np.dot(X.T, y))\n", "inv_time = time.time() - start\n", "\n", "# np.linalg.solve\n", "start = time.time()\n", "solve_w = np.linalg.solve(np.dot(X.T, X), np.dot(X.T, y))\n", "solve_time = time.time() - start\n", "\n", "# np.linalg.lstsq\n", "start = time.time()\n", "lstsq_w, _, _, _ = np.linalg.lstsq(X, y, rcond=None)\n", "lstsq_time = time.time() - start\n", "\n", "print(\">>inv\")\n", "print(\"time\", inv_time)\n", "print(\"solution\", inv_w)\n", "\n", "print(\"\\n>>solve\")\n", "print(\"time\", solve_time)\n", "print(\"solution\", solve_w)\n", "\n", "print(\"\\n>>lstsq\")\n", "print(\"time\", lstsq_time)\n", "print(\"solution\", lstsq_w)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Reference\n", "\n", "[1] Breen, J., 2015. Understanding the Dot Product and the Cross Product. \n", "Available online: http://www.math.ucla.edu/~josephbreen/Understanding_the_Dot_Product_and_the_Cross_Product.pdf \n", "[2] Kolter, Z., 2008. Linear Algebra Review and Reference. \n", "Available online: http://cs229.stanford.edu/section/cs229-linalg.pdf \n", "[3] Sanderson, G. (3Blue1Brown), 2016. Essence of linear algebra. \n", "Available online: https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.3" } }, "nbformat": 4, "nbformat_minor": 2 }