After business understanding, data understanding and data preparation, the CRISP-DM cycle reaches its center: Modeling. This chapter and the next three follow the deck 6 - Modeling, which is where the course finally trains models — and, as the deck title announces, evaluates them. The whole deck is hands-on: it uses scikit-learn on the Iris dataset, one of the classical benchmarks of machine learning.
The deck opens the phase with the two classical definitions of machine learning, and both are worth memorizing verbatim:
Machine Learning is the field of study that gives computers the ability to learn without being explicitly programmed.
Arthur Samuel, 1959
A computer program is said to learn from experience E with respect to some task T and some performance measure P, if its performance on T, as measured by P, improves with experience E.
Tom Mitchell, 1997
Samuel's sentence gives the spirit — no explicit programming — and Mitchell's gives the contract: experience E, task T, performance measure P. Every model in the next chapters is an answer to Mitchell's contract: the training data is the experience, the prediction is the task, and accuracy is the performance measure.
In the deck, this definition is followed by a single word that will become the red thread of the whole lecture: under and overfitting.
Before writing a single line of code, the deck shows two xkcd strips that frame the two failure modes of every model:
The pedagogical point is deliberately visual: a model can be wrong in two opposite directions, and both are diagnosed by looking at how the model behaves on data it has not seen during training. Chapter 9 will make the same idea quantitative with learning curves; Chapter 10 will add the cross-validation machinery to measure it properly.
Machine learning is not just the application of some algorithms to get the best accuracy. You need to understand why a model is behaving in a certain way — this is very important, especially for the exam! Do not stop at the first (good) result: questioning your algorithm and pipeline is essential. Do not rely on external code without knowing what the code is doing — if you cannot explain your code, the exam is not passed.
This is why the chapters that follow spend as much time on reading models (trees, boundaries, learning curves) as on training them. The exam in this course is oral, and the questions are on all theoretical and practical aspects: being able to narrate why a model draws a given boundary is the skill being tested.
There are many types of machine learning algorithms, and the deck classifies them in broad categories according to three criteria:
Whether they are trained with human supervision:
This course focuses on supervised learning (next section).
Whether they can learn incrementally:
Whether they compare new data to known data points, or detect patterns/models in the training set:
Keep this taxonomy in mind: Chapter 11 returns to the instance-based vs model-based contrast with k-NN on one side and the perceptron on the other.
All the hands-on work of this lecture happens in scikit-learn, the Python machine learning library:
import sklearn as sk
print(sk.__version__)
The version printed in the deck is 1.6.1.
Every scikit-learn algorithm obeys one consistent interface, the Estimator:
estimator = Estimator(param1=1, param2=2); parameters are readable as attributes, estimator.param1..fit() method that performs the training: estimator.fit(X_train, y_train).estimator.estimated_param_..predict() unseen data: y_pred = estimator.predict(X_test).The underscore convention is the single most useful detail of the API: you set the hyperparameters before fitting, the model learns the parameters during fitting, and the trailing underscore is scikit-learn's way of telling the two apart.
The deck walks the whole pipeline in five lines — choose a model, choose its hyperparameters, fit, predict, evaluate. This is the canonical scikit-learn usage pattern, and every chapter from here on is a variation of it:
Notice the discipline in the five steps: max_depth=2 is a hyperparameter (chosen before fitting), the tree structure learned by fit() is a parameter (learned during fitting), and accuracy_score(y_test, y_pred) evaluates on data the model never saw. This split — choose / fit / evaluate on unseen data — is the backbone of the whole course.
The course focuses on supervised learning tasks: the training includes the desired solutions, i.e. the labels. Within supervised learning, two tasks:
| Task | Definition | Example |
|---|---|---|
| Classification | Approximating a mapping function f from input variables X to discrete output variables y — the function predicts the class or category for a given observation. | A spam filter trained with many example emails along with their class (spam or ham). |
| Regression | Approximating a mapping function f from input variables X to a continuous output variable y — a real value, such as an integer or floating-point value. | Predicting the price of a car given a set of features (mileage, age, brand, …). |
Everything trained in the next chapters is a classifier — Iris has three classes of flowers — but the same Estimator API works for regression with minimal changes.
For a supervised learning problem we need input data along with labels, and we must split the data between test and training set. How? That is the question the deck keeps open and answers in section 13.
scikit-learn uses data in the form of an N-dimensional matrix, split into two objects:
And then the deck asks the crucial question: how do we distinguish target and feature columns? The answer is conceptual, not technical: the target is the column we want to predict — everything else, the columns we are allowed to observe, becomes the features. The choice of what to predict is a modeling decision, made before any code runs.
scikit-learn ships toy datasets that can be loaded with one call. The deck lists the load_* functions:
import sklearn.datasets as datasets
for dataset in [func for func in dir(datasets) if 'load_' in func]:
print(dataset)
load_breast_cancer
load_diabetes
load_digits
load_files
load_iris
load_linnerud
load_sample_image
load_sample_images
load_svmlight_file
load_svmlight_files
load_wine
Three of these will appear in this course: load_iris here, and load_wine in the final exercise of the lecture (Chapter 11).
The Iris dataset is the classic benchmark: 150 flowers, 4 measurements, 3 species (setosa, versicolor, virginica). The deck loads it and builds a DataFrame:
from sklearn.datasets import load_iris
import pandas as pd
iris = load_iris() # Load the iris dataset
df = pd.DataFrame(data=iris.data, columns=iris.feature_names) # DataFrame with the iris data
df['species'] = iris.target # Add the species column
# df['species'] = df['species'].map({0: 'setosa', 1: 'versicolor', 2: 'virginica'})
| sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | species | |
|---|---|---|---|---|---|
| 0 | 5.1 | 3.5 | 1.4 | 0.2 | 0 |
| 1 | 4.9 | 3.0 | 1.4 | 0.2 | 0 |
| 2 | 4.7 | 3.2 | 1.3 | 0.2 | 0 |
| 3 | 4.6 | 3.1 | 1.5 | 0.2 | 0 |
| 4 | 5.0 | 3.6 | 1.4 | 0.2 | 0 |
| … | … | … | … | … | … |
| 145 | 6.7 | 3.0 | 5.2 | 2.3 | 2 |
| 146 | 6.3 | 2.5 | 5.0 | 1.9 | 2 |
| 147 | 6.5 | 3.0 | 5.2 | 2.0 | 2 |
| 148 | 6.2 | 3.4 | 5.4 | 2.3 | 2 |
| 149 | 5.9 | 3.0 | 5.1 | 1.8 | 2 |
Then the deck profiles it — the df.info() output is itself a first data-understanding step:
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150 entries, 0 to 149
Data columns (total 5 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 sepal length (cm) 150 non-null float64
1 sepal width (cm) 150 non-null float64
2 petal length (cm) 150 non-null float64
3 petal width (cm) 150 non-null float64
4 species 150 non-null int64
dtypes: float64(4), int64(1)
memory usage: 6.0 KB
150 rows, 5 columns, no missing values, 4 numeric features and 1 integer label — a clean dataset that lets the lecture focus on modeling rather than cleaning.
Pick a species and read its mean profile. Notice that the two petal features separate the species almost perfectly, while the sepal features overlap — this is exactly what the tree in Chapter 9 will exploit.
Back to the open question: how do we split? The answer is train_test_split, with a test size and a random seed:
from sklearn.model_selection import train_test_split
X = df.drop("species", axis=1) # features matrix
y = df["species"] # target array
seed = 42 # Setup random seed. Why?
test_size = 0.2
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=seed)
print(f"X_train: {X_train.shape}")
print(f"y_train: {y_train.shape}")
print(f"X_test: {X_test.shape}")
print(f"y_test: {y_test.shape}")
X_train: (120, 4)
y_train: (120,)
X_test: (30, 4)
y_test: (30,)
The comment in the code — "Setup random seed. Why?" — is the topic of the next two sections, and the deck will answer it thoroughly. train_test_split randomly shuffles the data before the split is implemented; the random_state parameter controls that shuffle.
Move the test size and read the resulting shapes, exactly as the deck prints them.
Why does the code set a seed at all? Because train_test_split shuffles randomly, and randomness is the lack of definite pattern or predictability in information. The deck makes the distinction precise:
The deck's example: when throwing two dice, the outcome of any single roll is unpredictable, but a sum of 7 will tend to occur twice as often as 4. The simulation code rolls a die 100,000 times and two dice 100,000 times, plotting both histograms: the single die is flat, the sum of two dice is triangular, peaking at 7.
A Pseudorandom Number Generator (PRNG) is an algorithm for generating a sequence of numbers whose properties approximate the properties of sequences of random numbers. Two key properties:
print(np.random.randint(1, 7, size=10))
print(np.random.randint(1, 7, size=10))
np.random.seed(42)
print(np.random.randint(1, 7, size=10))
np.random.seed(42)
print(np.random.randint(1, 7, size=10))
np.random.seed(42)
print(np.random.randint(1, 7, size=10))
print(np.random.randint(1, 7, size=10))
[6 6 2 5 4 1 5 1 5 2]
[2 3 1 2 2 6 3 6 5 3]
[4 5 3 5 5 2 3 3 3 5]
[4 5 3 5 5 2 3 3 3 5]
[4 5 3 5 5 2 3 3 3 5]
[4 3 6 5 2 4 6 6 2 4]
Read the output carefully: the two unseeded rolls differ; the three seeded rolls are identical; and the roll after the third seeding differs again, because the generator advanced past the seeded point.
Simulate a seeded PRNG. Roll without a seed and the sequence changes; roll with seed 42 twice and the sequence is identical — exactly the reproducibility the deck demonstrates.
Back to train_test_split(..., random_state=seed): the parameter controls the randomness of the shuffle, and that is essential for three reasons:
random_state, you can ensure that others can replicate your results exactly.And the famous question: why 42? The deck answers with the classic reference: The Hitchhiker's Guide to the Galaxy — 42 is "the answer to the ultimate question of life, the universe, and everything". In code, any integer would work; 42 is the community's joke, and a widely shared convention that makes examples comparable.
train_test_split(X, y, test_size=..., random_state=...) — the seed makes the split reproducible.clf.fit(X_train, y_train).accuracy_score(y_test, clf.predict(X_test)) — never on the training set, or you are measuring memory, not learning.Samuel (1959): "Machine Learning is the field of study that gives computers the ability to learn without being explicitly programmed." Mitchell (1997): "A computer program is said to learn from experience E with respect to some task T and some performance measure P, if its performance on T, as measured by P, improves with experience E."
(1) Whether they are trained with human supervision — supervised, unsupervised, semi-supervised, reinforcement. (2) Whether they can learn incrementally — online vs batch learning. (3) Whether they compare new data to known data points, or detect patterns/models in the training data — instance-based vs model-based learning. This course focuses on supervised learning.
An Estimator is a consistent interface for a wide range of ML applications: an algorithm that learns from the data (fits the data), usable with classification, regression, and clustering. All parameters can be set when creating the estimator and are readable as attributes; every estimator exposes .fit(X_train, y_train) for training and .predict(X_test) for new data. Once fitted, all the estimated parameters become attributes ending with an underscore — e.g. estimator.estimated_param_ — marking what the model learned, as opposed to what you chose.
Classification: approximating a mapping function f from inputs X to discrete outputs y, predicting the class or category for a given observation — e.g. a spam filter trained with many example emails along with their class (spam or ham). Regression: approximating f from X to a continuous output y — a real value, such as an integer or floating-point value — e.g. predicting the price of a car given mileage, age, brand, etc.
scikit-learn uses data as an N-dimensional matrix: the feature matrix X (e.g. a Pandas DataFrame) where samples are the individual objects described by the dataset and features describe each sample quantitatively; and the target array y (e.g. a Pandas Series) holding the labels. The distinction is conceptual: the target is the column we want to predict, everything else we are allowed to observe becomes the features — the choice is a modeling decision made before any code runs.
load_iris() gives 150 samples with 4 features (sepal length, sepal width, petal length, petal width, all in cm, float64) and a target with 3 species (0, 1, 2 — setosa, versicolor, virginica, int64). Built as a DataFrame plus a species column it is 150 rows × 5 columns; df.info() shows 150 non-null values in every column, dtypes float64(4) + int64(1), memory usage 6.0 KB. A clean dataset with no missing values.
X = df.drop("species", axis=1); y = df["species"]; X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) gives X_train: (120, 4), y_train: (120,), X_test: (30, 4), y_test: (30,). The function randomly shuffles the data before the split is implemented; random_state controls the shuffle.
A Pseudorandom Number Generator is an algorithm for generating a sequence of numbers whose properties approximate the properties of random sequences. The generated sequence is not truly random — it is completely determined by the initial value, called seed. PRNGs matter in practice for their reproducibility: with the same seed, the same sequence is generated — the deck shows three seeded rolls of a die producing identical outputs.
Reproducibility: the same random_state lets others replicate your results exactly. Consistency in model evaluation: when comparing models or tuning hyperparameters, the training/test split cannot change, otherwise accuracy differences could come from the split instead of the model. Debugging and testing: a stable split lets you debug code and test configurations reliably. And 42? It is the answer to life, the universe and everything — from The Hitchhiker's Guide to the Galaxy.
Machine learning is not just the application of some algorithms to get the best accuracy. You need to understand why a model is behaving in a certain way — especially for the exam. Do not stop at the first (good) result; questioning your algorithm and pipeline is essential. Do not rely on external code without knowing what it does: if you cannot explain your code, the exam is not passed.