Concepts / Introduction to Model Evaluation with scikit-learn

Introduction to Model Evaluation with scikit-learn

The first is the model to evaluate, the second is the data (the features), and the third is the target (the ground-truth labels).

  • Programming
Interactive lab

Try it: K-Fold Cross-Validation and Model Selection

How hold-out and k-fold cross-validation choose among candidate models: each fold takes a turn as the validation set, the fold errors are averaged, the lowest average picks the model, and only then is the untouched test set used once — while tuning on the test set would report an optimistic number.

How it works

  1. Set 10 test points aside; they take no part in any choice.
  2. Optionally shuffle the 20 development points (seeded), then cut them into k folds (hold-out: one chunk validates, the rest trains).
  3. For every round, train each candidate polynomial on the other folds and measure its validation MSE on the held-out fold.
  4. Average each candidate's fold errors (with repeated shuffling: average the p run scores; p × k fits per candidate).
  5. Select the candidate with the lowest average, retrain it on all development points, and evaluate it once on the test set.
  6. For contrast, show which candidate the test set itself would have picked — that figure is leaked and optimistic.

Default run (12 steps): 20 development points (sorted by x as they arrived) and 10 test points set aside. Candidates: polynomial degree 0, 1, 2, 3, 4, 5, 6. Plan: 5-fold cross-validation, shuffled with seed 1 → 5 fits per candidate (p × k = 1 × 5), 35 in total. … 5-fold cross-validation picks degree 3 (score 0.0768); its honest test MSE is 0.0889. Picking by test error would report degree 4 at 0.0868 — a leaked, optimistic figure.

Simplified: One fixed toy dataset (20 development + 10 test points from sin(2πx) with noise) and polynomial least squares of degree 0–6 as the candidate models. Shuffling uses a seeded mulberry32 Fisher–Yates; folds are contiguous chunks like scikit-learn's KFold. Hold-out uses the first of k chunks as the validation set.

Educational simulation

Loading the simulation…

Running a Complete Evaluation

This article starts by running a complete evaluation before explaining any of its parts. The program loads the iris dataset, creates a logistic regression model, and asks scikit-learn to evaluate that model with cross-validation. Read the program once, then look at the line it prints. The rest of the article explains how that line is produced and what each number in it means.

python
Output (expected)
An array of accuracy values, one per fold. This article does not show exact numbers; the length of the array is the part to focus on.
Notes on the code
  • load_iris(return_X_y=True) returns the features as X and the labels as y
  • LogisticRegression() creates the model that will be evaluated
  • cross_val_score is called with no cv argument, so the default number of folds is used
  • print(scores) shows the returned array

What do you think happens?

When cross_val_score is called without a cv argument, how many values will the printed array contain?

  • One, one overall score

  • Three, one per fold

  • Five, the default fold count

  • 150, one per row of iris

Reveal answer
Answer:

Three, one per fold

The default is three-fold cross-validation, and the function returns one accuracy value for each fold. The length of the array therefore matches the number of folds, which is three by default.

The Three Inputs to the Call

Before moving on, notice the three objects in the call: the model, the data, and the labels. The general form of the call takes these as its first three positional arguments. The first is the model to evaluate, the second is the data (the features), and the third is the target (the ground-truth labels). Those three inputs are all that cross_val_score needs to run the evaluation. The next question is what it hands back.

python
Notes on the code
  • model: the scikit-learn estimator to evaluate
  • X: the features, one row per sample
  • y: the target labels, one per row of X
  • cv: optional, sets how many folds are used
Definition

cross_val_score evaluates a model with cross-validation. Its main parameters are the model, the data, and the target. The cv argument is optional and controls the number of folds.

Splitting Data into Folds

Cross-validation evaluates a model by splitting the data into folds and then training and testing on different subsets. In K-fold cross-validation, the data is split into k subsets. The model is evaluated once for each fold, so the function produces one score per fold. The default for cross_val_score is three-fold cross-validation, which means the data is divided into three subsets.

Each fold holds out one subset as test data and trains on the remaining subsets. The held-out subset changes from fold to fold, so every subset is held out exactly once. The diagram below shows the three-fold case. Read it as a rotation: the same three blocks appear in every fold, but a different block is the test block each time.

Block 1held out in fold 1Block 2held out in fold 2Block 3held out in fold 3
Which rows go into the training set and which into the test set for each fold, and how does the held-out block shift from one fold to the next?

In the fold where a block is held out, the model is scored on that block. In every other fold, the same block is part of the training data. Because each block is the test block exactly once, the three scores together cover the whole dataset rather than resting on one arbitrary split.

What Happens Inside One Fold Cycle

Within a single fold, the work follows a fixed sequence: the model is fit on the training rows, it predicts on the held-out rows, and the predictions are scored as accuracy. Then the process moves to the next fold and repeats. The diagram traces one pass through this cycle.

Fit on training rowsPredict held-out rowsScore accuracyNext foldthenthenrepeat
What happens in sequence during one fold (fit on training rows, predict on held-out rows, score), and how does the loop repeat across all folds?

The loop runs once per fold. With three folds, the sequence runs three times, and each run produces one accuracy value. The model is retrained from the training rows in each fold, so the three results are independent evaluations of the same model on different held-out blocks.

Reading the Returned Array

The value cross_val_score returns is an array, and each element is the result of one held-out fold. A single number is not the result of the whole procedure. The array is a list of per-fold results, and its positions follow fold order: the first element comes from the first fold, the second from the second fold, and so on.

Fold 1scores[0]Fold 2scores[1]Fold 3scores[2]
Which fold produced each number in the returned array, and in what order do the array positions correspond to folds?

Changing the Number of Folds with cv

The cv parameter sets the number of folds, and the length of the returned array matches that number. The next example keeps the same model and data and passes cv=5. Predict the length of the array from the rule above before you run it: the array should contain five values, one for each of the five folds.

python
Output (expected)
An array of five accuracy values, followed by the number 5.
Notes on the code
  • The model and data are unchanged from the first program
  • Only the cv argument differs
  • len(scores) counts the values in the returned array
CallFolds usedLength of returned array
cross_val_score(model, X, y)three (default)3
cross_val_score(model, X, y, cv=5)five5

Changing cv changes the number of folds and the number of returned values together

Tracing Where an Output Diverges

When an output does not match your expectation, the useful question is where the divergence began. Work through the chain in order: the inputs you passed, the value of cv, the number of values in the array, and then the values themselves. Checking in this order lets you find the failing step instead of rereading the whole program.

  1. 1
    Check the inputs: confirm that the model, the features, and the labels are the ones you intended
  2. 2
    Check cv: confirm whether you passed a value, and what it is
  3. 3
    Check the length: count the values in the array and compare it with the number of folds
  4. 4
    Check the values: only if the length is right, examine the individual accuracy numbers

A mismatch in length usually points to the cv argument. A mismatch in the values, with the correct length, points to the data or the model, because the same fold structure was used. This split tells you which part of the call to inspect first.

Mistakes That Hide the Fold Count

  • Common mistake

    Expecting a single overall score from the call

    What it looks like
    scores = cross_val_score(model, X, y) followed by reading scores as one accuracy number
    Why it fails

    The function returns one accuracy value for each fold, so the result is an array of per-fold results.

    Do this instead

    Treat scores as a list with one entry per fold, and compute any summary from it deliberately.

  • Common mistake

    Assuming the output length is always three

    What it looks like

    Passing cv=5 and expecting three printed values

    Why it fails

    Three is only the default. The cv parameter sets the number of folds, and the array length matches it.

    Do this instead

    Predict the length from the cv value, then check it with len(scores).

  • Common mistake

    Rereading the whole program when output looks wrong

    What it looks like

    Scanning every line of the code after seeing an unexpected printed array

    Why it fails

    The divergence usually starts at a specific step, and the array length narrows it quickly.

    Do this instead

    Follow the ordered chain: inputs, cv, length, then values.

Practice: Predicting the Output

EASY

Without running the code, predict how many values the following call returns, and explain your reasoning in terms of folds: scores = cross_val_score(model, X, y, cv=10). Then say which fold produced the first value in the array.

Hints
  • The cv parameter sets the number of folds.
  • The length of the returned array matches the number of folds.
  • The array positions follow fold order, so the first position comes from the first fold.
Worked example

Locating a length mismatch

A learner expects five values from cross_val_score(model, X, y, cv=5), but the printed array has three values. Where should the investigation start?

  1. 1
    Check the inputs

    The model, features, and labels are the same as intended, so the inputs are not the first suspect.

  2. 2
    Check cv

    The call has cv=5 in it, so the requested fold count is five. The mismatch is not explained by a missing argument.

  3. 3
    Check the length

    Three values against an expected five is a length mismatch, which the source identifies as pointing to the cv argument.

  4. 4
    Locate the call

    Look at the exact call that produced the array. A different cv value in the code that actually ran, such as a leftover default or an edited copy, would explain three values.

Result

The divergence began at the cv argument, because the length of the array is the first thing to check. The values themselves do not need to be examined until the length matches.

Key Takeaways

  • cross_val_score takes a model, data, and target, and it returns an array with one accuracy value per fold.
  • By default it uses three-fold cross-validation, so the default output has three values; the cv parameter changes the fold count and the array length together.
  • Each fold holds out one subset as test data and trains on the rest, and the array positions follow fold order.
  • When output looks wrong, check the inputs, then cv, then the array length, and only then the individual values.

Related concepts

More concepts starting with I