Train-Test Splits and Their Limitations
The training set is the material the model is built from.
Running a Complete Evaluation First
This article starts by running a complete evaluation and then works backward through it. The program below loads the iris dataset, creates a logistic regression model, and asks scikit-learn to evaluate that model with cross-validation. Read the code once, then look at what it prints before reading the explanation that follows.
- X holds the features (the data), one row per example
- y holds the labels (the target), the known answer for each row
LogisticRegression()is the model that will be evaluatedcross_val_scorereceives the model, the data, and the target, and returns an array
Before you move on, notice the three objects in the call: the model, the data, and the labels. Those three inputs are all that cross_val_score needs to run the evaluation. The next question is what it hands back.
What do you think happens?
Before reading on, make a guess: will the printed result be a single accuracy number, or something with more than one value? If more than one, how many?
Reveal answer
A list with one value per fold, three values by default
By default cross_val_score performs three-fold cross-validation and returns three accuracy values, one for each fold. The printed result is an array of per-fold results, not one overall score.
Reading the Returned Array
The general form of the call takes the model to evaluate as its first positional argument, the data (the features) as its second, and the target (the ground-truth labels) as its third. A fourth argument, cv, is optional and controls how many folds are used. When you do not pass cv, the default is three 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. Each value in the returned array is the result of one held-out fold. The array is therefore a list of per-fold results rather than a single overall score.
Read the diagram as a rule rather than a picture. Each fold is held out once, the model is trained on the remaining folds each time, and the output array holds one accuracy value per held-out fold, in fold order. A single number is not the result of the whole procedure. It is the list of per-fold results.
The length of the returned array equals the number of folds. Check the length first whenever the output looks different from what you expected.
Changing the Number of Folds
The cv parameter changes the number of folds. Keep the same model and data, and pass cv=5 instead of relying on the default. The rule from the previous section still applies: the number of folds sets the length of the returned array.
- cv=5 sets the number of folds to five
len(scores)counts the values in the returned array
What do you think happens?
Using the rule that the array length matches the number of folds, how many values do you expect len(scores) to print when cv=5?
Reveal answer
5
The cv parameter sets the number of folds, and the length of the returned array matches that number. Five folds give five per-fold results.
Predicting the effect of changing cv from 3 to 5
A call uses cross_val_score with the default cv, and then the same call is changed to cv=5. What changes in the output?
- 1Identify the default
With no cv argument, the default is three-fold cross-validation, which returns three values.
- 2Apply the new cv value
With cv=5 the data is split into five folds, so each fold is held out once across five rounds.
- 3Count the returned values
Each held-out fold produces one value, so the array now has five entries instead of three.
- 4Note what stays the same
The model and the data are unchanged. Only the fold structure, and therefore the number of per-fold results, has changed.
The array grows from three values to five, one per held-out fold.
Tracing Each Round of the Loop
Cross-validation is a repeated train-and-evaluate loop. The same steps run once for every fold, and the loop's output is collected in order. Following one round from start to finish makes the numbers in the returned array easier to interpret.
The loop has no hidden side effects that change the answer. It trains on the training folds, evaluates on the held-out fold, keeps the result, and moves on. When the loop finishes, the collected results are what cross_val_score returns.
Tracing an Unexpected Output
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.
- 1Check the inputs: confirm that the model, the data, and the target are the ones you intended.
- 2Check cv: confirm the number of folds you think was used, including the default if cv was omitted.
- 3Check the length: count the values in the array. A length mismatch usually points to the cv argument.
- 4Check the values: if the length is correct but the numbers differ, suspect the data or the model, since the same fold structure was used.
Why Judging a Model on Its Own Data Fails
Suppose you have a dataset and you 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 the examples it has already seen. That is not the same as measuring how well it will handle new cases.
A train-test split divides a dataset into a training set and a test set. It is the standard way to keep those two questions apart: how well the model fits known examples, and how well it works on new ones. Keep this question in mind as you read: if the model is supposed to work on new data, which part of the dataset should be used to judge it, and which part should be used to build it?
Roles of the Training Set and the Test Set
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 contains data that is new to the model, which is exactly what makes it useful for judging generalization.
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. The training set is used to build the model, and the test set is used to evaluate it.
Mistakes That Break the Split
Common mistake Judging the model only on the data it was built from
Why it failsThis measures how well the model fits examples it has already seen, not how it will handle new cases.
Do this insteadHold out a test set that the model never sees while it is being built, and judge the model on that.
Common mistake Using the test set to build the model
Why it failsOnce the test set feeds model building, it is no longer data the model has not seen, and the evaluation loses its meaning.
Do this insteadLet only the training set feed model building. Keep the test set aside until the model has been built.
Common mistake Assuming the array holds one overall score rather than a list of per-fold results
Common mistake Changing cv and expecting the same number of values to come back
| Aspect | Correct split | Broken split |
|---|---|---|
| Training set | Used only to build the model | Also includes data later used for testing |
| Test set | New to the model, used only for evaluation | Already seen during model building |
| Evaluation meaning | Shows how the model may handle new data | Mostly measures fit to known examples |
| Data flow | Test set reaches evaluation only | Test set feeds back into model building |
A correct split keeps the test set separate from model building. A broken split lets test data influence the model.
Practice Predicting Fold Counts
A colleague calls cross_val_score with cv=4 on a dataset and prints the returned array. Without running the code, predict how many values appear, and then state which part of the output you would check first if the count surprised you.
Hints
- Recall that the array length matches the number of folds.
- If the count surprises you, check the cv argument before checking the numbers.
Key Takeaways
cross_val_scoretakes the model, the data, and the target as its main inputs and returns an array of per-fold results, not one overall score.- By default it performs three-fold cross-validation, and the cv parameter sets the number of folds, which sets the length of the returned array.
- A train-test split separates the data used to build a model (the training set) from the data used to judge how well it generalizes (the test set).
- The test set must stay separate from model building; using it to build the model, or judging only on training data, breaks the purpose of the split.
- When an output looks wrong, trace the chain in order: inputs, cv, array length, then values, to find where the divergence began.