I.9 · Mathematical Language
Every symbol has a shape.
Machine-learning papers are written in a compact notation, and almost every misreading comes from a shape or an index. Learn the conventions of this book once, check that products fit, and see which symbol is a number, which a set, and which a place.
An array and its shape:
Try these
from 1 to 8, whole numbers only
from 1 to 8, whole numbers only
from 0 to 8, whole numbers only
from 0 to 8, whole numbers only
Each cell shows its position in the flattened array, counting from 0 and reading row by row. Thick outline: the chosen entry. Tinted: the rest of its row and column.
- · index
- example (row); one step in i moves 3 positions
- · index
- feature (column); one step in j moves 1 position
- the shape of a matrix, as NumPy's .shape
- (4, 3)
- number of entries, 4 · 3
- 12
- the chosen entry, indices from 1
- X₂,₃
- the same entry in NumPy, each index minus 1
- X[1, 2]
- its position in the flattened array
- 1 * 3 + 2 = 5
- the whole row, shape (3,)
- X[1, :]
- the whole column, shape (4,)
- X[:, 2]
= exact · ⚠ undefined
The Python being run
import numpy as np
X = np.arange(12).reshape(4, 3)
X.shape # (4, 3)
X[1, 2] # X₂,₃ is 5X₂,₃ is X[1, 2]: subtract 1 from every index, because mathematics counts from 1 and NumPy from 0. The entry sits at position 5 when the array is read row by row (the last index changes fastest). A row is one example, a column is one feature.
Try it before reading on. Look at the matrix: its rows are examples and its columns are features. Point at an entry with and and read the NumPy expression that names it. Then point beyond the last row, and then at . One of those raises an error in Python and the other does not. By the end of the chapter you will read the shape of every object in a formula, check that a product exists, and tell the minimum of a loss from the place where it is reached.
Intuition: a symbol is a promise about shape
A formula in a paper is a sentence in which every symbol makes a promise. A plain italic letter such as promises one number. A bold letter such as promises a list of numbers, and the book always means a column. A capital letter such as promises a table. A calligraphic letter such as promises a stack of tables. If you know the promise, you can read most of a formula without knowing what it computes: you only ask whether the pieces fit.
That is also how you find errors. A product of two tables exists only if their sizes agree in one place. A gradient has one entry for every parameter. An average needs at least one thing to average. Each of these is a check on shape or on count, and none of them needs any arithmetic.
Two decisions run through the whole book and are worth learning now, because they are where a paper and a program part ways:
- Examples are rows, features are columns. The data matrix has rows, one per example, and columns, one per feature. An individual example is a row of written as a column vector.
- Mathematics counts from 1, NumPy from 0. The entry in the -th row and -th column is written with indices and and stored at
X[i - 1, j - 1]. Mixing the two conventions is the commonest way to read the right number from the wrong place.
Definition
Objects and their shapes. The set is the set of columns of real numbers, the set of tables with rows and columns, and the set of stacks of tables with rows and columns each. The statement is therefore a statement about shape. NumPy writes the same facts as tuples: a vector is (d,), a matrix (n, d), a tensor (B, T, d). A NumPy vector of shape (d,) is one-dimensional; it is not a column of shape (d, 1), and the difference matters in a few operations.
Indices and positions. An entry of a matrix has two indices, . An array is stored as one long list, row by row (row-major order). In a matrix with columns, the entry with zero-based indices is at position . In a tensor with shape , zero-based indices sit at position . One step along the last axis moves one position, and one step along the first axis moves a whole slab.
Products and transposes. The transpose swaps rows and columns, so an matrix becomes . The product of an matrix and a matrix exists exactly when the inner sizes match, and then its shape is the outer pair, . The matrix–vector product is the special case . The product of a data matrix is always defined and always square, with one row and one column per feature. The elementwise product, written , multiplies entry by entry and needs equal shapes; it is a * b in NumPy, whereas the matrix product is A @ B.
Norms. The norm is a measure of size. Three are used constantly:
For every they satisfy , and each is only for the zero vector. When no subscript is written, means the Euclidean norm .
Minimum against arg min. For a loss , the minimum is the smallest value the loss takes; it is a number. The arg min is the set of places where that value is reached; it is a set, and it may have more than one element. Adding the same constant to every loss changes the minimum and leaves the arg min unchanged. NumPy’s np.argmin is a third thing again: the position, counting from , of the first smallest entry of an array.
Expectation. For values with probabilities that add to ,
When the probabilities come from counts , they are . If every count is , the division is by and there is no distribution, so the expectation is undefined; it is not . The expectation is a weighted average, and it need not be one of the values: a die never shows .
Gradient. For a function of a parameter vector , the gradient collects one slope for each parameter, so it lies in as well: the gradient has the shape of . How such slopes are found belongs to Part III, Calculus. Here only the notation matters, together with one fact about computing: an estimate made with a small step is an approximation, written with , and it is never exactly the gradient.
Mechanics: how to read and check a formula
Objects, indices and positions
The stage at the top of the chapter is the place to practise indices. Switch between the four kinds of object and watch the readout: the shape tuple, the number of entries, the NumPy expression for the chosen entry, its position in the flattened array and the stride along each axis (how many positions one step along that axis moves). With a vector, only one index exists; with a tensor there are three, and the picture shows one slab per example.
Three things deserve a deliberate test. First, point at the last entry of each kind of object and confirm that its position is one less than the number of entries. Second, set an index one beyond its axis and read the verdict: Python raises an error, and the stage says so instead of picking a nearby entry. Third, set an index to . In mathematics that index does not exist; in NumPy a negative index is legal and counts from the end, so a program may silently read the wrong entry. The stage tells those two outcomes apart.
Do the shapes fit?
Do the shapes fit?
Try these
from 1 to 8, whole numbers only
from 1 to 8, whole numbers only
from 1 to 8, whole numbers only
Rectangles are drawn to scale. For a product the inner two sizes must be equal; the outer two are the shape of the result.
- the first operand, shape (n, d)
- (4, 3)
- the vector, shape (p,)
- (3,)
- the inner dimensions, which must be equal
- 3 = 3
- the product, shape (outer dimensions)
- (4,)
= exact · ⚠ undefined
The Python being run
import numpy as np
X = np.ones((4, 3))
w = np.ones(3)
X @ w # shape (4,)A matrix times a vector needs as many columns as the vector has entries (d = p). The result has one entry per row: one number per example. Result shape: (4,).
is read: X times w.
Read the rule off the picture and not off a formula: for a product, put the two operands next to each other and look at the sizes that touch. When they are equal they cancel, and what remains is the shape of the answer. The preset with a mismatch shows the failure; the transposed product shows the usual repair, and the Gram preset shows why never fails. Note that the order matters: the product may exist while does not.
Three ways to measure a vector
Three ways to measure a vector:
Try these
from −4 to 4, up to 2 decimal places
from −4 to 4, up to 2 decimal places
The vector (3, 4) drawn from the origin. Three small unit balls are drawn around the origin: a diamond for ℓ₁, a circle for ℓ₂ and a square for ℓ∞. The larger outline is the ℓ₂ ball of radius 5, and the vector lies on it.
Dashed: the unit balls, diamond (ℓ₁), circle (ℓ₂) and square (ℓ∞). Solid: the ball of the chosen norm through x. The three outlines differ in shape, not only in colour.
- ℓ₁ norm, the sum of absolute values
- 7
- ℓ₂ norm, the Euclidean length
- 5
- ℓ∞ norm, the largest absolute value
- 4
- the sum of the squares, exact
- 25
= exact · ≈ rounded to 6 decimals
The Python being run
import numpy as np
x = np.array([3, 4])
np.linalg.norm(x, ord=1) # l1
np.linalg.norm(x) # l2, the default
np.linalg.norm(x, ord=np.inf) # l-infinityFor x = (3, 4): ℓ∞ ≤ ℓ₂ ≤ ℓ₁ ≤ 2·ℓ∞ always holds, and it holds here (4 ≤ 5 ≤ 7 ≤ 2 · 4). The vector lies on the boundary of the ℓ₂ ball (a circle) that passes through it.
The three balls differ in shape, and that is the whole idea. The norm of the vector is the factor by which the unit ball of that norm must be stretched to reach it, so the diamond, the circle and the square give three different answers for the same arrow. The chain of inequalities in the definition is visible: the square contains the circle and the circle contains the diamond. Check the preset on a coordinate axis, where all three norms agree, and the preset on a diagonal, where they are as far apart as they can be.
Minimum, arg min and the position NumPy reports
Where is the loss smallest? against
Try these
from −3 to 3, whole numbers only
from 0 to 6, whole numbers only
| position | θ | L(θ) | smallest? |
|---|---|---|---|
| 0 | −4 | 37 | no |
| 1 | −3 | 26 | no |
| 2 | −2 | 17 | no |
| 3 | −1 | 10 | no |
| 4 | 0 | 5 | no |
| 5 | 1 | 2 | no |
| 6 | 2 | 1 | minimiser, the one np.argmin reports |
| 7 | 3 | 2 | no |
| 8 | 4 | 5 | no |
Changing the offset b moves the minimum value but never the arg min: adding the same number to every loss cannot change which points are smallest.
- the smallest loss: a VALUE
- 1
- where the loss is smallest: a POINT
- {2}
- np.argmin(L): the 0-based POSITION of the first minimum
- 6
- thetas[np.argmin(L)]: the parameter at that position
- 2
= exact
The Python being run
import numpy as np
thetas = np.arange(-4, 5)
L = (thetas - 2) ** 2 + 1
np.min(L) # 1: a value
thetas[np.argmin(L)] # 2: a parameter
np.argmin(L) # 6: a positionThe minimum is the VALUE 1. The arg min is the PLACE where it is reached: the point 2. np.argmin(L) returns neither: it returns the position 6 (counting from 0) of the FIRST minimum, and thetas[6] = 2.
Change the offset and watch which readout moves. Choose the loss with two wells and read the three results side by side: a set with two elements, a single number, and a single position. They answer three different questions, and a program that stores one of them in a variable meant for another is wrong without raising any error.
An average with weights
An average with weights:
Try these
from 0 to 99, whole numbers only
from 0 to 99, whole numbers only
from 0 to 99, whole numbers only
from 0 to 99, whole numbers only
from 0 to 99, whole numbers only
from 0 to 99, whole numbers only
| xi | ci | pi | pixi |
|---|---|---|---|
| 1 | 1 | 1/21 | 1/21 |
| 2 | 2 | 2/21 | 4/21 |
| 3 | 3 | 1/7 | 3/7 |
| 4 | 4 | 4/21 | 16/21 |
| 5 | 5 | 5/21 | 25/21 |
| 6 | 6 | 2/7 | 12/7 |
The probabilities are not typed in: they are the counts divided by their total. That division is the step that fails when every count is 0.
- the total count
- 21
- the probabilities added (must be exactly 1)
- 1
- the expectation
- 13/34.333333
- the most frequent value
- 6
= exact · ≈ rounded to 6 decimals · ⚠ undefined
The Python being run
import numpy as np
values = np.array([1, 2, 3, 4, 5, 6])
counts = np.array([1, 2, 3, 4, 5, 6])
p = counts / counts.sum() # p_i = c_i / total
np.average(values, weights=p) # about 13/3The probabilities are the counts divided by the total 21, so they add to exactly 1. E[X] = 13/3 is not a value X can take: an expectation is an average, not a likely outcome. The most frequent value (the mode) is 6; the expectation is a different summary.
The expectation is an ordinary sum, the kind written with sigma in the previous chapter, with weights that add to . Compare it with the value seen most often (the mode): they are different numbers in general, and the expectation need not be a value that ever occurs. Press the no-data preset and see that the stage will not invent a distribution.
The shape of a gradient
A gradient has the shape of θ: against a finite difference
A preview of Part III, Calculus: this stage is about the notation (what is, what shape it has, and what a numerical estimate of it looks like), not about how to differentiate.
Try these
from −4 to 4, up to 2 decimal places
from −4 to 4, up to 2 decimal places
| step h | error in θ₁ | error in θ₂ |
|---|---|---|
| 10⁻¹ | ≈ 0.100000 | ≈ 0.300000 |
| 10⁻³ (chosen) | ≈ 0.001000 | ≈ 0.003000 |
| 10⁻⁶ | ≈ 0.000001 | ≈ 0.000003 |
| 10⁻⁹ smallest | ≈ 1.65481×10⁻⁷ | ≈ 0.000001 |
| 10⁻¹³ | ≈ −0.010480 | ≈ 0.008172 |
Every number in this table is a computed double, so every line says ≈. The exact gradient comes from the formula, not from any step.
- shape of θ and of the gradient: the same
- (2,)
- the function value
- 13
- exact partial derivative, 2θ₁
- 2
- exact partial derivative, 6θ₂
- 12
- finite difference in θ₁, h = 10⁻³ (a computed double)
- 2.001000
- finite difference in θ₂, h = 10⁻³ (a computed double)
- 12.003000
- estimate minus exact, in θ₁
- 0.001000
- estimate minus exact, in θ₂
- 0.003000
= exact · ≈ computed, rounded to 6 decimals
The Python being run
import numpy as np
def f(theta):
return theta[0] * theta[0] + 3 * theta[1] * theta[1]
t1, t2 = 1.0, 2.0
theta = np.array([t1, t2])
grad = np.array([2 * t1, 6 * t2]) # exact, shape (2,)
h = 1e-3
e_j = np.array([1.0, 0.0])
(f(theta + h * e_j) - f(theta)) / h # an estimate of grad[0]The gradient ∇f = (2, 12) has the same shape as θ: one entry per parameter. The finite difference is only an estimate, off by about 0.003 at h = 10⁻³; among these five steps the smallest error at this θ is at h = 10⁻⁹. A smaller step is not always a better one: past some point rounding error grows.
Treat this as a preview of Part III. Nothing in the stage asks you to differentiate; it shows that the symbol names a column with the same number of entries as , and that a numerical estimate is only an approximation whose error first falls and then grows as the step shrinks.
Worked examples
Each example is short enough to do by hand, and each can be checked against a stage. The numbers are not the ones in the exercises at the end of the chapter.
A batch of images. A mini-batch holds images, each a grid with rows of numbers. It is a tensor of shape , and it holds numbers. In zero-based terms the entry with batch index , row and column is at position . Flattening each image to a vector of length gives a matrix of shape with the same numbers.
Does the product exist? Let be and . The inner sizes are and , so exists and has shape : one number per example. Now let . Then does not exist (), but does: gives , one number per feature. The matrix is and is .
Three norms. For : , and . The chain holds. For the Euclidean norm is , an irrational number that a program can only approximate; the stage keeps it as a radical.
Place against value. Take the loss on the integers from to . The smallest value is , reached only at , so the minimum is and the arg min is . In an array that starts at , the position that np.argmin returns is . Adding to the loss makes the minimum and leaves and the position untouched.
An expectation from counts. A spinner showed twice, once and once. The total is , the probabilities are , and . Doubling every count changes nothing, since the probabilities are the same.
Executable reference code
Run the Python examples below to follow the calculations step by step. The comments record what each line returns.
First shapes, indices, positions and products: a vector, a data matrix, a tensor, the conversion between mathematical and NumPy indices, the flattened position of an entry, the transpose, matrix products with their shapes, and the error raised when the inner sizes disagree.
import numpy as np
# Conventions: a vector is a column of d numbers; a data matrix X has n EXAMPLES as rows and d FEATURES as columns.
# Mathematics counts from 1 (X_{ij}, i = 1..n, j = 1..d); NumPy counts from 0, so X_{ij} is X[i - 1, j - 1].
X = np.arange(12).reshape(4, 3) # n = 4 examples, d = 3 features; the entry in row r, column c is 3 * r + c
x = np.array([2.0, -1.0, 0.5]) # a vector in R^3
w = np.array([1.0, 0.0, -1.0])
W = np.arange(6).reshape(3, 2)
a = np.array([1.0, 2.0, 3.0])
b = np.array([4.0, 5.0, 6.0])
X3 = np.arange(24).reshape(2, 3, 4) # a tensor: B = 2 examples, T = 3 positions, d = 4 features
n = X.shape[0] # the number of examples (rows)
d = X.shape[1] # the number of features (columns)
B, T, d3 = X3.shape
shape_vector = x.shape # (3,): a one-dimensional array, NOT a column (3, 1)
shape_matrix = X.shape # (4, 3)
i, j = 2, 3 # a mathematical (1-based) example number and feature number
entry = X[i - 1, j - 1] # X_{2,3} is X[1, 2] = 3 * 1 + 2 = 5
row = X[i - 1, :] # the whole example i, a vector with d = 3 entries
column = X[:, j - 1] # the whole feature j, a vector with n = 4 entries
flat_position = int(np.ravel_multi_index((i - 1, j - 1), X.shape)) # row-major: (i - 1) * d + (j - 1) = 5
same_entry = X.ravel()[flat_position] == entry # True: the flattened array lists row 1 first, then row 2, ...
Xt = X.T # the transpose swaps the axes: shape (3, 4)
Xw = X @ w # (4 x 3)(3) -> (4,): one number per example
XW = X @ W # (4 x 3)(3 x 2) -> (4, 2)
G = X.T @ X # (3 x 4)(4 x 3) -> (3, 3): one row and one column per feature
dot = a @ b # the dot product: 1*4 + 2*5 + 3*6 = 32.0, a scalar
hadamard = a * b # the elementwise product, written with a circled dot in the notation
indicator = (x > 0).astype(int) # the indicator 1[x_j > 0], entry by entry
try:
X @ np.ones(4) # X has d = 3 columns but this vector has 4 entries: the inner dimensions differ
mismatch = 'no error'
except ValueError:
mismatch = 'ValueError'
element_of_tensor = X3[1, 2, 3] # the last element: 1 * 12 + 2 * 4 + 3 = 23
tensor_slice_shape = X3[1].shape # one example of the batch: (3, 4), T positions with d features each
indices = []
for i in range(1, n + 1): # the mathematical index range 1, ..., n; the matching row is X[i - 1]
indices.append(int(X[i - 1, 0]))
print(shape_vector, shape_matrix, int(entry), row.tolist(), column.tolist(), flat_position, bool(same_entry))
print(Xt.shape, Xw.tolist(), XW.shape, G.shape, float(dot), hadamard.tolist(), indicator.tolist(), mismatch)
print(n, d, B, T, d3, int(element_of_tensor), tensor_slice_shape, indices)Then norms and the three meanings of minimum: the three norms with the chain of inequalities, the Euclidean norm of a vector whose length is not a whole number, and min, argmin and np.argmin told apart on a table with a tie.
import math
from fractions import Fraction
import numpy as np
# Norms. For x = (3, 4) every norm is a whole number; for (1, 1) the l2 norm is sqrt(2), an irrational number that no double stores exactly.
x = np.array([3.0, 4.0])
l1 = np.linalg.norm(x, ord=1) # |3| + |4| = 7
l2 = np.linalg.norm(x) # sqrt(3**2 + 4**2) = 5
linf = np.linalg.norm(x, ord=np.inf) # max(|3|, |4|) = 4
chain = bool(linf <= l2 <= l1) # ||x||_inf <= ||x||_2 <= ||x||_1 for every vector
# The same l2 norm exactly: the squared norm is a rational number, and an integer square root decides whether the norm is rational.
sum_of_squares = Fraction(3) ** 2 + Fraction(4) ** 2
root = math.isqrt(25)
exact_l2 = Fraction(root) if root * root == sum_of_squares else None # Fraction(5, 1)
diagonal = np.linalg.norm(np.array([1.0, 1.0])) # 1.4142135623730951, the nearest double to sqrt(2)
diagonal_squared_is_two = diagonal * diagonal == 2.0 # False: squaring the rounded root does not return exactly 2
# arg min versus min. The loss on a grid of candidate parameters: L(theta) = (theta**2 - 1)**2 + 1 has TWO minimisers, -1 and 1.
thetas = np.arange(-4, 5)
L = (thetas**2 - 1) ** 2 + 1
best_value = np.min(L) # the minimum VALUE: 1
best_position = int(np.argmin(L)) # the 0-based POSITION of the FIRST minimum: 3
best_theta = thetas[np.argmin(L)] # the parameter at that position: -1 (the other minimiser, 1, is not reported)
all_minimisers = thetas[L == best_value].tolist() # [-1, 1]: argmin as a SET
p = np.array([0.1, 0.5, 0.4])
most_likely = int(np.argmax(p)) # the position of the largest entry: 1
# Conventions: log is the natural logarithm; bits need log2.
nats = np.log(math.e) # 1.0
bits = np.log2(8.0) # 3.0
print(float(l1), float(l2), float(linf), chain, exact_l2, float(diagonal), diagonal_squared_is_two)
print(int(best_value), best_position, int(best_theta), all_minimisers, most_likely, float(nats), float(bits))Finally the notation of averages, sums over an axis and the gradient: an expectation from counts with exact fractions, the failure of the division by zero, a conditional probability as a ratio, sums along each axis, and a finite difference that only approximates the gradient.
from fractions import Fraction
import numpy as np
# Expectation of a finite distribution: E[X] = sum_i p_i x_i, with p_i >= 0 and sum_i p_i = 1.
# Counts become probabilities by dividing by their total: p_i = c_i / sum(c). The division is the taught step, and it is only defined for a non-zero total.
values = np.array([1, 2, 3, 4, 5, 6]) # the faces of a die
counts = np.array([1, 2, 3, 4, 5, 6]) # how often each face was seen
p = counts / counts.sum() # 1/21, 2/21, ..., 6/21 (floating point)
sum_of_p = float(p.sum()) # about 1.0
expectation = np.average(values, weights=p) # sum(p * values): about 13/3 = 4.333...
exact_p = [Fraction(int(c), int(counts.sum())) for c in counts]
exact_expectation = sum(pi * int(v) for pi, v in zip(exact_p, values)) # Fraction(13, 3), exact
exact_sum_of_p = sum(exact_p) # Fraction(1, 1)
fair = [Fraction(1, 6)] * 6
fair_expectation = sum(pi * int(v) for pi, v in zip(fair, values)) # Fraction(7, 2): a die never shows 3.5
try:
Fraction(0, int(np.zeros(6).sum())) # all counts zero: the total is 0, so there is no distribution
zero_total = 'no error'
except ZeroDivisionError:
zero_total = 'ZeroDivisionError'
# Probability notation as arithmetic on a small table of joint counts: P(A | B) = P(A and B) / P(B).
mask = values > 4 # the event A: the die shows more than 4
p_A = np.sum(p[mask]) # P(A) = (5 + 6) / 21
p_AB = Fraction(5, 21) # P(A and B) with B = "the face is odd": only face 5 is odd and above 4
p_B = Fraction(1 + 3 + 5, 21) # P(B) = 9/21
p_A_given_B = p_AB / p_B # Fraction(5, 9)
# Sums and means over an axis, with the index convention stated: a.sum(axis=0) adds DOWN the rows.
a = np.array([[1, 2, 3], [4, 5, 6]])
total = np.sum(a) # 21
by_column = a.sum(axis=0).tolist() # [5, 7, 9]: one sum per column (feature)
by_row = a.sum(axis=1).tolist() # [6, 15]: one sum per row (example)
product = np.prod(a) # 720
mean = np.mean(a) # 3.5
# The gradient of f(theta) = theta1**2 + 3 * theta2**2 has the SAME SHAPE as theta, and a finite difference only approximates it.
t1, t2 = 1.0, 2.0
theta = np.array([t1, t2])
grad = np.array([2 * t1, 6 * t2]) # (2, 12), exact for these values
same_shape = grad.shape == theta.shape # True
def f(theta):
return theta[0] * theta[0] + 3 * theta[1] * theta[1] # the same operations, in the same order, as the stage
h = 1e-3
e_j = np.array([1.0, 0.0]) # the first coordinate direction
estimate = (f(theta + h * e_j) - f(theta)) / h # about 2.001: close to 2 but not equal
error = float(estimate - grad[0]) # about 0.001: the step h shows up in the error
x_abs = abs(-2.5) # |x| = 2.5
A = {1, 2, 3}
member = 2 in A # x in A
print(float(sum_of_p), float(expectation), exact_expectation, exact_sum_of_p, fair_expectation, zero_total)
print(float(p_A), p_A_given_B, int(total), by_column, by_row, int(product), float(mean))
print(grad.tolist(), same_shape, round(float(estimate), 3), round(error, 3), x_abs, member)Explore: the notation table
The table lists the symbols of the book with how each is read aloud, what it means, the shape of what it names, the Python that writes the same thing and where the idea is developed later. Search it by a word, narrow it by category, and use it when a later chapter shows a symbol you do not recognise. Every Python snippet marked as runnable appears in the reference code above.
The notation table
Show
Showing all 47 entries.
Objects and shapes
- Read as
- x in R
- Means
- A single real number, written in italics.
- Shape
- a number: () in NumPy
- Python
- no direct equivalent
- Developed in
- Part I · Scalars, quantities, units, powers and roots
- Read as
- bold x in R to the d
- Means
- A column of d real numbers (d components). In this book a vector is always a column.
- Shape
- d × 1; (d,) in NumPy
- Python
x = np.array([2.0, -1.0, 0.5])- Developed in
- Part II · Linear Algebra
- Read as
- capital X in R n by d
- Means
- A data matrix: n examples as rows, d features as columns.
- Shape
- n × d; (n, d) in NumPy
- Python
X = np.arange(12).reshape(4, 3)- Developed in
- Part II · Linear Algebra
- Read as
- calligraphic X in R B by T by d
- Means
- A batch of B sequences, each with T positions and d features per position.
- Shape
- B × T × d; (B, T, d)
- Python
X3 = np.arange(24).reshape(2, 3, 4)- Developed in
- Part VIII · Vectorized computation
- Read as
- n
- Means
- The number of examples: the number of rows of X.
- Shape
- a whole number
- Python
n = X.shape[0]- Developed in
- Part V · Data summaries: mean, median, mode
- Read as
- d
- Means
- The number of features: the number of columns of X, or the length of a feature vector.
- Shape
- a whole number
- Python
d = X.shape[1]- Developed in
- Part II · Linear Algebra
- Read as
- B and T
- Means
- The batch size and the sequence length of a tensor.
- Shape
- two whole numbers
- Python
B, T, d3 = X3.shape- Developed in
- Part VII · From token logits to language-model loss
- Read as
- x, example i
- Means
- The i-th example, a row of X written as a column vector. The index is a superscript in parentheses, so it cannot be mistaken for a power.
- Shape
- d × 1
- Python
row = X[i - 1, :]- Developed in
- Part V · Data summaries: mean, median, mode
Indices and slices
- Read as
- X sub i j
- Means
- The entry in row i and column j. The mathematical indices start at 1.
- Shape
- a number
- Python
X[i - 1, j - 1]- Developed in
- Part II · Matrices as linear transformations
- Read as
- x, example i, feature j
- Means
- Feature j of example i: the same number as X_{ij}.
- Shape
- a number
- Python
x_i[j - 1]- Developed in
- Part V · Regression fundamentals
- Read as
- X sub i, colon
- Means
- The whole row i: example i with all its d features.
- Shape
- d entries; (d,)
- Python
X[i - 1, :]- Developed in
- Part II · Matrix × vector
- Read as
- X sub colon, j
- Means
- The whole column j: feature j across all n examples.
- Shape
- n entries; (n,)
- Python
X[:, j - 1]- Developed in
- Part II · Matrix × vector
- Read as
- i from 1 to n
- Means
- The index takes the values 1, 2, …, n, both ends included. In Python the same loop is range(1, n + 1).
- Shape
- a list of n values
- Python
for i in range(1, n + 1):- Developed in
- Part I · Indexed quantities and summation
Products, transpose and norms
- Read as
- X transpose
- Means
- Swaps rows and columns: an n × d matrix becomes d × n.
- Shape
- d × n
- Python
X.T- Developed in
- Part II · Transpose and orthogonality
- Read as
- X times w
- Means
- Matrix times vector: one number per example. The inner dimensions must agree (d columns, d entries).
- Shape
- n entries; (n,)
- Python
X @ w- Developed in
- Part II · Matrix × vector
- Read as
- X times W
- Means
- Matrix times matrix: (n × d)(d × k) gives n × k.
- Shape
- n × k
- Python
X @ W- Developed in
- Part II · Matrix × matrix as composition
- Read as
- X transpose X
- Means
- The d × d matrix of feature-by-feature inner products: (d × n)(n × d) gives d × d.
- Shape
- d × d
- Python
X.T @ X- Developed in
- Part V · Covariance matrices and multivariate spread
- Read as
- a transpose b
- Means
- The dot product: the sum of a_i b_i, one number.
- Shape
- a number
- Python
a @ b- Developed in
- Part II · Dot product, angle, and orthogonality
- Read as
- a circle-dot b
- Means
- The elementwise product: multiply entry by entry. The result has the shape of a and b.
- Shape
- same as a and b
- Python
a * b- Developed in
- Part VIII · Vectorized computation
- Read as
- the two-norm of x
- Means
- The Euclidean length: the square root of the sum of the squares of the entries.
- Shape
- a number ≥ 0
- Python
np.linalg.norm(x)- Developed in
- Part II · Norms, magnitude, and normalization
- Read as
- the one-norm of x
- Means
- The sum of the absolute values of the entries.
- Shape
- a number ≥ 0
- Python
np.linalg.norm(x, ord=1)- Developed in
- Part II · Norms, magnitude, and normalization
- Read as
- the infinity-norm of x
- Means
- The largest absolute value among the entries.
- Shape
- a number ≥ 0
- Python
np.linalg.norm(x, ord=np.inf)- Developed in
- Part II · Norms, magnitude, and normalization
- Read as
- indicator that x j is positive
- Means
- The indicator: 1 when the statement in brackets is true, 0 when it is false.
- Shape
- 0 or 1
- Python
(x > 0).astype(int)- Developed in
- Part IV · Events and probability mass
Sums and averages
- Read as
- the sum of a i for i from 1 to n
- Means
- The accumulator of a loop: add the terms one by one. Both bounds are included.
- Shape
- a number
- Python
np.sum(a)- Developed in
- Part I · Indexed quantities and summation
- Read as
- the sum of X i j over i
- Means
- The sum down column j: one number per feature. In NumPy this is a sum over axis 0, the row axis.
- Shape
- d entries
- Python
a.sum(axis=0)- Developed in
- Part VIII · Vectorized computation
- Read as
- the product of a i for i from 1 to n
- Means
- Multiply the factors one by one. An empty product is 1.
- Shape
- a number
- Python
np.prod(a)- Developed in
- Part VII · Logarithms and log probabilities in computation
- Read as
- x bar equals one over n times the sum
- Means
- The mean: the sum divided by the number of terms, n, which is the last index only when the indices start at 1.
- Shape
- a number
- Python
np.mean(a)- Developed in
- Part V · Data summaries: mean, median, mode
Parameters and optimisation
- Read as
- theta
- Means
- The parameters of a model, collected in one vector.
- Shape
- p × 1; (p,)
- Python
- no direct equivalent
- Developed in
- Part VI · Objective functions and optimization landscapes
- Read as
- L of theta
- Means
- The loss: one number that says how badly the parameters θ fit the data.
- Shape
- a number
- Python
- no direct equivalent
- Developed in
- Part VI · Objective functions and optimization landscapes
- Read as
- arg min over theta of L of theta
- Means
- The parameter value (a point, or a set of points when there is a tie) at which the loss is smallest. It is a location, not a loss value.
- Shape
- same as θ
- Python
thetas[np.argmin(L)]- Developed in
- Part VI · Local vs global optima and stationary points
- Read as
- min over theta of L of theta
- Means
- The smallest value the loss takes. It is a loss value, not a location.
- Shape
- a number
- Python
np.min(L)- Developed in
- Part VI · Local vs global optima and stationary points
- Read as
- arg max over k of p k
- Means
- The position (class, outcome) with the largest entry, as a 0-based position in NumPy.
- Shape
- a whole number
- Python
np.argmax(p)- Developed in
- Part VII · Logits and softmax
- Read as
- theta hat
- Means
- An estimate of θ computed from the data. A hat marks an estimate; it is not the true value.
- Shape
- same as θ
- Python
- no direct equivalent
- Developed in
- Part V · Estimators and estimation
- Read as
- theta star
- Means
- The optimal parameter: a minimiser of the loss. A star marks an optimum.
- Shape
- same as θ
- Python
- no direct equivalent
- Developed in
- Part VI · Objective functions and optimization landscapes
Probability
- Read as
- P of A
- Means
- The probability of the event A: a number from 0 to 1.
- Shape
- a number in [0, 1]
- Python
p_A = np.sum(p[mask])- Developed in
- Part IV · Events and probability mass
- Read as
- P of A given B
- Means
- The probability of A when B is known to have happened: P(A and B) divided by P(B), defined only when P(B) > 0.
- Shape
- a number in [0, 1]
- Python
p_AB / p_B- Developed in
- Part IV · Conditional probability as restricted sample space
- Read as
- the expectation of X
- Means
- The probability-weighted average of the values. It need not be a value that X can take.
- Shape
- a number
- Python
np.average(values, weights=p)- Developed in
- Part IV · Expectation
- Read as
- X is distributed as p
- Means
- The random variable X follows the distribution p.
- Shape
- a distribution
- Python
- no direct equivalent
- Developed in
- Part IV · Random variables
Functions and gradients
- Read as
- f from R d to R
- Means
- A function that takes d numbers and returns one number: a loss is such a function of the parameters.
- Shape
- in: d entries, out: a number
- Python
def f(theta):- Developed in
- Part I · Functions as input → transformation → output
- Read as
- the partial derivative of f with respect to theta j
- Means
- How fast f changes when only θ_j changes. A finite difference approximates it; it is not equal to it.
- Shape
- a number
- Python
(f(theta + h * e_j) - f(theta)) / h- Developed in
- Part III · Multivariable functions and partial derivatives
- Read as
- the gradient of L with respect to theta
- Means
- The vector of all the partial derivatives of L. It has the same shape as θ: one entry per parameter.
- Shape
- same as θ
- Python
grad = np.array([2 * t1, 6 * t2])- Developed in
- Part III · Gradient and directional derivatives
Conventions and signs
- Read as
- log of x
- Means
- The natural logarithm, also written ln. Bits need log₂, always written with the base.
- Shape
- a number
- Python
np.log(math.e)- Developed in
- Part VII · Logarithms and log probabilities in computation
- Read as
- log base two of x
- Means
- The logarithm in bits.
- Shape
- a number
- Python
np.log2(8.0)- Developed in
- Part VII · Self-information
- Read as
- absolute value of x
- Means
- The distance of the number x from 0.
- Shape
- a number ≥ 0
- Python
abs(-2.5)- Developed in
- Part I · Scalars, quantities, units, powers and roots
- Read as
- x is in A
- Means
- x is an element of the set A.
- Shape
- true or false
- Python
2 in A- Developed in
- Part I · Sets, intervals, and relations
- Read as
- approximately equal
- Means
- A rounded or computed value, such as a floating-point result. An equals sign promises an exact value.
- Shape
- a statement
- Python
- no direct equivalent
- Developed in
- Part VIII · Floating-point representation
- Read as
- a is defined to be b
- Means
- A definition, not a claim to be checked.
- Shape
- a definition
- Python
- no direct equivalent
- Developed in
- Part I · Algebraic expressions, equations and inequalities
These questions have definite answers; check each against the stages above.
- In the shape stage, choose a matrix with more rows than columns and point at the last entry. How many entries are there, and what is the position of the last one? Does the same hold for a tensor?
- Set the matrix index to and then to one more than the number of rows. Which of the two raises an error in Python, and why can the other be worse?
- In the product stage, find sizes for which fails but works. What does that say about which of the two sizes is the number of examples?
- In the norm stage, find a vector for which all three norms are equal. Then find one for which the Euclidean norm is not a whole number, and read how the stage writes it.
- In the arg min stage, choose the flat bottom and read the three results. How many elements does the arg min have, and which one does
np.argminreport? - In the expectation stage, choose the fair die. Is the expectation one of the faces? Then set one count to and the others unchanged: what happens to the probability of that face?
- In the gradient stage, choose the smallest step and then the largest. Which error is smaller? Is it the smallest step that gives the best estimate?
- In the table, search for “norm” and then for a word that does not occur. What does the table say, and how do you get the full list back?
Common mistakes
Numerical considerations
Everything in the stages is exact except where it says otherwise. In a program, three habits are worth learning from the start.
Comparing two floating-point numbers with == is the same trap in a smaller setting. The Euclidean norm of is , which no double represents exactly; squaring the computed value gives a number that is close to and often not equal to it. Compare with a tolerance, as np.isclose does, or keep exact values while you can, as the stages do.
Two more habits: an average of an empty batch should be handled explicitly and reported as undefined instead of letting a division by zero propagate as nan; and a Euclidean norm squares every entry before adding, so a vector with entries near overflows to infinity even though its length is a perfectly ordinary double. np.linalg.norm([1e200, 1e200]) returns inf (with a warning), while math.hypot(1e200, 1e200), which scales before squaring, returns about .
Connections to engineering and machine learning
- Data matrices. A table of examples and features is . A model gives one prediction per example, and the first thing to check in a pipeline is that
X.shape[1]equalslen(w). - Batches of sequences. A language model works on tensors of shape : a batch, positions and features. Most shape bugs in deep-learning code are an axis in the wrong place, which is why shapes belong in comments and in assertions.
- The Gram matrix. is a table with one row and column per feature. It reappears in least squares, in covariance and in principal components, which later parts take up.
- Regularisation. The penalties and are the first two norms of the weights. The diamond and the circle in the norm stage are the reason the first tends to produce zero weights and the second only small ones.
- Training is an arg min. Fitting a model means finding in . The minimum value tells you how good the fit is, the arg min tells you what the parameters are, and when the loss has a flat direction the arg min is a whole set.
- Losses are expectations. A population loss is and the loss on a batch is its estimate by an average over the examples in the batch. The expectation notation is the language of Part IV, Probability, and the averages over examples are the sums of the previous chapter.
- Gradient descent. The update only makes sense because the gradient has the shape of . When a program raises a shape error in an update, the first suspect is a gradient with a different shape.
Exercises
Answer each question and press Check. A wrong answer tells you which mistake it looks like. After one attempt you may open a worked solution.
Summary and checkpoints
- A symbol promises a shape: italic for a number, bold for a column vector , a capital for a table, a calligraphic capital for a stack of tables. The data matrix has examples as rows and features as columns.
- Mathematics counts from and NumPy from : the entry with indices is
X[i - 1, j - 1]. Arrays are stored row by row; an entry at zero-based of a matrix with columns is at position . A negative index wraps; an index past the end raises an error. - A product exists when the inner sizes agree and has the outer pair as its shape. The transpose swaps the sizes. is always .
- The norms , and are three measures of size with .
- The minimum is a value, the arg min is a set of places, and
np.argminis the position of the first smallest entry. - with ; it is undefined when there are no observations.
- The gradient has the shape of , and a finite-difference estimate is an approximation with a best step.
Checkpoints. You can move on when you can, without the stage:
- Say the shape of , of , of and of for a data matrix with examples and features.
- Convert a mathematical pair of indices to the NumPy expression and to the flattened position.
- Decide whether a product of two shapes exists and give the shape of the result, or the two sizes that disagree.
- State the three norms and the order of their sizes.
- Distinguish the minimum, the arg min and
np.argmin, and say which of the three changes when a constant is added to the loss. - Compute an expectation from counts and say when it does not exist.