2010-05-27 21:50:20 +08:00
|
|
|
"""
|
|
|
|
|
===============================
|
|
|
|
|
Plot classification probability
|
|
|
|
|
===============================
|
2010-06-16 17:35:24 +08:00
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
This example illustrates the use of
|
|
|
|
|
:class:`sklearn.inspection.DecisionBoundaryDisplay` to plot the predicted class
|
|
|
|
|
probabilities of various classifiers in a 2D feature space, mostly for didactic
|
|
|
|
|
purposes.
|
2010-06-16 17:35:24 +08:00
|
|
|
|
2026-03-30 16:28:34 +08:00
|
|
|
The first three columns show the predicted probability for varying values of
|
2025-03-07 17:46:05 +08:00
|
|
|
the two features. Round markers represent the test data that was predicted to
|
|
|
|
|
belong to that class.
|
2021-10-22 21:33:22 +08:00
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
In the last column, all three classes are represented on each plot; the class
|
|
|
|
|
with the highest predicted probability at each point is plotted. The round
|
|
|
|
|
markers show the test data and are colored by their true label.
|
2010-05-27 21:50:20 +08:00
|
|
|
"""
|
2011-06-05 00:15:44 +08:00
|
|
|
|
2024-06-18 01:57:02 +08:00
|
|
|
# Authors: The scikit-learn developers
|
2024-06-13 21:51:09 +08:00
|
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
2010-04-23 01:58:11 +08:00
|
|
|
|
2026-03-30 16:28:34 +08:00
|
|
|
# %%
|
2025-03-07 17:46:05 +08:00
|
|
|
import matplotlib as mpl
|
2014-05-15 04:31:03 +08:00
|
|
|
import matplotlib.pyplot as plt
|
2010-04-23 01:58:11 +08:00
|
|
|
import numpy as np
|
2025-03-07 17:46:05 +08:00
|
|
|
import pandas as pd
|
2023-10-11 16:29:57 +08:00
|
|
|
from matplotlib import cm
|
2010-04-23 01:58:11 +08:00
|
|
|
|
2018-09-25 01:22:40 +08:00
|
|
|
from sklearn import datasets
|
2025-03-07 17:46:05 +08:00
|
|
|
from sklearn.ensemble import HistGradientBoostingClassifier
|
2015-08-02 17:28:02 +08:00
|
|
|
from sklearn.gaussian_process import GaussianProcessClassifier
|
|
|
|
|
from sklearn.gaussian_process.kernels import RBF
|
2023-10-11 16:29:57 +08:00
|
|
|
from sklearn.inspection import DecisionBoundaryDisplay
|
2025-03-07 17:46:05 +08:00
|
|
|
from sklearn.kernel_approximation import Nystroem
|
2011-09-02 17:00:02 +08:00
|
|
|
from sklearn.linear_model import LogisticRegression
|
2025-03-07 17:46:05 +08:00
|
|
|
from sklearn.metrics import accuracy_score, log_loss, roc_auc_score
|
|
|
|
|
from sklearn.model_selection import train_test_split
|
|
|
|
|
from sklearn.pipeline import make_pipeline
|
|
|
|
|
from sklearn.preprocessing import (
|
|
|
|
|
KBinsDiscretizer,
|
|
|
|
|
PolynomialFeatures,
|
|
|
|
|
SplineTransformer,
|
|
|
|
|
)
|
2010-04-23 01:58:11 +08:00
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
# %%
|
|
|
|
|
# Data: 2D projection of the iris dataset
|
|
|
|
|
# ---------------------------------------
|
2010-04-23 01:58:11 +08:00
|
|
|
iris = datasets.load_iris()
|
2011-12-20 01:16:51 +08:00
|
|
|
X = iris.data[:, 0:2] # we only take the first two features for visualization
|
2010-04-23 01:58:11 +08:00
|
|
|
y = iris.target
|
|
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
X_train, X_test, y_train, y_test = train_test_split(
|
|
|
|
|
X, y, test_size=0.5, random_state=42
|
|
|
|
|
)
|
|
|
|
|
|
2010-04-23 01:58:11 +08:00
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
# %%
|
|
|
|
|
# Probabilistic classifiers
|
|
|
|
|
# -------------------------
|
|
|
|
|
#
|
|
|
|
|
# We will plot the decision boundaries of several classifiers that have a
|
|
|
|
|
# `predict_proba` method. This will allow us to visualize the uncertainty of
|
|
|
|
|
# the classifier in regions where it is not certain of its prediction.
|
2010-04-23 01:58:11 +08:00
|
|
|
|
2018-09-25 01:22:40 +08:00
|
|
|
classifiers = {
|
2026-01-29 18:48:33 +08:00
|
|
|
"Logistic regression\n(C=0.1)": LogisticRegression(C=0.1),
|
|
|
|
|
"Logistic regression\n(C=100)": LogisticRegression(C=100),
|
2025-03-07 17:46:05 +08:00
|
|
|
"Gaussian Process": GaussianProcessClassifier(kernel=1.0 * RBF([1.0, 1.0])),
|
|
|
|
|
"Logistic regression\n(RBF features)": make_pipeline(
|
|
|
|
|
Nystroem(kernel="rbf", gamma=5e-1, n_components=50, random_state=1),
|
|
|
|
|
LogisticRegression(C=10),
|
2018-09-25 01:22:40 +08:00
|
|
|
),
|
2026-01-29 18:48:33 +08:00
|
|
|
"Gradient Boosting": HistGradientBoostingClassifier(random_state=42),
|
2025-03-07 17:46:05 +08:00
|
|
|
"Logistic regression\n(binned features)": make_pipeline(
|
|
|
|
|
KBinsDiscretizer(n_bins=5, quantile_method="averaged_inverted_cdf"),
|
|
|
|
|
PolynomialFeatures(interaction_only=True),
|
|
|
|
|
LogisticRegression(C=10),
|
|
|
|
|
),
|
|
|
|
|
"Logistic regression\n(spline features)": make_pipeline(
|
|
|
|
|
SplineTransformer(n_knots=5),
|
|
|
|
|
PolynomialFeatures(interaction_only=True),
|
|
|
|
|
LogisticRegression(C=10),
|
2018-09-25 01:22:40 +08:00
|
|
|
),
|
|
|
|
|
}
|
2010-06-16 17:35:24 +08:00
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
# %%
|
|
|
|
|
# Plotting the decision boundaries
|
|
|
|
|
# --------------------------------
|
|
|
|
|
#
|
|
|
|
|
# For each classifier, we plot the per-class probabilities on the first three
|
|
|
|
|
# columns and the probabilities of the most likely class on the last column.
|
|
|
|
|
|
2010-06-16 17:35:24 +08:00
|
|
|
n_classifiers = len(classifiers)
|
2025-03-07 17:46:05 +08:00
|
|
|
scatter_kwargs = {
|
|
|
|
|
"s": 25,
|
|
|
|
|
"marker": "o",
|
|
|
|
|
"linewidths": 0.8,
|
|
|
|
|
"edgecolor": "k",
|
|
|
|
|
"alpha": 0.7,
|
|
|
|
|
}
|
|
|
|
|
y_unique = np.unique(y)
|
2010-06-16 17:35:24 +08:00
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
# Ensure legend not cut off
|
|
|
|
|
mpl.rcParams["savefig.bbox"] = "tight"
|
2023-10-11 16:29:57 +08:00
|
|
|
fig, axes = plt.subplots(
|
|
|
|
|
nrows=n_classifiers,
|
2025-03-07 17:46:05 +08:00
|
|
|
ncols=len(iris.target_names) + 1,
|
|
|
|
|
figsize=(4 * 2.2, n_classifiers * 2.2),
|
2023-10-11 16:29:57 +08:00
|
|
|
)
|
2025-03-07 17:46:05 +08:00
|
|
|
evaluation_results = []
|
|
|
|
|
levels = 100
|
2023-10-11 16:29:57 +08:00
|
|
|
for classifier_idx, (name, classifier) in enumerate(classifiers.items()):
|
2025-03-07 17:46:05 +08:00
|
|
|
y_pred = classifier.fit(X_train, y_train).predict(X_test)
|
|
|
|
|
y_pred_proba = classifier.predict_proba(X_test)
|
|
|
|
|
accuracy_test = accuracy_score(y_test, y_pred)
|
|
|
|
|
roc_auc_test = roc_auc_score(y_test, y_pred_proba, multi_class="ovr")
|
|
|
|
|
log_loss_test = log_loss(y_test, y_pred_proba)
|
|
|
|
|
evaluation_results.append(
|
|
|
|
|
{
|
|
|
|
|
"name": name.replace("\n", " "),
|
|
|
|
|
"accuracy": accuracy_test,
|
|
|
|
|
"roc_auc": roc_auc_test,
|
|
|
|
|
"log_loss": log_loss_test,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
for label in y_unique:
|
2023-10-11 16:29:57 +08:00
|
|
|
# plot the probability estimate provided by the classifier
|
|
|
|
|
disp = DecisionBoundaryDisplay.from_estimator(
|
|
|
|
|
classifier,
|
2025-03-07 17:46:05 +08:00
|
|
|
X_train,
|
2023-10-11 16:29:57 +08:00
|
|
|
response_method="predict_proba",
|
|
|
|
|
class_of_interest=label,
|
|
|
|
|
ax=axes[classifier_idx, label],
|
|
|
|
|
vmin=0,
|
|
|
|
|
vmax=1,
|
2025-03-07 17:46:05 +08:00
|
|
|
cmap="Blues",
|
|
|
|
|
levels=levels,
|
2023-10-11 16:29:57 +08:00
|
|
|
)
|
2026-01-29 18:48:33 +08:00
|
|
|
axes[classifier_idx, label].set_title(f"Class {iris.target_names[label]}")
|
2023-10-11 16:29:57 +08:00
|
|
|
# plot data predicted to belong to given class
|
|
|
|
|
mask_y_pred = y_pred == label
|
|
|
|
|
axes[classifier_idx, label].scatter(
|
2025-03-07 17:46:05 +08:00
|
|
|
X_test[mask_y_pred, 0], X_test[mask_y_pred, 1], c="w", **scatter_kwargs
|
2014-05-15 10:35:13 +08:00
|
|
|
)
|
2025-03-07 17:46:05 +08:00
|
|
|
|
2023-10-11 16:29:57 +08:00
|
|
|
axes[classifier_idx, label].set(xticks=(), yticks=())
|
2025-03-07 17:46:05 +08:00
|
|
|
# add column that shows all classes by plotting class with max 'predict_proba'
|
|
|
|
|
max_class_disp = DecisionBoundaryDisplay.from_estimator(
|
|
|
|
|
classifier,
|
|
|
|
|
X_train,
|
|
|
|
|
response_method="predict_proba",
|
|
|
|
|
class_of_interest=None,
|
|
|
|
|
ax=axes[classifier_idx, len(y_unique)],
|
|
|
|
|
vmin=0,
|
|
|
|
|
vmax=1,
|
|
|
|
|
levels=levels,
|
|
|
|
|
)
|
|
|
|
|
for label in y_unique:
|
|
|
|
|
mask_label = y_test == label
|
2026-01-29 18:48:33 +08:00
|
|
|
max_col = len(y_unique)
|
|
|
|
|
axes[classifier_idx, max_col].scatter(
|
2025-03-07 17:46:05 +08:00
|
|
|
X_test[mask_label, 0],
|
|
|
|
|
X_test[mask_label, 1],
|
2026-06-10 12:19:57 +08:00
|
|
|
c=max_class_disp.target_colors_[[label], :],
|
2025-03-07 17:46:05 +08:00
|
|
|
**scatter_kwargs,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
axes[classifier_idx, 3].set(xticks=(), yticks=())
|
|
|
|
|
axes[classifier_idx, 3].set_title("Max class")
|
2023-10-11 16:29:57 +08:00
|
|
|
axes[classifier_idx, 0].set_ylabel(name)
|
2010-06-16 17:35:24 +08:00
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
# colorbar for single class plots
|
|
|
|
|
ax_single = fig.add_axes([0.15, 0.01, 0.5, 0.02])
|
2014-05-15 04:31:03 +08:00
|
|
|
plt.title("Probability")
|
2023-10-11 16:29:57 +08:00
|
|
|
_ = plt.colorbar(
|
2025-03-07 17:46:05 +08:00
|
|
|
cm.ScalarMappable(norm=None, cmap=disp.surface_.cmap),
|
|
|
|
|
cax=ax_single,
|
|
|
|
|
orientation="horizontal",
|
2023-10-11 16:29:57 +08:00
|
|
|
)
|
2010-04-23 01:58:11 +08:00
|
|
|
|
2025-03-07 17:46:05 +08:00
|
|
|
# colorbars for max probability class column
|
|
|
|
|
max_class_cmaps = [s.cmap for s in max_class_disp.surface_]
|
|
|
|
|
|
|
|
|
|
for label in y_unique:
|
|
|
|
|
ax_max = fig.add_axes([0.73, (0.06 - (label * 0.04)), 0.16, 0.015])
|
|
|
|
|
plt.title(f"Probability class {label}", fontsize=10)
|
|
|
|
|
_ = plt.colorbar(
|
|
|
|
|
cm.ScalarMappable(norm=None, cmap=max_class_cmaps[label]),
|
|
|
|
|
cax=ax_max,
|
|
|
|
|
orientation="horizontal",
|
|
|
|
|
)
|
|
|
|
|
if label in (0, 1):
|
|
|
|
|
ax_max.set(xticks=(), yticks=())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# %%
|
|
|
|
|
# Quantitative evaluation
|
|
|
|
|
# -----------------------
|
|
|
|
|
pd.DataFrame(evaluation_results).round(2)
|
|
|
|
|
|
|
|
|
|
# %%
|
|
|
|
|
# Analysis
|
|
|
|
|
# --------
|
|
|
|
|
#
|
|
|
|
|
# The two logistic regression models fitted on the original features display
|
|
|
|
|
# linear decision boundaries as expected. For this particular problem, this
|
|
|
|
|
# does not seem to be detrimental as both models are competitive with the
|
|
|
|
|
# non-linear models when quantitatively evaluated on the test set. We can
|
|
|
|
|
# observe that the amount of regularization influences the model confidence:
|
|
|
|
|
# lighter colors for the strongly regularized model with a lower value of `C`.
|
|
|
|
|
# Regularization also impacts the orientation of decision boundary leading to
|
|
|
|
|
# slightly different ROC AUC.
|
|
|
|
|
#
|
|
|
|
|
# The log-loss on the other hand evaluates both sharpness and calibration and
|
|
|
|
|
# as a result strongly favors the weakly regularized logistic-regression model,
|
|
|
|
|
# probably because the strongly regularized model is under-confident. This
|
|
|
|
|
# could be confirmed by looking at the calibration curve using
|
|
|
|
|
# :class:`sklearn.calibration.CalibrationDisplay`.
|
|
|
|
|
#
|
|
|
|
|
# The logistic regression model with RBF features has a "blobby" decision
|
|
|
|
|
# boundary that is non-linear in the original feature space and is quite
|
|
|
|
|
# similar to the decision boundary of the Gaussian process classifier which is
|
|
|
|
|
# configured to use an RBF kernel.
|
|
|
|
|
#
|
|
|
|
|
# The logistic regression model fitted on binned features with interactions has
|
|
|
|
|
# a decision boundary that is non-linear in the original feature space and is
|
|
|
|
|
# quite similar to the decision boundary of the gradient boosting classifier:
|
|
|
|
|
# both models favor axis-aligned decisions when extrapolating to unseen region
|
|
|
|
|
# of the feature space.
|
|
|
|
|
#
|
|
|
|
|
# The logistic regression model fitted on spline features with interactions
|
|
|
|
|
# has a similar axis-aligned extrapolation behavior but a smoother decision
|
|
|
|
|
# boundary in the dense region of the feature space than the two previous
|
|
|
|
|
# models.
|
|
|
|
|
#
|
|
|
|
|
# To conclude, it is interesting to observe that feature engineering for
|
|
|
|
|
# logistic regression models can be used to mimic some of the inductive bias of
|
|
|
|
|
# various non-linear models. However, for this particular dataset, using the
|
|
|
|
|
# raw features is enough to train a competitive model. This would not
|
|
|
|
|
# necessarily the case for other datasets.
|