Before any network can be trained, three mathematical tools have to be in place. The slides state the reason for each one, and each reason maps to a later chapter of this course.
| Tool | Why deep learning needs it |
|---|---|
| Linear algebra | DL requires working with large datasets, which we can conveniently think of as matrices. Linear algebra gives us a powerful set of techniques for working with them. |
| Differential calculus | In DL, models are trained by updating their parameters successively so that they get better and better as they see more and more data. Determining which way to move each parameter at each step requires derivatives and differentiation. |
| Automatic differentiation | DL requires evaluating the partial derivatives of a function at given values automatically. Automatic differentiation computes exact derivatives in constant time. |
The word exact matters. Automatic differentiation is not numerical approximation by finite differences: it applies the chain rule mechanically over a decomposition of the program into primitive operations, so the derivatives it returns are exact up to floating point.
Three objects, one idea: an array with a growing number of axes.
| Object | Definition | Notation |
|---|---|---|
| Vector | A list of real (scalar) values. x ∈ ℝn means the vector x consists of n real-valued scalars. |
Bold-faced, lower-cased letters: x, y, z |
| Matrix | An array of real values with two axes. A ∈ ℝm×n means m rows and n columns; element aij belongs to the i-th row and j-th column. |
Bold-faced, capital letters: X, Y, Z |
| Tensor | A generic way of describing n-dimensional arrays with an arbitrary number of axes. Vectors are first-order tensors and matrices are second-order tensors. | Bold-faced, capital letters |
Tensors become important when working with color images, which arrive as arrays with 3 axes: height, width, and a channel axis for stacking the color channels (RGB). Indexing works like matrices: given an RGB image X, the element xijk is the value at the i-th row, j-th column and k-th channel.
The transpose flips a matrix over its diagonal by exchanging rows and columns: given A ∈ ℝm×n, its transpose AT is an n×m matrix. The trace of a square matrix A ∈ ℝn×n is the sum of the elements on its main diagonal:
tr(A) = ∑i=1n aii
Given any two tensors of identical shape, the result of any binary element-wise operation is a tensor of the same shape. For two matrices A, B ∈ ℝm×n:
aij + bij.A ⊙ B): each entry is aij · bij.Adding or multiplying a tensor by a scalar does not change its shape: every element is added to, or multiplied by, the scalar. Likewise, applying a real function f: ℝ → ℝ (for instance sin) to a tensor yields a tensor of the same shape with f applied element by element. This is exactly how activation functions are applied to a whole layer in Chapter 3.
The average μ(A) is a scalar: the sum of all elements divided by the total number of elements. For A ∈ ℝm×n that denominator is m · n. This tiny definition returns as the μ in the Pix2Pix and CycleGAN loss functions of Chapter 11.
| Operation | Inputs | Result | Formula |
|---|---|---|---|
| Dot (scalar) product | Two equal-length vectors x, y ∈ ℝd |
A scalar | x · y = ∑i=1d xi · yi |
| Matrix product | A ∈ ℝm×k, B ∈ ℝk×n |
C = AB ∈ ℝm×n |
cij = ∑t=1k ait · btj |
| Frobenius inner product | Two tensors of identical shape A, B ∈ ℝm×n |
A scalar | 〈A, B〉F = ∑i ∑j aij · bij |
The matrix product has one hard constraint: the number of columns in the first matrix must equal the number of rows in the second. The result has the rows of the first and the columns of the second. Try it below: this shape rule is the single most common source of runtime errors when you build a network by hand.
A and column of B are combined by a dot product into the single element cij of C; the inner dimension k is consumed and disappears from the result.A norm maps a vector to a scalar that tells us how big the vector magnitude is. It is one of the most useful operators in linear algebra, and in this course it appears as a regularization penalty (Chapter 9), as a gradient clipping threshold (Chapter 4) and as a loss function (Chapter 3).
A vector norm f: ℝn → ℝ must satisfy four properties:
f(αx) = |α| · f(x) — absolute homogeneity;f(x + y) ≤ f(x) + f(y) — the triangle inequality;f(x) ≥ 0;f(x) = 0 ⇔ xᵢ = 0 for all i.The Lp norm of a vector x ∈ ℝn is:
‖x‖p = ( ∑i=1n |xi|p )1/p
The Euclidean distance is the L2 norm: ‖x‖2 = √(∑i=1n xi2). The subscript is often omitted, so a bare ‖x‖ means the L2 norm.
The Frobenius norm of a matrix A ∈ ℝm×n is the square root of the sum of the squares of its elements:
‖A‖F = √( ∑i ∑j aij2 )
The Frobenius norm satisfies all the properties of vector norms — it is, in effect, the L2 norm of the matrix flattened into a vector.
Do not confuse the Frobenius inner product (a scalar built from two tensors of the same shape) with the Frobenius norm (a scalar built from one tensor). The relationship is the familiar one: ‖A‖F = √〈A, A〉F. The inner product is what a convolutional filter computes at every spatial position in Chapter 6.
In DL we use loss functions that are differentiable with respect to the model parameters. This means that for each parameter we can determine how rapidly the loss would increase or decrease, were we to increase or decrease that parameter by an infinitesimally small amount. That single sentence is the whole reason calculus is in this course.
Given f: ℝ → ℝ, y = f(x), the derivative can be written in several equivalent ways:
f′(x) = y′ = ∂y/∂x = ∂f/∂x = (∂/∂x) f(x)
| Rule | Derivative |
|---|---|
∂/∂x (c) with c constant | 0 |
∂/∂x (x) | 1 |
∂/∂x (xn) — the power rule | n · xn−1 |
∂/∂x (1/x) | −1/x² |
∂/∂x (ex) | ex |
∂/∂x (ln x) | 1/x |
| Rule | Formula |
|---|---|
| Sum rule | ∂/∂x [f(x) + g(x)] = ∂f/∂x + ∂g/∂x |
| Product rule | ∂/∂x [f(x) · g(x)] = f(x) · ∂g/∂x + (∂f/∂x) · g(x) |
| Constant addition | ∂/∂x [c + f(x)] = ∂f/∂x |
| Constant multiple rule | ∂/∂x [c · f(x)] = c · ∂f/∂x |
| Chain rule | ∂/∂x [f(g(x))] = (∂f/∂g(x)) · (∂g/∂x) |
The last row is the one the rest of the course is built on. Backpropagation (Chapter 4) is nothing but the recursive application of the chain rule over a computational graph.
In DL, functions often depend on many variables. To calculate the partial derivative of f(x₁, …, xₙ) with respect to xᵢ, we treat the other variables as constants and differentiate with respect to xᵢ. It is written ∂y/∂xᵢ, ∂f/∂xᵢ or f′xᵢ.
The partial derivatives of a multivariate function with respect to all its variables are concatenated into the gradient vector. Given f: ℝn → ℝ and x ∈ ℝn, the gradient ∇f(x) is a vector of n partial derivatives:
∇f(x) = [ ∂f/∂x₁, ∂f/∂x₂, …, ∂f/∂xn ]T
The Jacobian generalises the gradient to a multivariate vector function. Given f: ℝn → ℝm taking x ∈ ℝn and producing y = f(x) ∈ ℝm, the Jacobian Jf (also written ∇f) is an m×n matrix whose i-th row is ∇fiT, the transpose of the gradient of the i-th component:
Jf = [ ∂fi/∂xj ] with 1 ≤ i ≤ m, 1 ≤ j ≤ n
| Object | Function type | Shape of the result |
|---|---|---|
| Derivative | ℝ → ℝ | scalar |
| Gradient | ℝn → ℝ | vector of length n |
| Jacobian | ℝn → ℝm | matrix m×n |
Be ready to state the three shapes in the table above without hesitation, and to say why a loss function always produces a gradient and never a Jacobian: because a loss maps the parameter vector to a single scalar, so m = 1 and the Jacobian degenerates into the transposed gradient.
The slides work an example that is worth reproducing in full, because the intuition it builds is the one that makes gradient descent obvious later.
Consider the multiplication of two numbers, f(x, y) = x · y. By the constant multiple rule:
∂f/∂x = 1 · y = y∂f/∂y = x · 1 = xso the gradient vector is ∇f = [y, x]T. Now instantiate it at x = 4, y = −3, where f(x, y) = −12:
| Quantity | Value | Reading |
|---|---|---|
∂f/∂x | −3 | Increasing x a little decreases f at rate 3. |
∂f/∂y | 4 | Increasing y a little increases f at rate 4. |
∇f | [−3, 4]T | The direction of steepest increase at the point (4, −3). |
The slides read this as a sensitivity: the partial derivative of f with respect to each variable is the sensitivity of the function in the surrounding of its current value. From that reading the operating rule follows directly.
To obtain a higher output value: decrease variables with a negative partial derivative and increase variables with a positive one. Vice versa for a lower output. This is true only for small variations: in the example above, when x or y change sign, the gradient changes its direction.
Turn it upside down — we want the loss lower, not higher — and you have the gradient descent update of Chapter 5: move each parameter against its partial derivative.
∇f = [−3, 4]T.Consider the composite function f(x, y, z) = (x + y) · z. It decomposes into two primitives:
q(x, y) = x + yf(q, z) = q · zThe partial derivatives of both are immediate:
| Derivative | Value | Rule used |
|---|---|---|
∂q/∂x | 1 | sum rule |
∂q/∂y | 1 | sum rule |
∂f/∂q | z | product / constant multiple rule |
∂f/∂z | q | product / constant multiple rule |
But we are not interested in the intermediate value ∂f/∂q. What we want are the partial derivatives of f with respect to its inputs x, y and z. The chain rule delivers them:
∂f/∂x = (∂f/∂q) · (∂q/∂x) = z · 1 = z
∂f/∂y = (∂f/∂q) · (∂q/∂y) = z · 1 = z
Notice the structure: a local gradient (∂q/∂x, known at the node) multiplied by an upstream gradient (∂f/∂q, arriving from above). Chapter 4 gives this pattern a name and applies it to every operator in a neural network.
Differentiation is a crucial step in nearly all DL optimization algorithms. Automatic differentiation is a set of techniques to numerically evaluate the derivative of a function specified by a computer program, and derivatives of arbitrary order can be computed automatically by applying the chain rule.
In reverse mode — the mode used by every deep learning framework — derivatives are computed in two phases:
Take the same function, f(x, y, z) = (x + y) · z, at the point (x, y, z) = (−2, 5, −4). The slides walk it operator by operator.
| Phase | Step | Computation | Result |
|---|---|---|---|
| Forward | the + node | q = −2 + 5 | q = 3 |
the × node | f = 3 · (−4) | f = −12 | |
| Reverse | local gradients of × | ∂f/∂q = z, ∂f/∂z = q | −4 and 3 |
local gradients of + | ∂q/∂x = 1, ∂q/∂y = 1 | 1 and 1 | |
| chain into x | −4 · 1 | ∂f/∂x = −4 | |
| chain into y | −4 · 1 | ∂f/∂y = −4 |
Step through it yourself. Advance the forward pass to fill the node values, then the reverse pass to propagate the gradients back to the inputs.
f = (x + y)·z at (−2, 5, −4). Solid cobalt is the forward phase filling in node values; dashed vermilion is the reverse phase, where every edge multiplies the gradient arriving from above by the local gradient stored at the node.The two-phase description is the answer expected for “how does automatic differentiation work?”: (1) a forward phase that decomposes the function into primitives with a computational graph and evaluates each one, and (2) a reverse phase that applies the chain rule from the outputs back to the inputs. Be able to run the (−2, 5, −4) example on paper, ending with ∂f/∂x = ∂f/∂y = −4 and ∂f/∂z = 3.
The laboratory of this course uses TensorFlow with Keras. Its automatic differentiation entry point is tf.GradientTape, which records the forward phase and replays it in reverse on demand.
The equivalent in PyTorch, the other framework discussed in the guest lecture, uses the tensor graph directly:
import torch
x = torch.tensor(-2.0, requires_grad=True)
y = torch.tensor( 5.0, requires_grad=True)
z = torch.tensor(-4.0, requires_grad=True)
q = x + y # forward phase: primitive 1
f = q * z # forward phase: primitive 2
f.backward() # reverse phase: chain rule, outputs to inputs
print(f.item()) # -12.0
print(x.grad, y.grad, z.grad) # tensor(-4.) tensor(-4.) tensor(3.)
A guest lecture by Lorenzo Pellegrini (RTDa, University of Bologna) surveys the tools that implement everything above. It starts from the machine learning software stack, where the general-purpose landscape splits neatly into two:
For deep learning, the lecture observes, the line between prototyping and production is more blurred: Caffe / Caffe 2, Torch / PyTorch, TensorFlow 2, plus CNTK, MxNet, Gluon and Chainer. The reason is a narrower focus:
Prototyping with neural nets on massive datasets needs efficiency, so the same engine has to serve both roles.
The lecture proposes seven features along which open-source DL frameworks differ. They are a useful checklist when you choose a stack for the team project.
Static vs dynamic graph. A static graph is define-AND-run: the whole computation graph is declared first, then executed. A dynamic graph is define-BY-run: the graph is built as the code executes, one operation at a time.
This is the axis with the clearest practical consequence: dynamic graphs are easier to debug and are the natural fit for highly-dynamic architectures, while static graphs give the runtime a complete picture to optimize.
Differentiation support. Four types are distinguished:
Section 9 of this chapter is a description of the third one, which is what both TensorFlow and PyTorch implement.
Platform support: Windows, Unix (macOS, Linux), Android, iOS, embedded systems.
Hardware acceleration: CUDA / OpenCL support, OpenMP and MPI support, AI chips.
Types of models supported: fully-connected NNs, CNNs, RNNs and so on, plus general purpose algebraic functions.
Utils support: a zoo of pre-trained models, data format and loading, monitoring and visualization tools.
Performance: very different from task to task and model to model — the lecture explicitly refuses to give a single ranking.
Multi-CPU / GPU support: two types of parallelization, data parallelism and model parallelism, run on CPUs, GPUs or distributed clusters.
API level matters through four features: usability, flexibility, expandability, and how easy it is to debug.
Licence: Apache 2.0, MIT, BSD, GNU GPL, freemium. Most allow commercial use, but the patent licence deserves a look too.
Community support: contribution diversity, active development, supportive Q&A, number of users.
| TensorFlow (Google) | PyTorch (Facebook) | |
|---|---|---|
| Graph | Static / dynamic | Dynamic |
| Differentiation | Automatic | Automatic |
| Platforms | Windows, Unix, embedded | Windows, Unix, embedded |
| Graph scope | General purpose graph | General purpose graph |
| Parallelism | Multi-CPU/GPUs | Multi-CPU/GPUs |
| APIs | C++ and Python | C++ and Python |
| Licence | Apache 2.0 | BSD |
| Community | High support | High support |
Their distinctive strengths, as the lecture lists them:
Straight from the guest lecture: you do not need to choose one framework for life. Be flexible and understand the core and common ideas. On-the-run conversion may be painful. Choose the best depending on the task at hand and the resources that already exist.
The number of columns of the first matrix must equal the number of rows of the second. Given A ∈ ℝm×k and B ∈ ℝk×n, the product C = AB has the rows of the first and the columns of the second, so C ∈ ℝm×n with cij = ∑t ait btj.
The dot product takes two equal-length vectors and returns a scalar, the sum of products of corresponding elements. The Hadamard product (element-wise multiplication, A ⊙ B) takes two tensors of identical shape and returns a tensor of the same shape. The Frobenius inner product takes two tensors of identical shape and returns a scalar, the sum of products of corresponding elements over all axes.
f(αx) = |α| f(x); f(x + y) ≤ f(x) + f(y) (triangle inequality); f(x) ≥ 0; and f(x) = 0 if and only if every component of x is zero. The Frobenius norm of a matrix satisfies all of them.
The gradient applies to a function f: ℝn → ℝ and is a vector of n partial derivatives. The Jacobian applies to a multivariate vector function f: ℝn → ℝm and is an m×n matrix whose i-th row is the transposed gradient of the i-th output component.
∂f/∂x = y = −3 and ∂f/∂y = x = 4, so ∇f = [−3, 4]T while f = −12. Reading: to increase f we should decrease x (negative partial) and increase y (positive partial). This holds only for small variations, since when x or y change sign the gradient changes direction.
Because q is an intermediate value, not an input. What we need are the derivatives with respect to the actual inputs x, y and z. The chain rule composes the upstream gradient with the local one: ∂f/∂x = (∂f/∂q)(∂q/∂x) = z · 1 = z, and likewise for y.
Forward phase: the original function is decomposed into a set of primitive functions by means of a computational graph, and each expression is evaluated (the node values are computed and stored). Reverse phase: derivatives are calculated by applying the chain rule from the outputs back to the inputs, each node multiplying the gradient it receives by its own local gradient.
Forward: q = −2 + 5 = 3, then f = 3 · (−4) = −12. Reverse: at the multiplication node ∂f/∂q = z = −4 and ∂f/∂z = q = 3; at the addition node ∂q/∂x = ∂q/∂y = 1; chaining gives ∂f/∂x = −4 · 1 = −4 and ∂f/∂y = −4 · 1 = −4.
A static graph is define-AND-run: the graph is fully declared before execution. A dynamic graph is define-BY-run: the graph is constructed as the program executes. TensorFlow supports both, PyTorch is dynamic; dynamic graphs are easier to debug and better suited to highly-dynamic architectures.
Numerical, symbolic, automatic and hard-coded differentiation. DL frameworks use automatic differentiation, which computes exact derivatives by mechanically applying the chain rule over the computational graph.