Welcome to the final lesson of our first module on the foundations of supervised learning. Over the past few lessons, we've assembled a comprehensive toolkit of evaluation metrics—from MSE for regression to accuracy, F1-score, and AUC for classification. These metrics tell us how good a model is. Today, we address an equally important question: how should we measure these metrics to get a reliable estimate of how our model will perform on new, unseen data?
So far, we have implicitly assumed a single split of our data into training and testing sets. However, the performance score you get can be sensitive to the specific data points that happen to land in your test set. To build your deep understanding of ML, we need a more statistically robust method. Our goal today is to implement k-fold cross-validation to estimate model generalization performance. We will explore why this is necessary, how it works mechanically, and how to implement it efficiently.
The Problem with a Single Split: The Optimism of Training Error
At its core, supervised learning aims to build a model that generalizes well—that is, it makes accurate predictions on new data it has never seen before. The error on such new data is called the generalization error.
A naive approach would be to train a model and evaluate it on the same training data. The resulting error, called the training error, is a poor and systematically optimistic estimate of the generalization error. The model has already "seen" these data points, so it's not a fair test of its predictive power. This is like giving students an exam with the exact same questions they saw in the study guide; their score wouldn't be a reliable measure of their true understanding.
Given your comfort with mathematical concepts, we can briefly formalize this. The training process minimizes the error on the training set, so it naturally finds patterns, including random noise. This leads to an "optimism" in the training error. For a linear model, the relationship can be expressed as:
Where is the number of data points, is the number of model parameters, and is the variance of the irreducible noise in the data. The key insight is that the expected generalization error is always higher than the training error. This "optimism" term, , grows with model complexity () and data noise (), and shrinks as we get more data ().
To get a more honest assessment, we use a hold-out test set. While this is a huge improvement, the performance score still depends on which particular data points ended up in the test split. A different random split might yield a different score. How can we get a more stable and reliable estimate?
[PDF] Model Selection 1 Choosing Models 2 Generalization and Optimism ...
These lecture notes from Carnegie Mellon University provide a concise mathematical argument for why training error is an optimistic estimate of a model's true performance.
Please read the first three pages, focusing on the section Why is the Training Error Smaller than the Generalization Error? (pp. 1-2). This will formalize the concept of "optimism" and explain why minimizing training error alone leads to overfitting.
K-Fold Cross-Validation: The Solution
K-fold cross-validation (CV) provides an elegant solution. Instead of one split, it creates k splits and gives us an average performance score, making our estimate of generalization error much more robust.
The procedure is as follows:
- The dataset is randomly shuffled and partitioned into k equal-sized subsets, or "folds".
- The model is trained k times. In each iteration, one distinct fold is held out as the test set, and the remaining k-1 folds are used for training.
- A performance score (e.g., accuracy, MSE, AUC) is calculated for each of the k iterations.
- The final cross-validation score is the average of these k scores. We can also compute the standard deviation to understand the variability of the performance.
The StatQuest video below gives a fantastic visual intuition for this process.
Machine Learning Fundamentals: Cross Validation
This video, "Machine Learning Fundamentals: Cross Validation," clearly explains the motivation for and the mechanics of k-fold cross-validation.
Watch the video from the beginning until 05:43. Pay close attention to how the data is repeatedly split and how the results from each fold are aggregated to produce a more reliable performance estimate.
The image below illustrates a 5-fold cross-validation. In each iteration, a different fifth of the data is used for testing, and the rest is used for training.

Implementing Cross-Validation in Python
Now let's translate this theory into practice using scikit-learn. Since your goal is to master the mechanics, we will first build a cross-validation loop manually before using scikit-learn's convenient high-level function.
The "Manual" Loop with StratifiedKFold
For classification tasks, it's often better to use stratified k-fold CV. This ensures that each fold has approximately the same percentage of samples from each target class as the complete set. This is crucial for imbalanced datasets, where a random split might accidentally create a fold with no examples of a rare class.
The codebasics video demonstrates this process clearly, first showing how to use KFold to get the indices for each split, and then building a loop with StratifiedKFold to train and evaluate models.
Machine Learning Tutorial Python 12 - K Fold Cross Validation
This practical tutorial from the 'codebasics' channel walks through the implementation of k-fold CV, from the basic principle to a full coding example.
First, watch the conceptual explanation of the k-fold process from 03:25 to 04:15. Then, see how the KFold object generates training and test indices from 08:21 to 10:19. Finally, and most importantly, watch the manual implementation loop from 12:03 to 18:27, where he uses StratifiedKFold to evaluate several models. This shows you exactly what happens under the hood.
The manual loop gives you full control and makes the process transparent. You create a StratifiedKFold object, use its .split() method to iterate through train/test indices, and run your training/evaluation logic inside the loop.
The Simple Way: cross_val_score
While the manual loop is great for understanding, scikit-learn provides a helper function, cross_val_score, that automates the entire process. This is what you will typically use in practice. It takes an estimator (your model), the full dataset ( and ), and the number of folds (), and returns an array of scores from each fold.
3.1. Cross-validation: evaluating estimator performance
The official scikit-learn documentation provides a definitive guide to cross-validation concepts and their implementation.
First, read the introduction to the k-fold CV procedure to solidify the concept. Next, focus on the section Computing cross-validated metrics. This introduces the cross_val_score function and shows how to use it with a simple code example. Notice the scoring parameter, which allows you to use any of the metrics we've learned (e.g., 'f1_macro', 'roc_auc'). Finally, read the section on Stratified K-fold to understand its importance for classification. The cross_val_score function automatically uses StratifiedKFold for classification models.
As you can see, a single call to cross_val_score encapsulates the entire loop we built manually, making your code cleaner and less error-prone.
CV in the Larger Workflow
It's critical to understand where cross-validation fits into a complete machine learning project. We use it to get a reliable performance estimate for a given model or to compare different models/hyperparameters. However, this entire process should be done on the training data only.
A standard workflow is:
- Split your entire dataset into a training set and a final, held-out test set (e.g., 80% train, 20% test). You do not touch the test set until the very end.
- Use k-fold cross-validation on the training set to find the best model architecture and hyperparameters.
- Once you have selected your final model, train it one last time on the entire training set.
- Evaluate this final model's performance once on the held-out test set. This score is your final report on the model's generalization ability.
This separation prevents any information from the test set from "leaking" into your model selection process, ensuring an unbiased final evaluation.

Conclusion
In this lesson, we established a robust method for evaluating model performance that moves beyond a single, potentially misleading train-test split. By understanding and implementing k-fold cross-validation, you now have a cornerstone technique for any supervised learning project.
Key Takeaways:
- A model's performance on its training data is an overly optimistic estimate of its ability to generalize to new data.
- K-fold cross-validation provides a more stable and reliable estimate of generalization performance by training and testing a model on k different subsets of the data and averaging the results.
- For classification, StratifiedKFold is preferred as it preserves the class distribution in each fold.
- Scikit-learn's
cross_val_scorefunction is a convenient, high-level tool for performing cross-validation. - Cross-validation is used for model selection and hyperparameter tuning on a training set, while a final, held-out test set is reserved for the ultimate evaluation.
This lesson concludes our foundational module. You are now equipped with the core concepts of supervised learning: framing problems, understanding the bias-variance tradeoff, diagnosing model fit, and robustly evaluating performance. In our next module, we will begin building our first family of models, starting with the mathematical derivation and implementation of Linear Regression.