web-dev-qa-db-fra.com

sklearn.LabelEncoder avec des valeurs jamais vues auparavant

Si un sklearn.LabelEncoder a été installé sur un ensemble d'apprentissage, il risque de se rompre s'il rencontre de nouvelles valeurs lorsqu'il est utilisé sur un ensemble de test.

La seule solution que je pourrais trouver pour cela est de mapper tout ce qui est nouveau dans le jeu de tests (c'est-à-dire n'appartenant à aucune classe existante) à "<unknown>", puis d'ajouter explicitement une classe correspondante à la LabelEncoder par la suite:

# train and test are pandas.DataFrame's and c is whatever column
le = LabelEncoder()
le.fit(train[c])
test[c] = test[c].map(lambda s: '<unknown>' if s not in le.classes_ else s)
le.classes_ = np.append(le.classes_, '<unknown>')
train[c] = le.transform(train[c])
test[c] = le.transform(test[c])

Cela fonctionne, mais existe-t-il une meilleure solution?

Mettre à jour

Comme @sapo_cosmico le souligne dans un commentaire, il semble que ce qui précède ne fonctionne plus, étant donné que je suppose qu'il s'agit d'un changement d'implémentation dans LabelEncoder.transform, qui semble maintenant utiliser np.searchsorted (je ne sais pas si c'était le cas auparavant) . Ainsi, au lieu d’ajouter la classe <unknown> à la liste des classes déjà extraites de la LabelEncoder, elle doit être insérée dans l’ordre trié:

import bisect
le_classes = le.classes_.tolist()
bisect.insort_left(le_classes, '<unknown>')
le.classes_ = le_classes

Cependant, comme cela semble assez maladroit dans l'ensemble, je suis certain qu'il existe une meilleure approche pour cela.

42
cjauvin

J'ai l'impression que ce que vous avez fait est assez similaire à ce que font les autres quand ils sont confrontés à cette situation.

Des efforts ont été déployés pour ajouter la possibilité d'encoder des étiquettes invisibles à LabelEncoder (voir en particulier https://github.com/scikit-learn/scikit-learn/pull/3483 et https: // github. com/scikit-learn/scikit-learn/pull/3599 ), mais il est en réalité plus difficile de modifier le comportement existant qu’il ne le semble à première vue.

Pour l'instant, il semble que le traitement des étiquettes "hors vocabulaire" soit laissé aux utilisateurs individuels de scikit-learn.

6
lmjohns3

LabelEncoder est fondamentalement un dictionnaire. Vous pouvez l'extraire et l'utiliser pour un codage futur:

from sklearn.preprocessing import LabelEncoder

le = preprocessing.LabelEncoder()
le.fit(X)

le_dict = dict(Zip(le.classes_, le.transform(le.classes_)))

Récupérer l'étiquette d'un seul nouvel élément, si l'élément est manquant, définir la valeur comme inconnue 

le_dict.get(new_item, '<Unknown>')

Récupérer des étiquettes pour une colonne Dataframe:

df[your_col].apply(lambda x: le_dict.get(x, <unknown_value>))
3
Rani

Je connais deux développeurs qui travaillent à la construction d’emballages autour des transformateurs et des pipelines Sklearn. Ils disposent de 2 transformateurs de codeur robustes (un codeur factice et un codeur d'étiquette) pouvant gérer des valeurs invisibles. Voici la documentation de leur bibliothèque skutil. Recherchez skutil.preprocessing.OneHotCategoricalEncoder ou skutil.preprocessing.SafeLabelEncoder. Dans leur SafeLabelEncoder(), les valeurs non vues sont automatiquement codées en 999999.

3
Jason

J'essayais de résoudre ce problème et j'ai trouvé deux moyens pratiques d'encoder des données catégoriques provenant de trains et d'ensembles de test avec et sans l'aide de LabelEncoder. Les nouvelles catégories sont remplies avec un cégoire connu "c" (comme "autre" ou "manquant"). La première méthode semble fonctionner plus rapidement. J'espère que cela vous aidera.

import pandas as pd
import time
df=pd.DataFrame()

df["a"]=['a','b', 'c', 'd']
df["b"]=['a','b', 'e', 'd']


#LabelEncoder + map
t=time.clock()
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
suf="_le"
col="a"
df[col+suf] = le.fit_transform(df[col])
dic = dict(Zip(le.classes_, le.transform(le.classes_)))
col='b'
df[col+suf]=df[col].map(dic).fillna(dic["c"]).astype(int)
print(time.clock()-t)

#---
#pandas category

t=time.clock()
df["d"] = df["a"].astype('category').cat.codes
dic =df["a"].astype('category').cat.categories.tolist()
df['f']=df['b'].astype('category',categories=dic).fillna("c").cat.codes
df.dtypes
print(time.clock()-t)
2
Yury

S'il s'agit simplement de former et de tester un modèle, pourquoi ne pas simplement étiqueter en code l'ensemble de données. Et utilisez ensuite les classes générées à partir de l'objet encodeur.

encoder = LabelEncoder()
encoder.fit_transform(df["label"])
train_y = encoder.transform(train_y)
test_y = encoder.transform(test_y)
1
Namrata Tolani

J'ai récemment rencontré ce problème et j'ai pu trouver une solution assez rapide au problème. Ma réponse résout un peu plus que ce problème, mais cela fonctionnera également pour votre problème. (Je trouve que c'est plutôt cool)

Je travaille avec des trames de données Pandas et à l’origine, j’ai utilisé la méthode sklearns labelencoder () pour coder mes données, que je choisirais ensuite d’utiliser dans d’autres modules de mon programme.

Cependant, l'encodeur d'étiquettes dans le prétraitement de sklearn n'a pas la possibilité d'ajouter de nouvelles valeurs à l'algorithme d'encodage. J'ai résolu le problème de l'encodage de plusieurs valeurs et de l'enregistrement des valeurs de mappage ainsi que de la possibilité d'ajouter de nouvelles valeurs à l'encodeur (voici un aperçu de ce que j'ai fait):

encoding_dict = dict()
for col in cols_to_encode:
    #get unique values in the column to encode
    values = df[col].value_counts().index.tolist()

    # create a dictionary of values and corresponding number {value, number}
    dict_values = {value: count for value, count in Zip(values, range(1,len(values)+1))}

    # save the values to encode in the dictionary
    encoding_dict[col] = dict_values

    # replace the values with the corresponding number from the dictionary
    df[col] = df[col].map(lambda x: dict_values.get(x))

Ensuite, vous pouvez simplement enregistrer le dictionnaire dans un fichier JSON et le récupérer et ajouter la valeur de votre choix en ajoutant une nouvelle valeur et la valeur entière correspondante.

Je vais expliquer un raisonnement derrière en utilisant map () au lieu de replace (). J'ai trouvé que l'utilisation de pandas replace () prenait plus d'une minute pour parcourir environ 117 000 lignes de code. L'utilisation de la carte a porté ce temps à un peu plus de 100 ms.

TLDR: au lieu d’utiliser le prétraitement de Sklearns, travaillez avec votre cadre de données en créant un dictionnaire de mappage et en mappant vous-même les valeurs.

0
Ethan Kulla