(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisbon

How to do cross-validation in Machine Learning

João Barros 05 de July de 2026 4 min read

Cross-validation is one of the most reliable ways to evaluate a Machine Learning model. Instead of trusting a single split between training and test data, the model is evaluated several times on different portions of the data, which produces a much more stable estimate of its real performance. This technique is especially valuable when you have little data, because it uses every example both for training and for testing. Below you'll see how to do cross-validation in Machine Learning with scikit-learn, with simple, ready-to-run examples.

Prerequisites

  • Python 3 installed on your computer.
  • The scikit-learn and NumPy libraries installed (pip install scikit-learn).
  • Knowing what a model is and the difference between training and test data.
  • A code editor or Jupyter Notebook to run the examples.

Step 1: Prepare the data

For this example we use the iris dataset, which already comes with scikit-learn and needs no download. We separate the features (X) from the class we want to predict (y). The features are the columns that describe each flower — the length and width of the petals and sepals — and the class is the species we want to identify.

from sklearn.datasets import load_iris

data = load_iris()
X = data.data
y = data.target
print(X.shape)

The result (150, 4) means 150 examples and 4 features. It is a small, balanced dataset, perfect for learning.

Step 2: Choose the model

Cross-validation works with any scikit-learn model. We'll use a logistic regression as an example. The max_iter parameter ensures training has enough iterations to converge.

from sklearn.linear_model import LogisticRegression

model = LogisticRegression(max_iter=200)

Note that we haven't trained anything yet: the cross-validation function will take care of that for us. If you prefer another algorithm, just replace this line — the rest of the code stays the same.

Step 3: Apply cross-validation

The cross_val_score function does all the work. With cv=5 it splits the data into 5 parts (called folds), trains the model 5 times and, each time, uses a different part for testing and the rest for training. That is where the name k-fold comes from. By repeating the evaluation across several splits, you reduce the risk of a misleading result caused by a particularly easy or hard split.

from sklearn.model_selection import cross_val_score

scores = cross_val_score(model, X, y, cv=5)
print(scores)

The result is a list of 5 accuracy values, one per fold. Each value is between 0 and 1. Using 5 or 10 folds is the most common choice.

Step 4: Interpret the results

A single number is not enough: what matters is the average and the variation across folds. The mean is the best estimate of the model's performance; the standard deviation shows whether that performance is consistent.

print("Mean accuracy:", scores.mean().round(3))
print("Std deviation:", scores.std().round(3))

A low standard deviation means the model behaves similarly across all parts of the data — a sign of a reliable result. A high one suggests the model is unstable or that there is too little data. For example, a mean of 0.97 with a deviation of 0.02 indicates the model is right about 97% of the time, consistently.

Step 5: Get more control with StratifiedKFold

In classification problems it is best that each fold keeps the same class proportion as the full dataset. StratifiedKFold does exactly that, and with shuffle=True and random_state the results become reproducible. Without stratification, a fold could end up with too many examples of a single species, distorting the evaluation.

from sklearn.model_selection import StratifiedKFold

kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kfold)
print(scores.mean().round(3))
Tip: always use the same random_state so you can compare experiments fairly.

Check the result

If everything went well, print(scores) shows an array with 5 numbers between 0 and 1, and the mean appears as a single value (for example, around 0.97 on the iris dataset). Confirm that the mean is close to the individual fold values. If a convergence warning appears, increase max_iter; if the values vary a lot, you may need more data or a different model.

Conclusion

You can now evaluate a model robustly, without depending on the luck of a single data split. The natural next step is to combine cross-validation with GridSearchCV to choose the best hyperparameters, or use cross_val_predict to inspect predictions fold by fold. Try swapping LogisticRegression for a RandomForestClassifier: will the mean accuracy go up?