7 Several variables

A function of several variables usually refers to a function
where is a natural number. We have already seen functions of several variables with . In particular, in Chapter 4 , we saw linear functions (in connection with linear programming) like
This is a rather simple function of several variables with in (7.1) . In general functions as in (7.1) can be wildly complicated. One of the main purposes of this chapter is to zero in on the class of differentiable functions in (7.1) . In Chapter 6 we defined what it means for a function of one variable to be differentiable. This was inspired by zooming in at a point on the graph of the function. In several variables (for ) one has to be a bit clever in the definition of differentiability. The upshot is that the derivative at a point now is a row vector (or more generally a matrix) instead of being a single number. As an example, using notation that we introduce in this chapter, the derivative of the function in (7.2) at is
This notation means that partial differentiation with respect to a variable occurs i.e., one fixes the variable and computes the derivative with respect to this variable viewing all the other variables as constants.
First some treasured memories from the author's past.

7.1 Introduction

Many years ago (1986-89), I had a job as a financial analyst in a bank (now a hotel!) working (often late at night) with a spectacular view of Copenhagen from the office circled below.
This was long before a financial analyst became a quant and machine learning became a buzz word. Digging through my old notes from that time, I found the outlines below.
These were notes I made in connection with modelling the yield curve for zero coupon bonds. I had to fit a very non-linear function in several variables to financial data and had to use effective numerical tools (and programming them 1
in APL). Tools that are also used today in machine learning and data science.
Ultimately we are interested in solving optimization problems like
where and is a differentiable (read nice for now) function.
Training neural networks is a fancy name for solving an optimization problem, where usually and is built just like in the least squares method from some data points. The difference is that in neural networks, is an incredibly complicated (differentiable) function composed of several intermediate functions. We do not, as in the method of least squares, have an explicit formula for finding a minimum. We have to rely on iterative methods. One such method is called gradient descent. By the end of this chapter, in Section 7.9 , you will use it to train genuine neural networks — from a single neuron to a network reading handwritten digits and, at the very end, one that writes — live in these notes.
Recall the definition of a function being differentiable at a point with derivative . Here we measured the change of in terms of the change (in ). It had to have the form
where is a function continuous in with and small. If you divide both sides of (7.3) by you recover the usual more geometric definition of differentiability as a limiting slope:
We wish to define differentiability at for a function . In this setting the quotient
in (7.4) does not make any sense. There is no way we can divide a vector by a vector , unless of course as in (7.4) , where we faced usual numbers.
The natural thing here is to generalize the definition in (7.3) . First let us recall what functions look like.

7.2 Vector functions

We will flesh out the general Definition 1.104 in a special case below.
A function takes a vector as input and gives a vector as output. This means that every coordinate in the output must be a function of i.e.,
for . So in total, we may write as
Each of the (coordinate) functions are functions from to .
Look back at Exercise 1.125 . Write down precisely the vector function occurring there.
The function is rotating a vector degrees counter clockwise. What are and in
Hint
Try rotating some specific vectors like degrees. Do you see a pattern?

7.3 Differentiability

The definition of differentiability for a function mimics (7.3) , except that is replaced by . Also the open interval is replaced by an open subset and the (open) interval is replaced by an open subset containing .
Notice, however, that now the derivative is a matrix!
Let be a function with an open subset. Then is differentiable at if there exists
  1. an matrix ,
  2. an open subset with , such that for every ,
  3. a function continuous at with ,
such that
In this case, the matrix is called the (matrix) derivative of at and denoted by .
The function is called differentiable if it is differentiable at every .
For one variable we saw that differentiability at means that the graph looks like a line when you zoom in around . Definition 7.3 says the same thing one dimension up: for a function the graph is a surface in , the derivative is a matrix , and describes a plane --- the tangent plane at . Differentiability at means: zoom in on the point and the surface becomes indistinguishable from this plane.
You can watch this happen in the live cell below. The graph of is drawn in blue together with its tangent plane at the black point, shown as a red grid. Run the cell and drag the slider to shrink the window around the point --- the curved blue surface flattens into the red grid. Rotate the picture with the mouse to see it from all sides.

Live Python

For a taste of what non-differentiable looks like, try the ice cream cone at the point : the cone looks exactly the same at every zoom level --- its tip never flattens into a plane, no matter how far you drag the slider.
How do we compute the matrix derivative in the above definition? We need to look at the representation of in (7.5) and introduce the partial derivatives.

7.3.1 Partial derivatives

A function of one variable has a derivative with respect to . For a function of several variables we have a well defined derivative with respect to each of these variables. These are called the partial derivatives (if they exist) and they are defined below.
Let be a function, where is an open subset of . Fix a point and let
for . If is differentiable at according to Definition 6.17 (recalled in (7.3) ), then we say that the partial derivative of with respect to exists at and use the notation
The partial derivative with respect to a specific variable is computed by letting all the other variables appear as constants.
To get a feeling for the definition and computation of partial derivatives, take a look at the example below, where we compute using the classical (geometric) definition of the one variable derivative.
Consider the function given by
Then
where . This example illustrates that can be computed just like in the one variable case, when the other variables () are treated as constants. Notice that
Partial derivatives behave almost like the usual derivatives of one variable functions. You simply fix one variable that you consider the "real" variable and treat the other variables as constants.
Below are examples of python code computing partial derivatives. Notice that the variables must be declared first.

Live Python

The computations above point to a really surprising result. It seems that it makes no difference if you compute the partial derivative with respect to and then with respect to or the other way around. You could, just for fun, try this out on the more complicated function
This result is formulated in Theorem 7.11 below.
Use the python cell above to verify the computation of the partial derivative in Example 7.7 .
The following result tells us how to compute the matrix derivative.
Let be a function with an open subset. If is differentiable at , then the partial derivatives
exist for and and the matrix in Definition 7.3 is
The -th column in is . Putting for in Definition 7.3 gives
The -th coordinate of this identity of -dimensional vectors can be written
where
and (7.6) shows that .
Compute the matrix derivative of the vector function in Exercise 7.2 .
For a function with an open subset, the partial derivative, if it exists for every , is a new function
We will use the notation
for the iterated (second order) partial derivative.
The first part of following result is a converse to Proposition 7.9 . The second part contains the surprising symmetry of the second order partial derivatives under rather mild conditions. We will not go into the proof of this result, which is known as Clairaut's theorem.
Let be a function with an open subset. If the partial derivatives for exist at every with
continuous (for and ), then is differentiable. If the second order partial derivatives exist for a function and are continuous functions, then
for .
Verify (by hand!) the symmetry of the second order partial derivatives for the function in Example 7.7 i.e., show that
Verify that given by
is a differentiable function by computing
and applying Theorem 7.11 . Check also that

7.4 Newton-Raphson in several variables!

There is a beautiful generalization of the Newton-Raphson method to several variable functions . Consider first that you would like to solve the system
of non-linear equations in the two variables and . Notice that we are talking non-linear here. This is so much more difficult than the systems of linear equations that you encountered in a previous chapter.
However, just like we used Newton's method in one variable for solving a non-linear equation, Newton's method for finding a zero for a function generalized to the iterative scheme
provided that the matrix derivative is invertible.
The reason that (7.8) works comes again from the powerful definition of differentiability in Definition 7.3 using that
provided that is small. In fact, you get (again) (7.8) from (7.9) by putting to , replacing is close to by and then isolating .
For the equations in (7.7) , the iteration scheme (7.8) becomes
Verify the claim in (7.10) by applying (7.8) to
Carry out sufficiently many iterations starting with the vector in (7.10) to see the iteration stabilize. You should do this using a computer, for example by modifying the python code in the last half of Example 7.16 .

7.5 Local extrema in several variables

For a function , where , the derivative at is called the gradient for at . Classically, it is denoted i.e.,
The definition below is inspired by the one variable case (see Definition 6.36 ).
Let be a function, where is an open subset. Suppose that the partial derivatives exist at . Then is called a critical point for if .
Consider the function given by
corresponding to finding critical points for the function
You can left click and hold the graph computed below (after it has rendered) and rotate the surface to get a feeling for what (7.11) looks like. Zooming in is also possible.

Live Python

Here
In the python code below, Newton's method is started at and iterated four times.

Live Python

If is not a critical point for we can use the gradient vector to move in a direction making strictly smaller/larger. This is very important for optimization problems.
Let be a differentiable function, where is an open subset. Suppose that and for . Then
for small.
By the differentiability of ,
where is a function satisfying for . For with we have
When tends to zero from the right, it follows that for small .
Lemma 7.17 looks innocent, but it is the bread and butter in the training of neural networks. In mathematical terms, training means minimizing a function. In machine learning terms, above is called the learning rate. One iteration (why do I choose ?)
of Lemma 7.17 is the central ingredient in an epoch in training a neural network.
Let us briefly pause and see Lemma 7.17 in action. Consider the function given by
and with . In this case and . Therefore we may find a small , such that . How do we choose optimally? If is too big we fail and land up in a worse point than . Here
This is a quadratic polynomial, which is minimal for . Therefore the minimal value reached in the direction of is . The process now continues replacing by .

7.5.1 Gradient descent

Lemma 7.17 turns into an algorithm the moment we choose the direction : then , so the lemma applies at every non-critical point. The result is the single most important algorithm in machine learning.
Gradient descent. Choose a starting point and a learning rate . Repeat
until is sufficiently small.
Two things are yours to choose: the starting point and the learning rate. The learning rate is a genuine dilemma. Chosen too small, the algorithm crawls; chosen too big, the steps overshoot the valley and the algorithm can even explode. You can experience all three regimes in the cell below, which runs gradient descent on
— a stretched bowl with its minimum at — and draws the path on top of the level curves of . With the path zigzags across the narrow valley before settling at the bottom. Try (patient crawling) and (disaster).

Live Python

Which of the following statements about gradient descent are true?
A larger learning rate always gives faster convergence.
If , then the update leaves unchanged.
Gradient descent always finds a global minimum.
Each step moves in a direction in which decreases, provided the learning rate is small enough.
The result below is the multi variable generalization of looking for local extrema by putting in the one variable case.
Let be a differentiable function, where is an open subset. If is a local extremum, then is a critical point for .
Suppose that . If is a local minimum, then we may use in Lemma 7.17 to deduce that for small. This contradicts the local minimality of . If is a local maximum we can apply Lemma 7.17 with and to reach a similar contradiction. Therefore and is a critical point for .
Compute the critical points of
Is a local maximum or a local minimum for ?
Hint
Look at
and and (along with Theorem 6.50 ).
We will prove later that a differentiable function is strictly convex if the socalled Hessian matrix given by
is positive definite for every . This is a multivariable generalization of the fact that is strictly convex if for every .
Now let
3D graph
You can left click the surface computed below after it has rendered and rotate or zoom in.

Live Python

  1. Show that is strictly convex.
  2. Compute the critical point(s) of .
    Hint
    This is a numerical computation! Modify the relevant python cell for Newton's method in the previous chapter to do it.
  3. For a differentiable convex function we have in general that
    for every . This is a multivariable generalization of Theorem 6.61 .
    Explain how one can use (7.13) to find a global minimum for the function in (7.12) . Is this minimum unique? Is for every ?

7.6 How a nudge propagates

Before we state the chain rule in several variables, let us understand it the physicist's way. Forget graphs and slopes for a moment and reread the definition of the derivative in (7.3) as a statement about cause and effect:
if you nudge the input of by a tiny amount , the output moves by — plus junk that is tiny compared to .
In symbols,
where the junk has the redeeming feature that : divide it by and it still vanishes. So the derivative is an exchange rate between nudges: wiggle in, wiggle out, at the rate .

Machines in series

Now put two machines in a row: goes into , and what comes out goes into ,
Nudge by . The first machine responds with a nudge of on its output — but that is a nudge on the input of , which therefore responds with
The total exchange rate from to is the product of the two rates. That is the chain rule. There is nothing more to it: exchange rates in series multiply — just as two gearboxes coupled in a row multiply their gear ratios.
You do not have to take this on faith. The cell below measures all three rates by actually nudging.

Live Python

Where did the junk go? The nudge arriving at is not exactly — it carries junk. But feeding junk through multiplies it by and adds junk of its own, and junk times a constant, or junk of junk, is still tiny compared to . The honest bookkeeping of exactly this is the entire proof of the chain rule; the idea is nothing more than rates in series multiply.

Wires in parallel

Several variables add one — and only one — new phenomenon. Take a function of two variables and nudge both inputs at once, by and by . Do it in two steps: first nudge , then . The first step responds with . The second step responds with — evaluated at the slightly nudged point, but being off by a tiny amount in where we read the rate changes the response only by junk. Altogether,
Nudges arriving on different wires simply add.
Series and parallel is all there is. Suppose feeds two intermediate quantities and , which both feed :
Nudge by . Along the upper route, moves by , and responds to that with — rates in series multiply. The lower route contributes . The two nudges arrive at on different wires, so they add:
Multiply the rates along each route, then add over the routes.
This slogan is the entire chain rule in several variables. The next section dresses it in matrices — and the fit is perfect, because the entry of a matrix product is precisely a sum over intermediate wires of products of rates. Matrix multiplication could have been invented for this purpose.
Let us measure a two-route example. The function
will follow us for the rest of this chapter. Nudging at the point and reading off the exchange rate gives a number you should remember — you will meet it again twice, computed by two entirely different methods.

Live Python

Why machine learning cares

A neural network is nothing but millions of these little machines wired in series and in parallel, and training it means nudging. Tweak one weight buried deep inside the network by , and the tweak propagates: multiplied by a local rate at every machine it passes through, added up wherever routes merge, until it arrives at the loss as the response . A parameter with a large exchange rate is worth nudging; one with rate zero is not — and gradient descent (Lemma 7.17 ) nudges every parameter against its own rate, all at once. The backward pass you will implement in Section 7.7 and use for real in Section 7.9 is just the systematic collection of all these exchange rates in a single sweep.

7.7 The chain rule

Suppose you want to compute the value of the function for . Then you would start by evaluating the inner function , then applying and finally . This computation can be illustrated in the (computational) graph
where you plug into the leftmost node and fill in each node taking input from its left neighbor
Suppose we want to compute for . Can we use the computational graph for this?
Recall the chain rule for functions of one variable. Here we have functions and , such that for . If is differentiable at and is differentiable at , the chain rule says that is differentiable at with
The chain rule tells us that
This expression involves three derivatives corresponding to the three edges in the computational graph. We can illustrate the chain rule by labeling each edge with the derivative of its end node:
Then the derivative can be computed by as the product of the labels evaluated on their left nodes in the filled in computational graph:
i.e., . This observation is the basis of the famous backpropagation rule used in training neural networks.
The chain rule for functions of one variable generalizes verbatim to functions of several variables:
for compatible multivariate functions and when you replace usual multiplication by matrix multiplication.
Let and with , open subsets and . If is differentiable at and is differentiable at , then is differentiable at with
The proof of the chain rule in this general setting uses Definition 7.3 just as in the one variable case. It is not conceptually difficult, but severely cumbersome. We will not give it here.

7.7.1 Matrix multiplication graphically

To really understand the chain rule, it pays to view the matrix multiplication in (7.14) in a new light (inspired by computer science and neural networks).
An matrix is a rectangular table with rows and columns containing numbers. We may also view it as a (bipartite) graph with left nodes, right nodes and an edge from the left node to the right node with weight . This is best illustrated by an example, which also tells you how matrix multiplication is (beautifully) interpreted in this setting.
The matrix
is represented below as a graph
Similarly the matrix
is represented as
You know that the matrix product is a matrix. Let us line and up graphically:
There are three paths from to and three paths from to . Here are the three paths from to :
Finally, the matrix product is represented by the graph
The number on the edge from to is gotten by adding the products of the weights on each of the three paths from to i.e., . This is the graphical interpretation of matrix multiplication!

7.7.2 Unpacking the chain rule

The matrix multiplication in (7.14) looks deceivingly simple. Let us write it out. Assume for simplicity that is a function in the variables and that is a function in the variables :
Then is a function in the variables :
and we wish to compute , which is an matrix with entries
where represent the rows and the columns. Here (7.14) says that
When using the chain rule in computations it pays to use the graphical interpretation of matrix multiplication in subsection 7.7.1 with edges labeled by the derivatives in a computational graph. We illustrate this below.
The function given by
may be evaluated using the computational graph
where is the function and is the function . Similar to the one variable case discussed in the beginning of section 7.7 , we label each edge, but now by the partial derivative of the function in its ending node with respect to the variable in its beginning node:
From the graphical interpretation of the matrix product and the chain rule you follow the two paths from the input node to the output node and conclude that
Here is another example of the chain rule in action through a computational graph. In the end you will see an implementation in a famous python library.
Consider the example
from Example 7.7 . Even though superficially looks rather simple, it is composed of several smaller functions as displayed in the computational graph
Every node in the above graph, except the input nodes (with no ingoing arrows), represents some function . For example the node represents a function and represents a function .
To emphasize that the non-input nodes really are functions we replace them by letters:
Here we see that
where
The gradient is then available from the decorated graph below
by multiplying the decorations on each path from the top to the input variable and the summing up. For example,
Computational graphs and the chain rule are important components in machine learning libraries. Below is an example of the computation of in the computational graph above using the sympy library.

Live Python

7.7.3 Backpropagation from scratch

There is no magic in how sympy — or pytorch — computes these derivatives. The complete mechanism is the recipe you just used by hand: label every edge of the computational graph with the derivative of its end node with respect to its beginning node, multiply the labels along each path from the output down to an input, and add up the products. The program below implements exactly this and nothing more, in about twenty five lines.
A Node holds a number together with the edges of the computational graph: which nodes it was computed from, and the label on each incoming edge. Each operation writes down its own edge labels — for instance for a product. The function backprop then walks every path backwards from the output, multiplying the labels as it goes and adding the product into grad when it reaches a node.

Live Python

Compare with the sympy window above: the same numbers come out. Two details are worth savoring. First, records two edges into , each with label — so the two paths contribute , and the power rule appears all by itself. Second, backprop is the graphical matrix multiplication of subsection 7.7.1 in executable form: every path is followed, the labels along it are multiplied, and parallel paths are added.
When you train neural networks in Section 7.9 , the backward pass is this walk written out with matrices. And pytorch's celebrated autograd is again this walk, organized so that each node is visited only once (in reverse order of computation), which is what lets it scale to graphs with billions of nodes.
Construct a computational graph for
and detail the computation of the gradient in this context.
Compute the gradient of at using the sympy window above (or pytorch in Google Colab).
Consider and given by
Compute using the chain rule and check the result with an explicit computation of the derivative of .
We wish to show that the function given by
is convex. This means that we need to prove that
for every and every with . This can be accomplished from the one variable case in the following way. Define
and show that is convex by using the chain rule to show that . Show how the convexity of follows from this by using that

7.8 Logistic regression

The beauty of the sigmoid function is that it takes any value and turns it into a probability by
i.e., and .
Graph of the sigmoid function

Live Python

Prove that
and
We will not go into all the details (some of which can be traced to introductory probability and statistics), but suppose that we have an outcome , which may or may not happen.
We have an idea, that the probability of is dependent on certain parameters and observations that fit into the sigmoid function as
An example of this could be where denote the gray scale of each pixel in a image. The event is whether the image contains the digit :
Here would be the probability that the image contains the digit .

7.8.1 Estimating the parameters

Suppose also that we have a table of observations (data set)
where each row has observations along with a binary variable , which is if was observed to occur and if not.
Assuming that (7.15) holds, the probability of observing the observations in (7.16) is
Notice that (7.17) is a function of the parameters for fixed observations .
We wish to choose the parameters so that is maximized (this is called maximum likelihood estimation). So we are in fact here, dealing with an optimization problem, which is usually solved by gradient descent (for ) or solving the equations
Instead of maximizing one usually maximizes the logarithm
Notice that we have used Exercise 7.30 and the logarithm rules and in the computation above.
Suppose that the event is assumed to be dependent on only one observation i.e., above. For example, could be the event of not showing up on a Monday paired with the amount of sleep in the weekend.
Here
and
Explain how the end result of the computation of in Example 7.31 is obtained and compute .
I remember exactly where I was when first hearing about the Challenger 2 See byuistats.github.io for more details on this example disaster in 1986.
YouTube video
This dreadful event was caused by failure of a socalled O-ring. The O-rings had been tested before the launch for failure (=1 below) at different temperatures (in F) resulting in the (partial) table below.
At the morning of the launch the outside temperature was (uncharacteristically low for Florida) degrees Fahrenheit. We wish to use logistic regression to compute the probability that the O-ring fails.
The model is exactly a single neuron — the one you will train again in Section 7.9 — and we can fit it right here with the tools of this chapter, no library and no black box: maximize from Example 7.31 by gradient ascent, i.e., repeat
We walk with the gradient, since we are maximizing.
One preparation first — a step used everywhere in machine learning. We standardize the input:
where
are the mean and the variance of the temperatures ( is the standard deviation). The standardized inputs have mean and standard deviation — small, comparable numbers around , no matter how the raw data are scaled. Why we bother will become clear in a moment, when we try skipping this step. We train the neuron on the standardized input and translate back afterwards. Nothing is lost in the translation, because
so the trained neuron is the same function of the original temperature as with

Live Python

The mathematics was there before the launch: at degrees Fahrenheit the trained neuron puts the probability of O-ring failure at .
So why did we standardize? Try the ascent directly on the raw temperatures and watch it fail, miserably — seeing how it fails teaches more than the success did. The cell below tries: steps with , starting from . The two gradient lines in the loop are precisely from the exercise above — the only change from the successful cell is the missing standardization.

Live Python

At a maximum the gradient is . The cell reports the gradient — after steps the ascent is nowhere near the top. The culprit is the factor in the second coordinate of the gradient. The very first step is
which makes . Then overflows, every probability snaps to or , and from there the ascent bounces back and forth without ever settling down. Shrinking the learning rate does not save us: with the -updates calm down, but now — which has to climb above — moves in steps of size about . Rerun the cell with this and watch barely leave ; even two million steps would only push it to about . One fixed learning rate cannot serve two parameters living on such different scales — standardization put them on the same scale, and the ascent sailed home.
Professional libraries fit logistic regression in a few lines. Run the scikit-learn code below and compare , and the failure probability with the neuron trained in Example 7.33 .

Live Python

The option solver='lbfgs' chooses an algorithm for maximizing and C=25 controls a socalled regularization. Try removing C=25 first and then solver='lbfgs'. What happens?

Chatbot Prompt

Explain the function LogisticRegression in sklearn. In particular, what do the parameters in

model = LogisticRegression(C=25, solver='lbfgs') model.fit(X,y)

mean?
In the button below is a naive implementation of gradient ascent for the Challenger data set — a variant of the training in Example 7.33 that adjusts the step size with successive negative powers of , as in the introduction to this chapter, instead of standardizing the input. Run experiments with different initial values and number of iterations, and compare the quality of the solutions in terms of the gradient (which is available in the output from the Naive code).
Yes, you are allowed (and encouraged) to use generative AI tools here!
Naive code

Live Python

7.9 Training neural networks

Every ingredient is now on the table: partial derivatives, the gradient, the chain rule on computational graphs, gradient descent (Section 7.5.1 ) and the activation functions — sigmoid and ReLU — from the very first chapter. In this section we put them to work and train genuine neural networks — first one so small that every number can be inspected by hand, then one that reads handwritten digits, and in the end one that writes. Everything runs live in these notes, and no mathematics beyond this chapter is involved.

7.9.1 A single neuron

The smallest neural network is a single neuron with one input: it takes a number , forms and applies the sigmoid function,
The numbers and are called the weight and the bias. They are the parameters the network is allowed to change when it learns. You have seen this function before: it is exactly the probability (7.15) of logistic regression — a single neuron is logistic regression.
Run the cell below to see how and bend and shift the sigmoid, then change the values and rerun.

Live Python

7.9.2 Training a single neuron

Training means: choose and so that the neuron reproduces known data as well as possible. Suppose eight students report how many hours they studied and whether they passed () or failed () an exam. (You met these eight students in Exercise 5.22 , where the best straight line through the data predicted "probabilities" beyond — here comes the better idea it promised.) A neuron with sigmoid activation is the logistic model of Section 7.8 , so we already know what "as well as possible" should mean: maximize the log-likelihood. Machine learning prefers to minimize, so we flip the sign and call the result the cross-entropy loss: writing ,
which we minimize by gradient descent — the same computation as gradient ascent on , in new clothes. The gradient is a small miracle of cancellation. By the chain rule and the formula from Exercise 7.30 , the -th term of responds to the sum at the rate
— the derivative of the sigmoid cancels completely, leaving
Prediction too high: push down. Prediction too low: push up. Every data point pushes with a force equal to its error — few formulas in machine learning are used more often than this one. It is all a neural network library ever does, just for many more parameters. The training loop below is four lines.

Live Python

The right hand plot is the famous loss curve — the first thing every machine learning engineer looks at during training. The left hand plot shows what the neuron has learned: a threshold around hours of study. One thing to notice: because the data are perfectly separated at hours, longer training keeps steepening the threshold — the weights grow forever, chasing probabilities and they can never quite reach. Real data are rarely this clean. Play with the data and watch the neuron adapt.

7.9.3 A genuine network: solving XOR

A single neuron has a hard limit. In Exercise 5.9 you showed that no line separates the four points (labeled blue) and (labeled red) — and a single neuron can only draw a line. This little data set, known as XOR, famously stalled neural network research for years.
The way out is to compose neurons into a network with a hidden layer: two neurons look at the input, and a third neuron looks at what they found,
The composed function is no longer a bent line — and thanks to the chain rule we can still compute the gradient of the cross-entropy loss with respect to all nine parameters; the miraculous cancellation from the single neuron applies to the output neuron unchanged. The code below does exactly that in vectorized form: the forward pass evaluates the network, the backward pass walks the computational graph in reverse, exactly as in Section 7.7 . This is backpropagation.

Live Python

Look at the picture: the network has invented a diagonal band — a shape no single line can produce. Each hidden neuron contributes one line, and the output neuron combines them. Deep learning is this phenomenon repeated at scale.
Backpropagation by hand. Consider the simple chain network
where
and is the sigmoid function. This neural network has input and output . Let be a function of the output . For fixed , we consider as a function of via
Backpropagation for training neural networks is using the chain rule for computing the gradient
Explain how to do this — it is exactly what the backward pass in the code above does.

7.9.4 Reading handwritten digits

We end with real data: images of handwritten digits. The sklearn library ships small gray scale images. Before training anything, look at the data — the cell below shows the first image in the collection together with the matrix of gray values behind it.

Live Python

An image is nothing but an matrix of numbers. Flattening the matrix row by row turns it into a vector in — and the machinery of this chapter applies. The network we now train is a composition
exactly as promised in the very first chapter. Written out: an image, stored as a row vector , is processed in two layers
where is a matrix of weights, holds the biases of the hidden neurons, is a matrix and . Output number of is a score between and for "this image shows the digit " — ten separate yes/no neurons, so the scores need not sum to — and the network's guess is the output with the largest score. Counting as in the first chapter, the network has
parameters — all of them trained by gradient descent, in your browser.
The hidden activation function is your choice in the code: the sigmoid or the ReLU from the first chapter. The output layer always uses the sigmoid, so that its ten scores land between and . Backpropagation only needs the derivative of the activation: for the sigmoid, while the ReLU has derivative for and for — this is the function dact in the code.
Before you run it, read the code with this dictionary. The training images are stacked as the rows of the matrix Xtr, so Xtr @ W1 + b1 computes all hidden vectors in one matrix multiplication. The line T = np.eye(10)[labels] turns the digit into the target vector with in entry and elsewhere (one-hot encoding — the same trick as for words in Chapter 5). The loss is the cross-entropy, summed over the ten outputs — exactly as for the single neuron, ten times.
So how does the code find all partial derivatives? This is backpropagation, and it is nothing but the chain rule of Section 7.7 — the nudge bookkeeping of Section 7.6 — organized cleverly. Let us walk through it for a single image with target vector . Write the two layers with their intermediate sums,
and let
be the cross-entropy loss.
Start at the loss and move backwards. How much does respond to a nudge of the sum entering output neuron ? The nudge passes through the sigmoid and into the -th term of the loss — and the single-neuron cancellation strikes again:
This is the line dy in the code. With in hand, the gradients of the output layer's parameters are immediate: the weight enters the sum multiplied by , so
— the lines dW2 and db2.
Push the nudge one layer further back. The hidden value influences the loss through all ten outputs, one path per output neuron, so its exchange rate is a sum over the ten paths — exactly as in the computational graphs of Section 7.7.1 :
Passing the nudge through the activation of hidden neuron then gives
— the line dh, where the sum over is the matrix product dy @ W2.T and is the function dact. The first layer's gradients now follow exactly as before:
— the lines dW1 and db1.
Notice the economy of the scheme. One forward pass stores and ; one backward pass computes and then ; and out come all partial derivatives. The price of the whole gradient is roughly two evaluations of the network, no matter how many parameters there are — this is why the loss nudges are propagated backwards, layer by layer, and why the method is called backpropagation. Finally, the code does all of this for the training images at once: stacking the images as rows turns the products into the matrix product h.T @ dy, which sums over the images automatically, and the division by len(Xtr) makes the mean loss over the data. Training takes a few seconds.

Live Python

Now switch activation to "relu" and rerun. Which activation reads more of the unseen images correctly here? Notice that the ReLU asks for a smaller learning rate — its unbounded output makes big steps dangerous.
Around nine out of ten unseen images are read correctly — by a network you trained from scratch and whose every line of mathematics you have now met. Scaling this recipe up is modern AI: more pixels, more layers, and libraries such as pytorch that apply the chain rule automatically to computational graphs with billions of nodes — exactly the graphs of Section 7.7 . Such libraries do not run inside a web page, but you can use them for free in Google Colab.

7.9.5 A network that writes

The networks above read: they take an image and answer a question about it. But the neural network you use every day — the chatbot from Section 1.1writes. That looks like a completely different ability. It is not. Writing is reading in disguise: a network writes by answering, over and over again, the classification question
picking a symbol according to the answer, appending it to the text and asking again. In this final section you will train a genuine writing network. A chatbot learns from trillions of words; we scale the idea down to a miniature that learns from a couple of hundred Danish first names — and then invents new ones.
Here is the plan. Our alphabet consists of the letters that occur in the names plus a special symbol "." marking the end of a name — symbols in all. The network gets a context of consecutive symbols and must predict the symbol that follows. Every name in the list becomes a handful of training examples; padding with dots supplies the start, and the final dot teaches the network how names end. The name anna alone contributes five:
Symbols become vectors by one-hot encoding, exactly as in (5.16) : each of the three context symbols becomes a one-hot vector in , and gluing the three together gives an input vector . The target is one of classes. Structurally we are back at the digits network — reading handwritten digits was "which of classes?", writing names is "which of classes?", asked repeatedly.
One ingredient needs an upgrade. For writing, the output numbers must form a probability distribution: they must be positive and sum to , because we are going to roll a die weighted by them. The digits network used ten separate sigmoids, which obey no such constraint. The standard fix is the softmax function, which turns any vector of scores into probabilities:
The exponentials make every output positive, and dividing by their sum makes the outputs sum to . You have met this recipe before: it is exactly (5.17) , which computed the attention weights in Section 5.5.3 — there the outputs weighted a blend of word vectors, here they are read as probabilities. Softmax is also the many-class face of the sigmoid (see Exercise 7.36 below).
As loss we keep cross-entropy in the form maximum likelihood handed us in (7.17) : if the correct next symbol has class and the network outputs the probabilities , the loss of the example is
the price for the probability the network gave to what actually came next. And now the small miracle of Section 7.9 repeats itself: the nudge of the loss with respect to the scores is again prediction minus target,
where is the one-hot target. This is the same formula that ran the single neuron, the XOR network and the digits network — so the backward pass below is, line for line, the code you have already studied.
Why the cancellation survives
Insert into the loss and use :
Now differentiate with respect to . The first term contributes if and otherwise — that is exactly . For the second term the chain rule gives
and adding the two contributions gives . Compare this with the computation for the single neuron in Section 7.9 — same cancellation, same reason: the derivative of undoes the exponential.
So the network is
a sigmoid hidden layer followed by a softmax output layer — counting as in Exercise 1.126 , that is parameters. Before training these are small random numbers, so the network spreads its probability almost evenly over the symbols and the mean loss starts near : the loss of pure guessing. Watch it fall.
After training comes the payoff: writing. Start from the empty context "...", let the network output its probabilities, roll a die weighted by them, append the resulting symbol to the context and repeat — until the die shows ".": the network has decided that its name is finished. This is composition one last time: the network fed its own output, over and over. The cell below does all of it — the data, the training and twenty rolls of the die. Training takes a little longer than the digits network; the names are worth the wait.

Live Python

Look at the output. Some inventions are flagged: the network has simply memorized a name from the list. But most are new, and they do not look like random letters — they look Danish. From nothing but gradient descent on cross-entropy, the network has learned the statistics of Danish names: which letters like to follow which, how names tend to begin, and when they should stop. A few outputs are honest garbage; a window of three symbols only carries so much memory. The balance on display here — between memorizing the training data and learning its shape — is the overfitting story of Chapter 2 (Figure 2.41 ) in new clothes.
Now zoom out. Replace the symbols by roughly word fragments (tokens), the context of symbols by a context of many thousands of tokens, the two layers by around a hundred, the parameters by hundreds of billions, and the list of names by a large chunk of everything ever written — and you have a modern large language model. When the chatbot from Section 1.1 answers you, it does literally what the cell above does: compute probabilities for the next token with softmax, roll the die, append, repeat. There is one genuinely new architectural idea in the scaled-up version — attention, layers that let far-apart parts of the context exchange information, built on the dot products of Section 5.3.2 — but no new kind of mathematics: matrices, activation functions, cross-entropy, the chain rule and gradient descent, all of which you now own.
Softmax with classes is the sigmoid: show that
where . So a softmax output layer with two classes is a single sigmoid neuron reading the difference of the two scores — logistic regression once more. Hint
Divide the numerator and the denominator of
by .
The cell prints the mean loss before training and after training.
  1. Explain why the loss starts close to .
  2. After training the mean loss is close to . Compute . In what sense does the network now give the correct next symbol "a third of the probability" — and how much better is that than the it started from?
Hint
Before training the weights are small random numbers, so all probabilities are close to and every example costs about . For the second part: if every example had loss exactly , the probability given to the correct symbol would be every time.
The cell is yours — experiment.
  1. Set the context length to , so the network only sees one symbol back, and rerun. The invented names get worse in a specific way — describe it. What statistics can the network still learn?
  2. Try . More of the output gets flagged as memorized — why is a longer context more prone to memorizing a small dataset?
  3. Change the die roll np.random.default_rng(1) to a different seed and rerun the last part — new names, same network. Then replace the names by a word list of your own choice (capital cities, Pokémon, dinosaurs, ...) and train a different writer.

7.10 Lagrange multipliers

The method of Lagrange multipliers is a super classical way of solving optimization problems with non-linear (equality) constraints. We will only consider the special case
where both and are differentiable functions.
There is a very useful trick for attacking (7.19) . One introduces an extra variable (a Lagrange multiplier) and the Lagrangian function given by
The main result is the following.
Suppose that is a local maximum/minimum for (7.19) . Then there exists , such that is a critical point for .
So to solve (7.19) we simply (well, this is not always so simple) look for critical points for . This amounts to solving the (non-linear) equations coming from i.e.,
For we can quickly give a sketch of the idea behind the proof. The (difficult) fact is that we may find a differentiable function in one variable , such that
and the local minimum has the form .
Once we have this, the chain rule does its magic. We consider the one variable functions
For both of these we have (why?). The chain rule now gives a non-zero vector orthogonal to and . This is only possible if they are parallel as vectors i. e. , there exists , such that
Consider the minimization problem
First of all, why does this problem have a solution at all? We write the non-linear equations
up coming from the critical points of the Lagrange function. Now we know that these can be solved and that amongst our solutions there is a minimum!
Computing the distance from the line to the point gives rise to the minimization problem
Solve this minimization problem using Theorem 7.39 .
Use Theorem 7.39 to maximize subject to .
Hint
Here you end up with the system
of linear equations in and , where you regard as a constant. Use Gaussian elimination to solve this system in order to derive a (nice) quadratic equation in coming from
where you assume that . Handle the case separately.
Consider the subset . Why is a closed subset? Why is bounded?
Hint
To prove that is bounded you can keep fixed in
and solve for . A last resort is using the plot in the Hint button below, but that does not give any real insight unless you explain how the plot is made from the equation (7.22) .
How does this relate to Theorem 5.84 ?
Does the optimization problem have a geometric interpretation?
Hint

Live Python

A rectangular box has side lengths , and . What is its maximal volume when we assume that lies on the plane
for .
A company is planning to produce a box with volume . For design reasons it needs different materials for the sides, top and bottom. The cost of the materials per square meter is dollar for the sides, dollars for the bottom and the top. Find the measurements of the box minimizing the production costs.
Hint
Let and be the measurements. Use to rewrite the Lagrange equations so that and are expressed in terms of .
The sum
is called the (Shannon) entropy of the discrete probability distribution . One may use Jensen's inequality applied to the convex function to prove that

7.11 Optimization using the interior and boundary of a subset

Suppose that is a closed subset and is a continuous function. Recall (see Theorem 5.84 ) that the optimization problem
always has a solution if in addition to being closed is also bounded. To solve such an optimization problem, it often pays to decompose as
where is the boundary of (recall Definition 5.56 ) and the interior of (recall Definition 5.58 ). The strategy is then to look for an optimal solution both in and and then compare these. In some sense we are making a "recursive" call to a lower dimensional optimization problem for the boundary . This is illustrated by the basic example: and . Here and . Notice that is finite here.
If is an element of , then there exists an open subset , such that . Therefore the following proposition holds, when you take Proposition 7.20 into account.
Consider an optimization problem
where is a subset, a differentiable function and an optimal solution to (7.23) . If , then is a critical point of .
Basically, to solve an optimization problem like (7.23) one needs to consider the boundary and interior as separate cases. For points on the boundary we cannot use the critical point test in Proposition 7.20 . This test only applies to the interior points.
Usually the boundary cases are of smaller dimension and easier to handle as illustrated in the example below.
Consider the minimization problem
from Example 7.40 . Let us modify it to
where
We are now minimizing not only over the unit circle, but the whole unit disk. Here
Proposition 7.46 guides us first to look for optimal points in . Here we use Proposition 7.20 to show that there can be no optimal points in , because the gradient of the function is
Therefore the boundary needs to be analyzed and the usual technique (as was implicit in Lagrange multipliers) is to find a parametrization for the points satisfying . There are two of those (one for the upper unit circle and one for the lower unit circle):
where . This means that the optimization problem for the boundary turns into the two simpler optimization problems of minimizing
subject to . These can as one variable optimization problems be solved the usual way.
The exercises below are taken from an older Calculus course at Aarhus.
Solve the two optimization problems
where . But first give a reason as to why they both are solvable.
Hint
First find and . Then try with Proposition 7.46 supposing that a maximal point really is to be found in and not on .
Solve the two optimization problems
where . But first give a reason as to why they both are solvable.
Solve the two optimization problems
where is the triangle with vertices in and . But first give a reason as to why they both are solvable.
Use Proposition 7.46 to give all the minute details in applying Theorem 7.39 to solve Exercise 7.44 .
First rewrite to the problem, where you minimize subject to by using . Then explain why this problem may be solved by restricting with upper and lower bounds on and . The minimum () is attained in a critical point and not on the boundary. For one may optimize over the compact subset
and analyze what happens when .