Neural networks explained

What a neural network is: inputs, weights, a bias and an activation, then training by gradient descent, shown on a robot that steers from its depth sensor. Change a weight, press Run and watch what it does, then train the tiny network and watch the loss fall.

Guidefree, runs in your browser

A neural network is a way of turning numbers in into numbers out, using a long list of adjustable numbers called weights. Each small part of it multiplies its inputs by weights, adds them up, and squashes the total. On its own that does nothing clever. What makes a network useful is that the weights can be learned from examples: show it inputs with the right answers, and nudge every weight a little in whichever direction makes it less wrong. Warren McCulloch and Walter Pitts described an artificial neuron in 1943, and Frank Rosenblatt built the perceptron, a machine that learned its own weights, in 1958. The networks that recognise faces in photos and the ones behind chatbots use the same multiply, add and squash, with far more weights. On this page a small robot steers with a network small enough to read, and each demo below is a playground: change a weight, press Run, and watch what the robot does.

Every demo uses the same 1 metre mat as the k-nearest neighbours guide. A wall 72 cm long lies across it 60 cm in front of where the robot starts, with a 14 cm gap at each end. In the first three demos the overhead view draws the robot's path as a blue line, and the chart shows two lines: turn, the network's output, from -1 (turn left hard) to +1 (turn right hard), with its scale on the right; and nearest, the robot's nearest depth reading in cm. The last demo trains a network and plots its loss, how wrong it still is, then drives with it.

The idea in one line

output = activation(w1 × x1 + w2 × x2 + ... + w16 × x16 + bias)
  • The inputs x1 to x16 are numbers the robot measured.
  • Each input has a weight, w1 to w16. A big weight means that input matters a lot; a negative weight means it pushes the answer the other way.
  • The bias is one more number, added on at the end. It sets what the output is when the inputs say nothing much.
  • The activation squashes the total into a fixed range. The one on this page is tanh, which turns any number into one between -1 and 1.

That is one neuron. A network is several of them: the outputs of one layer are the inputs of the next. Lesson A tiny network puts it in one sentence: multiply, add, squash, repeat.

Inputs: a view is 16 numbers

A network never sees a wall. It sees numbers. The robot's depth sensor measures 64 distances in 8 rows of 8, spread 45 degrees across, and rows 2 and 3 look straight ahead. Those 16 readings, tof_grid()[16:32], are the inputs on this page, left to right along row 2 and then row 3. The lesson See as numbers prints them.

Networks train badly on raw numbers like 400 (the sensor's reading when it sees nothing in range), so lesson 8.4 shifts and scales every reading first:

x = (reading − 30) / 20

A reading of 30 cm becomes 0, 50 cm becomes 1, and 16 cm becomes -0.7. The hand-set demos below also count anything past 50 cm as 50, so open space is always exactly 1.

One neuron, with weights set by hand

Here is a neuron that steers. Its 16 weights are laid out the way the sensor sees:

Columns 0 to 3 (left) Columns 4 to 7 (right)
Row 2 -0.2 0.05
Row 3 -0.2 0.05

and its bias is 1.2. The output is the turn: the robot always drives forward at 50, and turns at 60 times the output, so +1 is a hard right and -1 a hard left.

Work it through for a few views:

  • Open space. Every reading is 50 or more, so every x is 1. The sum is 8 × -0.2 + 8 × 0.05 = -1.2, the bias adds 1.2, the total is 0, and tanh(0) = 0. Drive straight.
  • A wall straight ahead, 30 cm away. Every x is about 0, so only the bias is left: tanh(1.2) = 0.83. Turn right, hard.
  • A wall on the left only, 16 cm away. The left half gives 8 × -0.2 × -0.7 = 1.12, the right half gives 8 × 0.05 × 1 = 0.4, and with the bias the total is 2.72. tanh(2.72) = 0.99. Turn right, away from it.
  • A wall on the right only, 16 cm away. The left half gives -1.6, the right half -0.28, and with the bias the total is -0.68. tanh(-0.68) = -0.59. Turn left, away from it.

The left weights are bigger than the right ones on purpose. With equal weights, a wall straight ahead would give both halves the same readings and they would cancel out. Making them unequal means a wall ahead always tips the robot to the right.

Hand-set weights: the robot drives straight for about 2.3 s, turns right when the wall is 42 cm away, then loops round in front of the wall for 20 seconds without touching anything.
The program
from bugbot import *
import math
connect()

# change these numbers and press Run
# one weight for each of the 16 level readings, laid out
# the way the sensor sees them: row 2, then row 3, left to right
W = [-0.2, -0.2, -0.2, -0.2, 0.05, 0.05, 0.05, 0.05,
     -0.2, -0.2, -0.2, -0.2, 0.05, 0.05, 0.05, 0.05]
B = 1.2              # the bias

def neuron(view):
    # scale each reading to about -1 to +1, as lesson 8.4 does;
    # anything past 50 cm is far enough, so it counts as 50
    x = [(min(d, 50) - 30) / 20 for d in view]
    total = sum(w * xi for w, xi in zip(W, x)) + B
    return math.tanh(total)       # squash it into -1 to +1

print("weights, row 2:", W[:8])
print("weights, row 3:", W[8:])
print("bias:", B)
trail = []
for tick in range(200):           # 20 seconds
    view = tof_grid()[16:32]
    turn = neuron(view)           # the output: -1 left, +1 right
    drive(50, 0, 60 * turn)
    plot("turn", turn)
    plot("nearest", min(view))
    x, y = position()
    trail.append((50 + x, 10 + y))
    draw("path", trail, "blue", "line")
    if tick % 10 == 0:
        print("t =", tick / 10, " nearest", min(view), " turn", round(turn, 2))
    wait(0.1)
stop()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

The turn line stays at 0 until the nearest reading drops below 50 cm, then climbs as the wall gets closer. Small outputs do nothing: the motors ignore a turn command below about 15, which is an output of 0.25. The robot starts to turn at about 2.3 s, when the output passes 0.4 and the wall is 42 cm away. After that it turns right whenever something comes near, so it goes round in clockwise loops, and its nearest reading never drops below 10 cm.

The activation: why squash

tanh is flat at both ends: tanh(0) = 0, tanh(1.2) = 0.83, tanh(3) = 0.995, and no input, however big, gets past 1. That keeps the output in a range the program can use. A wall 5 cm away cannot ask for a turn of 500.

There is a second reason, which matters once there are layers. Without the squash, a weighted sum of weighted sums is just one weighted sum with different weights, so stacking layers would add nothing. The bend in the activation is what lets a network with layers do things one neuron cannot. Other common activations are the sigmoid, which squashes into 0 to 1, and ReLU, which keeps positive numbers and turns negative ones into 0.

A bad set of weights

Here is a set that looks sensible. Negative on the left, positive on the right, the same size, and no bias: turn towards whichever side has more room.

Balanced weights and no bias: the output never moves more than 0.03 from zero, and the robot drives straight into the middle of the wall at 6.4 s.
The program
from bugbot import *
import math
connect()

# change these numbers and press Run
# one weight for each of the 16 level readings, laid out
# the way the sensor sees them: row 2, then row 3, left to right
W = [-0.2, -0.2, -0.2, -0.2, 0.2, 0.2, 0.2, 0.2,
     -0.2, -0.2, -0.2, -0.2, 0.2, 0.2, 0.2, 0.2]
B = 0.0              # the bias

def neuron(view):
    # scale each reading to about -1 to +1, as lesson 8.4 does;
    # anything past 50 cm is far enough, so it counts as 50
    x = [(min(d, 50) - 30) / 20 for d in view]
    total = sum(w * xi for w, xi in zip(W, x)) + B
    return math.tanh(total)       # squash it into -1 to +1

print("weights, row 2:", W[:8])
print("weights, row 3:", W[8:])
print("bias:", B)
trail = []
for tick in range(200):           # 20 seconds
    view = tof_grid()[16:32]
    turn = neuron(view)           # the output: -1 left, +1 right
    drive(50, 0, 60 * turn)
    plot("turn", turn)
    plot("nearest", min(view))
    x, y = position()
    trail.append((50 + x, 10 + y))
    draw("path", trail, "blue", "line")
    if tick % 10 == 0:
        print("t =", tick / 10, " nearest", min(view), " turn", round(turn, 2))
    wait(0.1)
stop()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

The robot starts square to the wall, so the left half and the right half of its view read almost the same all the way in. The two halves cancel, the total stays at 0, and the nearest line falls to 4 cm while the turn line stays flat. The weights are right for a wall off to one side and useless for a wall straight ahead, and straight ahead is the first thing this robot meets.

That is the usual way a network fails. Nothing crashes and no error appears. It gives confident, wrong answers for a situation its weights never allowed for.

Change one weight

The bias is a weight too: a weight on an input that is always 1. Go back to the good weights and change only the bias, from 1.2 to 0.

Bias 0: the robot turns left at once, drives up the left side of the mat, through the gap at the left end of the wall at about 12 s and along the far side. No bumps.
The program
from bugbot import *
import math
connect()

# change these numbers and press Run
# one weight for each of the 16 level readings, laid out
# the way the sensor sees them: row 2, then row 3, left to right
W = [-0.2, -0.2, -0.2, -0.2, 0.05, 0.05, 0.05, 0.05,
     -0.2, -0.2, -0.2, -0.2, 0.05, 0.05, 0.05, 0.05]
B = 0.0              # the bias

def neuron(view):
    # scale each reading to about -1 to +1, as lesson 8.4 does;
    # anything past 50 cm is far enough, so it counts as 50
    x = [(min(d, 50) - 30) / 20 for d in view]
    total = sum(w * xi for w, xi in zip(W, x)) + B
    return math.tanh(total)       # squash it into -1 to +1

print("weights, row 2:", W[:8])
print("weights, row 3:", W[8:])
print("bias:", B)
trail = []
for tick in range(200):           # 20 seconds
    view = tof_grid()[16:32]
    turn = neuron(view)           # the output: -1 left, +1 right
    drive(50, 0, 60 * turn)
    plot("turn", turn)
    plot("nearest", min(view))
    x, y = position()
    trail.append((50 + x, 10 + y))
    draw("path", trail, "blue", "line")
    if tick % 10 == 0:
        print("t =", tick / 10, " nearest", min(view), " turn", round(turn, 2))
    wait(0.1)
stop()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

In open space the total is now -1.2 + 0 = -1.2, and tanh(-1.2) = -0.83, so the robot turns left as soon as it starts. Near things still push it right, so when it comes close to the edge of the mat it turns to run along it, with the edge on its left, and the gap happens to be on its way. Nobody told it where the gap was.

Now try B = 0.6. In open space the output is -0.54, a gentler left turn. The robot drifts left, runs into the left end of the wall at 8.2 s, and stays there for the rest of the run. Pressed against the corner, its output sits between 0.08 and 0.26, a turn command of 5 to 16, which is not enough to turn it off.

One number moved, and the robot went from looping in front of the wall, to getting through it, to getting stuck on it. A network with thousands of weights is far harder to set by hand, and nobody tries. The weights are learned instead.

How a network learns: nudging the weights

Learning needs three things: examples with the right answers, a way to measure how wrong the network is on them, and a rule for changing the weights to make it less wrong.

The measure is the loss. For one output it can be as plain as the square of the difference between what the network said and what it should have said. Add that up over all the examples and you have one number for how wrong the whole network is. Training is making that number small.

The rule is gradient descent. Think of the loss as the height of a hilly landscape, with one direction for every weight. You are standing somewhere on it, in fog, and want to get to the bottom. You cannot see the valley, but you can feel which way the ground slopes under your feet, so you take a small step downhill, feel again, and take another. The gradient is the slope: for each weight, which way it should move to make the loss smaller, and how strongly. The learning rate is the size of the step.

For one neuron with no squash and the squared difference as the loss, the step for each weight works out to:

new weight = weight − learning rate × error × input

where error is the output minus the right answer. The input is in there because it says how much that weight was to blame: a weight on an input of 0 made no difference to the output, so it is left alone.

One worked update

A neuron with one input and one weight, w = 0.4, and a bias b = 0. The robot sees a wall 20 cm ahead, so the input is x = (20 − 30) / 20 = -0.5, and the right answer is 1: turn right, hard. The learning rate is 0.5.

  1. Forward. Output = 0.4 × -0.5 + 0 = -0.2. It says turn slightly left.
  2. Measure. Error = -0.2 − 1 = -1.2. The squared error is 1.44.
  3. Backward. For the weight: error × input = -1.2 × -0.5 = 0.6. For the bias, whose input is always 1: -1.2 × 1 = -1.2.
  4. Step. w = 0.4 − 0.5 × 0.6 = 0.1, and b = 0 − 0.5 × -1.2 = 0.6.

Run the same view through again: 0.1 × -0.5 + 0.6 = 0.55. The error is now -0.45 and the squared error 0.2, down from 1.44 in one step. Training is this, repeated for every weight and every example, many times over. Each pass through all the examples is an epoch.

Nobody works the steps out by hand for a real network. An algorithm called backpropagation works out the gradient for every weight in every layer in one pass backwards through the network, starting from the error at the outputs. It is the four backward lines in lesson 8.4, and they do exactly this. dZ = (P - Y) / len(DATA) is the error at each output, the network's answer minus the right one. dW2 = H.T @ dZ is input times error for every weight in the last layer at once. W2 -= lr * dW2 is the step. The lines in between pass the error back through tanh to the first layer.

Training the tiny network

This is the network from lesson 8.4, trained on its 34 examples exactly as the lesson trains it. Each example is 16 readings and one of four labels: open, wall-ahead, gap-left (the wall ends to the robot's left) or gap-right. The 16 inputs are mixed by one table of weights, W1, into 8 hidden numbers, each squashed by tanh. Those 8 are mixed by a second table, W2, into 4 outputs, one per label, and the biggest output is the answer. That is 16 × 8 + 8 × 4 = 160 weights, plus 12 biases.

Training uses a different loss from the worked update, because the answer is a label rather than a number. The 4 outputs are turned into probabilities that add up to 1 (the lesson's P), and the loss is lower the more probability the network gives to the right label. The labels become one-hot rows, a 1 in the column of the right answer, so there is something to subtract: P - Y.

After training, the program sends the robot's first view through the network and prints the 8 hidden numbers and the 4 outputs. Then it drives: forward while the answer is open, and turning right on the spot whenever it is anything else. The dots on the mat show the answer at each point: green for open, red for wall-ahead, blue for gap-left and orange for gap-right.

Training: the loss falls from 1.15 to 0.013 in 300 epochs and all 34 examples come out right. Then it drives, turning right whenever the answer is not open, and touches nothing in 20 seconds.
The program
from bugbot import *
import numpy as np
connect()

# the 34 labelled views from lesson 8.4
DATA = [
    ([54, 52, 51, 51, 51, 51, 52, 54, 54, 52, 51, 51, 51, 51, 52, 54], 'open'),
    ([16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], 'wall-ahead'),
    ([36, 47, 46, 46, 46, 16, 16, 16, 36, 47, 46, 46, 46, 16, 16, 16], 'gap-left'),
    ([16, 16, 16, 46, 46, 46, 47, 36, 16, 16, 16, 46, 46, 46, 47, 36], 'gap-right'),
    ([400, 400, 61, 61, 61, 61, 400, 400, 96, 62, 61, 61, 61, 61, 62, 64], 'open'),
    ([52, 50, 49, 49, 49, 49, 50, 52, 51, 50, 49, 49, 49, 49, 50, 51], 'open'),
    ([39, 38, 37, 37, 37, 37, 38, 39, 39, 38, 37, 37, 37, 37, 38, 39], 'open'),
    ([400, 400, 61, 61, 61, 61, 400, 400, 64, 62, 61, 61, 61, 61, 62, 64], 'open'),
    ([52, 50, 49, 49, 49, 49, 50, 52, 51, 50, 49, 49, 49, 49, 50, 51], 'open'),
    ([39, 38, 37, 37, 37, 37, 38, 39, 39, 38, 37, 37, 37, 37, 38, 39], 'open'),
    ([400, 400, 61, 61, 61, 61, 400, 400, 64, 62, 61, 61, 61, 61, 62, 96], 'open'),
    ([52, 50, 49, 49, 49, 49, 50, 52, 51, 50, 49, 49, 49, 49, 50, 51], 'open'),
    ([39, 38, 37, 37, 37, 37, 38, 39, 39, 38, 37, 37, 37, 37, 38, 39], 'open'),
    ([26, 25, 25, 25, 25, 25, 25, 26, 26, 25, 25, 25, 25, 25, 25, 26], 'wall-ahead'),
    ([18, 17, 17, 17, 17, 17, 17, 18, 17, 17, 17, 17, 17, 17, 17, 17], 'wall-ahead'),
    ([9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], 'wall-ahead'),
    ([26, 25, 25, 25, 25, 25, 25, 26, 26, 25, 25, 25, 25, 25, 25, 26], 'wall-ahead'),
    ([18, 17, 17, 17, 17, 17, 17, 18, 17, 17, 17, 17, 17, 17, 17, 17], 'wall-ahead'),
    ([9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], 'wall-ahead'),
    ([26, 25, 25, 25, 25, 25, 25, 26, 26, 25, 25, 25, 25, 25, 25, 26], 'wall-ahead'),
    ([18, 17, 17, 17, 17, 17, 17, 18, 17, 17, 17, 17, 17, 17, 17, 17], 'wall-ahead'),
    ([9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9], 'wall-ahead'),
    ([24, 34, 55, 59, 59, 59, 29, 30, 24, 33, 55, 59, 59, 59, 29, 30], 'gap-left'),
    ([24, 34, 51, 51, 51, 51, 25, 22, 24, 33, 51, 51, 51, 51, 25, 22], 'gap-left'),
    ([24, 34, 43, 43, 43, 43, 44, 18, 24, 33, 43, 43, 43, 43, 44, 18], 'gap-left'),
    ([42, 58, 59, 59, 29, 29, 29, 30, 42, 58, 59, 59, 29, 29, 29, 30], 'gap-left'),
    ([42, 52, 51, 51, 21, 21, 21, 22, 42, 52, 51, 51, 21, 21, 21, 22], 'gap-left'),
    ([42, 44, 43, 43, 13, 13, 13, 13, 42, 44, 43, 43, 13, 13, 13, 13], 'gap-left'),
    ([30, 29, 29, 29, 59, 59, 58, 42, 30, 29, 29, 29, 59, 59, 58, 42], 'gap-right'),
    ([22, 21, 21, 21, 51, 51, 52, 42, 22, 21, 21, 21, 51, 51, 52, 42], 'gap-right'),
    ([13, 13, 13, 13, 43, 43, 44, 42, 13, 13, 13, 13, 43, 43, 44, 42], 'gap-right'),
    ([30, 29, 59, 59, 59, 55, 34, 24, 30, 29, 59, 59, 59, 55, 33, 24], 'gap-right'),
    ([22, 25, 51, 51, 51, 51, 34, 24, 22, 25, 51, 51, 51, 51, 33, 24], 'gap-right'),
    ([18, 44, 43, 43, 43, 43, 34, 24, 18, 44, 43, 43, 43, 43, 33, 24], 'gap-right'),
]

np.random.seed(1)
labels = sorted(set(l for _, l in DATA))
# 16 readings, centred near 0 and about -1 to +1
X = (np.array([f for f, _ in DATA], dtype=float) - 30) / 20
# one-hot: a 1 in the column of the right label
Y = np.zeros((len(DATA), len(labels)))
for i, (_, l) in enumerate(DATA):
    Y[i, labels.index(l)] = 1
# random weights to start: 16 inputs -> 8 hidden -> 4 outputs
W1 = np.random.randn(16, 8) * 0.5; b1 = np.zeros(8)
W2 = np.random.randn(8, len(labels)) * 0.5; b2 = np.zeros(len(labels))

# train: forward, measure, backward, step
lr = 0.5             # the learning rate
for epoch in range(1, 301):
    # forward
    H = np.tanh(X @ W1 + b1)
    Z = H @ W2 + b2
    # outputs as probabilities, and how wrong they are (the loss)
    P = np.exp(Z - Z.max(axis=1, keepdims=True)); P /= P.sum(axis=1, keepdims=True)
    loss = -np.mean(np.log(P[np.arange(len(DATA)), Y.argmax(axis=1)] + 1e-9))
    # backward: how wrong, per output, then per weight
    dZ = (P - Y) / len(DATA)
    dW2 = H.T @ dZ; db2 = dZ.sum(axis=0)
    dH = dZ @ W2.T * (1 - H ** 2)
    dW1 = X.T @ dH; db1 = dH.sum(axis=0)
    # the step: nudge every weight a little downhill
    W1 -= lr * dW1; b1 -= lr * db1; W2 -= lr * dW2; b2 -= lr * db2
    plot("loss", loss)
    wait(0.02)                    # so the chart spreads the epochs out
    if epoch % 50 == 0:
        print(f"epoch {epoch}: loss {loss:.3f}")
pred = np.tanh(X @ W1 + b1) @ W2 + b2
print(f"accuracy: {round(np.mean(pred.argmax(axis=1) == Y.argmax(axis=1)) * 100)}%")

def forward(view):
    # the same scaling, then the trained weights
    x = (np.array(view, dtype=float) - 30) / 20
    hidden = np.tanh(x @ W1 + b1)
    return hidden, hidden @ W2 + b2

# one view, all the way through the network
hidden, out = forward(tof_grid()[16:32])
print("hidden:", np.round(hidden, 2))
for label, value in zip(labels, out):
    print(f"{label:>10}: {value:.2f}")

# now drive with it: forward while it says open, otherwise turn right
COLOUR = {"open": "green", "wall-ahead": "red",
          "gap-left": "blue", "gap-right": "orange"}
trail = {label: [] for label in COLOUR}
for tick in range(200):           # 20 seconds
    hidden, out = forward(tof_grid()[16:32])
    label = labels[out.argmax()]  # the biggest output wins
    if label == "open":
        drive(50, 0, 0)
    else:
        drive(0, 0, 40)
    x, y = position()
    trail[label].append((50 + x, 10 + y))
    draw(label, trail[label], COLOUR[label])
    if tick % 10 == 0:
        print("x =", round(x), " y =", round(y), " ", label)
    wait(0.1)
stop()
Put this demo on your own site

Paste it into a school website, Moodle, Google Sites or a blog. More options on the embed page.

The loss line is the learning. It starts at 1.15, which means the network gives the right label a probability of about 0.32 on average, hardly better than guessing one label in four. It falls steeply at first and then more and more slowly, to 0.12 by epoch 50 and 0.013 by epoch 300, when the right label gets a probability of about 0.99. The steep start and long tail are the shape of nearly every training curve: the first steps fix the big mistakes, and the rest polish.

For the robot's first view, the outputs are 6.20 for open, 0.08 for gap-left, -1.99 for wall-ahead and -2.95 for gap-right, so the answer is open. All 8 hidden numbers print as 1 or -1. Four of the start view's readings are 400, which the scaling turns into 18.5, and a total that big squashes each tanh to its limit.

On the drive, the robot says open until it is about 29 cm from the wall, then turns right until the answer is open again and drives on. Watch the dots while it turns: the answer flickers between open and gap-right. Those views, half turned towards the wall, lie between the examples it learned from, and a small change in the readings tips the biggest output from one label to the other.

Try lr = 0.05 and lr = 3 and watch the loss line. Too small and it creeps down; too big and it bounces. Lesson 8.4 has more to try.

Why a hidden layer helps

A single neuron adds up its inputs and compares the total with a threshold. However its weights are set, it can only split the possible inputs with one straight cut: one side yes, the other side no. Some rules cannot be drawn with one straight cut. The famous one is exclusive or, "one of the two but not both". For a robot: steer when exactly one side is blocked, and go straight both in open space and in a corridor with a wall on each side. No set of weights on "left blocked" and "right blocked" gets all four cases right. With a hidden layer, one hidden number can learn "blocked on the left and clear on the right", another the other way round, and the output adds them up. Each extra layer builds new features out of the ones before it, which is why networks with many layers, called deep networks, can learn complicated things. The 34 examples here are easy enough that no hidden layer is needed at all, but it helps even so. Take it out and train the same way, and the network gets 30 of the 34 right after 300 epochs and does not get all 34 until about epoch 2,600. With the hidden layer it gets all 34 right within 40 epochs.

Overfitting

A network that gets all its training examples right has learned those examples. Whether it has learned the rule behind them is a separate question, and the only way to answer it is to test on examples it has never seen. With enough weights a network can learn every example, noise and mistakes included, and then do worse on new data than a simpler model would. That is overfitting. The 34 examples above hold only 24 different views, because some were recorded more than once. Leave one of the 24 views out, train on the rest, and ask the network about the one it has not seen: done for each view in turn, it gets 20 of the 24 right. So 100% on its examples is closer to 83% on new views. The usual fixes are more varied examples, fewer weights, stopping training before the loss on held-back examples starts to rise, and always testing on examples kept out of training. The lesson Generalisation goes further.

From this robot to image recognition and chatbots

The networks behind image recognition and chatbots are built from the same parts: weighted sums, a bias, an activation, layer after layer, trained by gradient descent with backpropagation. What changes is the size. An image network takes the pixels of a photo as its inputs, three numbers for each pixel, and has millions of weights, arranged so that early layers pick out edges and later layers pick out shapes and objects. A chatbot's network, a large language model, turns text into numbers, and is trained on a huge amount of text to predict the next piece of it; it has billions of weights. Training those takes large computers weeks or months. The loop inside is still forward, measure, backward, step.

Other ways to learn from data

A neural network is one way for a robot to learn. The k-nearest neighbours guide uses the same 34 examples a different way: it keeps every one and compares each new view with them, with no training at all. The Q-learning guide has no labelled examples, only a score after each action, and learns a table of which action is best in each situation. The lesson Learning a behaviour sets out when learning is worth it, and Fitting a model fits a model with two weights in one step, without any gradient descent, which is the right tool whenever a problem allows it.

Questions

What is a neural network in simple terms?

A calculation that turns numbers in into numbers out, made of many small parts called neurons. Each neuron multiplies its inputs by weights, adds them up with a bias, and squashes the total with an activation. The weights are not written by hand but learned from examples, by nudging them over and over to make the answers less wrong. On this page the inputs are 16 depth readings and the outputs steer a robot.

How does a neural network learn?

It is shown examples with the right answers. For each one it works out its own answer (the forward pass), measures how wrong it is (the loss), works out which way each weight should move to reduce the loss (the gradient, found by backpropagation), and moves every weight a small step that way. Repeated over all the examples many times, the loss falls. The network on this page went from a loss of 1.15 to 0.013 in 300 passes.

What are weights and biases in a neural network?

A weight is a number that says how much one input counts towards a neuron's total, and in which direction: a negative weight pushes the total down. The bias is added on after the weighted sum, and sets what the neuron does when the inputs add up to nothing. On this page, changing only the bias of a steering neuron from 1.2 to 0 turned a robot that looped in front of a wall into one that went through the gap at its end.

What is an activation function and why is it needed?

A function applied to a neuron's total before it is passed on, such as tanh, sigmoid or ReLU. It keeps outputs in a useful range, and it bends the calculation. Without that bend, several layers of weighted sums add up to a single weighted sum, and the extra layers could learn nothing a single layer cannot.

What is a hidden layer?

A layer of neurons between the inputs and the outputs. Its numbers are not given in the data and not read as answers; the network works out for itself what they should detect. The network on this page has one hidden layer of 8 numbers between 16 inputs and 4 outputs. Hidden layers let a network learn rules that one straight cut through the inputs cannot express, such as exclusive or.

What is gradient descent?

A way of finding the weights that make the loss smallest by walking downhill. At the current weights, work out the slope of the loss for each weight, and move each weight a small step in the downhill direction. The step size is the learning rate. On this page one step on one example cut the squared error from 1.44 to 0.2.

What is backpropagation?

The algorithm that works out the gradient for every weight in a network. It starts with the error at the outputs and passes it backwards through the layers, so that each weight learns how much it contributed to the error. In lesson 8.4 it is four lines of numpy.

What is an epoch in machine learning?

One pass through all the training examples. The network on this page trains for 300 epochs on 34 examples, taking one step after each epoch. Big networks usually take a step after every small batch of examples instead, so one epoch is many steps.

What is the learning rate?

The size of each step in gradient descent. Too small and training creeps; too big and the loss bounces about and may never settle. The network on this page uses 0.5. It plays the same part as the gain in a controller.

What is overfitting in a neural network?

When a network learns its training examples so closely that it does worse on new ones. It shows up as a loss that keeps falling on the training examples while the loss on held-back examples stops falling or rises. On this page the network gets all 34 of its examples right but 20 of 24 views it was not trained on.

What is the difference between a neural network and k-nearest neighbours?

K-nearest neighbours keeps every example and compares each new input with all of them; it has no training step, and it slows down as the examples grow. A neural network uses the examples once, to set its weights, and after that it can throw them away; each answer costs the same small amount of arithmetic however many examples it was trained on.

Is ChatGPT a neural network?

Yes. Chatbots such as ChatGPT run on large language models, which are neural networks with billions of weights. They are trained on a huge amount of text to predict the next piece of text, then trained further to give helpful answers. The neurons inside do the same multiply, add and squash as the ones on this page.

What is deep learning?

Machine learning with neural networks that have many layers. Each layer builds new features out of the layer before it, so a deep image network can go from pixels to edges to shapes to objects. The network on this page, with one hidden layer, is shallow.

How do you make a neural network in Python?

With numpy: store each layer's weights as a table (an array), and the forward pass is np.tanh(X @ W1 + b1) @ W2 + b2, where @ multiplies whole tables at once. Train it with a loop that runs the forward pass, measures the loss, works out the gradients and subtracts lr times each gradient from its weights. The last demo on this page is exactly that, from lesson 8.4. Libraries such as PyTorch and TensorFlow work out the gradients for you.

Are neural networks on the GCSE or A level specification?

Not by name. The GCSE and A level Computer Science specifications (AQA, OCR, Pearson Edexcel, Eduqas) do not ask students to build or train a neural network. Pearson Edexcel GCSE Computer Science does ask students to know about the issues raised by artificial intelligence, machine learning and robotics, and a robot that learns to steer is a concrete example to discuss. A small network also makes a good A level Computer Science programming project: the maths is multiplying and adding, and the loss curve is a clear way to show it works.

Learn it step by step

These lessons build the same ideas one at a time, each with tasks the simulator marks.

  1. 8.1 See as numbers Learning, Robot club
  2. 8.4 A tiny network Learning, Robot club
  3. U12.1 Learning a behaviour Learning, and the capstone, University
  4. U12.2 Fitting a model to data Learning, and the capstone, University
Open the lessons