Library Tutorial

The Journey to Building the Best Classifier: A Regular Story

It was an overcast Wednesday morning when Alex decided to tackle a classification challenge. Armed with the sklearn datasets and a toolbox of custom Python functions from the classifierpromax package, Alex set out to build the best possible model to classify data efficiently and effectively.

import pandas as pd
import numpy as np
from classifierpromax.ClassifierTrainer import ClassifierTrainer
from classifierpromax.ClassifierOptimizer import ClassifierOptimizer
from classifierpromax.FeatureSelector import FeatureSelector
from classifierpromax.ResultHandler import ResultHandler
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn import datasets

Loading the Dataset

Alex started by loading a familiar dataset from sklearn. For this project, the Iris dataset served as the training grounds—a well-known dataset for classifying different types of iris flowers.

# Load the dataset
iris = datasets.load_iris()
X, y = pd.DataFrame(iris.data, columns=iris.feature_names), pd.Series(iris.target)

# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=421)
pd.concat([X_train, y_train], axis=1)
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) 0
90 5.5 2.6 4.4 1.2 1
29 4.7 3.2 1.6 0.2 0
65 6.7 3.1 4.4 1.4 1
82 5.8 2.7 3.9 1.2 1
135 7.7 3.0 6.1 2.3 2
... ... ... ... ... ...
30 4.8 3.1 1.6 0.2 0
79 5.7 2.6 3.5 1.0 1
19 5.1 3.8 1.5 0.3 0
53 5.5 2.3 4.0 1.3 1
87 6.3 2.3 4.4 1.3 1

75 rows × 5 columns

With the data ready, Alex built a preprocessing pipeline to ensure the input features were standardized.

# Preprocessing pipeline
preprocessor = make_pipeline(StandardScaler())

Training the Initial Models

The first step in Alex’s workflow was training baseline models using the ClassifierTrainer function. This function automatically trained several models, including Logistic Regression, SVC, and Random Forest, and evaluated them using cross-validation metrics.

trained_models, initial_scores = ClassifierTrainer(preprocessor, X_train, y_train, seed=421)
ResultHandler(initial_scores)
dummy logreg svc random_forest
mean std mean std mean std mean std
fit_time 0.002787 2.413336e-04 0.006328 0.000476 0.004342 0.000713 0.096318 0.000433
score_time 0.007225 3.381562e-04 0.010718 0.000100 0.009658 0.002247 0.012941 0.000052
test_accuracy 0.333333 0.000000e+00 0.933333 0.081650 0.946667 0.055777 0.933333 0.047140
train_accuracy 0.346667 7.453560e-03 0.953333 0.013944 0.966667 0.020412 1.000000 0.000000
test_precision 0.111111 1.551584e-17 0.939333 0.079588 0.951111 0.054772 0.948063 0.033914
train_precision 0.120222 5.093266e-03 0.953569 0.013877 0.967508 0.019637 1.000000 0.000000
test_recall 0.333333 0.000000e+00 0.933333 0.081650 0.946667 0.055777 0.933333 0.047140
train_recall 0.346667 7.453560e-03 0.953333 0.013944 0.966667 0.020412 1.000000 0.000000
test_f1 0.166667 0.000000e+00 0.932929 0.082477 0.946397 0.055859 0.932088 0.049128
train_f1 0.178519 6.625387e-03 0.953296 0.013956 0.966621 0.020468 1.000000 0.000000

As Alex examined the results, they noticed that Random Forest showed promise, but its performance could likely improve with feature selection and hyperparameter tuning.

Selecting Features for Simplicity

With a sense that the dataset contained redundant features, Alex decided to use the FeatureSelector function to refine the models further. They opted for Recursive Feature Elimination (RFE) to identify and select the most informative features.

feature_selected_models = FeatureSelector(
    preprocessor, trained_models, X_train, y_train, method='RFE', n_features_to_select=2
)

Now equipped with models that only used the top two features, Alex could already see improvements in simplicity and interpretability. But there was one final step to maximize the models’ potential.

Optimizing Model Hyperparameters

The models were performing well, but Alex wanted them to be great. Enter the ClassifierOptimizer function. This function searched for the best hyperparameters using RandomizedSearchCV, tuning each model to its optimal configuration.

optimized_models, optimized_scores = ClassifierOptimizer(
    feature_selected_models, X_train, y_train, scoring='f1', n_iter=10, random_state=421
)
ResultHandler(optimized_scores)
Training logreg...
Best parameters for logreg: {'logisticregression__C': np.float64(1.1904538755038707), 'logisticregression__class_weight': 'balanced'}
Training svc...
Best parameters for svc: {'svc__C': np.float64(0.4621351227119489), 'svc__class_weight': None}

Training random_forest...
Best parameters for random_forest: {'randomforestclassifier__max_depth': 6, 'randomforestclassifier__n_estimators': 25}
logreg svc random_forest
mean std mean std mean std
fit_time 0.016752 0.000316 0.007162 0.000186 0.343617 0.012400
score_time 0.011257 0.000176 0.007564 0.000063 0.009763 0.000181
test_accuracy 0.946667 0.055777 0.946667 0.055777 0.933333 0.047140
train_accuracy 0.950000 0.011785 0.953333 0.007454 1.000000 0.000000
test_precision 0.959175 0.040828 0.951556 0.054714 0.948063 0.033914
train_precision 0.950475 0.011793 0.953927 0.007123 1.000000 0.000000
test_recall 0.946667 0.055777 0.946667 0.055777 0.933333 0.047140
train_recall 0.950000 0.011785 0.953333 0.007454 1.000000 0.000000
test_f1 0.945556 0.057790 0.946667 0.055779 0.932088 0.049128
train_f1 0.950006 0.011785 0.953321 0.007461 1.000000 0.000000

After a few minutes of computation, the function returned the optimized models and their performance metrics. Alex was thrilled to see that the Random Forest classifier now had the best F1 score among all models.

Summarizing the Results

To present the results clearly, Alex used the ResultHandler function to compile the performance metrics and hyperparameters into a tidy DataFrame.

results_df = ResultHandler(initial_scores, optimized_scores)
results_df
dummy_baseline logreg_baseline logreg_optimized random_forest_baseline random_forest_optimized svc_baseline svc_optimized
fit_time 0.002787 0.006328 0.016752 0.096318 0.343617 0.004342 0.007162
score_time 0.007225 0.010718 0.011257 0.012941 0.009763 0.009658 0.007564
test_accuracy 0.333333 0.933333 0.946667 0.933333 0.933333 0.946667 0.946667
train_accuracy 0.346667 0.953333 0.950000 1.000000 1.000000 0.966667 0.953333
test_precision 0.111111 0.939333 0.959175 0.948063 0.948063 0.951111 0.951556
train_precision 0.120222 0.953569 0.950475 1.000000 1.000000 0.967508 0.953927
test_recall 0.333333 0.933333 0.946667 0.933333 0.933333 0.946667 0.946667
train_recall 0.346667 0.953333 0.950000 1.000000 1.000000 0.966667 0.953333
test_f1 0.166667 0.932929 0.945556 0.932088 0.932088 0.946397 0.946667
train_f1 0.178519 0.953296 0.950006 1.000000 1.000000 0.966621 0.953321

Testing the Final Model

The optimized Random Forest model was deployed for testing on the test dataset. Alex measured its performance and confirmed that it generalized well beyond the training data.

# Testing the final Random Forest model
final_model = optimized_models['random_forest']
y_pred = final_model.predict(X_test)

print(classification_report(y_test, y_pred))
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        25
           1       0.96      0.96      0.96        26
           2       0.96      0.96      0.96        24

    accuracy                           0.97        75
   macro avg       0.97      0.97      0.97        75
weighted avg       0.97      0.97      0.97        75

Reflection

By combining tools like ClassifierTrainer, FeatureSelector, ClassifierOptimizer, and ResultHandler, Alex had created an efficient, repeatable, and transparent machine learning pipeline. It was a productive day of problem-solving, leaving Alex eager to tackle their next project.