Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Open In Colab Open In Kaggle

This notebook covers material from Chapters 3 of Géron, and builds on the original notebooks made available on Github.

Notebook Setup

The following cells will set up the libraries we need to run our classification tasks, as well as load the data we need to run a classifier on the MNIST handwritten digit database.

We need to load the MNIST handwritten digit dataset from OpenML. We won’t be loading it as a Pandas dataframe, but will instead use the Dictionary / ndrray representation.

You can read more about the MNIST handwritten digit dataset here.

Originally, Géron uses the entire MNIST dataset, which can take too long when training several models. As a result, we will generate and use a smaller set of data, taking care to balance it. \

Double click the cell below to inspect it - otherwise, you can run it and the balanced input data will be stored in x_bal, while the corresponding target data will be stored in bal_y.

0 had 6903 instances in the dataset. Digit 0 has been rebalanced.
1 had 7877 instances in the dataset. Digit 1 has been rebalanced.
2 had 6990 instances in the dataset. Digit 2 has been rebalanced.
3 had 7141 instances in the dataset. Digit 3 has been rebalanced.
4 had 6824 instances in the dataset. Digit 4 has been rebalanced.
5 had 6313 instances in the dataset. Digit 5 has been rebalanced.
6 had 6876 instances in the dataset. Digit 6 has been rebalanced.
7 had 7293 instances in the dataset. 
Digit 7 has been rebalanced.
8 had 6825 instances in the dataset. Digit 8 has been rebalanced.
9 had 6958 instances in the dataset. Digit 9 has been rebalanced.
The image should be of a(n) 7
<Figure size 640x480 with 1 Axes>

Exercise 1 - An MNIST Classifier with 97% accuracy

MNIST_pics.png

Caption: So many digits! 🔢 Can you classify them all?

For this exercise, we will look for the model with the best performance using Scikit’s Grid Search, which performs a “round robin” search on a set of possible hyperparameters to find the model with the best performance.

Search for Scikit’s K-neighbors Classifier to find the documentation page. You will find a list of parameters that users can use when making the classifier.

Here is the list of tasks for this exercise.

  1. Define a list of dictionaries with the set of hyperparameters that are to be tested:

Hyper-ParameterValues to Test
Weightsuniformdistance
# of Neighbors3456
  1. Instantiate the KNeighborsClassifier Model

  2. Fit the models to your training data

  3. Extract the optimal model parameters

  4. Evaluate your best model



Let’s begin!

Q1) Set up the parameter dictionary of your GridSearchCV

Q2) Make an instance of the KNeighborsClassifier model*

Q3) Use GridSearch’s fit method to train your models.*

We will define a grid search using the knn_clf model and param_grid. We will set the number of folds for the cross-validation to 6, and use the most verbose setting to get the best idea of how well each cv run performs.

Now that the models have been trained, let’s figure out which one is the best one. \

🔍

Now it’s time to test your model! Let’s import the accuracy score metric from scikit and see how our model fares.

Q5) Use the grid_search’s to predict the targets from the test dataset.

(we’ll use this to evaluate the accuracy of your model!)

Congratulations! If you’ve done everything right, you should have a model with an accuracy of 96.9% We’re so close to 97% that we can taste it.

🍲

Let’s see if we can bridge the gap with a bit of data augmentation, working off of the smaller mnist dataset we made in the previous part of the exercise. The type of augmentation we’ll be using is shifting, in which the images in the dataset are shifted up/down/left/right by a number of pixels to make “new” datapoints.

List of Tasks:

  1. Augment the digit dataset using shifted versions of the images

  2. Train a KneighborsClassifier model on the augmented data set using the best parameters found in Exercise 1

  3. Evaluate the accuracy of the model trained with the augmented data

Let’s start by importing the shift tool from SciPy. You can find the documentation for the shift function here

And we’ll define a function that shifts an image from out dataset up & down and/or left & right.

Let’s go ahead and verify that the shift_image function works as intended... That way we won’t run into issues further down the line.

Q6) Verify that the shift function is working as intended and plot a down-shifted and left-shifted data sample.*

Now that we’ve verified that the shift function works, let’s go ahead and use it to augment our training dataset.

Q7) Augment the training dataset

If everything is working as intended, you shoudl have 27 000 samples. Thats a lot of handwritten digits.

✍ 😖

We now have an augmented dataset! Let’s train a model using the best hyperparameters we previously found with our grid search.

Q8) Load a new K Nearest Neighbors classifier using the .best_params_ information*

Hint: The documentation of the KNeighborsClassifier is at this link

Q9) Fit the new model using the augmented x and y data*

Hint: The fit method of the KNeighborsClassifier is documented at this link

Q10) Evaluate the accuracy of the model trained with the augmented data*

Hint: You should have already loaded the accuracy_score here

Congratulations for finishing the first exercise! If everything went well and you used the exact same arguments we did, you should have gotten an accuracy of 97.5% \

🥳

Exercise 2 - Tackling the Titanic dataset

noaa-e0eHtnr7eeU-unsplash.jpg

Caption: Can you predict which passengers will survive their ride on the Titanic? 🚣 🧊

In this exercise we will be attempting to predict whether or not a passenger on the titanic survived or not, based on their attributes (e.g., age, sex, passenger class, where they embarked, and so on). For this exercise, we will be relying on Pandas DataFrames.

Let’s take a moment to talk about our dataset! The dataset we’ll be using today was part of a machine learning challenge - what this means for you is that today we have training data with labels, but our test data has no test labels (i.e., we have y_train but not y_test).

Exercise Tasks:

  1. Calculate the mean of the age the corresponding standard deviation for the female and male populations in the passenger list.

  2. Set up a pipeline for the numerical attributes that implements an “median” imputer and a scaler

  3. Set up a pipeline for the categorical attributes that implements a “most frequent” imputer and a one-hot categorical encoder

  4. Prepare the training and testing datasets for the Titanic data. What’s the difference between .fit_transform() and .transform() ?

  5. Train a random forest classifier with 100 estimators

  6. Make a prediction using the trained random forest classifier. How good is the accuracy?

  7. Compare this to a 10-fold cross validation mean.

  8. Train a support-vector clustering algorithm and compare its 10-fold cross validation mean to that of the random forest classifier.

Let’s begin by fetching the data from the internet and loading it into memory. To do this, the code cell defines a fetch_titanic_data function that downloads the titanic dataset and a load_titanic_data function that let’s us load the “train.csv” data or “test.csv” data.

Q11) Fetch and load the titanic training and test data. Then, set the passenger ID as the index.

The data is already split into a training set and a test set. However, please remember that the test data does not contain the labels: your goal is to train the best model you can using the training data, then make your predictions on the test data.

Let’s take a peek at the training set. Use the .head() method to return the first 5 rows of the training dataset.

If you’re on colab, you can instead use Google’s interactive data table!


The dataset should include the following attributes:

  • PassengerId: a unique identifier for each passenger

  • Survived: that’s the target, 0 means the passenger did not survive, while 1 means he/she survived.

  • Pclass: passenger class.

  • Name, Sex, Age: self-explanatory

  • SibSp: how many siblings & spouses of the passenger aboard the Titanic.

  • Parch: how many children & parents of the passenger aboard the Titanic.

  • Ticket: ticket id

  • Fare: price paid (in pounds)

  • Cabin: passenger’s cabin number

  • Embarked: where the passenger embarked the Titanic. C=Cherbourg, Q=Queenstown, S=Southampton.

Using DataFrames allow us to easily calculate statistics from the data. Here we will use the .median() and .std() methods to get an picture of the passenger population.

Q12) Calculate the median and standard deviation for each listed sex category

Hint: The “pre-filled” code below uses pandas’ native median and std functions. If you prefer using Numpy, Numpy also has functions for the median and the standard deviation.

If you did everything right so far, you’ll find a median age of 27/29 with a standard deviation of 14.11/14.68 for female/male sexes.

It’s important that we be aware of the gaps in the data available. In order to do this, let’s rely on the DataFrames .info() method:

Okay, the Age, Cabin and Embarked attributes are sometimes null (less than 891 non-null), especially the Cabin (77% are null). We will ignore the Cabin for now and focus on the rest. The Age attribute has about 19% null values, so we will need to decide what to do with them. One reasonable option is to replace null values with the median age. We could be a bit smarter by predicting the age based on the other columns (for example, the median age is 37 in 1st class, 29 in 2nd class and 24 in 3rd class), but we’ll keep things simple and just use the overall median age.

(Note: this will be done in a later step, when we develop the data pipeline)

The Name and Ticket attributes may have some value, but they will be a bit tricky to convert into useful numbers that a model can consume. So for now, we will ignore them.

Let’s take a look at the numerical attributes:

  • Less than 50% of the passengers survived (About 38%, to be more precise). That’s close enough to 40%, so accuracy will be a reasonable metric to evaluate our model.

  • The mean Fare was £32.20, which does not seem so expensive, but the Bank of England estimates the value to be around £3889 in 2021.

  • The mean Age was a little under 30 years old.

  • Over half of the passengers were in 3rd Class

We will now verify that the Survived class is indeed 0 or 1.

We will also look at the types of values we have in our categorical variables, namely: Passenger Class, Sex, and Embarqued.

Reminder: The Embarked attribute tells us where the passenger embarked: C=Cherbourg, Q=Queenstown, S=Southampton.

Now let’s build our data preprocessing pipelines, starting with the pipeline for numerical attributes. In this section we will set up a Pipeline class, which will fill the missing values in the columns using the mean value (see the SimpleImputer documentation) and scale the data (see the StandardScaler documentation).

Q13) Set up the numerical attribute pipeline using a) an “imputer” that uses SimpleImputer with “median” set as the strategy and b) a “scaler” that uses a default StandardScaler

Hint: The documentation for the Pipeline class is at this link

Now we can build the pipeline for the categorical attributes. Here, we will use one-hot encoding using the OneHotEncoder preprocessor in Scikit.

Q14) Set up the categorical attribute pipeline using: a) an “imputer” that uses SimpleImputer with “most_frequent” set as the strategy and b) a “cat_encoder” that uses a OneHotEncoder with the sparse_output parameter set to False

Finally, let’s join the numerical and categorical pipelines using scikit’s ColumnTransformer:

We now have a preprocessing pipeline that takes the raw data and outputs numerical input features that we can feed to any Machine Learning model we want, and we can also generate the truth variable from the ‘Survived’ data.

Let’s generate our train and test data.


Note! We will continue using X_train, y_train, X_test, and y_test as the variable names for our training and testing datasets. However, the notebook only has one namespace. Thus, runnng the code below will overwrite the X_train/test and y_train/test variables from exercises 1 & 2.

Q15) Prepare the training and testing datasets. Think about the difference between .fit_transform() and .transform()!

Hint 1: Remember to load “Survived” into y_train!

Hint 2: Here are the documentation links for the transform and fit_transform methods of the Pipeline class.

Hint 3: Because the dataset is from a Kaggle competition, there is no “Survived” category in the test set test_data to not give away the answer.

Age has a value of 0
SibSp has a value of 0
Parch has a value of 0
Fare has a value of 0
Pclass has a value of 0
Sex has a value of 0
Embarked has a value of 0

If you have done everything as we planned out so far, you should see the following:

Age has a value of -0.5657358173332453
SibSp has a value of 0.4327933656785018
Parch has a value of -0.4736736092984604
Fare has a value of -0.5024451714361923
Pclass has a value of 0.0
Sex has a value of 0.0
Embarked has a value of 1.0

Now that we’ve finally prepared the pipeline, we can begin training algorithms! Let’s start by training a type of classifier called a RandomForestClassifier. This type of a classifier is an ensemble classifier, which employs a number of estimators that we have to define. Today, we will use 100 members in our ensemble.

Q16) Import a RandomForestClassifier from scikit and instantiate it with 100 estimators. Then, fit the model

Hint: The documentation for the RandomForestClassifier is at this link

Great, our model is trained! Let’s use it to make predictions on the test set. Let’s also check the accuracy of our model on the test dataset.

Wait a second... we don’t have labels for our test dataset!

😯

What do we do now? Well, the best we can do is to check how well our model is able to fit our training data.
Please note that we do this with the very real risk of overfitting our training data!!!

Q17) Predict a set of answers using the trained model and compare it using the accuracy_score metric. Then, print out a statement reporting the accuracy

Hint 1: Here’s the documentation for the accuracy_score metric

Hint 2: You don’t have a set of labels for the test set (i.e., you don’t have y_test), so you can’t measure the accuracy using X_test. You do, however, have another dataset with both inputs X and labels y!

Hint 3: Though you won’t be able to know if the model is over/underfitting the data if you measure the accuracy on the train set, it will give you an idea as to whether the model is able to make accurate predictions from the input data.

If you did everything like we did during development of the notebook, you should get an accuracy of 98.0%! However, this accuracy is the performance of the model to predict the data that it was trained on. If we make the analogy to a student, we gave them the list of questions and answers before the test. Are we surprised that they did well?

While our model gave us a prediction and we were able to compare how accurate our predictions were, we’re also interested in knowing how good our model really is.

It’s true that now we could just build a CSV file with the predictions based on the test set (respecting the format excepted by Kaggle), then upload it and hope for the best.

But we can do better than hope.

Why don’t we use cross-validation to have an idea of how good our model is? This means we’ll divide the data into several groups of data, and we’ll use all but one for training and test on the remaining group. We’ll do this as many times as we have groups, which will give use an idea of how well our model is able to predict data it has never seen before.

This schematic from the scikit learn documentation might be useful to help you understand...


Image Source: Scikit-Learn Cross Validation Documentation

Q18) Import the cross_val_score function and produce a set of 10-fold cross validation scores using the training data. Then, print the mean score. How does it compare with the answer to Q14?

Hint: You may consult the documentation of cross_val_score

If you did everything the way we did, you’ll get a mean accuracy of 81.4%.

We will now compare to the leaderboard for the Titanic competition on Kaggle. How does our model compare?

Don’t be discouraged by those with 100% accuracy - since you can easily find the list of victims of the Titanic, it seems likely that there was little Machine Learning involved in their performance.

Let’s move on from random forests and try an SVC, i.e., a support-vector clustering algorithm. You can find the scikit documentation here.

Q19) Train a support-vector clustering algorithm and generate a set of cross-validated scores

Hint: Don’t hesitate to recycle code from before!

If everything went well, this model should look better. Notice though that what we’ve printed so far is the mean score from the cross validation. While we do appreciate models having higher mean accuracy, consistency is also very important when making predictions! Let’s visualize the scores using a box plot (Géron thanking Nevin Yilmaz as the proponents of this idea), where the points will represent the performance on the testing fold in each run.

It’s also important to note that the boxplot() function detects outliers (called “fliers”) and does not include them within the whiskers. Specifically, if the lower quartile is Q1 and the upper quartile is Q3 , then the interquartile range IQR=Q3−Q1 (this is the box’s height), and any score lower than Q1−1.5×IQR is a flier, and so is any score greater than Q3+1.5×IQR .

But instead of just looking at the mean accuracy across the 10 cross-validation folds, let’s plot all 10 scores for each model, along with a box plot highlighting the lower and upper quartiles, and “whiskers” showing the extent of the scores (thanks to Nevin Yilmaz for suggesting this visualization). Note that the boxplot() function detects outliers (called “fliers”) and does not include them within the whiskers. Specifically, if the lower quartile is Q1Q_1 and the upper quartile is Q3Q_3, then the interquartile range IQR=Q3−Q1IQR = Q_3 - Q_1 (this is the box’s height), and any score lower than Q1−1.5×IQRQ_1 - 1.5 \times IQR is a flier, and so is any score greater than Q3+1.5×IQRQ3 + 1.5 \times IQR.

If all goes as expected, your plot should look like this:

The random forest classifier should have gotten a very high score on one of the 10 folds, but overall should have a lower mean score and a bigger spread. This suggests that the SVM classifier is more likely to generalize well.

Bonus Questions

Exercise 1 Bonus Objectives:

  1. Train and evaluate the models using precision as the metric

  2. Extract & plot instances in which the model was confused

  3. Create an imbalanced dataset and study the effect on the model

  4. Implement a rotation augmentation strategy

  5. Try addressing the imbalance in the dataset using data augmentation

A.) We trained and tested the model’s performance using accuracy as the metric. What happens when you use precision as the scoring metric instead? Note: you’ll have to redefine the grid_search scoring parameter

B.) Extract some samples that the model made a mistake on and plot them. Can you tell what the digit is?

C.) In the setup we reduced the size of the dataset while ensuring that it remained perfectly balanced (♦ ♦ ♦ ♦ ♦♦). What happens to the performance of the model when using an unbalanced dataset? Make one of the digits dominate the dataset (e.g., making over 50% of the dataset one of the digits) and try training the algorithm and test it on the balanced test dataset.

**D.)**We’ve implemented a shift strategy, but there’s a myriad of ways of augmenting your dataset! Try implementing a rotation augmentation strategy using Scipy’s ndimage.rotate

E.) We’ve implemented shifts as augmentations in the dataset. In a previous challenge we developed an unbalanced dataset - try addressing the imbalance in the dataset by augmenting the dataset!

Exercise 2 Bonus Objectives:

  1. Choose two other models and try using them to predict whether or not a passenger would survive. Use grid-search to try out several hyperparameters and determine which model worked best out of the four.

  2. Try converting numerical attributes to categorical attributes, such as age group instead of age, or those travelling alone vs with company.