CSI 4506 - automne 2026
Version: oct. 13, 2026 11h53
Après avoir terminé le devoir 1, vous avez produit un fichier .csv contenant des données nettoyées.
Pourquoi d’autres étapes seraient-elles nécessaires?
Author: Ronny Kohavi and Barry Becker
Source: UCI - 1996
Please cite: Ron Kohavi, “Scaling Up the Accuracy of Naive-Bayes Classifiers: a Decision-Tree Hybrid”, Proceedings of the Second International Conference on Knowledge Discovery and Data Mining, 1996
Prediction task is to determine whether a person makes over 50K a year. Extraction was done by Barry Becker from the 1994 Census database. A set of reasonably clean records was extracted using the following conditions: ((AAGE>16) && (AGI>100) && (AFNLWGT>1)&& (HRSWK>0))
This is the original version from the UCI repository, with training and test sets merged.
Variables are all self-explanatory except fnlwgt. This is a proxy for the demographic background of the people: “People with similar demographic characteristics should have similar weights”. This similarity-statement is not transferable across the 51 different states.
Description from the donor of the database:
The weights on the CPS files are controlled to independent estimates of the civilian noninstitutional population of the US. These are prepared monthly for us by Population Division here at the Census Bureau. We use 3 sets of controls. These are: 1. A single cell estimate of the population 16+ for each state. 2. Controls for Hispanic Origin by age and sex. 3. Controls by Race, age and sex.
We use all three sets of controls in our weighting program and “rake” through them 6 times so that by the end we come back to all the controls we used. The term estimate refers to population totals derived from CPS by creating “weighted tallies” of any specified socio-economic characteristics of the population. People with similar demographic characteristics should have similar weights. There is one important caveat to remember about this statement. That is that since the CPS sample is actually a collection of 51 state samples, each with its own probability of selection, the statement only applies within state.
Ronny Kohavi and Barry Becker. Data Mining and Visualization, Silicon Graphics.
e-mail: ronnyk ‘@’ live.com for questions.
Downloaded from openml.org.
['Never-married', 'Married-civ-spouse', 'Widowed', 'Divorced', 'Separated', 'Married-spouse-absent', 'Married-AF-spouse']
Categories (7, str): ['Divorced', 'Married-AF-spouse', 'Married-civ-spouse', 'Married-spouse-absent', 'Never-married', 'Separated', 'Widowed']
Principes de représentation des données
Représentation numérique : de nombreux algorithmes d’apprentissage exigent des vecteurs d’attributs numériques.
La signification compte : la représentation doit respecter le caractère non ordonné ou ordonné des catégories.
Considérons l’attribut workclass, qui possède huit valeurs distinctes, telles que ‘Federal-gov’ et ‘Local-gov’.
w = 1, 2, 3, 4, 5, 6, 7 ou 8w = [0,0,0], [0,0,1], [0,1,0], \ldots ou [1,1,1]w = [1,0,0,0,0,0,0,0], [0,1,0,0,0,0,0,0], \ldots ou [0,0,0,0,0,0,0,1]L’encodage one-hot (one-hot encoding) constitue un choix courant pour les catégories non ordonnées, ou nominales.
w = 1, 2, 3, etc., laisse croire que ‘Federal-gov’ et ‘Local-gov’ sont semblables, tandis que ‘Federal-gov’ et ‘Without-pay’ ne le sont pas.w = [0,0,0], [0,0,1], etc., peut induire l’algorithme en erreur en suggérant une similarité fondée sur des motifs numériques.L’encodage one-hot transforme une variable catégorielle en vecteur binaire. Chaque catégorie est représentée par un vecteur qui contient un seul « 1 » et des « 0 » partout ailleurs.
array([[0., 0., 0., 1., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 1.]])
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_openml
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.pipeline import Pipeline
# Charger le jeu de données 'Heart-Disease' depuis OpenML
data = fetch_openml(name='Heart-Disease', version=1, as_frame=True)
df = data.frame
# Remplacer '?' par NaN et convertir les colonnes en valeurs numériques
for col in df.columns:
df[col] = pd.to_numeric(df[col], errors='coerce')
# Supprimer les lignes qui contiennent des valeurs manquantes
df.dropna(inplace=True)
# Définir les attributs et convertir la cible en absence ou présence
X = df.drop(columns=['target'])
y = (df['target'] > 0).astype(int)
# Colonnes à encoder avec OneHotEncoder
columns_to_encode = ['sex', 'cp', 'fbs', 'restecg', 'exang', 'slope', 'thal']
# Repérer les colonnes numériques
numerical_columns = X.columns.difference(columns_to_encode)
# Diviser les données avant d'appliquer les transformations
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Appliquer OneHotEncoder et StandardScaler avec ColumnTransformer
preprocessor = ColumnTransformer(
transformers=[
(
'onehot',
OneHotEncoder(drop='first', handle_unknown='ignore'),
columns_to_encode,
),
('scaler', StandardScaler(), numerical_columns)
]
)
# Le pipeline ajuste le prétraitement et le classificateur sur l'entraînement
heart_pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', LogisticRegression()),
])
heart_pipeline.fit(X_train, y_train)# Récupérer les noms des attributs et les coefficients du pipeline ajusté
feature_names = heart_pipeline[
'preprocessor'
].get_feature_names_out()
coefficients = heart_pipeline['classifier'].coef_[0]
# Créer un DataFrame qui associe les attributs et les coefficients
coef_df = pd.DataFrame({
'Attribut': feature_names,
'Coefficient': coefficients
})
# Afficher les coefficients associés à 'cp'
cp_features = coef_df[coef_df['Attribut'].str.contains('_cp')]
print("\nCoefficients associés à 'cp' :")
print(cp_features)
Coefficients associés à 'cp' :
Attribut Coefficient
1 onehot__cp_1.0 0.754906
2 onehot__cp_2.0 1.440017
3 onehot__cp_3.0 1.255102
# Visualiser les coefficients ajustés
coef_plot_df = coef_df.sort_values(by='Coefficient')
plt.figure(figsize=(8, 6))
plt.barh(coef_plot_df['Attribut'], coef_plot_df['Coefficient'])
plt.title('Coefficients des attributs')
plt.xlabel('Valeur du coefficient')
plt.ylabel('Attribut')
plt.tight_layout()
plt.show()L’encodage ordinal attribue des valeurs numériques à des catégories selon leur ordre ou leur rang intrinsèque.
Pour des valeurs comme « faible », « moyen » et « bon », un encodage ordinal peut préserver l’ordre.
La discrétisation transforme un attribut numérique continu ou finement gradué en un ensemble fini d’intervalles.
Exemple : regrouper les âges en catégories comme « nourrisson », « enfant », « adolescent », « adulte » et « personne âgée ».
Avantage possible :
Inconvénients :
import pandas as pd
import numpy as np
from sklearn.preprocessing import FunctionTransformer
bins = [0, 1, 13, 20, 60, np.inf]
labels = ['nourrisson', 'enfant', 'adolescent', 'adulte', 'personne âgée']
transformer = FunctionTransformer(
pd.cut, kw_args={'bins': bins, 'labels': labels, 'retbins': False}
)
X = np.array([0.5, 2, 15, 25, 97])
transformer.fit_transform(X)['nourrisson', 'enfant', 'adolescent', 'adulte', 'personne âgée']
Categories (5, str): ['nourrisson' < 'enfant' < 'adolescent' < 'adulte' < 'personne âgée']
La mise à l’échelle des attributs (feature scaling) place leurs valeurs dans des plages comparables.
Pourquoi certains estimateurs exigent-ils des attributs d’échelles comparables?
Nous voulons prédire le prix d’une maison par régression des k plus proches voisins (KNN) à partir de deux attributs :
import numpy as np
import pandas as pd
# Trois exemples (pièces, pieds carrés); prix pour b et c seulement
point_names = ["a", "b", "c"]
X = np.array([
[4, 1500.0], # a (requête)
[8, 1520.0], # b (entraînement)
[4, 1300.0], # c (entraînement)
], dtype=float)
prices = pd.Series([np.nan, 520_000, 390_000], index=point_names, name="prix")
df = pd.DataFrame(X, columns=["pièces", "pieds_carrés"], index=point_names)
display(df)
display(prices.to_frame())Remarque. Les exemples b et c forment l’ensemble d’entraînement. L’exemple a constitue la requête dont nous voulons prédire le prix.
| pièces | pieds_carrés | |
|---|---|---|
| a | 4.0 | 1500.0 |
| b | 8.0 | 1520.0 |
| c | 4.0 | 1300.0 |
| prix | |
|---|---|
| a | NaN |
| b | 520000.0 |
| c | 390000.0 |
Lorsqu’un attribut possède une échelle beaucoup plus grande, comme la superficie, il peut dominer la somme.
| a | b | c | |
|---|---|---|---|
| a | 0.000000 | 20.396078 | 200.000000 |
| b | 20.396078 | 0.000000 | 220.036361 |
| c | 200.000000 | 220.036361 | 0.000000 |
Pour éviter toute fuite de données, calculons les paramètres de mise à l’échelle à partir des seules données d’entraînement (b et c), puis transformons les données d’entraînement et la requête :
| pièces | pieds_carrés | |
|---|---|---|
| a | -1.0 | 0.818182 |
| b | 1.0 | 1.000000 |
| c | -1.0 | -1.000000 |
| a | b | c | |
|---|---|---|---|
| a | 0.000000 | 2.008247 | 1.818182 |
| b | 2.008247 | 0.000000 | 2.828427 |
| c | 1.818182 | 2.828427 | 0.000000 |
Un régresseur 1-NN prédit le prix de l’exemple d’entraînement le plus proche. Comparons le même estimateur avec et sans mise à l’échelle.
from sklearn.neighbors import KNeighborsRegressor
X_train = df.loc[train_idx].values
y_train = prices.loc[train_idx].values
X_query = df.loc[query_idx].values
knn_plain = KNeighborsRegressor(n_neighbors=1, metric="euclidean")
knn_plain.fit(X_train, y_train)
knn_scaled = Pipeline([
("scaler", StandardScaler()),
("knn", KNeighborsRegressor(n_neighbors=1, metric="euclidean"))
])
knn_scaled.fit(X_train, y_train)pred_plain = knn_plain.predict(X_query)[0]
pred_scaled = knn_scaled.predict(X_query)[0]
plain_index = knn_plain.kneighbors(
X_query, return_distance=False
)[0, 0]
scaled_query = knn_scaled["scaler"].transform(X_query)
scaled_index = knn_scaled["knn"].kneighbors(
scaled_query, return_distance=False
)[0, 0]
knn_comparison = pd.DataFrame(
{
"prédiction": [pred_plain, pred_scaled],
"plus proche voisin": [
train_idx[plain_index], train_idx[scaled_index]
],
},
index=["sans mise à l'échelle", "avec mise à l'échelle"],
)| Prédiction | Plus proche voisin | |
|---|---|---|
| Sans mise à l’échelle | 520 000 $ | b |
| Avec mise à l’échelle | 390 000 $ | c |
La mise à l’échelle min-max transforme chaque attribut dans un intervalle choisi, habituellement [0,1].
\frac{x_i^{(j)} - \min^{(j)}}{\max^{(j)} - \min^{(j)}}
Le minimum et le maximum doivent être appris à partir des données d’entraînement.
La standardisation soustrait la moyenne d’entraînement et divise par l’écart-type d’entraînement.
\frac{x_i^{(j)} - \mu^{(j)}}{\sigma^{(j)}}
L’attribut d’entraînement transformé possède une moyenne de 0 et une variance de 1, mais sa distribution ne devient pas normale et son étendue n’est pas bornée.
RobustScaler.| Propriété | Standardisation | Mise à l’échelle min-max |
|---|---|---|
| Plage de sortie | Non bornée | Habituellement [0, 1] |
| Centre | Moyenne à 0 | Non centrée |
| Sensibilité aux valeurs aberrantes | Par la moyenne et l’écart-type | Par le minimum et le maximum |
| Usage typique | Variances comparables et attributs centrés à zéro | Plage bornée prescrite |
Le flux de travail correct :
Pipeline.from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
minmax_norm_values = scaler.fit_transform(norm_values.reshape(-1, 1))
# Tracer l'histogramme
plt.hist(minmax_norm_values, bins=20, edgecolor='black')
plt.title('MinMaxScaler')
plt.xlabel('Valeur')
plt.ylabel('Fréquence')
plt.show()Les valeurs manquantes désignent l’absence d’une donnée à un endroit où une valeur est attendue dans un jeu de données.
L’imputation des données consiste à remplacer les valeurs manquantes par des valeurs de substitution, généralement calculées à l’aide de méthodes statistiques ou d’apprentissage automatique.
Remplaçons une valeur numérique manquante par la moyenne ou la médiane d’entraînement de l’attribut.
array([[ 4., 11.]])
Limite : l’imputation univariée ignore les relations entre les attributs et réduit la variabilité observée.
Attributs catégoriels : utilisez la catégorie la plus fréquente ou une catégorie explicite "manquant" lorsque cette représentation convient.
Une valeur imputée peut être accompagnée d’un attribut binaire qui indique que la valeur originale était manquante.
Méthode fondée sur les voisins :
Méthodes itératives fondées sur des modèles :
La leçon 7 a montré que le déséquilibre des classes peut masquer une mauvaise performance sur une classe peu fréquente et que l’évaluation doit refléter le problème de décision.
Cette leçon pose une question différente :
Comment la procédure d’entraînement peut-elle répondre au déséquilibre des classes sans contaminer l’évaluation?
imbalanced-learn.L’ingénierie des attributs (feature engineering) consiste à créer, à transformer et à sélectionner des variables à partir des données brutes afin d’améliorer la performance des modèles d’apprentissage automatique.
import numpy as np
np.random.seed(42)
X = 6 * np.random.rand(100, 1) - 3
y = 0.5 * X ** 2 - X + 2 + np.random.randn(100, 1)
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.figure(figsize=(6,4))
plt.plot(X, y, "b.")
plt.xlabel("$x$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.axis([-3, 3, 0, 10])
plt.grid(True)
plt.show()from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X, y)
X_new = np.array([[-3], [3]])
y_pred = lin_reg.predict(X_new)
plt.figure(figsize=(6,4))
plt.plot(X, y, "b.")
plt.plot(X_new, y_pred, "r-")
plt.xlabel("$x$", fontsize=18)
plt.ylabel("$y$", rotation=0, fontsize=18)
plt.axis([-3, 3, 0, 10])
plt.show()Un modèle linéaire représente mal ce jeu de données.
PolynomialFeaturesProduit une nouvelle matrice d’attributs composée de toutes les combinaisons polynomiales dont le degré est inférieur ou égal au degré indiqué. Par exemple, pour un échantillon d’entrée bidimensionnel de la forme [a, b], les attributs polynomiaux de degré 2 sont [1, a, b, a^2, ab, b^2].
PolynomialFeaturesAvec deux attributs a et b, PolynomialFeatures et degree=3 ajoutent a^2, a^3, b^2, b^3, ainsi que ab, a^2b et ab^2.
Avertissement
Avec include_bias=True, le nombre total d’attributs de sortie est
\binom{D+d}{d} = \frac{(D+d)!}{d!D!},
où D est le nombre d’attributs d’entrée. Avec include_bias=False, il faut en soustraire un.
lin_reg = LinearRegression()
lin_reg = lin_reg.fit(X_poly, y)
X_new = np.linspace(-3, 3, 100).reshape(100, 1)
X_new_poly = poly_features.transform(X_new)
y_new = lin_reg.predict(X_new_poly)
plt.figure(figsize=(5, 3))
plt.plot(X, y, "b.")
plt.plot(X_new, y_new, "r-", linewidth=2, label="Prédictions")
plt.xlabel("$x_1$")
plt.ylabel("$y$", rotation=0)
plt.legend(loc="upper left")
plt.axis([-3, 3, 0, 10])
plt.grid()
plt.show()Les données ont été produites par l’équation suivante, à laquelle un bruit gaussien a été ajouté.
y = 0.5 x^2 - 1.0 x + 2.0
Voici le modèle appris.
\hat{y} = 0.56 x^2 + (-1.06) x + 1.78
L’augmentation des données (data augmentation) crée des exemples d’entraînement supplémentaires au moyen de transformations qui devraient préserver l’étiquette cible.
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
numeric_pipeline = Pipeline([
('imputer', SimpleImputer(strategy="median", add_indicator=True)),
('scaler', StandardScaler()),
])
preprocessor = ColumnTransformer([
(
"numeric",
numeric_pipeline,
make_column_selector(dtype_include=np.number),
),
(
"categorical",
OneHotEncoder(handle_unknown="ignore"),
make_column_selector(dtype_exclude=np.number),
),
])
full_pipeline = Pipeline([
("preprocessor", preprocessor),
("classifier", LogisticRegression()),
])Marcel Turcotte
École de science informatique et de génie électrique (ÉSIGÉ)
Université d’Ottawa