import numpy as np
import matplotlib.pyplot as pl
from mpl_toolkits.mplot3d import Axes3D
Linear Algebra ✅
1 Vectors
1.1 A single vector
#
# A 2D vector
#
= np.array([1, 2])
v v
array([1, 2])
#
# We can plot it in an 2D plot as a point
#
0]], [v[1]], s=100)
pl.scatter([v[True)
pl.grid(-5, 5))
pl.xlim((-5, 5)); pl.ylim((
1.2 Higher dimensional vector
= np.array([1, 2, 3])
v v
array([1, 2, 3])
#
# We can plot it in a 3D grid
#
= pl.figure()
fig = fig.add_subplot(projection='3d')
ax -5, 5)
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(0]], [v[1]], [v[2]], s=100); ax.scatter([v[
1.3 A collection of vectors
= np.random.normal(0, 1, size=(1000,))
x_list = np.random.normal(0, 2, size=(1000,))
y_list = np.vstack([x_list, y_list])
v_list
=(6,6))
pl.figure(figsize-10, 10))
pl.xlim((-10, 10))
pl.ylim((
0, :], v_list[1, :], s=1); pl.scatter(v_list[
2 Matrices
- Matrix construction
- Transpose
- Multiplication
- Rotational matrices
2.1 Constructing matrices
#
# We construct matrices using nested Python arrays
#
= np.array([
M 1, 2, 3],
[4, 5, 6]
[
])
M
array([[1, 2, 3],
[4, 5, 6]])
2.2 Transpose and multiplication
#
# We can apply matrix transformations.
#
np.transpose(M)
array([[1, 4],
[2, 5],
[3, 6]])
#
# We can apply matrix transformations.
#
M.T
array([[1, 4],
[2, 5],
[3, 6]])
#
# Matrix multiplication is done by `@` operator
#
= np.ones((4, 2))
X
@ M X
array([[5., 7., 9.],
[5., 7., 9.],
[5., 7., 9.],
[5., 7., 9.]])
2.3 Scaling Matrices
= np.linspace(0, 2 * np.pi, 20)
t = 2*np.cos(t)
xs = np.sin(t)
ys
= np.vstack([xs, ys])
points
def show(*args):
=(6,6))
pl.figure(figsizeTrue)
pl.grid(-5, 5)
pl.xlim(-5, 5);
pl.ylim(for i in range(0, len(args), 2):
= args[i]
points = args[i+1]
color = points[0]
xs = points[1]
ys =100, color=color) pl.scatter(xs, ys, s
'gray') show(points,
= np.array([
M1 2, 0],
[0, 2]
[
])
= M1 @ points
new_points
'gray', new_points, 'blue') show(points,
= np.array([
M2 0.5, 0],
[0, 1.5]
[
])
'gray', M2 @ points, 'blue') show(points,
3 Rotational matrix
def rotation_M(theta):
return np.array([
[np.cos(theta), np.sin(theta)],-np.sin(theta), np.cos(theta)]
[ ])
= rotation_M(np.pi/4)
M3 'gray', M3 @ points, 'blue') show(points,
3.1 Composition of transformations
#
# We can compose the matrices together
#
= M3 @ M2 @ M1
M4
'gray', M4 @ points, 'blue') show(points,
#
# We can compose the matrices together
#
'gray', M1 @ M2 @ M3 @ points, 'blue') show(points,
4 Distances
- Inner product (or dot product)
- Vector norms
- Distance as a norm
- Cosine similarity
4.1 Inner product
= np.array([1, 2])
x = np.array([3, 4.5])
y = np.array([0, 2]) z
=(6,6))
pl.figure(figsize-6, 6)
pl.xlim(-6, 6)
pl.ylim(True)
pl.grid(0], x[1], 'x', fontsize=24)
pl.text(x[0], y[1], 'y', fontsize=24)
pl.text(y[0], z[1], 'z', fontsize=24)
pl.text(z[0], [0], s=200); pl.scatter([
#
# Inner product
#
= np.dot(x, y)
a a
12.0
@ y x
12.0
4.2 Norm of a vector
The norm of a vector \(x\) is its length, denoted by \(\|x\|\).
It can be computed as:
\[\left<x, x\right> = \|x\|^2 \]
Thus,
\[ \|x\| = \sqrt{\left<x, x\right>} \]
@ x) np.sqrt(x
2.23606797749979
@ y) np.sqrt(y
5.408326913195984
@ z) np.sqrt(z
2.0
4.3 Distance
\[ d(x, y) = \|x - y\| \]
def d(x, y):
= x - y
v return np.sqrt(v @ v)
#
# Distance is the norm of the difference vector
#
d(x, y)
3.2015621187164243
# Distance is symmetric
d(y, x)
3.2015621187164243
d(x, z)
1.0
d(y, z)
3.905124837953327
= np.array([x, y, z])
points points
array([[1. , 2. ],
[3. , 4.5],
[0. , 2. ]])
import scipy.spatial
scipy.spatial.distance_matrix(points, points)
array([[0. , 3.20156212, 1. ],
[3.20156212, 0. , 3.90512484],
[1. , 3.90512484, 0. ]])
4.4 Cosine similarity
\[ \cos(\theta) = \frac{\left<x, y\right>}{ \|x\| \|y\|} \]
def sim(x, y):
return (x @ y) / np.sqrt(x @ x) / np.sqrt(y @ y)
sim(x, x)
1.0
sim(x, y)
0.9922778767136676
sim(x, z)
0.8944271909999159
np.arccos(sim(x, x))
0.0
* 180 / np.pi np.arccos(sim(x, y))
7.1250163489018075
* 180 / np.pi np.arccos(sim(x, z))
26.56505117707799
4.5 Projection
Did you know that the projection of some vector \(u\) onto another vector \(v\) is given by:
Make \(v\) into a unit vector:
\[ v \mapsto \frac{v}{\|v\|} \]
Compute the length of projection:
\[\begin{eqnarray*} \mathbf{proj}(u, v) &=& \cos(\theta)\|u\| v \\ &=& \frac{\left<u, v\right>} {\|u\|\|v\|} \|u\|v \\ &=& \left<u, v\right> v \end{eqnarray*}\]
def proj(u, v):
= v / np.sqrt(v @ v)
v return u @ v * v
= np.array([1, 1])
u = np.array([1, 2])
v = proj(u, v)
w
w
array([0.6, 1.2])
=(6,6))
pl.figure(figsize0, 2.5)
pl.xlim(0, 2.5)
pl.ylim(0, 0, u[0], u[1], head_width=0.04, width=0.01, color='gray');
pl.arrow(0, 0, v[0], v[1], head_width=0.04, width=0.02, color='green');
pl.arrow(0, 0, w[0], w[1], head_width=0.04, width=0.01, color='red'); pl.arrow(
5 Lines and Planes
5.1 Line through the origin
\[L_u = \{ cu : c\in\mathbb{R} \}\]
def line(u):
= np.linspace(-10, 10, 20)
c return c[:, np.newaxis] @ u[np.newaxis, :]
= np.array([1, 1])
u = line(u)
points True)
pl.grid(0, 0, s=200)
pl.scatter(0], points[:, 1], s=50); pl.scatter(points[:,
5.2 Line not through the origin
\[ L_{u, b} = \{ cu + b: c\in\mathbb{R} \}\]
def line_offset(u, b):
return line(u) + b
= np.array([1, 1])
u = np.array([0, -5])
b = line_offset(u, b)
points
True)
pl.grid(0, 0, s=200)
pl.scatter(0], b[1], s=100)
pl.scatter(b[0], points[:, 1], s=50); pl.scatter(points[:,
5.3 Plane through the origin
\[P_{u,v} = \{ au + bv: a\in\mathbb{R}, b\in\mathbb{R} \}\]
def plane(u, v):
= line(u) # (n, 3)
a_line = line(v) # (n, 3)
b_line = a_line[:, np.newaxis, :] + b_line[np.newaxis, :, :]
grid return grid
= np.array([-0.35606907, -0.45829173, 0.8143608 ])
u = np.array([ 0.73476628, -0.675748 , -0.05901828])
v = plane(u, v)
P P.shape
(20, 20, 3)
= pl.figure(figsize=(6,6))
fig = fig.add_subplot(projection='3d')
ax -5, 5)
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(= P.reshape(-1, 3)
points 0], points[:, 1], points[:, 2], s=10); ax.scatter(points[:,
5.4 Plane defined by normal vector
Given a vector \(w\),
\[P_w = \{ x\in\mathbb{R}^k : \left<x, w\right> = 0 \}\]
We can translate the definition of a plane between the normal vector \(w\), and its two spanning vectors \(u, v\).
#
# Given a vector
#
= np.array([2, 3, 4])
w w
array([2, 3, 4])
#
# We can find an orthogonal vector
#
def get_normal_vector(w):
= np.random.random(3)
rand = proj(rand, w)
rand_proj return rand - rand_proj
print("w =", w)
print("normal to w =", get_normal_vector(w))
print("sim(u, w) =", sim(w, get_normal_vector(w)))
w = [2 3 4]
normal to w = [ 0.1282955 0.38550103 -0.35327353]
sim(u, w) = 3.649751635494471e-16
#
# We can find an orthogonal vector from two known vectors: u, w
#
= np.cross(w, get_normal_vector(w))
v print("v =", v)
print("sim(v, w) =", sim(v, w))
v = [ 0.87744265 1.78406671 -1.77677136]
sim(v, w) = 0.0
6 Projection and Separation
- Projection on to a line
- Projection on to a plane
- Separation by a plane
6.1 Projection onto a line
Let \(L_u\) be a line through the origin, defined by a direction vector \(u\).
Let \(v\) be any vector. We can compute the projection of \(v\) on \(L_u\).
\[\mathbf{proj}_L(v) = \mathbf{proj}(v, u)\]
= np.array([1, 2])
u = np.array([-10, 15])
v = proj(v, u)
w
= line(u)
points =(6,6))
pl.figure(figsizeTrue)
pl.grid(-20, 20)
pl.xlim(-20, 20)
pl.ylim(0], points[:, 1], s=20);
pl.scatter(points[:, 0], v[1], color='red', s=150);
pl.scatter(v[0], w[1], color='red', s=50); pl.scatter(w[
6.2 Projection on a line revisited
Consider \(L_u\) being a line through the origin, defined by vector \(u\).
Let \(v\) be a vector. The projection of \(v\) onto \(L\) can be computed by a normal vector of \(L\).
Let \(t\) be a vector, normal to \(u\). Namely,
\[ \left<t, u\right> = 0 \]
Then, we have the following relation:
\[ v = \mathbf{proj}(v, t) + \mathbf{proj}(v, u) \]
Therefore, the projection on the line is given by:
\[ \mathbf{proj}(v, L_u) = \mathbf{proj}(v, u) = v - \mathbf{proj}(v, t) \]
= np.array([1, 2])
u = np.array([-2, 1])
t
= np.array([-10, 15])
v = v - proj(v, t)
w
= line(u)
points =(6,6))
pl.figure(figsizeTrue)
pl.grid(-20, 20)
pl.xlim(-20, 20)
pl.ylim(0], points[:, 1], s=20);
pl.scatter(points[:, 0], v[1], color='red', s=150);
pl.scatter(v[0], w[1], color='red', s=50); pl.scatter(w[
6.3 Projection on to a plane
Consider a plan \(P_t\) through the origin, defined by its normal vector \(t\).
Let \(v\) be a vector. The projection of \(v\) onto \(P_u\) is given by:
\[ \mathbf{proj}(v, P) = v - \mathbf{proj}(v, t) \]
#
# The plane
#
= np.array([1, 1, 1])
t = get_normal_vector(t)
a = a / np.sqrt(a @ a)
a = np.cross(t, a)
b = b / np.sqrt(b @ b)
b = plane(a, b).reshape(-1, 3)
points
#
# The point
#
= np.array([5, 0, 2])
v
#
# The projection of v onto the plane
#
= v - proj(v, t)
w
print("w is normal to t: ", sim(w, t))
= pl.figure(figsize=(6,6))
fig = fig.add_subplot(projection='3d')
ax -5, 5)
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(0], points[:, 1], points[:, 2], s=10);
ax.scatter(points[:, 0], v[1], v[2], s=100, color='red');
ax.scatter(v[0], w[1], w[2], s=30, color='red'); ax.scatter(w[
w is normal to t: -2.881631309228584e-16
6.4 Planar separation
Let \(P_t\) be a plane through the origin, defined by its normal vector \(t\).
The plane divides the whole space into two partitions.
Above the plane: \[ \{x\in\mathbb{R}^n: \left<x, t\right> > 0\} \]
Below the plane: \[ \{x\in\mathbb{R}^n: \left<x, t\right> < 0\} \]
= np.random.randn(1000, 2)
X
=(6,6))
pl.figure(figsize-3, 3)
pl.xlim(-3, 3)
pl.ylim(True)
pl.grid(0], X[:, 1], s=1); pl.scatter(X[:,
= np.array([1, 1])
t
= X @ t > 0
I = X @ t < 0
J
=(6,6))
pl.figure(figsize-3, 3)
pl.xlim(-3, 3)
pl.ylim(True)
pl.grid(0], X[I, 1], s=1, color='red')
pl.scatter(X[I, 0], X[J, 1], s=1, color='blue'); pl.scatter(X[J,