Evalml: One Hot Encoder : supprimez une fonctionnalité redondante par défaut pour les fonctionnalités avec deux catégories

Créé le 5 mars 2021  ·  14Commentaires  ·  Source: alteryx/evalml

Notre encodeur unique crée une fonctionnalité pour chaque niveau de la fonctionnalité catégorielle d'origine :

from evalml.pipelines import OneHotEncoder
import pandas as pd
df = pd.DataFrame({"category": ["a", "b"], "number": [4,5 ]})
OneHotEncoder().fit_transform(df).to_dataframe()

image

Les colonnes category_a et category_b sont complètement colinéaires, ce qui en fait une redondante. Cela pourrait avoir des effets négatifs sur l'ajustement de l'estimateur. Je pense que nous devrions en supprimer un par défaut.

Pour info @rpeck

enhancement

Commentaire le plus utile

Troisième loi du code : Tu ne feras pas == Comparaisons avec les flotteurs

Tous les 14 commentaires

💯 %, nous devrions supprimer la colonne des cas négatifs.

Si nous faisons nous-mêmes l'OHE en premier, alors sklearn, espérons-le, ne les développera pas. Comme l'a dit Freddy, vous pouvez considérer cela comme la génération de deux colonnes qui ont une colinéarité parfaite.

Il y a deux problèmes que je vois avec l'expansion d'un binaire en deux colonnes plutôt qu'une :

  1. Comme d'autres formes de colinéarité de caractéristiques, cela gâche beaucoup de choses dans l'interprétabilité, car l'effet de la colonne source d'origine est divisé entre les deux colonnes OHE. Les nouveaux cumuls SHAP de Freddy résolvent ce problème, évidemment, mais des éléments tels que les tracés d'importance des fonctionnalités et de dépendance partielle auront toujours le problème.
  2. Les modèles d'arbre comme Random Forest et GBM échantillonnent au hasard leurs caractéristiques d'entrée. La colonne source dans ce cas sera échantillonnée au hasard deux fois plus souvent qu'elle devrait l'être, de sorte qu'elle peut avoir un impact démesuré sur le modèle.

@freddyaboulton Q : Le cadre de données ci-dessus pour les colonnes OHE les montre sous forme de flottants. Est-ce vraiment vrai ?

@rpeck Oui !

@freddyaboulton Quoi? C'est bizarre. Je n'ai jamais rien vu d'autre que de vrais booléens ou des entiers 0/1. Je me demande comment les modèles d'arbres gèrent cela. Il a une mauvaise odeur pour moi.

Troisième loi du code : Tu ne feras pas == Comparaisons avec les flotteurs

(ok, sauf si c'est avec Math.NaN )

Hum, je croyais qu'on faisait ça !

Je suis d'accord que nous devrions. Je pensais que c'était juste un indicateur que nous devions définir dans l'impl sous-jacent.

@dsherry @freddyaboulton On dirait que nous avons un support pour cela via notre paramètre drop mais ne prend en considération que les entrées de l'utilisateur et n'est pas utilisé par notre impl donc ce problème suit simplement la définition de la valeur par défaut pour drop à autre chose que Aucun ?

https://github.com/alteryx/evalml/blob/91775ffc26c47205adc0fb255832d828ead6e7c9/evalml/pipelines/components/transformers/encoders/onehot_encoder.py#L28

https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html

Nous pourrions choisir entre first ou if_binary , sans savoir quel est le bon appel.

@ angela97lin Vous avez raison de dire que changer la valeur par défaut suffirait ! Je pense que first est la voie à suivre car nous devrions éviter les caractéristiques parfaitement colinéaires même lorsque le nombre de catégories > 2. Qu'en pensez-vous @rpeck ?

Je lisais un peu cela et j'ai trouvé ce lien : https://inmachineswetrust.com/posts/drop-first-columns/

Points clés à retenir:

  • La suppression de colonnes n'est requise que lors de la création d'un modèle OLS sans régularisation (je pense que le régresseur linéaire appartient à cette catégorie : https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html, https:// scikit-learn.org/stable/modules/linear_model.html#ordinary-least-squares)
  • La suppression de colonnes codées one-hot modifie les paramètres et les prédictions d'un modèle de régression linéaire, affectant le modèle renvoyé. Pourtant, il m'est difficile de déterminer si c'est pour le mieux ou non.

Premier commentaire de RE @rpeck : "Comme d'autres formes de colinéarité de caractéristiques, cela gâche beaucoup de choses en termes d'interprétabilité, car l'effet de la colonne source d'origine est divisé entre les deux colonnes OHE. Les nouveaux cumuls SHAP de Freddy y répondent, évidemment, mais des choses comme les parcelles d'importance des fonctionnalités et de dépendance partielle auront toujours le problème. "

Cela a du sens pour les cas binaires, mais dans le cas où nous avons plusieurs catégories, supprimer une colonne aura toujours ce problème.

Peut-être ne devrions-nous pas le faire par défaut, mais devrions-nous mettre make_pipeline jour first comme paramètre si l'estimateur est un régresseur linéaire ?

Hélas, je n'ai pas une bonne compréhension des mathématiques sous-jacentes pour porter un jugement alors j'aimerais entendre vos pensées, @freddyaboulton @rpeck @dsherry

Post-discussion avec @freddyaboulton @rpeck @dsherry @chukarsten @jeremyliweishih

  • Nous ne le ferons que pour les cas binaires.
  • Un "agréable à avoir" est d'utiliser, dans le cas binaire, la classe minoritaire, mais sinon, il suffit de choisir l'une des deux catégories.

@ angela97lin sonne bien comme comportement par défaut de RE. Un autre avantage à avoir : la possibilité de remplacer ce comportement par défaut via les paramètres du composant

@dsherry Si je comprends bien, puisque nous mettons à jour la valeur par défaut de drop (un paramètre), les utilisateurs auront la possibilité de l'ignorer en définissant le paramètre du composant manuellement ?

Fouillé pour voir ce qui était nécessaire pour mettre cela en œuvre. En particulier, j'étais curieux de savoir à quel point il serait difficile de toujours supprimer la classe minoritaire dans le cas binaire.

Le résultat de ce creusement est :

  • Avec scikit-learn, il est assez difficile de sélectionner la catégorie à supprimer. D'après la documentation, cela semble faisable via l'option de tableau pour le paramètre drop (https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html). Cependant, après l'avoir essayé, il faut qu'une valeur d'index soit spécifiée pour chaque colonne. Par conséquent, ce qui suit, qui essaie de supprimer la catégorie spécifiée à l'index 0 pour la colonne 0 et aucune autre valeur pour les colonnes 1 et 2 erreurs :
import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder

X = pd.DataFrame({'col_1': ["a", "b", "b", "a", "b"],
                      'col_2': ["a", "b", "a", "c", "b"],
                      'col_3': ["a", "a", "a", "a", "a"]})

indices_to_drop = np.array([0, None, None])

ohe = OneHotEncoder(drop=indices_to_drop)
ohe.fit(X)
ValueError                                Traceback (most recent call last)
<ipython-input-4-a099fa2fc4a7> in <module>
----> 1 ohe.fit(X)

~/Desktop/venv/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in fit(self, X, y)
    417         self._fit(X, handle_unknown=self.handle_unknown,
    418                   force_all_finite='allow-nan')
--> 419         self.drop_idx_ = self._compute_drop_idx()
    420         return self
    421 

~/Desktop/venv/lib/python3.7/site-packages/sklearn/preprocessing/_encoders.py in _compute_drop_idx(self)
    394                                 ["Category: {}, Feature: {}".format(c, v)
    395                                     for c, v in missing_drops])))
--> 396                 raise ValueError(msg)
    397             return np.array(drop_indices, dtype=object)
    398 

ValueError: The following categories were supposed to be dropped, but were not found in the training data.
Category: 0, Feature: 0
Category: 1, Feature: None
Category: 2, Feature: None

Je crois que c'est aussi la moitié de ce que ce problème souligne : https://github.com/scikit-learn/scikit-learn/issues/16511

Une alternative que nous pouvons faire pour prendre en charge cela est de suivre manuellement les colonnes et les valeurs que nous voulons supprimer lors de l'ajustement. Transmettez les données à scikit-learn. Ensuite, élaguer les colonnes que nous avons stockées et spécifiées que nous voulions supprimer. Cependant, cela nécessite une certaine gestion logique pour déterminer l'original (fonctionnalité, valeur) à partir du nom de colonne transformé. (Nous avons cette logique dans get_feature_names mais cela nous aide à connecter les noms de colonnes en supposant que rien ne doit être supprimé...)

Tout cela pour dire, peut-être qu'utiliser le scikit-learn par défaut if_binary suffira pour le moment, et nous pouvons déposer un problème séparé pour toujours utiliser la classe minoritaire. Honnêtement, également en faveur de l'abandon de l'implémentation OHE de scikit-learn étant donné tout ce que nous avons dû contourner.

Ressources utiles :
Doc OHE : https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html
Code dans scikit-learn provoquant une inflexibilité : https://github.com/scikit-learn/scikit-learn/blob/95119c13af77c76e150b753485c662b7c52a41a2/sklearn/preprocessing/_encoders.py#L338
Problème connexe : https://github.com/scikit-learn/scikit-learn/issues/16511


Pour utiliser if_binary : scikit-learn nécessite que handle_unknown soit error . Cela ne fonctionne pas bien avec nos paramètres top_n , qui suppriment tout sauf les N premières catégories car les données à transformer ne sauront pas quoi faire avec les nouvelles catégories. Comme Becca l'a noté dans https://github.com/alteryx/evalml/pull/830 , nous devrions définir top_n sur None pour que ces paramètres fonctionnent.

Dans cet esprit, il est peut-être préférable de simplement lancer notre propre impl 🤔

Cette page vous a été utile?
0 / 5 - 0 notes