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()
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
💯 %, 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 :
@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://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:
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
@ 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 :
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 🤔
Commentaire le plus utile
Troisième loi du code : Tu ne feras pas == Comparaisons avec les flotteurs