Classification

Open In Colab

Now we’re ready to dive into Machine Learning! As you learned in Chapter 1 of the course textbook, there are two main paradigms in machine learning: supervised and unsupervised learning.

Within supervised learning, we mainly deal with two types of problems:

In this notebook, we’ll start with classification. The next notebook will focus on regression, the last notebook of the day will be on unsupervised learning.


Outline

  • Your classification first model
  • Testing your first model
  • Train/Test split
  • Repeatability
  • Using a different model
  • Exercise

Let’s get started by building your very first machine learning model! 🚀

1. Your classification first model

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# load the dataset
from sklearn.datasets import load_breast_cancer
X, y = load_breast_cancer(as_frame=True, return_X_y=True)

Creating a machine learning model with scikit-learn is as simple as this:

# create a machine learning model
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()

This line creates a Random Forest model with its default parameters. In the following weeks, step by step, you’ll learn more about how Random Forests work and what each parameter does. For now, think of it as a black box, it takes in data and makes predictions from it.

You can explore all the available parameters here: scikit-learn RandomForestClassifier documentation

For the model to make predictions, it first needs to learn from the data. This process is called fitting the model, which is why we use the .fit() method. It works as follows:

# fit the model
model.fit(X, y)
RandomForestClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.

Congrats, you’re officially a machine learning engineer… well not yet 😄

This short piece of code shows the basic steps of training a machine learning model using scikit-learn. You’ve already learned what X (the features) and y (the labels) mean in the previous notebook.

Step 1:

from sklearn.datasets import load_breast_cancer
X, y = load_breast_cancer(as_frame=True, return_X_y=True)
  • X contains the input features (tumor measurements).
  • y contains the target values (whether the tumor is malignant (0) or benign (1)).

Step 2:

from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()

Now we create a machine learning model, in this case, a Random Forest Classifier.

  • “Random Forest” is a model made up of many small decision trees that work together and vote to make a final prediction.
  • When we call RandomForestClassifier(), we create an untrained model, it doesn’t know anything yet.
  • For now, you don’t need to worry about how random forests work. What matters is that scikit-learn lets us easily use many different algorithms in the same way: create a model and fit it on data.
  • We’ll dive into how these algorithms actually work in the coming weeks.

Step 3:

model.fit(X, y)

This is where the learning happens.

  • The .fit() function trains the model by finding patterns that connect the input features (X) to the correct labels (y).
  • After this step, the model has “learned” from the data, meaning it can now make predictions on new, unseen examples.

So in summary:

  • We loaded some data (X, y).
  • We created a model (RandomForestClassifier).
  • We trained it on the data (model.fit(X, y)).

That’s the entire core idea of supervised machine learning, teaching a model to map inputs to outputs from examples.

2. Testing your first model

YOUR TURN

Now that our model is trained, we can use it to make predictions! Read the documentation and try to figure out how you can make predictions. Store the predictions in a variable called y_pred. The solution will be given below.

https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html#sklearn.ensemble.RandomForestClassifier.predict

# YOUR CODE HERE

Here we use our trained model to make predictions on the same data X we trained it on.

  • model.predict(X) asks the model: “Given these input features (X), what class do you think each example belongs to?”
  • The result is a list (or array) of predicted labels, one prediction for each row in X.

We store those predictions in a new variable called y_pred, which stands for “predicted y”.

# SOLUTION
y_pred = model.predict(X)
y_pred
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0,
       0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0,
       1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0,
       1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1,
       1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0,
       0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1,
       1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0,
       0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0,
       1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1,
       1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0,
       0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
       0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0,
       1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1,
       1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1,
       1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
       1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1,
       1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1,
       1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1])

We don’t write y = model.predict(X) because:

  • y already contains the true labels, the correct answers from the dataset.
  • y_pred contains the model’s guesses, what the model thinks the labels should be.

By keeping them separate, we can compare y_pred to y to see how well the model performed. Let’s check the prediction for the first patient:

print("Prediction:", y_pred[0])
print("Label:", y[0])
Prediction: 0
Label: 0

Our model was correct! But we would like to check all of our predictions:

y_pred == y
0      True
1      True
2      True
3      True
4      True
       ... 
564    True
565    True
566    True
567    True
568    True
Name: target, Length: 569, dtype: bool

it looks promising, but we cannot see all so let’s count how many of the predictions were correct:

(y_pred == y).value_counts()
target
True    569
Name: count, dtype: int64

All of the model’s predictions are correct! Sounds great, right? Well… not so fast. In machine learning, perfect accuracy usually means something’s fishy 🐟. It is possible with simple/small datasets but very rare in real life. Be suspicious, not proud!

Right now, our model was trained and tested on the same data. That means the model has already seen all the answers, it’s like me asking the exact same questions in the Python exam that I solved during the Q&A sessions! 🙂 Sure, you’d get a perfect score, but that doesn’t prove you truly understand the topic.

When we test a model on data it has already seen, we’re only checking how well it memorized the training examples, not how well it can handle new, unseen data.

To fix this, the best thing to do is to get completely new data. If we don’t have new data, the next best thing is to split our existing dataset into two parts, one for training, and one to treat as “unseen” test data.

This way, we can see whether the model truly learned to generalize, not just to memorize.

3. Train/Test split

Now let’s split our dataset into two halves. We’ll train the model on the first half and see how it performs on the second half.

# midpoint
X.shape[0]/2
284.5
# split the dataset: first half for training, second half for testing
X_train = X[:285]
X_test = X[285:]

y_train = y[:285]
y_test = y[285:]
# always check shapes and verify it makes sense
X_train.shape, y_train.shape, X_test.shape, y_test.shape
((285, 30), (285,), (284, 30), (284,))
# Always create a new model when you want to train from scratch.
# In scikit-learn, calling .fit() retrains the model completely (it doesn't reuse previous training).
# Still, it's good practice to create a fresh model instance to avoid confusion.
model = RandomForestClassifier()
# instead of model.fit(X, y)
model.fit(X_train, y_train)
RandomForestClassifier()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
# instead of model.predict(X)
y_pred = model.predict(X_test)
y_pred
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0,
       0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
       0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0,
       0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1,
       1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1,
       0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1,
       1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
       0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1,
       1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1,
       1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1])
# instead of using y
(y_pred == y_test).value_counts()
target
True     267
False     17
Name: count, dtype: int64

This makes much more sense! Our model is doing a great job, but not a perfect one, which is what we expect.

Now, let’s calculate the accuracy. Accuracy is a basic classification metric that simply measures how many of the predictions are correct out of all predictions.

There are many other metrics for classification, but we’ll explore those later.

num_of_correct_predictions = np.sum(y_pred == y_test) # True counts as 1, so this adds up all correct predictions
num_of_predictions = len(y_pred)
accuracy = num_of_correct_predictions / num_of_predictions
print(accuracy)
0.9401408450704225

As you can imagine, scikit-learn doesn’t just provide models, it also includes many handy tools for common machine learning tasks. So instead of writing our own code, we can simply call a built-in function to calculate accuracy:

# we should get the same result as our implementation of accuracy above
from sklearn.metrics import accuracy_score
accuracy = accuracy_score(y_test, y_pred)
print(accuracy)
0.9401408450704225

By now, I trust you don’t need me to spoon-feed you links anymore 😄 You can probably find the documentation for accuracy_score on your own.

Instead of linking every single function, here’s the full list of available metrics for you to explore: https://scikit-learn.org/stable/api/sklearn.metrics.html

Similarly, instead of manually splitting the dataset, scikit-learn provides a handy function called train_test_split that does it for us. The idea is simple: we keep most of the data for training (so the model can learn as much as possible) and reserve a smaller portion for testing (to evaluate how well it performs on unseen data). A common split is 80% for training and 20% for testing. The more data we give to the model during training, the better it can learn patterns, but if the test set is too small, our accuracy estimate becomes less reliable.

YOUR TURN

Try splitting X and y, into X_train, X_test, y_train, y_test using train_test_split by following the documentation. Set test_size=0.2. The solution will be given below.

# YOUR CODE HERE

check the shapes to verify you got 80% of the data for training and 20% for testing.

# SOLUTION
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# SOLUTION
X_train.shape, y_train.shape, X_test.shape, y_test.shape
((455, 30), (455,), (114, 30), (114,))
# repeat train/test with the new split
model = RandomForestClassifier()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(accuracy)
0.956140350877193

4. Repeatability

A good machine learning experiment should produce the same results every time you run it. Let’s check whether that’s true for our code. Run the cell below several times and see if you get the same accuracy each time.

# same as above
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
model = RandomForestClassifier()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(accuracy)
0.956140350877193

The accuracy changes almost every time you run the cell. That’s because there are two sources of randomness at play. First, train_test_split randomly shuffles the data before splitting it into training and test sets, so the model is trained and tested on different data each time. Second, the RandomForestClassifier itself uses randomness when building trees (for example, by choosing random subsets of features and samples). Both of these are normal and even useful for model diversity, but they make our results non-repeatable. To fix this, we can set a random_state (for example, random_state=42) in both functions. This acts like a “seed” that controls the randomness, ensuring that every run uses the same split and the same random decisions inside the model, so you’ll always get the same accuracy.

# run this cell multiple time to verify you get the same accuracy very run
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # added random state
model = RandomForestClassifier(random_state=42) # added random state
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(accuracy)
0.9649122807017544

if you change the random state, you might get a different accuracy, but reruns should still give the same result.

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=100) # random state = 100
model = RandomForestClassifier(random_state=100) # random state = 100
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(accuracy)
0.956140350877193

Never, ever change the random_state just to get a higher accuracy. Think about why that’s a bad idea, you wouldn’t be improving the model, only cherry-picking a lucky split. The goal of machine learning is to build models that generalize well, not ones that look good by chance.

Please take a moment to read about cherry-picking. It’s crucial to understand this concept, both to avoid doing it yourself and to recognize when others might be using it to make their results look better than they really are. Sadly, this practice is quite common.

5. Using a different model

scikit-learn contains many models:

https://scikit-learn.org/stable/supervised_learning.html

you can pick another one (for classification you need to use a classifier) and use it as follows:

# another model, a decision tree
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(random_state=0)
# the rest is the same
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print(accuracy)
0.9122807017543859

Note that some models require normalization of the input data which we will cover later. RandomForest and DecisionTree can be used without normalization.

6. Exercise

YOUR TURN

Train and test a classification model based on the vehicle dataset to predict the class of each vehicle from its silhouette properties. Here is the test accuracy I obtained, you should get something similar 0.7588.

from sklearn.datasets import fetch_openml
X, y = fetch_openml(name="vehicle", version=1, as_frame=True, return_X_y=True)

Note that the vehicle dataset contains 4 classes, and the target values are stored as strings rather than numbers. Scikit-learn can handle string targets directly, so there’s no need to convert them into numeric labels. We’ll learn about encoding later on.

np.unique(y)
array(['bus', 'opel', 'saab', 'van'], dtype=object)
# YOUR CODE HERE