Introduction au Machine Learning

Transformer les variables pour optimiser l'apprentissage du modèle

P3C3 : Transformez les variables pour faciliter l’apprentissage du modèle

Transformez des données

Transformer des données catégoriques textuelles en variables chiffrées avec le one hot encoding

Anticipez l’impact de beaucoup de variables

curse of dimensionality: quand trop de variables noient l’information et limites du one hot encoding. mentionner les autres types d’encoding et de la librairie dédiée à l’encoding

Créez de nouvelles variables

le feature engineering ou l’art de créer de nouvelles variables.

Illustration avec le dataset advertising et le carré, cube des variables.

À vous de jouer !

Application à un cas ou une variable catégorielle avec beaucoup de valeurs avec comme conséquence directe un problème de dimension (curse of dimensionality)


Il ne vous aura pas échappé que les modèles n’acceptent comme données d’entrée que des chiffres. Si votre dataset d’entraînement ou vos échantillons de prédictions contiennent du texte, vous aurez une erreur python.

Et comme vous avez pu le constater dans le dataset des arbres, les données textuelles sont fréquentes. Dans ce cours, nous n’aborderons que les categories textuelles et non pas le texte sous forme libre qui relève de techniques spécifiques appelées traitement du langage ou NLP (lien vers cours OC).

Le traitement apporté aux données catégoriques textuelles va dépendre du nombre de catégorie:

On distinguera le cas où l’on a 2 catégories, le cas binaire, et pour être très précis les cas, “un peu plus que 2” ou “carrément beaucoup”.

Reloadons les donnees et comme precedemment on se limite aux platanes

filename = ‘./../data/paris-arbres-2023-09-07.csv’ arbres = pd.read_csv(filename, sep = ‘;’) df = arbres[arbres.libelle_francais == ‘Platane’].copy()

La variable prend 2 valeurs distinctes et exclusives de type : oui/non, vrai/faux, mort/vivant, homme/femme, positif / negatif au test, etc

Sur le dataset des arbres la variable Remarquable est binaire.

df.remarquable.value_counts() remarquable NON 185232 OUI 179

Il est trivial de transformer ces valeurs en chiffres en choisissant de manière arbitraire une valeur pour chaque catégorie

df.loc[df.remarquable == NON, ‘remarquable’] = 0 df.loc[df.remarquable == OUI, ‘remarquable’] = 0

on obtient alors df.remarquable.value_counts() remarquable NON 0 OUI 1

Les choses se compliquent un peu et l’on distingue les cas ou les catégories sont ordonnées (ordinales) ou non.

Exemple de categories ordinales:

et de categories non ordonnées:

On peut remplacer les categories ordinales par des nombres croissant sans modifier la quantitité d’information.

Supposons que le stade de développement des arbres est ordonné suivant: Jeune (arbre) < Jeune (arbre)Adulte < Adulte < Mature

Une methode pour numériser ces catégories sera par exemple

categories = [‘Adulte’, ‘Jeune (arbre)Adulte’, ‘Jeune (arbre)’, ‘Mature’] for n, categorie in zip(range(1,len(categories)+1), categories): print(categorie, n) data.loc[data.stade_de_developpement == categorie, ‘stade_de_developpement’] = n

Et on obtiendra stade_de_developpement 1 78454 2 38503 3 36476 4 7406 avec une équivalence (mapping) entre les categories et les nombres 1 => Jeune (arbre) … 4 => Mature

La libraiire category_encoders comprend un grand nombre de techniques d’encodage de categories. Cette librairie fait partie de l’ecosystème scikit-learn et respecte la meme sémantique: fit, transform etc Vous pouvez l’installer avec

!pip install category_encoders

Pour encoder des categoriees ordinales, nous utilisons la classe OrdinalEncoder https://contrib.scikit-learn.org/category_encoders/ordinal.html#category_encoders.ordinal.OrdinalEncoder

OrdinalEncoder permet d’encoder des categories ordonnées ou sans ordre particulier. Pour que l’ordre des categories soit respecté dans l’encodage, il faut donner un mapping explicite a la fonction fit comme ceci

from category_encoders.ordinal import OrdinalEncoder mapping =[ {‘col’: ‘stade_de_developpement’, ‘mapping’: { np.nan: 0, ‘Jeune (arbre)’: 1, ‘Jeune (arbre)Adulte’: 2, ‘Adulte’: 3, ‘Mature’: 4 } } ]

encoder = OrdinalEncoder(mapping=mapping) stade = encoder.fit_transform(df.stade_de_developpement) stade.value_counts() on remplace les categories originales par leur equivalent numeriques df[‘stade_de_developpement’] = stade.copy() Et les stade de devleoppment sont maintenant numerisés df[‘stade_de_developpement’].value_counts(dropna = False) stade_de_developpement 3 21620 2 8356 1 5916 0 3350 4 3346

Notez que la forme du mappoing est un peu speciale. Elle permet d’encoder plusieurs colonnes a la fois en fournissant pour chaque colonne un dictionnaire d’equivalence. Notez aussi que l’on peut specifier l’encodage des valeurs manquantes. ici np.nan => 0.

cas non ordonnés

Si l’on remplace de la même façon les valeurs d’une categorie non ordonnées par une suite de nombres on introduit un ordre fictif dans les catégories. Cette information supplementaire ordre pourrait etre utilisé par le modèle alors qu’elle ne correspond a rien de concret. On voudrait éviter d’introduire une information arbitraire d’ordre dans les catégories.

Note: dans la pratique, remplacer les catégories par une valeur croissante ne m’a jamais vraiment posé de problème. Je n’ai jamais remarqué une dégradation importante des scores du modèle. Toutefois il y a une methode propre pour numériser des catégories non ordonnées appelé one hot encoding.

Le principe est d’associer pour chaque valeur de la categorie une nouvelle variable binaire qui indique si la variable originale avait la valeur en questions. Si la variable prend N valeurs distinct on intriduira donc N-1 nouvelles variables, la Nieme etant redondante.

Prenons un exemple avec nos arbres et la variable domanialité qui prends les 9 valeurs suivantes

domanialite Alignement 35747 CIMETIERE 3121 Jardin 2038 DASCO 816 PERIPHERIQUE 603 DJS 182 DFPE 71 DAC 7 DASES 3

Pour que l’exemple soit plus clair ne gardons que les arbres des 3 domanialités les plus fréquentes df = df [df.domanialite.isin([‘Alignement’, ‘Jardin’, ‘CIMETIERE’])].copy() df.reset_index(drop = True, inplace = True)

On va créer 2 variables booléenne : is alignement et is_jardin telle que

is_alignement = 1 if domanialite = ‘Alignement’, 0 sinon is_jardin = 1 if domanialite = ‘Jardin’, 0 sinon

is_alignement = 1 if (domanialite = ‘Alignement’) else 0 is_jardin = 1 if (domanialite = ‘Jardin’) else 0

Comme les catégories sont exclusives, un arbre ne peut pas être à la fois dans un alignement ou un jardin et dans un cimetière, la valeur cimetiere n’as pas besoin d’être explicité par une variable is_cimetiere. On a logiquement

domanialite = CIMETIERE si is_alignement =0 et is_jardin = 0

Tout cela est un peu lourd. Imaginez faire cela à la main pour une variable a 5, 10 ou plus de valeurs.

Cette technique qui s’appelle one_hot_encoding est implémentée dans scikit-learn dans le module preprocessing https://scikit-learn.org/0.16/modules/classes.html#module-sklearn.preprocessing

from sklearn import preprocessing enc = preprocessing.OneHotEncoder() labels = enc.fit_transform(df.domanialite.values.reshape(-1, 1)).toarray() labels.shape (40906, 3)

donc 3 colonnes correspondant aux 3 valeurs de domanialité

Il faut maintenant intégrer 2 de ces 3 colonnes (la 3ème est redondante) et supprimer la colonne originale. Donc un peu de manipulation de pandas.dataframe

df = pd.concat([df, pd.DataFrame(columns = [‘is_alignement’,’is_jardin’], data = labels[:, :2])], axis = 1)

Remarquez la chose suivante:

le dataset, si l’on ne considère que les variables qui apporte vraiment de l’info a seulement 8 variables (les variables idbase type_emplacement, … n’ont pas de valeurs ou n’apportent aucune info pour exploitable par un modèle.)

Si l’on prend en compte toutes les valeurs de domanialité, soit 9 valeurs différentes, on ajoute au final 8 nouvelles variables . On double presque le nombre de variables.

De plus la plupart de ces nouvelles variables ont principalement des 0 Par exmeple, la domanialité PERIPHERIQUE a seulement 5225 arbres sur un total de 207641 arbres soit 2,5% des arbres. La colonne one hot encoded correspondant à cette valeur va être à 97.5% des zéros donc extrêmement peu informative.

De même si on considère les variables comme espèces qui a 559 valeurs differentes, la techhnique du one hot necoding ajoutera 558 nouvelles variables. la plupart truffées de 0 dans sans grande valeur.

On va donc en quelque sorte noyer les infos des 7 variables restantes dans la masse des variables dédiés à l’espèce ou aux autres variables categoriques.

Quand le nombre de variable explose, on appelle ça le curse of dimension, le piège des grandes dimensions. Une dose finie d’information brute est delayée sur un trop grand nombre variables chacune peu informatives. Le modele aura du mal a extraire la substantifique moelle du dataset.

En conclusion, le one hot encoding n’est réellement utilisable que lorsque le nombre de valeurs de la variable à numériser est faible par rapport aux nombre de variable utiles du dataset.

Passons maintenant aux choses serieuses!

L’encodage binaire

Pour sortir du piège de la dimensionnalité, ma technique préférée de numérisation est l’encodage binaire disponible dans l’excellente librairie category_encoders

Prenons un exmeple de la variable domanialité qui a 9 variables. Le chiffre 9 en binaire s’ecrit 1001 soit 4 digits. En associant a chaque digit une variable booleenne 0/1 on peut encoder les valeurs de cette variable a 9 valeurs sur 4 dimension au lieu des 8 du one hot encoding. On aura par exemple Alignement => 1 => 0000 Jardin => 2 => 0001 CIMETIERE => 3 => 0010 etc … DASES => 9 => 1001

De meme la variable especes qui a 559 valeurs distinctes pour tout le dataset (pas seulement les platanes) ne necessitera que 10 variables au lieu de 558 (2^10 = 1024).

L’avantage enorme du binary encoding sur le one hot encoding est la reduction drastique du nombre de variables necessaires pour encoder la variable categorique originale. On perds par contre la correspondance directe entre la valeur de la variable booleanne qui ne reprensent plus qu’un des digit et la categorie originale.

Avec category_encoders, lencodage binaire de la variable especes suit

from category_encoders.binary import BinaryEncoder encoder = BinaryEncoder() espece_bin = encoder.fit_transform(data.espece) espece_bin.shape (207641, 10) espece_bin.head() espece_0 espece_1 espece_2 espece_3 espece_4 espece_5 espece_6 espece_7 espece_8 espece_9 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 1 0 2 0 0 0 0 0 0 0 0 1 1 3 0 0 0 0 0 0 0 1 0 0 4 0 0 0 0 0 0 0 1 0 1

feayture engieering

Revenons vers des choses plus artisanales que sont le feature engineering ou en français la création de nouvelles variables.

Le but du feature engineering est d’utiliser notre connaissance du domaine, du contexte, pour créer de nouvelles variables à partir des variables existantes. Cela ne suit aucune méthodologie claire et définie mais dépend de conclusions issues de l’analyse poussée des données, de la connaissance du contexte et du domaine.

On peut néanmoins mentionner comme techniques repandues:

En fait l’amélioration du dataset par le feature engineering revient a faire à un véritable travail de détective à partir

Dans notre contexte urbain forestier, nous pourrions augmenter notre dataset avec des données sur l’environnement de chaque arbre en integrant les images de google maps obtenus à partir des coordonnées géographiques contnues dans le dataset.

Autre exemple de feature engineering. Dans le chapitre 2 de la partie 2, nous avons construit une régression linéaire sur le dataset advertising. Pour rappel, le revenu dépend du budget de publicité dans 3 médias: tv, journaux et radio. La régression est de la forme ventes ~ tv + radio + journaux

Après analyse visuelle de la relation entre les ventes et la télé, nous avons vérifié qu’ajouter le carré de la variable tv améliorerait significativement le score du modèle. ventes ~ tv^2 + tv + radio + journaux

Dans le prochain chapitre nous allons revenir sur la partie modèle du binome (données - modèle) avec un concept essentiel au machine learning, l’overfit!