Concepts / Train Test Split

Train Test Split

A train-test split divides a dataset into a training set and a test set.

  • Programming

Why Judging a Model on Its Own Data Misleads

Suppose you have a dataset and want to build a machine learning model from it. A natural question follows: how will you know whether the model is any good? If you judge the model only on the same data it was built from, you are measuring how well it fits examples it has already seen. That is not the same as measuring how well it will handle new cases. The train-test split is the standard way to keep those two questions apart.

This article follows one small Python program that classifies records from the iris dataset with the k-Nearest Neighbors algorithm. The program is short, but it contains every step a typical supervised learning workflow needs: split the data, fit a model, and evaluate it. As you read, keep one question in mind at each line: what new piece of information exists now that did not exist before? Tracking that question is how you find where a result went wrong.

What do you think happens?

The program prints a score of 0.97. What does that number actually count?

  • The fraction of training instances the model labels correctly

  • The fraction of held-out test instances the model labels correctly

  • The number of neighbors used to make each prediction

Reveal answer
Answer:

The fraction of held-out test instances the model labels correctly.

The score() method evaluates the fitted model on the held-out testing data. A score of 0.97 means about 97 percent of those test instances were labeled correctly, under this particular split and this value of n_neighbors.

Splitting the Data Before Any Model Exists

Definition

A train-test split divides a dataset into a training set and a test set. In the source's scikit-learn style, the train_test_split() function receives the data array X (input features) and the target array y (labels), and returns four objects: X_train, X_test, y_train, and y_test. The X values hold the features and the y values hold the labels, so each split keeps every feature row matched with its label.

The split happens before the model is trained. The order of events matters: the training set is the only part the classifier sees when it is fitted, and the test set is held back until evaluation. The split is therefore a deliberate decision about which data the model is allowed to learn from and which data is reserved for judging it.

The source passes random_state=0 to the split. The random_state parameter makes the random splitting reproducible: running the same code again produces the same split and therefore the same score. Without a fixed value, a different random arrangement of the data can lead to a different score, so the number you report depends on the split you happened to draw.

How k-Nearest Neighbors Votes on a Label

The k-Nearest Neighbors classifier is a classification algorithm. To predict the label of a new instance, it looks at the k training instances closest to that new instance and takes a majority vote among their labels. The label that appears most often among those neighbors becomes the prediction. The letter k is the number of neighbors considered.

New instanceFind k closesttraining instancesCollect their labelsMajority votePredicted labelcompare to training pointsk neighborscount labelsmost frequent label
What happens when a new point arrives: its k nearest training neighbors are found, their labels are counted, and the most frequent label becomes the prediction.

The number of neighbors is passed as n_neighbors when the classifier is created, as in KNeighborsClassifier(n_neighbors=n). It is a hyperparameter: a setting you choose before training rather than a value the model learns from the data. The source example uses n_neighbors=1, which means each prediction simply copies the label of the single closest training point. With a larger k, the vote involves several neighbors, so the same training data can produce different predictions for different values of k. This matters at evaluation time, because the score you get depends on the settings you chose.

Tracing the Pipeline Line by Line

The program below reconstructs the steps the source describes. Read it as a sequence of state changes. Before each line, ask what information exists now that did not exist before.

python
Output (expected)
0.97
Notes on the code
  • load_iris() supplies the data and target arrays that become X and y
  • train_test_split() returns four objects; the test portion is held back
  • n_neighbors=1 is chosen before training, as a hyperparameter
  • fit() sees only X_train and y_train
  • score() is called on X_test and y_test, which the model never saw during fitting
Worked example

Following the state through the pipeline

Which objects exist after each step, and which of them the classifier is allowed to learn from?

  1. 1
    Load the full dataset

    X holds the input features and y holds the labels for every record. Nothing has been split yet.

  2. 2
    Split

    train_test_split returns X_train, X_test, y_train, and y_test. Each feature row remains paired with its label in both halves.

  3. 3
    Create the classifier

    KNeighborsClassifier(n_neighbors=1) exists, but it has not learned anything yet. The value 1 was chosen by the programmer.

  4. 4
    Fit

    knn.fit(X_train, y_train) uses only the training portion. The test portion has not been touched.

  5. 5
    Score

    knn.score(X_test, y_test) predicts labels for the held-out features and compares them with the held-out labels. The fraction that match is the score.

Result

The printed value is 0.97. It reflects a model built only from the training portion and judged only on the test portion.

The flow from the full dataset through the split, the fit step, and the score step is shown in the pipeline diagram below.

Full dataset X, ytrain_test_splitX_train, y_trainknn.fit()Score 0.97X_test, y_testknn.score()dividetraining portionheld-back portionbuilds modelevaluates modelfitted modelaccuracy
The full dataset is divided once. The training portion feeds fit(), the test portion is held back and reaches score() only, and the score is the output.

Reading the score() Output

The score() method evaluates the model's performance on the held-out testing data. The result is an accuracy-style score, and the source formats it with print to two decimal places. A score of 0.97 means the classifier labeled about 97 percent of the held-out test instances correctly, under the conditions of this particular split and this value of n_neighbors.

Roles of the Training and Test Sets

The two parts have different jobs. The training set is the material the model is built from. The test set is held back and used afterward to check the model's performance on data it was not built from. The test set is described as containing data that is new to the model, which is exactly what makes it useful for judging generalization.

Training setUsed to fit the modelSeen during fit()Test setUsed to evaluate themodelHeld back untilscore()rolesteprolestep
The training set builds the model; the test set is held back and used only to evaluate it.

Notice that the test set reaches the evaluation step but does not feed into building the model. That separation is the whole point of the technique. If the test set were also used to build the model, it would no longer be data the model had not seen, and the evaluation would lose its meaning.

Mistakes That Break the Split

  • Common mistake

    Fitting the model on the full dataset before splitting

    What it looks like
    knn.fit(X, y) followed by knn.score(X_test, y_test)
    Why it fails

    The model has already seen the rows that are supposed to be held back, so the score measures memorization of those rows rather than generalization.

    Do this instead
    Call fit() with X_train and y_train only.
  • Common mistake

    Judging a model only on the data it was built from

    What it looks like
    Reporting knn.score(X_train, y_train) as the model's quality
    Why it fails

    This measures how well the model fits examples it already saw, which is not the same as how it will handle new cases.

    Do this instead

    Evaluate on X_test and y_test, the held-out portion.

  • Common mistake

    Choosing settings by repeatedly checking the test score

    Why it fails

    If you keep adjusting n_neighbors until the test score looks best, the test set has effectively become part of model building, and the reported score is optimistic.

    Do this instead

    Keep the test set for a final evaluation and make decisions with separate data.

  • Common mistake

    Forgetting the split settings when reporting a score

    Why it fails

    The score depends on the random arrangement and on n_neighbors, so a number without its settings cannot be reproduced or compared.

Practice: Diagnosing an Unexpected Score

MEDIUM

A colleague runs the program above but changes the classifier line to KNeighborsClassifier(n_neighbors=5) and leaves everything else the same. Before running it, describe which line changed the state of the model, and which stage of the pipeline you would inspect first if the new score looked far too high.

Hints
  • n_neighbors is a hyperparameter chosen before training.
  • Ask which objects differ between the two runs: the classifier setting, the split, or the test data.
  • Check whether fit() was still called with X_train and y_train only.
EASY

Explain, in your own words, why a score computed on the training data would be a misleading measure of how the model will handle new records.

Hints
  • Think about which rows the model was fitted on.
  • The test set is described as containing data new to the model.

Key Takeaways

  • A train-test split divides a dataset into a training set, used to build the model, and a test set, held back to evaluate it on data the model has not seen.
  • A k-Nearest Neighbors classifier predicts a label by majority vote among the k training instances closest to a new instance; n_neighbors is a hyperparameter chosen before training.
  • The pipeline is split, fit on the training portion only, then score on the held-out test portion. The score is the share of test instances labeled correctly under that specific split and setting.
  • Mistakes such as fitting on the full dataset, scoring on training data, or tuning against the test set break the purpose of the split.
  • Fixing random_state makes the split reproducible, and any reported score should be recorded with its split and parameter settings.

Related concepts

More concepts starting with T