ML Atlas

Machine learning from zero · Lesson 1.2 · 3 of 33 lessons ready · 10 min

Data, features, labels and splits

In short

A dataset is a table of examples, features and labels. Why the test set must stay hidden, and why its size decides how much a score can be trusted.

Warm-up Two questions from earlier lessons. A minute, and it never blocks the lesson.
From lesson 1.1: What is machine learning? A model is right about 60% of the time on this tumour data. Why is that not impressive?

Show the answer

Correct answer: Because "always benign" is already right in nearly two thirds of cases

Most tumours in the dataset are benign, so a model must beat the trivial majority answer before it is useful.

From lesson 1.1: What is machine learning? You adjust a rule until it is right on every training example. What does that tell you about the next patient?

Show the answer

Correct answer: Very little — only data the rule never saw can show how it handles new cases

Training accuracy measures fit to known cases; only held-out data estimates performance on new ones.

If a teacher gives a class the exact questions that will appear in the final exam, and everyone scores full marks, what has the exam measured? Memory of those questions, nothing more. Machine learning faces the same problem every single time, and the way it solves it is surprisingly simple: keep some of the questions locked in a drawer.

Predict, then check Same model, 20 different random splits (30% of the biopsies in the test set, that is 171 of them). How much does the test score change between splits?
How sure are you?

Interactive lab

This interactive lab runs in a browser with JavaScript enabled. The text around it describes what the lab shows.

Show the answer

Correct answer: Noticeably: the worst and the best split are several percentage points apart

With 30% in the test set the score ranges from 90.1% to 96.5% (6.4 points), although the model and the data are the same; only which biopsies landed in the test set changes. A single test score is not the whole truth.

Try this

  • Set the test-share slider to a small value and then move the seed slider through several positions. Watch how much the test accuracy changes even though the model and the data stay the same.
  • Now set the test-share slider to a large value and repeat the seed experiment. Does the test accuracy jump around more or less than before?
  • Keep the seed fixed and move the test-share slider from one end to the other. Notice how the number of training rows shrinks as the test set grows.
  • Find the seed that gives the best test accuracy — and then ask yourself whether you would be allowed to report that number.

Now that you've tried it, let's see what was actually going on.

The idea in plain words

Almost every dataset in this course is a table. Each row is one example: one tumour, one patient, one passenger, one wine. Each column is a feature: a measured property such as a radius, a blood pressure, an age or an alcohol content. One special column holds the label — the answer we want the model to predict, such as "malignant" or "survived". A model is a rule that maps the features of a row to its label.

Before any learning happens, the rows are shuffled and divided. The bigger part, the training set, is what the model learns from. The smaller part, the test set, is locked away and used once, at the end, to estimate how the model will do on rows it has never seen. It plays the role of the questions in the drawer.

Think of a chef developing a new soup. She tastes it constantly while cooking — that is training. But to know whether customers will like it, she serves it to a few people who have never tasted it before and have no reason to be polite. Their verdict is the test score. If she only asked herself, she would always think the soup was perfect.

Why it works

The test set is useful only because the model never saw it. As long as the training and test rows come from the same source and were split at random, the test rows behave like future cases, and the test accuracy is a fair estimate of future accuracy.

But it is only an estimate, and here is the part beginners rarely hear. The test score depends on which rows happened to land in the test set. Shuffle the data with a different random seed and you get a different test set, and a different score — with exactly the same model and the same data. With a small test set the score can jump around noticeably, because a handful of unusual rows has a big effect on the percentage. Statistics gives a rough size for this wobble. For an accuracy p measured on n test rows, the standard error is about

SE ≈ √(p · (1 − p) / n)

so four times as many test rows only halves the wobble. This is why a single test score is never the whole truth.

There is also a trade-off. Every row you put into the test set is a row the model cannot learn from. A huge test set gives a reliable grade for a model that learned from too little; a tiny test set gives a well-trained model an unreliable grade. Common choices put somewhere between a fifth and a third of the rows in the test set, and the next lessons will show cleverer ways around the dilemma.

Common mistake

The most tempting error is trying many splits and keeping the one with the nicest test score. The test set has then been used to make a decision, and the reported score is optimistic. A quieter version of the same error is called data leakage: information from the test rows sneaks into training, for example when you compute averages for scaling or filling missing values on the whole table before splitting. The rule is strict: split first, then do everything else using the training rows only.

With a clean split in hand we can finally build a real model. In the next lesson we fit the simplest one there is — a straight line through a cloud of points.

Quiz

Three questions to check yourself. Pick an answer, or open the answer under a question. Keyboard: arrow keys choose, Enter or Space confirms.

1. Why must the test set stay untouched until the end?
Show the answer

Correct answer: Because it only estimates performance on new data if no decision was based on it

Once the test set has influenced a choice, its score is no longer an honest stand-in for future data.

2. You change only the random seed of the split and the test accuracy moves. What does this show?
Show the answer

Correct answer: A test score depends on which rows happened to land in the test set

Different seeds give different test rows, so the score varies by chance, more so with a small test set.

3. What is the cost of making the test set very large?
Show the answer

Correct answer: Fewer rows are left for training the model, because the rest is set aside

Every row moved to the test set is a row the model cannot learn from, so a bigger test set means a weaker-trained model.

Key takeaways

  • A dataset is a table: rows are examples, columns are features, one column is the label.
  • Split before anything else; the model learns from the training set and is graded once on the test set.
  • A test score is an estimate: a different random split gives a different number, especially with few test rows.
  • Picking the split with the best score, or preparing data on the whole table, leaks the test set into training.

Going further: formula, code, practice

The math

A test accuracy is an average of n right-or-wrong outcomes. If the true accuracy is p, the score you measure varies from split to split with standard error SE ≈ √(p · (1 − p) / n).

  • n is the number of test rows, not the size of the whole table.
  • SE shrinks with √n: four times as many test rows halve it.
  • It is largest at p = 0.5 and shrinks as p approaches 0 or 1.
  • About two thirds of the scores fall within one SE of p, and about 95% within two.
  • This SE covers the choice of test rows only. It says nothing about a shift between the data you have and the data you will meet later.
In code (Python)

The lab's setup: logistic regression on the 10 "mean" features, 30% of the biopsies in the test set (171 rows), 20 different splits:

import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X = X.filter(like="mean ")  # the 10 "mean" features, as in the lab
accs = []
for seed in range(20):  # same model, 20 different splits
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, stratify=y, random_state=seed)
    model = make_pipeline(StandardScaler(), LogisticRegression()).fit(Xtr, ytr)
    accs.append(model.score(Xte, yte))
n, p = len(yte), float(np.mean(accs))
print(f"test rows: {n}")
print(f"test accuracy: min {min(accs):.1%}, max {max(accs):.1%}")
print(f"standard error: {np.sqrt(p * (1 - p) / n):.1%} (n={n}), {np.sqrt(p * (1 - p) / (4 * n)):.1%} (n={4 * n})")

Output

test rows: 171
test accuracy: min 90.6%, max 97.1%
standard error: 1.8% (n=171), 0.9% (n=684)

scikit-learn shuffles differently from the lab, so the minimum and maximum differ slightly from the lab's 90.1% and 96.5%. The 684 is hypothetical: it needs more rows than the 569 in the data, so it only shows the formula. The message is the same: the score moves by several points with the model and data unchanged, and quadrupling the test rows halves the standard error.

For practitioners
  • Split first, then fit scalers and imputers on the training rows only. A Pipeline does this for you and keeps the test rows out of every fitted step.
  • Fix random_state so a result can be repeated, but choose it once, before looking at scores. Trying seeds until one looks good is peeking at the test set.
  • For classification pass stratify=y, so a rare class does not vanish from a small test set.
  • Rows that belong together must stay together. Several rows from one patient should all go to the same side, and data with a time order should be split by time, not at random. Otherwise the test set leaks.
  • Report a score together with its test size, and for small data repeat the split or use cross-validation (lesson 8) to show the spread.

Go deeper in the Atlas

Encyclopedia entries on the ideas in this lesson: