import numpy as np
import matplotlib.pyplot as pl
from mpl_toolkits.mplot3d import Axes3DLinear Algebra ✅
1 Vectors
1.1 A single vector
#
# A 2D vector
#
v = np.array([1, 2])
varray([1, 2])
#
# We can plot it in an 2D plot as a point
#
pl.scatter([v[0]], [v[1]], s=100)
pl.grid(True)
pl.xlim((-5, 5))
pl.ylim((-5, 5));
1.2 Higher dimensional vector
v = np.array([1, 2, 3])
varray([1, 2, 3])
#
# We can plot it in a 3D grid
#
fig = pl.figure()
ax = fig.add_subplot(projection='3d')
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-5, 5)
ax.scatter([v[0]], [v[1]], [v[2]], s=100);
1.3 A collection of vectors
x_list = np.random.normal(0, 1, size=(1000,))
y_list = np.random.normal(0, 2, size=(1000,))
v_list = np.vstack([x_list, y_list])
pl.figure(figsize=(6,6))
pl.xlim((-10, 10))
pl.ylim((-10, 10))
pl.scatter(v_list[0, :], v_list[1, :], s=1);
2 Matrices
- Matrix construction
- Transpose
- Multiplication
- Rotational matrices
2.1 Constructing matrices
#
# We construct matrices using nested Python arrays
#
M = np.array([
[1, 2, 3],
[4, 5, 6]
])
Marray([[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.Tarray([[1, 4],
[2, 5],
[3, 6]])
#
# Matrix multiplication is done by `@` operator
#
X = np.ones((4, 2))
X @ Marray([[5., 7., 9.],
[5., 7., 9.],
[5., 7., 9.],
[5., 7., 9.]])
2.3 Scaling Matrices
t = np.linspace(0, 2 * np.pi, 20)
xs = 2*np.cos(t)
ys = np.sin(t)
points = np.vstack([xs, ys])
def show(*args):
pl.figure(figsize=(6,6))
pl.grid(True)
pl.xlim(-5, 5)
pl.ylim(-5, 5);
for i in range(0, len(args), 2):
points = args[i]
color = args[i+1]
xs = points[0]
ys = points[1]
pl.scatter(xs, ys, s=100, color=color)show(points, 'gray')
M1 = np.array([
[2, 0],
[0, 2]
])
new_points = M1 @ points
show(points, 'gray', new_points, 'blue')
M2 = np.array([
[0.5, 0],
[0, 1.5]
])
show(points, 'gray', M2 @ points, 'blue')
3 Rotational matrix
def rotation_M(theta):
return np.array([
[np.cos(theta), np.sin(theta)],
[-np.sin(theta), np.cos(theta)]
])M3 = rotation_M(np.pi/4)
show(points, 'gray', M3 @ points, 'blue')
3.1 Composition of transformations
#
# We can compose the matrices together
#
M4 = M3 @ M2 @ M1
show(points, 'gray', M4 @ points, 'blue')
#
# We can compose the matrices together
#
show(points, 'gray', M1 @ M2 @ M3 @ points, 'blue')
4 Distances
- Inner product (or dot product)
- Vector norms
- Distance as a norm
- Cosine similarity
4.1 Inner product
x = np.array([1, 2])
y = np.array([3, 4.5])
z = np.array([0, 2])pl.figure(figsize=(6,6))
pl.xlim(-6, 6)
pl.ylim(-6, 6)
pl.grid(True)
pl.text(x[0], x[1], 'x', fontsize=24)
pl.text(y[0], y[1], 'y', fontsize=24)
pl.text(z[0], z[1], 'z', fontsize=24)
pl.scatter([0], [0], s=200);
#
# Inner product
#
a = np.dot(x, y)
a12.0
x @ y12.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>} \]
np.sqrt(x @ x)2.23606797749979
np.sqrt(y @ y)5.408326913195984
np.sqrt(z @ z)2.0
4.3 Distance
\[ d(x, y) = \|x - y\| \]
def d(x, y):
v = x - y
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
points = np.array([x, y, z])
pointsarray([[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
np.arccos(sim(x, y)) * 180 / np.pi7.1250163489018075
np.arccos(sim(x, z)) * 180 / np.pi26.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 = v / np.sqrt(v @ v)
return u @ v * vu = np.array([1, 1])
v = np.array([1, 2])
w = proj(u, v)
warray([0.6, 1.2])
pl.figure(figsize=(6,6))
pl.xlim(0, 2.5)
pl.ylim(0, 2.5)
pl.arrow(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');
5 Lines and Planes
5.1 Line through the origin
\[L_u = \{ cu : c\in\mathbb{R} \}\]
def line(u):
c = np.linspace(-10, 10, 20)
return c[:, np.newaxis] @ u[np.newaxis, :]u = np.array([1, 1])
points = line(u)
pl.grid(True)
pl.scatter(0, 0, s=200)
pl.scatter(points[:, 0], points[:, 1], s=50);
5.2 Line not through the origin
\[ L_{u, b} = \{ cu + b: c\in\mathbb{R} \}\]
def line_offset(u, b):
return line(u) + bu = np.array([1, 1])
b = np.array([0, -5])
points = line_offset(u, b)
pl.grid(True)
pl.scatter(0, 0, s=200)
pl.scatter(b[0], b[1], s=100)
pl.scatter(points[:, 0], points[:, 1], s=50);
5.3 Plane through the origin
\[P_{u,v} = \{ au + bv: a\in\mathbb{R}, b\in\mathbb{R} \}\]
def plane(u, v):
a_line = line(u) # (n, 3)
b_line = line(v) # (n, 3)
grid = a_line[:, np.newaxis, :] + b_line[np.newaxis, :, :]
return gridu = np.array([-0.35606907, -0.45829173, 0.8143608 ])
v = np.array([ 0.73476628, -0.675748 , -0.05901828])
P = plane(u, v)
P.shape(20, 20, 3)
fig = pl.figure(figsize=(6,6))
ax = fig.add_subplot(projection='3d')
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-5, 5)
points = P.reshape(-1, 3)
ax.scatter(points[:, 0], points[:, 1], points[:, 2], s=10);
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
#
w = np.array([2, 3, 4])
warray([2, 3, 4])
#
# We can find an orthogonal vector
#
def get_normal_vector(w):
rand = np.random.random(3)
rand_proj = proj(rand, w)
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
#
v = np.cross(w, get_normal_vector(w))
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)\]
u = np.array([1, 2])
v = np.array([-10, 15])
w = proj(v, u)
points = line(u)
pl.figure(figsize=(6,6))
pl.grid(True)
pl.xlim(-20, 20)
pl.ylim(-20, 20)
pl.scatter(points[:, 0], points[:, 1], s=20);
pl.scatter(v[0], v[1], color='red', s=150);
pl.scatter(w[0], w[1], color='red', s=50);
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) \]
u = np.array([1, 2])
t = np.array([-2, 1])
v = np.array([-10, 15])
w = v - proj(v, t)
points = line(u)
pl.figure(figsize=(6,6))
pl.grid(True)
pl.xlim(-20, 20)
pl.ylim(-20, 20)
pl.scatter(points[:, 0], points[:, 1], s=20);
pl.scatter(v[0], v[1], color='red', s=150);
pl.scatter(w[0], w[1], color='red', s=50);
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
#
t = np.array([1, 1, 1])
a = get_normal_vector(t)
a = a / np.sqrt(a @ a)
b = np.cross(t, a)
b = b / np.sqrt(b @ b)
points = plane(a, b).reshape(-1, 3)
#
# The point
#
v = np.array([5, 0, 2])
#
# The projection of v onto the plane
#
w = v - proj(v, t)
print("w is normal to t: ", sim(w, t))
fig = pl.figure(figsize=(6,6))
ax = fig.add_subplot(projection='3d')
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-5, 5)
ax.scatter(points[:, 0], points[:, 1], points[:, 2], s=10);
ax.scatter(v[0], v[1], v[2], s=100, color='red');
ax.scatter(w[0], w[1], w[2], s=30, color='red');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\} \]
X = np.random.randn(1000, 2)
pl.figure(figsize=(6,6))
pl.xlim(-3, 3)
pl.ylim(-3, 3)
pl.grid(True)
pl.scatter(X[:, 0], X[:, 1], s=1);
t = np.array([1, 1])
I = X @ t > 0
J = X @ t < 0
pl.figure(figsize=(6,6))
pl.xlim(-3, 3)
pl.ylim(-3, 3)
pl.grid(True)
pl.scatter(X[I, 0], X[I, 1], s=1, color='red')
pl.scatter(X[J, 0], X[J, 1], s=1, color='blue');