Skip to article content

Neural Networks

Back to Article
Linear Models
Download Notebook

Linear Models

Notebook Cell
# we will need matplotlib for plotting
import matplotlib.pyplot as plt
import numpy as np

%matplotlib inline

Linear algebra

Open in Binder

  1. Linear algebra is the study of vectors and certain rules to manipulate vectors.

Equation of a 2D line

There are two equations of lines

  1. y=mx+cy = m x + c

  2. ax+by+c=0a x + b y + c = 0

What are the advantages or disadvantages of both of those?

A 2D line is the set of all points (x, y) that satisfy the an equation ax+by+c=0a x + b y + c = 0 for given a,b,ca, b, c. For the same line there can be multiple valid parameters a,b,ca, b , c. Either aa or bb can be zero. But both aa and bb cannot be zero at the same time.

Ideally the equation of line must be written in the set notation:

L(a,b,c)={(x,y):ax+by+c=0,xR,yR}\calL(a, b, c) = \{(x, y): ax + by + c = 0, x \in \bbR, y \in \bbR \}

This equation is read as: the line L\mathcal{L} defined by the paramters a,b,ca,b,c is the set of all points (x,y)(x, y) such that it the x,yx,y satisfy the equation ax+by+c=0ax + by + c = 0 and xx and yy are in the set of all real numbers.

Implicit form

Line is a type of curve. Other curves can be parabola, circle etc. Every curve can be represented in implicit form like we did for the line above. In implicit form, the points are constrained by one or more equations to lie on the curve. The equations define a test whether the point lies on the curve or not.

C(p1,p2,,pn)={(x,y):f(x,y;p1,p2,,pn)=0,xR,yR}\calC(p_1, p_2, \dots, p_n) = \{(x, y): f(x, y; p_1, p_2, \dots, p_n) = 0, x \in \bbR, y \in \bbR\}

This equation is read as the curve C\calC defined by the paramters p1,p2,,pnp_1, p_2, \dots, p_n is the set of all points (x,y)(x, y) such that it the x,yx,y satisfy the equation f(x,y;p1,p2,,pn)=0f(x, y; p_1, p_2, \dots, p_n) = 0 and xx and yy are in the set of all real numbers.

Take circle with center (x0,y0)(x_0, y_0) and radius rr, as an example. The implicit form is:

C(x0,y0,r)={(x,y):(xx0)2+(yy0)2r2=0,xR,yR}\calC(x_0, y_0, r) = \{(x, y): (x-x_0)^2 + (y-y_0)^2 - r^2 = 0, x \in \bbR, y \in \bbR\}
Paramteric form of 2D Line

A parameteric form defines how the points on the curve are generated from a free paramters. For a line:

L(dx,dy,x0,y0)={(dxr+x0,dyr+y0):rR}\calL(d_x, d_y, x_0, y_0) = \{(d_xr+x_0, d_yr+y_0): r \in \bbR \}

This equation is read as: the line L\calL defined by the paramters dx,dy,x0,y0d_x,d_y,x_0,y_0 is the set of all points (dxr+x0,dyr+y0)(d_xr+x_0, d_yr+y_0) such that rr is any real number.

Take circle with center (x0,y0)(x_0, y_0) and radius rr, as an example. The parameteric form is:

C(x0,y0,r)={(rcos(θ)+x0,rsin(θ)+y0):θ[0,2π)}\calC(x_0, y_0, r) = \{(r\cos(\theta) + x_0, r\sin(\theta) + y_0): \theta \in [0, 2\pi)\}

In general, the paramteric form of a curve depend on some free paramters and the points are defined as functions of the free parameters.

Linear system of equations and Matrices

4x1+4x2=52x14x2=1\begin{align} 4 x_1 + 4 x_2 &= 5 \\ 2 x_1 - 4 x_2 &= 1 \end{align}

The equation

[4424]AR2×2[x1x2]xR2=[51]bR2,\begin{align} \underbrace{\begin{bmatrix} 4 & 4 \\ 2 & -4 \end{bmatrix}}_{A \in \mathbb{R}^{2 \times 2}} \underbrace{ \begin{bmatrix} x_1 \\ x_2 \end{bmatrix}}_{\mathbf{x} \in \mathbb{R}^2} = \underbrace{\begin{bmatrix} 5 \\ 1 \end{bmatrix}}_{\mathbf{b} \in \mathbb{R}^2}, \end{align}

can be written in matrix form as,

Ax=bA \mathbf{x} = \mathbf{b}

In general a system of mm equations with nn unknowns,

a11x1+a12x2++a1nxn=b1am1x1+am2x2++amnxn=bm\begin{align} a_{11} x_1 + a_{12} x_2 + \dots + a_{1n} x_n &= b_1 \\ \vdots \\ a_{m1} x_1 + a_{m2} x_2 + \dots + a_{mn} x_n &= b_m \end{align}

can be written in matrix form as

[a11a1nam1amn]ARm×n[x1xn]xRn=[b1bm]bRm.\begin{align} \underbrace{\begin{bmatrix} a_{11} & \dots & a_{1n} \\ \vdots & \ddots & \vdots \\ a_{m1} & \dots & a_{mn} \end{bmatrix}}_{A \in \mathbb{R}^{m \times n}} \underbrace{ \begin{bmatrix} x_1 \\ \vdots \\ x_n \end{bmatrix}}_{\mathbf{x} \in \mathbb{R}^n} = \underbrace{\begin{bmatrix} b_1 \\ \vdots \\ b_m \end{bmatrix}}_{\mathbf{b} \in \mathbb{R}^m}. \end{align}

Or

Ax=bA \mathbf{x} = \mathbf{b}

Vectors

We will denote vectors with bold font notations instead of the usualy x\vec{x} notation. The arrow notation vectors are sometimes called geometric vectors. We will make no such distinction. The set of all real numbers will be denoted as R\bbR. The set of all real 2D vectors is written as R2\bbR^2. When we write xR2\bfx \in \bbR^2, it means that x\bfx is in the set of real 2D vectors, hence a 2D real vector. We will write x\|\bfx\| for the magnitude of the vector, and xy\bfx \cdot \bfy for dot product between two vector x\bfx and y\bfy.

n-D vector

A n-D vector is also written as xRn\bfx \in \bbR^n and the vector has x=[x1;;xn]\bfx = [x_1; \dots; x_n] nn real components. Every vector has a magnitude x\|\bfx\| and a direction x^\hat{\bfx}. The magnitude and direction are given by:

x=x12+x22++xnnR\|\bfx\| = \sqrt{x_1^2 + x_2^2 + \dots + x_n^n} \in \bbR
x^=1xxRn\hat{\bfx} = \frac{1}{\|\bfx\|} \bfx \in \bbR^n

The direction vector x^\hat{\bfx} is a unit vector because its magnitide is one i.e. x^=1\|\hat{\bfx}\| = 1.

x = np.array([2, 3, 4, 5, 9, 10])
xmag = np.linalg.norm(x)
xunit = x / xmag

# For many many x vectors you can do this:
xs = np.array([[2, 3, 4, 5, 9, 10],
               [-3, 2, -5, 4, -10, 9]]) # 2 x 6
xmags = np.linalg.norm(xs, axis=-1, keepdims=True) # 2 x 1
xunits = xs / xmags # 2 x 6
print(xunit, xunits)
[0.13046561 0.19569842 0.26093123 0.32616404 0.58709527 0.65232807] [[ 0.13046561  0.19569842  0.26093123  0.32616404  0.58709527  0.65232807]
 [-0.19569842  0.13046561 -0.32616404  0.26093123 -0.65232807  0.58709527]]
Vector addition

Vector addition is element-wise addition

v+w=[v1vn]+[w1wn]=[v1+w1vn+wn]\bfv + \bfw = \begin{bmatrix} v_1 \\ \vdots \\ v_n \end{bmatrix} + \begin{bmatrix} w_1 \\ \vdots \\ w_n \end{bmatrix} = \begin{bmatrix} v_1 + w_1 \\ \vdots \\ v_n + w_n \end{bmatrix}

Geometrically the resulting vector can be obtained by triangle law or the parallelogram law.

Reference: [1]

v = np.arange(0, 10)
w = np.arange(10, 20)
v+w
array([10, 12, 14, 16, 18, 20, 22, 24, 26, 28])

Dot product of vectors

Dot product of two vectors is a scalar given by sum of element-wise product.

vu=[v1vn][u1un]=v1u1+v2u2++vnun\bfv \cdot \bfu = \begin{bmatrix} v_1 \\ \vdots \\ v_n \end{bmatrix} \cdot \begin{bmatrix} u_1 \\ \vdots \\ u_n \end{bmatrix} = v_1 u_1 + v_2 u_2 + \cdots + v_n u_n

Geometrically, dot product is closely related to the projection. Projection of vector v\bfv on u\bfu is the dot product of v\bfv with the direction of u\bfu

projuv=vu^\text{proj}_{\bfu}\bfv = \bfv \cdot \hat{\bfu}

Dot product of vector with itself gives the square of the magnitude vv=v2\bfv \cdot \bfv = \|\bfv\|^2.

Reference: [2]

v = np.arange(10)
u = np.arange(10, 20)
v @ u # v . u
np.int64(735)
v @ u / np.linalg.norm(u)
np.float64(15.723948706640511)
np.allclose(v @ v , np.linalg.norm(v)**2)
True

Matrices

Matrices are a group of vectors. A matrix can be obtained by vertical stacking of row (horizontal) vectors or horizontal stacking of column (vertical) vectors. It is common to represent all vectors as column vectors unless specified otherwise, so we consider a matrix V\bfV as a horizontal concatenation of column vectors v1,v2,,vn\bfv_1, \bfv_2, \dots, \bfv_n. Let each vector be m-dimensional viRm\bfv_i \in \bbR^m.

V=[v1v2vn]=[v1[1]v2[1]vn[1]v1[2]v2[2]vn[2]v1[m]v2[m]vn[m]]\bfV = \begin{bmatrix}\bfv_1 & \bfv_2 & \dots & \bfv_n\end{bmatrix} = \begin{bmatrix} \bfv_1[1]& \bfv_2[1] & \dots & \bfv_n[1]\\ \bfv_1[2]& \bfv_2[2] & \dots & \bfv_n[2]\\ \vdots & \vdots & \ddots & \vdots\\ \bfv_1[m] & \bfv_2[m] & \dots & \bfv_n[m] \end{bmatrix}

Such a matrix is said to be a m×nm \times n matrix. It is also written as VRm×n\bfV \in \bbR^{m \times n}.

v1 = np.arange(5)
v2 = np.arange(5, 10)
v3 = np.arange(10, 15)
V = np.hstack((v1[:, None], v2[:, None], v3[:, None]))
V
array([[ 0, 5, 10], [ 1, 6, 11], [ 2, 7, 12], [ 3, 8, 13], [ 4, 9, 14]])

Linear functions

What are linear functions?

Definition of Linear combination (2.11 in MML book)

Given mm vectors x1,x2,,xmRn\bfx_1, \bfx_2, \dots, \bfx_m \in \bbR^n, a linear combination of vectors is defined as, v:=α1x1+α2x2++αnxm \mathbf{v} := \alpha_1 \bfx_1 + \alpha_2 \bfx_2 + \dots + \alpha_n \bfx_m for every any scalar α1,α2,αnR\alpha_1, \alpha_2, \dots \alpha_n \in \bbR

Definition of Linear function

A function f:RnRmf: \mathbb{R}^n \to \mathbb{R}^m is linear in x\mathbf{x} when

  1. f(αx)=αf(x)f(\alpha\mathbf{x}) = \alpha f(\mathbf{x}) for all αR\alpha \in \mathbb{R} and xRn\mathbf{x} \in \mathbb{R}^n

  2. f(x+y)=f(x)+f(y)f(\mathbf{x} + \mathbf{y}) = f(\mathbf{x}) + f(\mathbf{y}) for all α,βR\alpha, \beta \in \mathbb{R} and x,yRn\mathbf{x}, \mathbf{y} \in \mathbb{R}^n

The above two properties can be combined in a single test

f(αx+βy)=αf(x)+βf(y)f(\alpha \mathbf{x} + \beta \mathbf{y}) = \alpha f(\mathbf{x}) + \beta f(\mathbf{y})
Examples of linear functions
  1. f([x1,x2])=3x1+2x2 f([x_1, x_2]) = 3x_1 + 2x_2

  2. f([x1,x2,x3])=4(3x1+2x2)+x3 f([x_1, x_2, x_3]) = 4(3x_1 + 2x_2) + x_3

  3. f([x1,x2,x3],[w1,w2,w3])=w1x1+w2x2+w3x3 f([x_1, x_2, x_3], [w_1, w_2, w_3]) = w_1x_1 + w_2x_2 + w_3 x_3

  4. f([x1,x2])=exp(log(3x1+3x2)) f([x_1, x_2]) = \exp(\log(3x_1 + 3x_2))

  5. f([x1,x2])=((3x1+3x2)2)(1/2) f([x_1, x_2]) = ((3x_1 + 3x_2)^2)^{(1/2)}

Homework (Linear Models). Problem 1

(10 marks) Show that the above functions are linear.

Examples of non-linear functions
  1. f([x1,x2])=x12 f([x_1, x_2]) = x_1^2

  2. f([x1,x2])=x13 f([x_1, x_2]) = x_1^3

  3. f([x1,x2])=sin(x1) f([x_1, x_2]) = \sin(x_1)

  4. f([x1,x2])=exp(x1) f([x_1, x_2]) = \exp(x_1)

  5. f([x1,x2])=log(x1) f([x_1, x_2]) = \log(x_1)

Homework (Linear Models). Problem 2

(10 marks) Show that the above functions are non-linear.

def f(x):
    return 4*(3*x[0] + 2*x[1]) + x[2]
x = np.random.rand(3)
y = np.random.rand(3)
alpha = np.random.rand()
beta = np.random.rand()
np.allclose(f(alpha * x + beta * y) ,
            alpha * f(x) + beta * f(y))
True

Properties of Matrices

Transpose of a Matrix

Transpose of a matrix V\bfV (denoted as V\bfV^\top) is an operation that swaps rows with columns and columns with rows. For example, the transpose of the above matrix will make it a vertical concatenation of row vectors.

V=[v1v2vn]=[v1[1]v1[2]vn[m]v2[1]v2[2]vn[m]vn[1]v2[2]vn[m]]\bfV^\top = \begin{bmatrix}\bfv_1^\top \\ \bfv_2^\top \\ \vdots \\ \bfv_n^\top\end{bmatrix} = \begin{bmatrix} \bfv_1[1]& \bfv_1[2] & \dots & \bfv_n[m]\\ \bfv_2[1]& \bfv_2[2] & \dots & \bfv_n[m]\\ \vdots & \vdots & \ddots & \vdots\\ \bfv_n[1] & \bfv_2[2] & \dots & \bfv_n[m] \end{bmatrix}

If VRm×n\bfV \in \bbR^{m \times n}, then VRn×m\bfV^\top \in \bbR^{n \times m}. The two dimensions get swapped.

V = np.arange(12).reshape(3, 4)
V, V.T
(array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]), array([[ 0, 4, 8], [ 1, 5, 9], [ 2, 6, 10], [ 3, 7, 11]]))

Tranpose of a column vector

All vectors are also matrices. By convention, all vectors are considered column matrices and hence called column vectors. A n-D vector v=[v1;;vn]Rn\bfv = [v_1; \dots; v_n] \in \bbR^n is by convention considered a n×1n \times 1 column matrix i.e. vRn×1\bfv \in \bbR^{n \times 1}.

v=[v1v2vn]Rn×1\bfv = \begin{bmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{bmatrix} \in \bbR^{n\times 1}

The transpose of a column vector is a row vector

v=[v1v2vn]R1×n\bfv^\top = \begin{bmatrix} v_1 & v_2 & \dots & v_n \end{bmatrix} \in \bbR^{1\times n}

Row vectors are always denoted with a tranpose of their corresponding column vector.

v = np.arange(5).reshape(5, 1)
v.shape, v.T.shape
((5, 1), (1, 5))

Matrix addition

You can add two matrices element-wise.

[a11a1nam1amn]ARm×n+[b11b1nbm1bmn]BRm×n:=[a11+b11a1n+b1nam1+bm1amn+bmn]A+BRm×n\begin{align} \underbrace{\begin{bmatrix} a_{11} & \dots & a_{1n} \\ \vdots & \ddots & \vdots \\ a_{m1} & \dots & a_{mn} \end{bmatrix}}_{A \in \mathbb{R}^{m \times n}} + \underbrace{\begin{bmatrix} b_{11} & \dots & b_{1n} \\ \vdots & \ddots & \vdots \\ b_{m1} & \dots & b_{mn} \end{bmatrix}}_{B \in \mathbb{R}^{m \times n}} := \underbrace{\begin{bmatrix} a_{11} + b_{11} & \dots & a_{1n} + b_{1n} \\ \vdots & \ddots & \vdots \\ a_{m1} + b_{m1} & \dots & a_{mn} + b_{mn} \end{bmatrix}}_{A + B \in \mathbb{R}^{m \times n}} \end{align}

The matrix addition is commutative A+B=B+A A + B = B + A

A = np.random.rand(3, 4)
B = np.random.rand(3, 4)
np.allclose(A + B, B+ A)
True

Matrix-scalar product

You can multiply matrices with a scalar (a real number)

α[a11a1nam1amn]ARm×n:=[αa11αa1nαam1αamn]αARm×n\begin{align} \alpha \underbrace{\begin{bmatrix} a_{11} & \dots & a_{1n} \\ \vdots & \ddots & \vdots \\ a_{m1} & \dots & a_{mn} \end{bmatrix}}_{A \in \mathbb{R}^{m \times n}} := \underbrace{\begin{bmatrix} \alpha a_{11} & \dots & \alpha a_{1n} \\ \vdots & \ddots & \vdots \\ \alpha a_{m1} & \dots & \alpha a_{mn} \end{bmatrix}}_{\alpha A \in \mathbb{R}^{m \times n}} \end{align}

Matrix scalar product is commutative

αA=Aα\alpha A = A \alpha
alpha = np.random.rand()
A = np.random.rand(3, 4)
np.allclose(alpha * A, A * alpha)
True

Matrix-vector product

For those who know dot product, matrix-vector product is best defined as a collection of dot products. Define a matrix VRm×n\bfV \in \bbR^{m \times n} as a vertical concatenation of mm n-dimensional row-vectors v1,vm\bfv_1^\top, \dots \bfv_m

V=[v1v2vm]\bfV = \begin{bmatrix}\bfv_1^\top \\ \bfv_2^\top \\ \vdots \\ \bfv_m^\top\end{bmatrix}

The matrix VRm×n\bfV \in \bbR^{m \times n} can be multiplied by a n-dimensional column vector uRn\bfu \in \bbR^{n} with the product defined as the vector-wise dot product vertically concatenated to result in another column vector:

Vu=[v1uv2uvmu]Rm\bfV \bfu = \begin{bmatrix}\bfv_1 \cdot \bfu \\ \bfv_2 \cdot \bfu \\ \vdots \\ \bfv_m \cdot \bfu\end{bmatrix} \in \bbR^{m}

When m=1m = 1, then V=v1\bfV = \bfv_1^\top and the matrix product is v1u=v1u\bfv_1^\top \bfu = \bfv_1 \cdot \bfu. Dot product between two vectors v\bfv and u\bfu is also written as vu\bfv^\top \bfu. Going forward we will prefer vu\bfv^\top \bfu notation for dot product instead of vu\bfv \cdot \bfu.

V = np.random.rand(3, 4)
u = np.random.rand(4)
V @ u
array([0.61997133, 0.38670963, 1.42804002])

Matrix-matrix product

Matrix-matrix product between two matrices VRm×n\bfV \in \bbR^{m \times n} and URn×p\bfU \in \bbR^{n \times p} can be defined in terms of matrix-vector product by writing U\bfU as a horizontal concatenation of pp nn-dimensional column vectors u1,u2,,up\bfu_1, \bfu_2, \dots, \bfu_p.

VU=[Vu1Vu2Vup]Rm×p=[v1u1v1u2v1upv2u1v2u2v2upvmu1vmu2vmup]\bfV \bfU = \begin{bmatrix} \bfV\bfu_1 & \bfV\bfu_2 & \dots & \bfV\bfu_p \end{bmatrix} \in \bbR^{m \times p}\\ = \begin{bmatrix} \bfv_1^\top \bfu_1& \bfv_1^\top \bfu_2 & \dots & \bfv_1^\top \bfu_p\\ \bfv_2^\top \bfu_1& \bfv_2^\top \bfu_2 & \dots & \bfv_2^\top \bfu_p\\ \vdots & \vdots & \ddots & \vdots\\ \bfv_m^\top \bfu_1 & \bfv_m^\top \bfu_2 & \dots & \bfv_m^\top \bfu_p \end{bmatrix}

The result is a horizontal concatenation of matrix-vector products, where the left matrix V\bfV gets multiplied with each column vector of right matrix U\bfU.

Another interpretation is that the matrix-matrix product are all possible dot products between left matrices’ row vectors with right matrices’ column vectors.

Matrix-matrix product or short matrix products do not commute i.e VUUV\bfV \bfU \ne \bfU \bfV in general.

Matrix-matrix product does follow distributive property A(B+C)=AB+ACA(B + C) = AB + AC.

V = np.random.rand(3, 4)
U = np.random.rand(4, 5)
V @ U
array([[0.9058884 , 0.22409012, 0.81158039, 0.65014481, 0.94369832], [0.86286196, 0.50831017, 0.76892233, 0.68919024, 1.10617922], [1.10100106, 0.41266198, 0.87124987, 0.88688025, 1.42231462]])

Identity matrix

In=[100010001]\bfI_n = \begin{bmatrix} 1 & 0 & \dots & 0 \\ 0 & 1 & \dots & 0 \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \dots & 1 \\ \end{bmatrix}

Square matrix

A square matrix is a matrix with number of rows equal to the number of columns.

Inverse of a square matrix

A matrix V1\bfV^{-1} is called the inverse of a square matrix V\bfV if V1V=V1=In\bfV^{-1} \bfV = \bfV^{-1} = \bfI_n. The inverse of a square matrix exists only when it is singular i.e the determinant of the matrix is non-zero det(V)0\det(\bfV) \ne 0.

I = np.eye(10)
I
array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 1., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 1., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 1., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 1., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 1., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 1., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 1., 0.], [0., 0., 0., 0., 0., 0., 0., 0., 0., 1.]])

Linearity of matrix multiplication

Consider a function fA:RnRmf_A : \bbR^n \to \bbR^m defined as

fA(x):=Axf_A(\bfx) := \bfA \bfx

.

Show that the function fAf_A is linear.

Proof

The function fA(x)f_A(\bfx) is linear if for any x,yRn\bfx,\bfy \in \bbR^n and α,βR\alpha, \beta \in \bbR,

fA(αx+βy)=αfA(x)+βfA(y)f_A(\alpha \bfx + \beta \bfy) = \alpha f_A(\bfx) + \beta f_A(\bfy)
A(αx+βy)=A(αx)+A(βy)\bfA (\alpha \bfx + \beta \bfy) = A (\alpha\bfx) + A (\beta\bfy)
Theorem

All linear functions of the form f:RmRnf : \bbR^m \to \bbR^n can be written as matrix multplications of the form f(x)=Fxf(\bfx) = F \bfx.

Proof:

Consider any given linear function f:RmRnf : \bbR^m \to \bbR^n which is linear. Because of linearity it satisifies f(αx+βy)=αf(x)+βf(y)f(\alpha \bfx + \beta \bfy) = \alpha f(\bfx) + \beta f(\bfy).

Consider special vectors, called the standard basis vectors, that have only one element as 1 and the rest are 0.

e1:=[1,0,,0]Rn\bfe_1 := [1, 0, \dots, 0]^\top \in \bbR^n

e2:=[0,1,,0]Rn\bfe_2 := [0, 1, \dots, 0]^\top \in \bbR^n

en:=[0,0,,1]Rn\bfe_n := [0, 0, \dots, 1]^\top \in \bbR^n

Any vector x=[x1,x2,,xn]Rn\bfx = [x_1, x_2, \dots, x_n]^\top \in \bbR^n can be written as a linear combination of standard basis vectors,

x=x1e1+x2e2++xnen\bfx = x_1 \bfe_1 + x_2 \bfe_2 + \dots + x_n \bfe_n

Hence the function evaluation at x\bfx, can also be written as the linear combination of function evaluations at standard basis vectors,

f(x)=f(x1e1+x2e2++xnen)f(\bfx) = f(x_1 \bfe_1 + x_2 \bfe_2 + \dots + x_n \bfe_n)

f(x)=x1f(e1)+x2f(e2)++xnf(en)f(\bfx) = x_1 f(\bfe_1) + x_2 f(\bfe_2) + \dots + x_n f(\bfe_n)

Note that f(e1)f(\bfe_1), f(e2)f(\bfe_2), are constants with respect to x\bfx. So you can arrange them into a constant matrix, independent of x\bfx,

f(x)=[f(e1)f(e2)f(en)]F[x1x2xn]xf(\bfx) = \underbrace{\begin{bmatrix} f(\bfe_1) & f(\bfe_2) & \dots & f(\bfe_n) \end{bmatrix}}_{F} \underbrace{\begin{bmatrix} x_1 \\ x_2 \\ \vdots \\ x_n \end{bmatrix}}_{\bfx}
f(x)=Fxf(\bfx) = F\bfx
Theorem

Show that composition f(g(x))f(g(\bfx)) of two linear functions, ff and gg is also a linear function.

Homework (Linear Models) Problem 3

(10 marks) Proof is left as a homework for the above theorem.

Using vectors for 2D line notation

def stylizeax(ax, limits):
    """Set ax style"""
    minx, maxx, miny, maxy = limits
     # x-axis, y=0
    ax.annotate("", 
                xy=(minx, 0),
                xytext=(maxx, 0),
                arrowprops=dict(arrowstyle="<->"),
                color='k')
    ax.text(maxx, 0, "x")
     # y-axis, x=0
    ax.annotate("", 
                xy=(0, miny),
                xytext=(0, maxy),
                arrowprops=dict(arrowstyle="<->"),
                color='k')
    ax.text(0, maxy, "y")
    

    ax.grid(True, which='both') # show the grid
    ax.set_aspect('equal') # set aspect ratio of the grid to 1:1
    
    
def points_on_line(a, b, c, Npts=6, scale=10):
    """Generate points on the line ax + by + c = 0"""
    # ax + by + c = 0
    # In parameteric form with free parameter r
    #   (x, y) = (-b*r + x0, a*r + y0)
    # where 
    #   x0 = - a*c / (a*a + b*b)
    #   y0 = - b*c / (a*a + b*b)
    # (x0, y0) is the point on the line closest to the origin
    uniformgrid = [i/Npts for i in range(-scale*Npts//2, scale*Npts//2, scale)]
    x0 = -a*c/(a*a + b*b)
    y0 = -b*c/(a*a + b*b)
    x = [-b*r + x0 for r in uniformgrid]
    y = [ a*r + y0 for r in uniformgrid]
    return x, y
    

Matplotlib

# Plot a line ax + by + c = 0
# a, b, c = 2.5, -1, -5 # pick numbers by hand

# pick a, b, c at random
import random
scale = 10
a, b, c = [scale*(random.random()-0.5) for _ in range(3)] # random number from -10 to 10

# Generate some sample points on a line
x, y = points_on_line(a, b, c, scale=scale)

# Plot the points
fig, ax = plt.subplots()
stylizeax(ax, (min(x), max(x), min(y), max(y)))
ax.plot(x, y, '*-') # the line
ax.set_title(f'{a:.1f}x{b:+.1f}y{c:+.1f} = 0') # print the equation
<Figure size 640x480 with 1 Axes>

We started from 2D linear models, but we want to work with N-D models where N can be even in thousands or millions. It makes sense to simplify the notation by using vector notation.

Recall that the implicit equation for a line is

L(a,b,c)={(x,y):ax+by+c=0,xR,yR}\calL(a, b, c) = \{(x, y): ax + by + c = 0, x \in \bbR, y \in \bbR \}

We will represent a 2D point (x, y) by a 2D vector x=[x;y]\bfx = [x; y] and the parameters (a,b)(a, b) with weight vector w=[a;b]\bfw = [a; b]. Let’s compute the dot product between the two newly defined vectors :

wx=wx=ax+by\bfw^\top \bfx = \bfw \cdot \bfx = ax + by

The equation of the line under new notation in full its full glory is

L(w,c)={x:wx+c=0,xR2}\calL(\bfw, c) = \{\bfx: \bfw \cdot \bfx + c = 0, \bfx \in \bbR^2 \}

Unique line notation

Same line ax+by+c=0ax + by + c = 0 can be represented by multiple equations for the same form. This representation of line is not unique. For example, equations 5x+2y+10=05x + 2y + 10 = 0 and 10x+4y+20=010x + 4y + 20 =0 represent the same line. In general, for any real number α0\alpha \ne 0 all equations αax+αby+αc=0\alpha a x + \alpha b y + \alpha c = 0 represent the same line. Once can choose an arbitrary non-zero α\alpha for making the equation unique.

In vector notation, all non-zero αwx+αc=0\alpha \bfw \cdot \bfx + \alpha c = 0 represent the same line. One good candidate for α\alpha is α=1w\alpha = \frac{1}{\|\bfw\|}, because this changes αw\alpha \bfw to w^\hat{\bfw} a unit vector.

L(w^,w0)={x:w^x+w0=0,xR2}\calL(\hat{\bfw}, w_0) = \{\bfx: \hat{\bfw} \cdot \bfx + w_0 = 0, \bfx \in \bbR^2 \}

where w0=cww_0 = \frac{c}{\|\bfw\|}.

Geometric interpretaion

The new equation of the line has a convinient geometric interpretation. Recall that w^x\hat{\bfw} \cdot \bfx is the projection of x\bfx on w^\hat{\bfw}. In other words, the equation projw^x+w0=0\text{proj}_{\hat{\bfw}}\bfx + w_0 = 0 constrains all vectors on the line to have a constant projection projw^x=w0\text{proj}_{\hat{\bfw}}\bfx = -w_0.

This means that vector w^\hat{\bfw} is perpendicular to the line and cuts the line a distance w0|w_0| from the origin.

import numpy as np # a vector algebra library

a = np.array([0, 1, 2, 3]) # a vector
print("a=", a)
b = np.array([4, 5, 6, 7]) # another vector
print("b=", b)
C = np.array([[0, 1, 2, 3],
              [4, 5, 6, 7]]) # A matrix
print("C=", C)
D = np.zeros((2, 4)) # a 2x4 matrix of zeros
print("D=", D)
E = np.random.rand(2,5) # Random 2x5 matrix of numbers between 0 and 1
print("E=", E)
a= [0 1 2 3]
b= [4 5 6 7]
C= [[0 1 2 3]
 [4 5 6 7]]
D= [[0. 0. 0. 0.]
 [0. 0. 0. 0.]]
E= [[0.6018821  0.02634302 0.13422675 0.44627918 0.65200175]
 [0.62550933 0.93727334 0.29797968 0.16805498 0.53928057]]
print("a*0.1 = ", a * 0.1) # element-wise multiplication
print("C*0.2 = ", C * 0.2) # element-wise multiplication
print("a*b = ", a * b)   # element-wise multiplication (Note: different from Matlab)
print("a*b*0.2 = ", a * b * 0.2)   # element-wise multiplication
print("C @ a = ", C @ a)   # matrix-vector product
print("C.T = ", C.T)     # matrix transpose
print("C.T @ D = ", C.T @ D) # matrix-matrix product
print("a * C = ", a * C)   # so called broadcasting; numpy specific
a*0.1 =  [0.  0.1 0.2 0.3]
C*0.2 =  [[0.  0.2 0.4 0.6]
 [0.8 1.  1.2 1.4]]
a*b =  [ 0  5 12 21]
a*b*0.2 =  [0.  1.  2.4 4.2]
C @ a =  [14 38]
C.T =  [[0 4]
 [1 5]
 [2 6]
 [3 7]]
C.T @ D =  [[0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]
 [0. 0. 0. 0.]]
a * C =  [[ 0  1  4  9]
 [ 0  5 12 21]]
Numpy: General Broadcasting Rules

When operating on two arrays, NumPy compares their shapes element-wise. It starts with the trailing (i.e. rightmost) dimension and works its way left. Two dimensions are compatible when

  1. they are equal, or

  2. one of them is 1.

Otherwise a ValueError is raised

Ref: https://numpy.org/doc/stable/user/basics.broadcasting.html

In the following example, both the A and B arrays have axes with length one that are expanded to a larger size during the broadcast operation:

A      (4d array):  8 x 1 x 6 x 1
B      (3d array):      7 x 1 x 5
Result (4d array):  8 x 7 x 6 x 5
A = np.random.rand(8, 1, 6, 1)
B = np.random.rand(7, 1, 5)
(A * B).shape # Returns the shape of the multi dimensional array
(8, 7, 6, 5)

Here are some more examples:

A      (2d array):  5 x 4
B      (1d array):      1
Result (2d array):  ?

A      (2d array):  5 x 4
B      (1d array):      4
Result (2d array):  ?

A      (3d array):  15 x 3 x 5
B      (3d array):  15 x 1 x 5
Result (3d array):  

A      (3d array):  15 x 3 x 5
B      (2d array):       3 x 5
Result (3d array):  ?

A      (3d array):  15 x 3 x 5
B      (2d array):       3 x 1
Result (3d array):  ?

def points_on_line(hatw, w0, Npts=6, scale=10):
    """ Generate some sample points on a line """
    assert hatw.shape == (2,) # only works for 2D
    perp_hatw = np.array([-hatw[1], hatw[0]])# vector perpendicular to hatw
    uniformgrid = np.linspace(-scale//2, scale//2, Npts)
    return perp_hatw * uniformgrid[:, None] - w0*hatw
# Plot a line ax + by + c = 0
scale = 10
# a, b, c = [scale*(random.random()-0.5) for _ in range(3)] # random number from -10 to 10
# abc = scale*(np.random.rand(3)-0.5)  # random number from -10 to 10
abc = [3, 2, -6] # pick your favorite line
w = abc[:2]
hatw = w / np.linalg.norm(w) # What does np.linalg.norm do?
w0 = abc[2] / np.linalg.norm(w)

# Generate some sample points on a line
x = points_on_line(hatw, w0, Npts=6, scale=scale) # Npts x 2 array

# Plot the points
fig, ax = plt.subplots()
ax.axis('equal')
stylizeax(ax, (x[:, 0].min(), x[:, 0].max(), x[:, 1].min(), x[:, 1].max())) # numpy allows for multi-dimensional slicing
ax.plot(x[:, 0], x[:, 1], '*-') # the line
pt0 = -w0*hatw
ax.annotate("", xytext=(0, 0), xy=(pt0[0], pt0[1]),
            arrowprops=dict(arrowstyle="->", color='r'))
ax.text(pt0[0], pt0[1], r"$-w_0\hat{\mathbf{w}}$", color='r')
<Figure size 640x480 with 1 Axes>

Linear regression: review

Let’s take the simple linear regression example from STS332 textbook (uploaded on brightspace;page 300; Table 6-1).

“As an illustration, consider the data in Table 6-1. In this table, y is the salt concentration (milligrams/liter) found in surface streams in a particular watershed and x is the percentage of the watershed area consisting of paved roads.”

%%writefile saltconcentration.tsv
#Observation	SaltConcentration	RoadwayArea
1	3.8	0.19
2	5.9	0.15
3	14.1	0.57
4	10.4	0.4
5	14.6	0.7
6	14.5	0.67
7	15.1	0.63
8	11.9	0.47
9	15.5	0.75
10	9.3	0.6
11	15.6	0.78
12	20.8	0.81
13	14.6	0.78
14	16.6	0.69
15	25.6	1.3
16	20.9	1.05
17	29.9	1.52
18	19.6	1.06
19	31.3	1.74
20	32.7	1.62
Overwriting saltconcentration.tsv
# numpy can import text files separated by seprator like tab or comma
salt_concentration_data = np.loadtxt("saltconcentration.tsv")
salt_concentration_data
array([[ 1. , 3.8 , 0.19], [ 2. , 5.9 , 0.15], [ 3. , 14.1 , 0.57], [ 4. , 10.4 , 0.4 ], [ 5. , 14.6 , 0.7 ], [ 6. , 14.5 , 0.67], [ 7. , 15.1 , 0.63], [ 8. , 11.9 , 0.47], [ 9. , 15.5 , 0.75], [10. , 9.3 , 0.6 ], [11. , 15.6 , 0.78], [12. , 20.8 , 0.81], [13. , 14.6 , 0.78], [14. , 16.6 , 0.69], [15. , 25.6 , 1.3 ], [16. , 20.9 , 1.05], [17. , 29.9 , 1.52], [18. , 19.6 , 1.06], [19. , 31.3 , 1.74], [20. , 32.7 , 1.62]])
# Plot the points
fig, ax = plt.subplots()
# Scatter plot using matplotlib
ax.scatter(salt_concentration_data[:, 2], salt_concentration_data[:, 1])
ax.set_xlabel(r"Roadway area %")
ax.set_ylabel(r"Salt concentration (mg/L)")
<Figure size 640x480 with 1 Axes>

Least squares regression

The problem of linear regression is to find a line that “best fits” the given data. That is we want all the points {(x1,y1),,(xn,yn)}\{(x_1, y_1), \dots, (x_n, y_n)\} to satisfy the equation of the line y=mx+cy = mx + c. Since we know that there exists no such line, so we will try to make ymx+cy \approxeq mx + c, by minimizing some error/distance/cost/loss function between yy and mx+cmx + c for every point (xi,yi)(x_i, y_i) in the dataset. The simplest error function that results in nice answers is squared distance:

e(xi,yi)=(yi(mxi+c))2e(x_i,y_i) = (y_i - (mx_i + c))^2

Then we can minimize the total error to find the line:

m,c=arg minm,ci=1ne(xi,yi)m^*, c^* = \arg~\min_{m, c} \sum_{i=1}^n e(x_i,y_i)

Geometrically, this error minimization corresponds to minimizing the stubs in the following figure:

Vectorization of Least square regression

Recall that the magnitude of a vector v=v12+v22++vnn \|\bfv\| = \sqrt{v_1^2 + v_2^2 + \dots + v_n^n} has a similar form to the error function. This suggests that we can define an error vector with the signed error for each data point as it’s elements

e=[y1(mx1+c)y2(mx2+c)yn(mxn+c)]\bfe = \begin{bmatrix}y_1 - (mx_1 + c)\\ y_2 - (mx_2 + c)\\ \vdots \\ y_n - (mx_n + c)\end{bmatrix}

Minimizing the total error is same as minimizing the square of error vector magnitude

m,c=arg minm,ce2m^*, c^* = \arg~\min_{m, c} \|\bfe\|^2

While we are at at it let us define x=[x1;;xn]\bfx = [x_1; \dots; x_n] to denote the vector of all x coordinates of the dataset and y=[y1;;yn]\bfy = [y_1; \dots; y_n] to denote y coordinates. Then the error vector is:

e=y(xm+1nc)\bfe = \bfy - (\bfx m + \mathbf{1}_n c)

where 1n\mathbf{1}_n is a n-D vector of all ones. Finally, we vectorize parameters of the line m=[m;c]\bfm = [m; c]. We will also need to horizontally concatenate x\bfx and 1n\mathbf{1}_n. Let’s call the result X=[x,1n]Rn×2\bfX = [\bfx, \mathbf{1}_n] \in \bbR^{n \times 2}. Now, the error vector looks like this:

e=yXm\bfe = \bfy - \bfX \bfm

Expanding the error magnitude:

e2=(yXm)(yXm)=yy+mXXm2yXm\|\bfe\|^2 = (\bfy - \bfX \bfm)^\top (\bfy - \bfX \bfm) \\ = \bfy^\top\bfy + \bfm^\top \bfX^\top \bfX \bfm - 2\bfy^\top \bfX \bfm
Homework (Linear Models): Problem 4

(10 marks)

Expand

(yXm)(yXm)(\bfy - \bfX \bfm)^\top (\bfy - \bfX \bfm)

and show that it is equal to

yy+mXXm2yXm\bfy^\top\bfy + \bfm^\top \bfX^\top \bfX \bfm - 2\bfy^\top \bfX \bfm

Our minimization problem in vectorized form is:

m=arg minmyy+mXXm2yXm\bfm^* = \arg~\min_{\bfm} \bfy^\top\bfy + \bfm^\top \bfX^\top \bfX \bfm - 2\bfy^\top \bfX \bfm

This is a quadratic equation in m\bfm that can be minimized by equating the derivate to zero.

Two rules of vector derivatives

There are two conventions in vector derivatives:

  1. Gradient convention

  2. Jacobian convention

Gradient convention

Under gradient convention the derivative of scalar-valued vector function function f(x):RnRf(\bfx): \bbR^n \to \bbR is defined as vertical stacking of element-wise derivatives

xf(x)=[f(x)x1f(x)xn]Rn\frac{\p }{ \p \bfx} f(\bfx) = \begin{bmatrix}\frac{\p f(\bfx)}{\p x_1} \\ \vdots \\ \frac{\p f(\bfx)}{\p x_n}\end{bmatrix} \in \bbR^n
Jacobian convention

Under gradient convention the derivative of scalar-valued vector function function f(x):RnRf(\bfx): \bbR^n \to \bbR is defined as horizontal stacking of element-wise derivatives

xf(x)=[f(x)x1f(x)xn]R1×n\frac{\p }{ \p \bfx} f(\bfx) = \begin{bmatrix}\frac{\p f(\bfx)}{\p x_1} & \dots & \frac{\p f(\bfx)}{\p x_n}\end{bmatrix} \in \bbR^{1 \times n}

For a vector-value vector function f(x):RnRm\bff(\bfx): \bbR^n \to \bbR^m, Jacobian of f(x)\bff(\bfx) is the vertical concatentation of gradients transposed, resulting in m×nm \times n matrix

Jx(f(x))=xf(x)=[f1(x)xfm(x)x]\bfJ_\bfx (\bff(\bfx)) = \frac{\p }{ \p \bfx} \bff(\bfx) = \begin{bmatrix}\frac{\p f_1(\bfx)}{\p \bfx} \\ \dots \\ \frac{\p f_m(\bfx)}{\p \bfx}\end{bmatrix}

We will use Jacobian convention in this course, because it works nicely with chain rule.

Derivative of a linear function

All scalar-valued linear functions of x\bfx can be written in the form f(x)=cxf(\bfx) = \bfc^\top \bfx.

xcx=c\begin{align} \frac{\p }{ \p \bfx} \bfc^\top \bfx = \bfc^\top\\ \end{align}
Derivative of a quadratic function

All scalar-valued homogeneous quadratic functions of x\bfx can be written in the form f(x)=xAxf(\bfx) = \bfx^\top \bfA \bfx.

xxAx=x(A+A)\begin{align} \frac{\p }{ \p \bfx} \bfx^\top \bfA \bfx = \bfx^\top (\bfA + \bfA^\top) \end{align}
Homework (Linear Models): Problem 5

(10 marks)

Proof of above two derivatives is left as an exercises.

Back to Least square regression

0=m(yy+mXXm2yXm)=2mXX2yX\begin{align} \mathbf{0}^\top &= \frac{\p }{\p \bfm} ( \bfy^\top\bfy + \bfm^\top \bfX^\top \bfX \bfm - 2\bfy^\top \bfX \bfm)\\ &= 2 {\bfm^*}^\top \bfX^\top \bfX - 2\bfy^\top \bfX \end{align}

This gives us the solution

m=(XX)1Xy\bfm^* = (\bfX^\top \bfX)^{-1} \bfX^\top \bfy

The symbol V1\bfV^{-1} is called inverse of matrix V\bfV.

The term (XX)1X(\bfX^\top \bfX)^{-1} \bfX^\top is also called the pseudo-inverse of a matrix X\bfX, denoted as X\bfX^\dagger.

n = salt_concentration_data.shape[0]
bfx = salt_concentration_data[:, 2:3]
bfy = salt_concentration_data[:, 1]
bfX = np.hstack((bfx, np.ones((bfx.shape[0], 1))))
bfX
array([[0.19, 1. ], [0.15, 1. ], [0.57, 1. ], [0.4 , 1. ], [0.7 , 1. ], [0.67, 1. ], [0.63, 1. ], [0.47, 1. ], [0.75, 1. ], [0.6 , 1. ], [0.78, 1. ], [0.81, 1. ], [0.78, 1. ], [0.69, 1. ], [1.3 , 1. ], [1.05, 1. ], [1.52, 1. ], [1.06, 1. ], [1.74, 1. ], [1.62, 1. ]])
bfm = np.linalg.inv(bfX.T @ bfX) @ bfX.T @ bfy
print("Method 1:", bfm)
bfm = np.linalg.solve(bfX.T @ bfX, bfX.T @ bfy)
print("Method 2:", bfm)
bfm, *_ = np.linalg.lstsq(bfX, bfy, rcond=None)
print("Method 3:", bfm)
Method 1: [17.5466671   2.67654631]
Method 2: [17.5466671   2.67654631]
Method 3: [17.5466671   2.67654631]
m = bfm.flatten()[0]
c = bfm.flatten()[1]

# Plot the points
fig, ax = plt.subplots()
ax.scatter(salt_concentration_data[:, 2], salt_concentration_data[:, 1])
ax.set_xlabel(r"Roadway area $\%$")
ax.set_ylabel(r"Salt concentration (mg/L)")
x = salt_concentration_data[:, 2]
y = m * x + c
# Plot the points
ax.plot(x, y, 'r-') # the line
<Figure size 640x480 with 1 Axes>