CSI 4506 - automne 2024
Version: sept. 30, 2024 08h54
La classification binaire est une tâche d’apprentissage supervisé dont l’objectif est de catégoriser des instances (exemples) en deux classes distinctes.
Une tâche de classification multi-classes est un type de problème d’apprentissage supervisé où l’objectif est de catégoriser des instances en trois classes distinctes ou plus.
Pour introduire le concept de frontière de décision (decision boundary), réexaminons le jeu de données Iris.
import seaborn as sns
import matplotlib.pyplot as plt
# Using string labels to ease visualization
df['species'] = df['species'].map({0: 'setosa', 1: 'versicolor', 2: 'virginica'})
# Display all pairwise scatter plots
sns.pairplot(df, hue='species', markers=["o", "s", "D"])
plt.suptitle("Pairwise Scatter Plots of Iris Features", y=1.02)
plt.show()
import numpy as np
# Transform the target variable into binary classification
# 'setosa' (class 0) vs. 'not setosa' (classes 1 and 2)
y_binary = np.where(iris.target == 0, 0, 1)
# Create a DataFrame for easier plotting with Seaborn
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['is_setosa'] = y_binary
print(y_binary)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 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 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 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 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]
Une frontière de décision est une limite qui partitionne l’espace des attributs en régions correspondant à des étiquettes de classe différentes.
Considérons deux attributs, par exemple longueur des pétales et largeur des sépales, la frontière de décision peut être une ligne.
Considérons deux attributs, par exemple longueur des pétales et largeur des sépales, la frontière de décision peut être une ligne.
On dit que les données sont linéairement séparables lorsque deux classes de données peuvent être parfaitement séparées par une unique frontière linéaire, telle qu’une ligne dans un espace bidimensionnel ou un hyperplan dans des dimensions supérieures.
(a) données d’entraînement, (b) courbe quadratique, et (c) fonction linéaire.
Les arbres de décision sont capables de générer des frontières de décision irrégulières et non linéaires.
Attribution: ibidem.
Une frontière de décision est une hypersurface qui partitionne l’espace des attributs en régions correspondant à différentes étiquettes de classe.
Malgré son nom, la régression logistique sert de technique de classification plutôt que de méthode de régression.
Les étiquettes en régression logistique sont des valeurs binaires, notées \(y_i \in \{0,1\}\), ce qui en fait une tâche de classification binaire.
L’objectif principal de la régression logistique est de déterminer la probabilité qu’un exemple donné \(x_i\) appartienne à la classe positive, c’est-à-dire \(y_i = 1\).
Considérons un seul attribut, par exemple longueur des pétales, et la valeur de l’étiquette (0, 1).
La ligne résultante s’étend à l’infini dans les deux directions, mais notre objectif est de contraindre les valeurs entre 0 et 1. Ici, 1 indique une forte probabilité que \(x_i\) appartienne à la classe positive, tandis qu’une valeur proche de 0 indique une faible probabilité.
La fonction logistique standard transforme une entrée à valeur réelle de \(\mathbb{R}\) en l’intervalle ouvert \((0,1).\) La fonction est définie comme suit :
\[ \sigma(t) = \frac{1}{1+e^{-t}} \]
Une courbe en forme de S, telle que la fonction logistique standard (également appelée sigmoïde), est qualifiée de fonction d’écrasement (squashing function) car elle transforme un large domaine d’entrée en une plage de sortie restreinte.
\[ \sigma(t) = \frac{1}{1+e^{-t}} \]
De manière analogue à la régression linéaire, la régression logistique calcule une somme pondérée des attributs d’entrée, exprimée comme suit : \[ \theta_0 + \theta_1 x_i^{(1)} + \theta_2 x_i^{(2)} + \ldots + \theta_D x_i^{(D)} \]
Cependant, l’utilisation de la fonction sigmoïde limite sa sortie à l’intervalle \((0,1)\) : \[ \sigma(\theta_0 + \theta_1 x_i^{(1)} + \theta_2 x_i^{(2)} + \ldots + \theta_D x_i^{(D)}) \]
Le modèle de Régression Logistique, sous sa forme vectorisée, est défini comme suit : \[ h_\theta(x_i) = \sigma(\theta x_i) = \frac{1}{1+e^{- \theta x_i}} \]
\[ h_\theta(x_i) = \sigma(\theta x_i) \]
Le modèle de Régression Logistique, sous sa forme vectorisée, est défini comme suit : \[ h_\theta(x_i) = \sigma(\theta x_i) = \frac{1}{1+e^{- \theta x_i}} \]
Les prédictions sont faites comme suit :
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import label_binarize
# Load the Iris dataset
iris = load_iris()
X, y = iris.data, iris.target
# Binarize the output
y_bin = label_binarize(y, classes=[0, 1, 2])
# Split the dataset into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y_bin, test_size=0.2, random_state=42)
# Predict on a new sample
new_sample = X_test[0].reshape(1, -1)
confidences = [clf.decision_function(new_sample) for clf in classifiers]
# Final assignment
final_class = np.argmax(confidences)
# Printing the result
print(f"Final class assigned: {iris.target_names[final_class]}")
print(f"True class: {iris.target_names[np.argmax(y_test[0])]}")
Final class assigned: versicolor
True class: versicolor
label_binarized
from sklearn.preprocessing import label_binarize
from pprint import pprint
# Original class labels
y_train = np.array([0, 1, 2, 0, 1, 2, 1, 0])
# Binarize the labels
y_train_binarized = label_binarize(y_train, classes=[0, 1, 2])
# Assume y_train_binarized contains the binarized labels
print("Binarized labels:\n", y_train_binarized)
# Convert binarized labels back to the original numerical values
original_labels = [np.argmax(b) for b in y_train_binarized]
print("Original labels:\n", original_labels)
Binarized labels:
[[1 0 0]
[0 1 0]
[0 0 1]
[1 0 0]
[0 1 0]
[0 0 1]
[0 1 0]
[1 0 0]]
Original labels:
[0, 1, 2, 0, 1, 2, 1, 0]
Chargement du jeu de données
Quel est le type de digits.data
Combien d’exemples (N
) et combien d’attributs (D
)?
Définition des valeurs de N
et D
target
a-t-il le même nombre d’entrées (exemples) que data
?
Quelles sont la largeur et la hauteur de ces images ?
Définition des valeurs de width
et height
Définition des valeurs de X
et y
X[0]
est un vecteur de taille width * height = D
(\(8 \times 8 = 64\)).
array([ 0., 0., 5., 13., 9., 1., 0., 0., 0., 0., 13., 15., 10.,
15., 5., 0., 0., 3., 15., 2., 0., 11., 8., 0., 0., 4.,
12., 0., 0., 8., 8., 0., 0., 5., 8., 0., 0., 9., 8.,
0., 0., 4., 11., 0., 1., 12., 7., 0., 0., 2., 14., 5.,
10., 12., 0., 0., 0., 0., 6., 13., 10., 0., 0., 0.])
Il correspond à une image de \(8 \times 8 = 64\).
array([[ 0., 0., 5., 13., 9., 1., 0., 0.],
[ 0., 0., 13., 15., 10., 15., 5., 0.],
[ 0., 3., 15., 2., 0., 11., 8., 0.],
[ 0., 4., 12., 0., 0., 8., 8., 0.],
[ 0., 5., 8., 0., 0., 9., 8., 0.],
[ 0., 4., 11., 0., 1., 12., 7., 0.],
[ 0., 2., 14., 5., 10., 12., 0., 0.],
[ 0., 0., 6., 13., 10., 0., 0., 0.]])
Afficher les n=5
premiers exemples.
Dans notre ensemble de données, chaque \(x_i\) est un vecteur d’attributs de taille \(D = 64\).
Ce vecteur est formé en concaténant les lignes d’une image de \(8 \times 8\).
La fonction reshape
est utilisée pour convertir ce vecteur de 64 dimensions en son format original d’image \(8 \times 8\).
Nous allons entraîner 10 classificateurs, chacun correspondant à un chiffre spécifique dans une approche Un-contre-Tous (OvA).
Chaque classificateur déterminera les valeurs optimales des \(\theta_j\) (associées aux attributs pixels), lui permettant de distinguer un chiffre de tous les autres.
Préparation de notre expérience en apprentissage automatique
Les algorithmes d’optimisation fonctionnent généralement mieux lorsque les attributs ont des plages de valeurs similaires.
Application du classificateur à notre ensemble de test
from sklearn.metrics import classification_report
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred))
precision recall f1-score support
0 1.00 1.00 1.00 21
1 0.95 1.00 0.97 19
2 1.00 1.00 1.00 20
3 1.00 1.00 1.00 26
4 1.00 1.00 1.00 20
5 0.93 0.88 0.90 16
6 1.00 1.00 1.00 21
7 1.00 1.00 1.00 11
8 1.00 0.94 0.97 18
9 0.89 1.00 0.94 8
accuracy 0.98 180
macro avg 0.98 0.98 0.98 180
weighted avg 0.98 0.98 0.98 180
Combien de classes ?
Les coefficients et les intercepts sont dans des tableaux distincts.
Les intercepts sont \(\theta_0\), tandis que les coefficients sont \(\theta_j, j \in [1,64]\).
array([[ 0. , -0.19, -0.08, 0.28, -0.03, -0.69, -0.52, -0.06],
[ 0. , -0.26, -0.03, 0.45, 0.55, 0.82, 0.03, -0.18],
[-0.04, 0.33, 0.36, -0.05, -0.91, 0.91, 0.08, -0.12],
[-0.04, 0.2 , 0.07, -0.72, -1.71, 0.08, 0.18, -0.02],
[ 0. , 0.38, 0.46, -0.54, -1.7 , -0.11, 0.1 , 0. ],
[-0.09, -0.14, 0.84, -0.99, -0.78, 0.08, 0.23, -0. ],
[-0.04, -0.27, 0.41, 0.12, 0.32, -0.02, -0.34, -0.5 ],
[ 0.01, -0.26, -0.39, 0.45, -0.6 , -0.1 , -0.34, -0.31]])
sklearn
sklearn
from mpl_toolkits.mplot3d import Axes3D
# Function to generate points
def generate_points_above_below_plane(num_points=100):
# Define the plane z = ax + by + c
a, b, c = 1, 1, 0 # Plane coefficients
# Generate random points
x1 = np.random.uniform(-10, 10, num_points)
x2 = np.random.uniform(-10, 10, num_points)
y1 = np.random.uniform(-10, 10, num_points)
y2 = np.random.uniform(-10, 10, num_points)
# Points above the plane
z_above = a * x1 + b * y1 + c + np.random.normal(20, 2, num_points)
# Points below the plane
z_below = a * x2 + b * y2 + c - np.random.normal(20, 2, num_points)
# Stack the points into arrays
points_above = np.vstack((x1, y1, z_above)).T
points_below = np.vstack((x2, y2, z_below)).T
return points_above, points_below
# Generate points
points_above, points_below = generate_points_above_below_plane()
# Visualization
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Plot points above the plane
ax.scatter(points_above[:, 0], points_above[:, 1], points_above[:, 2], c='r', label='Above the plane')
# Plot points below the plane
ax.scatter(points_below[:, 0], points_below[:, 1], points_below[:, 2], c='b', label='Below the plane')
# Plot the plane itself for reference
xx, yy = np.meshgrid(range(-10, 11), range(-10, 11))
zz = 1 * xx + 1 * yy + 0
ax.plot_surface(xx, yy, zz, alpha=0.2, color='gray')
# Set labels
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
# Set title and legend
ax.set_title('3D Points Above and Below a Plane')
ax.legend()
# Show plot
plt.show()
Marcel Turcotte
École de science informatique et de génie électrique (SIGE)
Université d’Ottawa