Python · Course navigation

Overfitting and cross-validation

Compare training accuracy with leave-one-out validation accuracy to choose k and detect memorization.

On this page
Before you start

k-nearest neighbors and accuracy.

  • Compare training accuracy with leave-one-out validation accuracy to choose k and detect memorization.
  • Predict the sample output and explain the state changes.
  • Complete the practice task and check the boundary cases.

The concept

A model overfits when it learns the noise and quirks of its training data instead of the underlying pattern. It then does very well on the data it has seen and worse on new data. The opposite problem, underfitting, is a model too simple to capture the pattern. The gap between training and validation performance is the main warning sign.

Cross-validation estimates how well a model will generalize. In k-fold cross-validation the data are split into k parts. Each part in turn is held out for validation while the model trains on the rest, and the scores are averaged. Leave-one-out is the extreme case with one example per fold. Cross-validation is used to choose hyperparameters such as k in k-NN or the depth of a tree. A separate test set should still be kept aside for the final, unbiased estimate, and any preprocessing must be fitted inside each training fold to avoid data leakage.

In the example, a one-dimensional dataset of 12 points has a noisy label at x = 5. With k = 1 the classifier memorizes its training data and has 100 percent training accuracy, but its leave-one-out accuracy is only 0.75. As k grows the classifier smooths over the noise: the training accuracy falls, while the leave-one-out accuracy rises to 0.833 at k = 5. With only 12 points these estimates are rough, so treat the numbers as an illustration of the pattern, not a precise ranking.

Code example

Python
xs = list(range(1, 13))
labels = [0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1]


def predict(train_x, train_y, x, k):
    order = sorted(range(len(train_x)), key=lambda i: abs(train_x[i] - x))
    votes = [train_y[i] for i in order[:k]]
    return 1 if sum(votes) * 2 > len(votes) else 0


def train_accuracy(k):
    return sum(predict(xs, labels, x, k) == y for x, y in zip(xs, labels)) / len(xs)


def leave_one_out_accuracy(k):
    correct = 0
    for i in range(len(xs)):
        rest_x = xs[:i] + xs[i + 1:]
        rest_y = labels[:i] + labels[i + 1:]
        correct += predict(rest_x, rest_y, xs[i], k) == labels[i]
    return correct / len(xs)


for k in (1, 3, 5):
    print(k, round(train_accuracy(k), 3), round(leave_one_out_accuracy(k), 3))
Output
1 1.0 0.75
3 0.917 0.75
5 0.833 0.833

Follow the code

Open the walkthrough to inspect the example one step at a time.

Show guided walkthrough
Guided walkthroughReady
1xs = list(range(1, 13))
2labels = [0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1]
3
4
5def predict(train_x, train_y, x, k):
6 order = sorted(range(len(train_x)), key=lambda i: abs(train_x[i] - x))
7 votes = [train_y[i] for i in order[:k]]
8 return 1 if sum(votes) * 2 > len(votes) else 0
9
10
11def train_accuracy(k):
12 return sum(predict(xs, labels, x, k) == y for x, y in zip(xs, labels)) / len(xs)
13
14
15def leave_one_out_accuracy(k):
16 correct = 0
17 for i in range(len(xs)):
18 rest_x = xs[:i] + xs[i + 1:]
19 rest_y = labels[:i] + labels[i + 1:]
20 correct += predict(rest_x, rest_y, xs[i], k) == labels[i]
21 return correct / len(xs)
22
23
24for k in (1, 3, 5):
25 print(k, round(train_accuracy(k), 3), round(leave_one_out_accuracy(k), 3))

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
Accuracy measured on the same points the model stores. With k = 1 each point is its own nearest neighbour.—
—
Leave-one-out: remove one example from the stored data, then predict it. The model never sees the point it is tested on.—
—
Compare candidate values of k using both measures.—
—
k = 1 scores 1.0 on training data but only 0.75 when validated: it memorizes the noise. k = 5 has lower training accuracy but the better validation score, 0.833.—
1 1.0 0.75
3 0.917 0.75
5 0.833 0.833

Behind the scenes

How Python processes this code

Python runs this source through its interpreter. It creates objects for values and resolves names as each statement runs.

Cost and common mistakes

Leave-one-out requires n evaluations of a model with prediction cost O(n log n) here, so O(n² log n) in total. k-fold with k = 5 or 10 is usually cheaper.

  • Training accuracy is not a measure of quality. A perfect training score, especially with k = 1, often signals memorization.
  • Do not use the validation score to pick hyperparameters and then report it as the final performance. Keep a separate test set.
  • Fit scalers and other preprocessing inside each training fold. Fitting them on all the data leaks information into validation.

Try it yourself

Why is training accuracy always 1.0 for 1-nearest-neighbour when the stored points are unique?

Reveal answer and reasoning

Every training point's nearest neighbour is itself, at distance zero, so it always predicts its own label.

Further reading