A network that memorised the year
Overfitting happens when a neural network learns its training examples so closely that it does badly on new ones. Spotting it, and stopping training at the right moment, separates a model that works in a demo from one that works in a shop.
A network with enough weights can memorise a whole year of bike hires, the random noise included.
By the end of this lesson
Note
I can spot overfitting and stop training before it gets worse.
Key words
| Word | What it means | Example |
|---|---|---|
| Training set | the rows used to set the weights | |
| Validation set | rows kept back to check on unseen data | |
| Overfitting | learning the training rows, noise and all | |
| Early stopping | ending training once validation stops improving | |
| Patience | how many epochs to wait for a new best | 3 |
The cells students fill in are left blank in this preview.
Keep some rows back to test the network
A network is judged on rows it has never seen. So you split the data first. The training set sets the weights. The validation set is kept back, and you only use it to measure the loss.
A common split is 80 percent for training and 20 percent for validation. A year of 365 days gives 292 and 73.
Worked example: splitting a year of 365 days
| Set | Share | Days | Used for |
|---|---|---|---|
| Training set | 80 percent | 292 | Setting the weights |
| Validation set | 20 percent | 73 | Checking on days it has not seen |
The cells students fill in are left blank in this preview.
Count the lines of the bike file
docker exec jupyter wc -l hires.csvShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Work out the two set sizes
docker exec jupyter python3 -c "print(int(365 * 0.8), 365 - int(365 * 0.8))"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Overfitting shows up as a growing gap
Early in training both losses fall. Then the network starts to fit the noise in the training rows. The training loss keeps falling, but the validation loss stops falling and starts to rise.
The distance between the two is the gap. A growing gap is the sign of overfitting.
Worked example: eight epochs of training
| Epoch | Training loss | Validation loss | Gap |
|---|---|---|---|
| 1 | 9.0 | 9.5 | 0.5 |
| 2 | 6.0 | 6.5 | 0.5 |
| 3 | 4.0 | 4.8 | 0.8 |
| 4 | 2.5 | 4.0 | 1.5 |
| 5 | 1.5 | 3.8 | 2.3 |
| 6 | 0.9 | 4.0 | 3.1 |
| 7 | 0.5 | 4.5 | 4.0 |
| 8 | 0.2 | 5.2 | 5.0 |
The cells students fill in are left blank in this preview.
Find the best epoch in Python
train = [9.0, 6.0, 4.0, 2.5, 1.5, 0.9, 0.5, 0.2]
validation = [9.5, 6.5, 4.8, 4.0, 3.8, 4.0, 4.5, 5.2]
best = validation[0]
best_epoch = 1
for n in range(8):
gap = validation[n] - train[n]
print("epoch", n + 1, "gap", round(gap, 1))
if validation[n] < best:
best = validation[n]
best_epoch = n + 1
print("best epoch:", best_epoch)
# Change the last validation loss from 5.2 to 3.0 and press Run.
# Which epoch is best now?
Early stopping ends training at the right time
Early stopping ends training once the validation loss has stopped improving. Patience is how many epochs in a row you wait without a new best before you stop.
Then you keep the weights from the best epoch, not the last one. The last ones have already started to overfit.
Worked example: stopping with patience 2 and 3
| Epoch | Validation loss | New best | Waited | Stops with |
|---|---|---|---|---|
| 5 | 3.8 | yes | 0 | nothing yet |
| 6 | 4.0 | no | 1 | nothing yet |
| 7 | 4.5 | no | 2 | patience 2 |
| 8 | 5.2 | no | 3 | patience 3 |
The cells students fill in are left blank in this preview.
More ways to reduce overfitting
Early stopping is one fix. Dropout switches off a random share of neurons at each training step, so none can lean on another. A weight penalty adds a cost to the loss for large weights.
You can also add more data, or use a smaller network that has fewer weights to memorise with.
Type this in your notebook: two sizes
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neural_network import MLPClassifier
from sklearn.preprocessing import StandardScaler
hires = pd.read_csv("hires.csv")
hires["weekend"] = hires["weekday"].isin(["Sat", "Sun"]).astype(int)
label = (hires["hires"] >= 300).astype(int)
X = StandardScaler().fit_transform(hires[["temp_c", "rain_mm", "weekend"]])
X_train, X_val, y_train, y_val = train_test_split(X, label, test_size=0.2, random_state=1)
for size in [(2,), (64, 64)]:
net = MLPClassifier(hidden_layer_sizes=size, max_iter=3000, random_state=0)
net.fit(X_train, y_train)
print(size, "training:", round(net.score(X_train, y_train), 3),
"validation:", round(net.score(X_val, y_val), 3))
# Then add early_stopping=True to MLPClassifier and run it again.
A common mistake
Warning
Many people think the model with the highest training score is the best one. A network can score 100 percent on rows it has seen by memorising them. Only the score on rows it has not seen shows whether it learned the pattern. Always read the two scores side by side.
Going further
A final test set is a third group of rows that you use once, at the very end. Why can you not use the validation set for that job?
Hint: you chose the stopping epoch by looking at the validation loss, so the validation set has already helped to pick the model.