53  AI: Training

In the last two chapters you set every weight and threshold yourself. You moved a slider, watched what happened, and moved it again until the network did what you wanted. That works for a network with ten parameters. It does not work for one with a million, and the language models behind AI assistants have hundreds of billions. Nobody sets those by hand. Nobody even knows what most of them should be. Instead, the network finds its own weights from examples, in a process called training. This chapter explains how. It turns out to be the same thing you did with the sliders, made systematic: change a weight a little, see whether the answer got better, and keep going in the direction that helps.

Learning from examples

A neural network is trained using training data: a large number of inputs, each paired with the output you want for it. For a network that recognizes splice sites, each input is a short DNA sequence and each desired output is 1 if the sequence contains a splice site and 0 if it does not. For a network that predicts how strongly a gene is expressed from its promoter sequence, the desired output is the measured expression level. The desired output is often called the target, or the label.

At the start the weights are set to small random numbers, so the network’s outputs are nonsense. Training then repeats three things many times. It runs the network on some examples and compares its outputs with the targets. It works out, for every weight, whether making that weight a little bigger or a little smaller would bring the outputs closer to the targets. And it changes every weight a little in the helpful direction. After enough rounds, the weights settle on values that make the network do the job.

Measuring how wrong: the loss

To change weights in a direction that makes the network predictions less wrong, we need a way to measure how wrong the network is. For a single example, the error is the difference between the network’s output \(y\) and the target \(t\). Training uses the square of that difference, \((y - t)^2\). Squaring makes every error positive, so an output that is too high and one that is too low both count as wrong, and it makes large errors count much more than small ones: an error of 2 counts 4, an error of 10 counts 100. Averaged over all the examples, this number is called the loss. A loss of 0 means the network gets every example exactly right. The bigger the loss, the worse it does. Other kinds of loss are used for other kinds of output, but they all work the same way: one number that gets smaller as the network gets better.

For demonstration, lets try this with a “network” that is just a single neuron with one input \(x\) and one weight \(w\). To keep it simple we assume the threshold is so small that we can ignore both that and the activation function (Figure 53.1). In this simplest possible case, the output is just \(y = wx\). To make it even simpler, our training data is just a single example: input \(x = 2\), target \(t = 6\). You can see at once that the right weight is \(w = 3\), because \(3 \times 2 = 6\). The training algorithm cannot see that. All it can do is try a weight and compute the loss:

\[ L(w) = (y - t)^2 = (2w - 6)^2 \tag{53.1}\]

x y L w t = 6 x = 2 y = w × 2 = 2w (2w − 6)²
Figure 53.1: The one-neuron network. The input \(x = 2\) is multiplied by the weight \(w\) to give the output \(y = 2w\), and the loss \(L\) compares \(y\) with the target \(t = 6\).

With \(w = 1\) the output is 2 and the loss is \((2 - 6)^2 = 16\). With \(w = 2\) the output is 4 and the loss is 4. With \(w = 3\) the loss is 0. Computed for every \(w\), the loss traces out the curve in Figure 53.2 and goal is to find the \(w\) that puts the loss at the bottom of that curve.

Figure 53.2: The loss \((2w - 6)^2\) of the one-neuron network for different values of its weight \(w\). The dots are the first steps of gradient descent from \(w = 1\) with a learning rate of 0.05. Each step goes downhill, and the steps get shorter as the curve flattens out near the bottom at \(w = 3\).

Walking downhill: gradient descent

With one weight you could simply try a lot of different values of \(w\) and pick the best. With a million weights that is of cause hopeless: trying just ten values for each would mean more combinations than there are atoms in the universe. The gradient descent training algorithm does not know what the curve looks like. All it cares about is which direction is downhill from where it is now. The steeper the downhill slope (gradient) the better. The slope of the loss curve at a given \(w\) says how much the loss changes when \(w\) is nudged a little. At \(w = 1\) the loss is \(16\). At \(w = 1.01\) the output is \(2.02\) so the loss is \((2.02 - 6)^2 = 15.8404\). So from \(w = 1\) to \(w = 1.01\), the loss went down by \(0.1596\), so the slope is \(-0.1596 / 0.01 = 15.96\).

Differentiating our simple loss function in Equation 53.1, gives the slope as a function of \(w\): \(8w - 24\). At \(w = 1\) that gives \(-16\) agreeing with the \(15.96\) computed above. Knowing the slope, the step downhill is

\[ w_{\text{new}} = w - \eta \times \text{slope} \]

where \(\eta\) (the Greek letter eta) is the learning rate, a small number that sets how big each step is. The minus sign is what makes the step go downhill: a negative slope makes \(w\) bigger, a positive slope makes it smaller. And because the step is proportional to the slope, the steps automatically get shorter as the ground flattens out near the bottom.

With \(\eta = 0.05\) and a start at \(w = 1\), the first step gives \(w = 1 - 0.05 \times (-16) = 1.8\). At \(w = 1.8\) the slope is \(8 \times 1.8 - 24 = -9.6\), and the next step gives \(w = 1.8 + 0.48 = 2.28\). You can see the steps as dots in Figure 53.2. Here is the same thing as a small Python program. Before you run it, predict roughly where \(w\) ends up after ten steps.

x = 2               # the input of our single training example
target = 6          # the output we want for it
w = 1               # a starting guess for the weight
learning_rate = 0.05

print("{:<6}{:<10}{:<10}".format('Step', 'Weight', 'Loss'))
for step in range(15):
    y = w * x                         # run the network
    loss = (y - target) ** 2          # how wrong is it?
    slope = 2 * (y - target) * x      # which way is downhill? (this is 8w - 24)

    print("{:<6}{:<10f}{:<20f}".format(step, w, loss))

    w = w - learning_rate * slope     # take a step downhill
Step  Weight    Loss      
0     1.000000  16.000000           
1     1.800000  5.760000            
2     2.280000  2.073600            
3     2.568000  0.746496            
4     2.740800  0.268739            
5     2.844480  0.096746            
6     2.906688  0.034829            
7     2.944013  0.012538            
8     2.966408  0.004514            
9     2.979845  0.001625            
10    2.987907  0.000585            
11    2.992744  0.000211            
12    2.995646  0.000076            
13    2.997388  0.000027            
14    2.998433  0.000010            

The learning rate matters. If it is too small, training takes too long. If it is too large, and each step overshoots and lands on the other side of the valley, possibly even higher up than where it started.

In a real network the loss depends on all the weights, but the slope is still measured separately for each one. I.e., how does the loss change when one weight is nudged and all others remain the same. The list of all these slopes is the gradient. Each step of gradient descent moves every weight a little, each in its own downhill direction. Even in a landscape of million directions, this approach would still move the loss downhill. However, for a network with a billion weights, changing each weight separately would require a billion nudges just to take a single step, and since training requires millions of steps - well you can do the math. Training large networks became possible only because (Rumelhart et al., 1986) came up with a much faster way to get all the slopes at once. The algorithm is called backpropagation

Backpropagation

Backpropagation finds every slope in a single backward pass through the network, which costs about as much as running the network once. Take a network with two neurons in a chain, as in Figure 53.3. To keep the arithmetic simple we again assume thresholds are so small that we do not have to take them or the activation function into account. The input \(x\) goes to a hidden neuron that computes \(h = w_1 x\). \(h\) then goes to an output neuron that computes \(y = w_2 h\).

The “forward pass” is just running the network like you have done many times already. If \(x = 1\), \(w_1 = 2\), \(w_2 = 1\) and the target is \(t = 4\), then \(h = 2 \times 1 = 2\), which in turn makes \(y = 1 \times 2 = 2\), producing a loss of \((2 - 4)^2 = 4\).

x h y L w₁ = 2 w₂ = 1 t = 4 x = 1 h = 2 × 1 = 2 y = 1 × 2 = 2 (2 − 4)² = 4
Figure 53.3: The forward pass through the two-neuron chain. Each value is computed from the one before it, from left to right, and the loss \(L\) compares the output \(y\) with the target \(t = 4\).

The “backward pass” starts from the end and works its way backwards, one neuron at a time. At every node, it evaluates how much the loss would change if that neuron’s value was nudged. The idea is to “propagate” the slope from the loss “backwards” across the hidden neurons, in proportion to the weight that connects them. Since the loss is \((y - t)^2\), the slope with respect to \(y\) is \(2(y - t) = 2 \times (2 - 4) = -4\). This means that if \(y\) went up by a little, the loss would go down by four times as much. That makes sense since the output \(y\) is lower than the target \(t\). Working backwards, we get to the \(w_2\) weight. Since \(y = w_2 h\), nudging \(w_2\) changes \(y\) by \(h\) times as much, so the slope for \(w_2\) is the slope for \(y\) times \(h\): \(-4 \times 2 = -8\). Nudging \(h\) changes \(y\) by \(w_2\) times as much, so the slope for \(h\) is \(-4 \times w_2 = -4 \times 1 = -4\). Finally, the weight \(w_1\). Since \(h = w_1 x\), nudging \(w_1\) changes \(h\) by \(x\) times as much, so the slope for \(w_1\) is \(-4 \times x = -4 \times 1 = -4\). Figure 53.4 collects the whole backward pass.

x h y L x = 1 h = 2 y = 2 t = 4 w₁ = 2 w₂ = 1 slope for w₁ −4 × x = −4 slope for w₂ −4 × h = −8 slope for y 2 × (2 − 4) = −4 slope for h −4 × w₂ = −4
Figure 53.4: The backward pass. The blue arrows pass the slope back from the loss, one neuron at a time. The slope for each weight is the slope of the neuron the weight leads into, times the value that travels along the weight.

The slopes for both \(w_1\) and \(w_2\) are negative, which means gradient descent will make both weights larger. \(w_2\), has the steeper slope and will grow fastest. In a network with many layers, the slopes flow backward layer by layer, each layer doing a little local multiplication, until every weight has its slope. In mathematics this way of combining slopes along a chain is called the chain rule.

With activation functions, the slope of the activation function enters as an additional factor at every neuron. That is why a step function discussed in ?sec-nn-activation does not work well in training. Its slope is 0 everywhere (except for the jump), and multiplying by 0 stops the chain propagating slope backwards. If the slope is 0, nudging the parameters in the remaining part of the network will not affect the loss. Without this connection between loss and parameter values, we cannot train the network.

The sigmoid activation function has non-zero slope, but across many multiplications across a large network, the tiny slopes far from 0 make the numbers become near-zero anyway. ReLU works best in training because its slope is 1 wherever the neuron is active, so the slope propagates undiminished. Its slope is 0 where it is silent, so even with ReLU, a node will stop learning if it is silent for every example in the training set.

Backpropagation in the tiny network

The two-neuron chain left out the thresholds and the activation function to keep the arithmetic simple. Now we put them back and do a forward and a backward pass through the tiny network from ?sec-nn-tiny, set to two hidden neurons. It has one input \(x\), two hidden neurons and one output neuron. Each hidden neuron computes its net input \(z_i = w_i x - \theta_i\) and puts it through the ReLU activation function, so \(h_i = f(z_i)\), where \(f(z) = \max(0, z)\). The output neuron has no activation function, as in the widget with “Apply \(f\) at output” unticked, so it computes \(y = v_1 h_1 + v_2 h_2 - \theta\). Training must find a slope for all seven parameters: the weights \(w_1\) and \(w_2\) into the hidden layer, the hidden thresholds \(\theta_1\) and \(\theta_2\), the weights \(v_1\) and \(v_2\) into the output, and the output threshold \(\theta\).

Let us start the network from the weights \(w_1 = 1\) and \(w_2 = -1\), give both hidden neurons the threshold 1, and set \(v_1 = 2\), \(v_2 = 3\) and \(\theta = 1\). The training example is the input \(x = 2\) with the target \(t = 3\).

The forward pass works through the network from left to right. The net inputs of the hidden neurons are \(z_1 = 1 \times 2 - 1 = 1\) and \(z_2 = -1 \times 2 - 1 = -3\). ReLU lets the positive value through and turns the negative one into 0, so \(h_1 = 1\) and \(h_2 = 0\). The second hidden neuron is silent. The output is \(y = 2 \times 1 + 3 \times 0 - 1 = 1\), and the loss is \((1 - 3)^2 = 4\). Figure 53.5 shows these values in gray.

x h₁ h₂ y L x = 2 w₁ = 1 w₂ = −1 θ₁ = 1 θ₂ = 1 h₁ = 1 h₂ = 0 v₁ = 2 v₂ = 3 θ = 1 y = 1 t = 3 (1 − 3)² = 4 −8 × 2 = −16 0 × 2 = 0 −8 × (−1) = 8 0 × (−1) = 0 −4 × 2 = −8 −8 × 1 = −8 −4 × 3 = −12 −12 × 0 = 0 −4 × 1 = −4 −4 × 0 = 0 −4 × (−1) = 4 2 × (1 − 3) = −4
Figure 53.5: The forward and backward pass through the tiny network. The gray numbers are the values from the forward pass, and the blue numbers are the slopes from the backward pass. Next to each weight and each threshold is the slope for that parameter. Under each hidden neuron, the first blue line is the slope for its output \(h_i\), and the second is that slope passed back through ReLU, which gives the slope for its net input \(z_i\). Under the output neuron is the slope for \(y\), where the backward pass begins.

Most of the backward pass works exactly as in the two-neuron chain. The slope for \(y\) is \(2(y - t) = -4\), and the slope for each weight is the slope of whatever the weight leads into, times the value that travels along the weight. Figure 53.5 shows all of these products. Four things are new, and they are what this example is for.

The first is that the output neuron receives two inputs. The backward pass sends the slope for \(y\) back along both incoming edges, and each branch gets the slope for \(y\) times its own weight: \(-4 \times v_1 = -8\) for \(h_1\) and \(-4 \times v_2 = -12\) for \(h_2\). The two branches do not affect each other. Each is computed as if the other were not there, and from here on the backward pass simply follows each branch on its own. An output neuron with a hundred inputs would send the same slope back along a hundred edges in the same way.

The second is the thresholds. A threshold is subtracted from the net input, so nudging it up by a little moves the net input down by the same amount. That is a factor of \(-1\), and the slope for a threshold is minus the slope of the net input it belongs to. For the output threshold that gives \(-4 \times (-1) = 4\), and for \(\theta_1\) it gives \(-8 \times (-1) = 8\). Both are positive, so gradient descent will make both thresholds smaller, which makes the neurons fire more easily and raises the output, as it should since the output is too low. In this way a threshold behaves just like a weight on an input that is always \(-1\), and training treats it as one.

The third is the activation function. To get from \(h_i\) back to the net input \(z_i\), the slope has to pass through ReLU, and nudging \(z_i\) changes \(h_i\) by the slope of ReLU at \(z_i\). Where \(z\) is positive, ReLU passes its input straight through, so its slope is 1. Where \(z\) is negative, the output stays at 0 however \(z\) is nudged, so its slope is 0. The first hidden neuron is active, and its slope passes through unchanged: \(-8 \times 1 = -8\). The second is silent, and its slope is stopped: \(-12 \times 0 = 0\). Everything behind it, \(w_2\) and \(\theta_2\), gets a slope of 0 too, and so does \(v_2\), because the value traveling along it is \(h_2 = 0\). This is a pity, because \(h_2\) had the steepest slope of all, so if the second neuron fired it could do the most to raise \(y\). For this one example that does no harm, but a neuron that is silent for every example in the training data never gets a slope and never learns. This is the dead neuron from ?sec-nn-relu. With the sigmoid instead of ReLU, the factor would be the slope of the sigmoid at \(z\), which is never more than \(0.25\), so the slope would shrink a little at every neuron it passes.

The fourth is that \(x\) feeds both hidden neurons. Nudging \(x\) would change \(z_1\) and \(z_2\) at the same time, so the slope for \(x\) is the sum of the slopes coming back along the two edges: \(-8 \times w_1 + 0 \times w_2 = -8\). Training never needs this slope, because \(x\) is the input and not a parameter. In a network with more than one hidden layer, though, every hidden neuron feeds every neuron in the next layer, just as \(x\) does here, and its slope is the sum of the slopes coming back along all its outgoing edges. Multiplying along each edge and adding where edges meet is all backpropagation ever does, however large the network.

The slopes that are not 0 all ask for a higher output: \(w_1\) and \(v_1\) will get larger, and \(\theta_1\) and \(\theta\) smaller. Training a real network is this, repeated. The forward pass computes every value, the backward pass computes every slope, and every parameter takes a small step downhill. The networks behind AI assistants have many more layers and billions of parameters, but every neuron in them does the same small local multiplications.

Training in practice

The network usually doesn’t run on all the training data at every step. Instead, it computes the loss and the slopes from just a random uses a small random handful of examples, called a batch, and. The step is a little less accurate, but there are far more of them for the same amount of computing, and the noise from the random batches even helps the network avoid getting stuck. One pass through all the training data is called an epoch, and training usually runs for many epochs.

The loss function of a real network isn’t a neat bowl like the one in Figure 53.2, but a landscape with countless hills, ridges, and valleys. Gradient descent finds only the bottom of the valley it happens to walk into, which may not be the deepest. For large networks, this matters less than you might fear: there are so many directions to move that a true dead end is rare, and many different sets of weights work about equally well.

The most important practical point is how to tell whether training worked. A network with enough parameters can drive its training-data loss almost to zero by memorizing it, like a student memorizing the answers to last year’s exam without understanding the subject. This is called overfitting, and a network that has overfitted does well on its training examples and badly on everything else. The only way to find out is to test the network on examples it has never seen. Before training starts, part of the data is therefore set aside as a test set, and the network is judged on that alone. It is the same principle you have used with code all term: the program is not right because it looks right, or because it works on the example you wrote it for. It is right when it passes tests it was not built to pass.

Even a network that does well on a test set has only learned what was in the data, and it learns whatever reduces the loss, including shortcuts nobody intended. A network trained to detect pneumonia in chest X-rays from several hospitals turned out to rely partly on features that identified which hospital an image came from, since the hospitals differed in how common pneumonia was among their patients. It generally did worse on X-rays from hospitals it had not been trained on (Zech et al., 2018). The loss rewards getting the training answers right, not getting them right for the right reasons.

Exercise 53-1

SOLO

Continue gradient descent for the one-neuron network by hand, with \(x = 2\), \(t = 6\) and \(\eta = 0.05\). You have reached \(w = 2.28\). Work out each answer on paper before you open it.

\(8 \times 2.28 - 24 = -5.76\). It is still negative, so the next step increases \(w\), but it is less steep than before because you are closer to the bottom.

\(2.28 - 0.05 \times (-5.76) = 2.28 + 0.288 = 2.568\). This should match step 3 in the output of the program above.

It was \(3 - 2.28 = 0.72\) and is now \(3 - 2.568 = 0.432\), which is \(0.6\) times as far. Every step with this learning rate leaves \(0.6\) of the remaining distance.


Exercise 53-2

SOLO

Type the training program from ?sec-train-descent into a file or a notebook cell (do not copy and paste it) and run it to check that you get the same output. Then change the learning rate. For each change, predict what will happen to \(w\) before you run the program.

The weight still moves toward 3, but much more slowly: each step leaves \(0.92\) of the remaining distance, so after ten steps \(w\) has only reached about \(2.13\). A small learning rate is safe but slow.

The weight jumps from 1 to 5, then back to 1, then to 5 again, forever, and the loss stays at 16. Each step goes exactly as far past the bottom as it started before it. The step is so long that it jumps across the valley to the same height on the other side.

It gets worse with every step. The weight goes from 1 to 5.8, then to \(-0.92\), and on to ever larger values on alternate sides of 3, and the loss grows quickly instead of shrinking. Each step overshoots so far that it lands higher up the other side of the valley. When the loss of a real network suddenly grows during training, a learning rate that is too large is the first thing to suspect.


Exercise 53-3

SOLO

Do the forward and backward pass for the two-neuron chain from ?sec-train-backprop by hand, but now with \(x = 2\), \(w_1 = 1\), \(w_2 = 3\) and target \(t = 5\).

\(h = 1 \times 2 = 2\), \(y = 3 \times 2 = 6\), and the loss is \((6 - 5)^2 = 1\).

For \(y\): \(2 \times (6 - 5) = 2\). For \(w_2\): \(2 \times h = 2 \times 2 = 4\). For \(h\): \(2 \times w_2 = 2 \times 3 = 6\). For \(w_1\): \(6 \times x = 6 \times 2 = 12\).

Both slopes are positive, so both weights will be made smaller, which makes sense because the output is too high. The slope for \(w_1\) is three times steeper than the slope for \(w_2\), so \(w_1\) will change most.

Compute the loss with \(w_1 = 1\) and with \(w_1 = 1.01\), keeping everything else the same, and divide the difference by \(0.01\):

x, w2, t = 2, 3, 5
loss_before = (w2 * (1 * x) - t) ** 2
loss_after = (w2 * (1.01 * x) - t) ** 2
print((loss_after - loss_before) / 0.01)

This prints a number close to 12 (about 12.36; the nudge is small but not infinitely small, so the result is not exact).