Wenn a sklearn.LabelEncoder
in ein Trainingsset eingebaut wurde, kann es brechen, wenn es bei Verwendung in einem Testset auf neue Werte stößt.
Die einzige Lösung, die ich dafür finden könnte, besteht darin, alles Neue im Testsatz (dh keine zu einer vorhandenen Klasse gehörend) zuzuordnen "<unknown>"
und anschließend explizit eine entsprechende Klasse hinzuzufügen LabelEncoder
:
# 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])
Das funktioniert, aber gibt es eine bessere Lösung?
Aktualisieren
Wie @sapo_cosmico in einem Kommentar hervorhebt, scheint das oben Gesagte nicht mehr zu funktionieren, da ich davon ausgehe, dass es sich um eine Implementierungsänderung handelt LabelEncoder.transform
, die jetzt zu verwenden scheint np.searchsorted
(ich weiß nicht, ob dies zuvor der Fall war). Anstatt die <unknown>
Klasse an die LabelEncoder
Liste der bereits extrahierten Klassen anzuhängen, muss sie in sortierter Reihenfolge eingefügt werden:
import bisect
le_classes = le.classes_.tolist()
bisect.insort_left(le_classes, '<unknown>')
le.classes_ = le_classes
Da sich dies jedoch insgesamt ziemlich klobig anfühlt, bin ich mir sicher, dass es dafür einen besseren Ansatz gibt.
quelle
Antworten:
Aufgrund dieses Problems mit unsichtbaren Daten wechselte ich schließlich zu Pandas ' get_dummies .
dummy_train = pd.get_dummies(train)
dummy_new = pd.get_dummies(new_data)
dummy_new.reindex(columns = dummy_train.columns, fill_value=0)
Tatsächlich werden alle neuen Funktionen, die kategorisch sind, nicht in den Klassifikator aufgenommen, aber ich denke, das sollte keine Probleme verursachen, da es nicht wissen würde, was mit ihnen zu tun ist.
quelle
dummies.columns
du stattdessendummy_train.columns
?dummy_train.columns
in einer eigenen Datei?LabelEncoder ist im Grunde ein Wörterbuch. Sie können es extrahieren und für zukünftige Codierungen verwenden:
from sklearn.preprocessing import LabelEncoder le = preprocessing.LabelEncoder() le.fit(X) le_dict = dict(zip(le.classes_, le.transform(le.classes_)))
Rufen Sie die Bezeichnung für ein einzelnes neues Element ab. Wenn das Element fehlt, setzen Sie den Wert als unbekannt
le_dict.get(new_item, '<Unknown>')
Beschriftungen für eine Dataframe-Spalte abrufen:
df[your_col] = df[your_col].apply(lambda x: le_dict.get(x, <unknown_value>))
quelle
Ich habe eine Klasse erstellt, um dies zu unterstützen. Wenn Sie ein neues Etikett haben, wird es als unbekannte Klasse zugewiesen.
from sklearn.preprocessing import LabelEncoder import numpy as np class LabelEncoderExt(object): def __init__(self): """ It differs from LabelEncoder by handling new classes and providing a value for it [Unknown] Unknown will be added in fit and transform will take care of new item. It gives unknown class id """ self.label_encoder = LabelEncoder() # self.classes_ = self.label_encoder.classes_ def fit(self, data_list): """ This will fit the encoder for all the unique values and introduce unknown value :param data_list: A list of string :return: self """ self.label_encoder = self.label_encoder.fit(list(data_list) + ['Unknown']) self.classes_ = self.label_encoder.classes_ return self def transform(self, data_list): """ This will transform the data_list to id list where the new values get assigned to Unknown class :param data_list: :return: """ new_data_list = list(data_list) for unique_item in np.unique(data_list): if unique_item not in self.label_encoder.classes_: new_data_list = ['Unknown' if x==unique_item else x for x in new_data_list] return self.label_encoder.transform(new_data_list)
Die Beispielverwendung:
country_list = ['Argentina', 'Australia', 'Canada', 'France', 'Italy', 'Spain', 'US', 'Canada', 'Argentina, ''US'] label_encoder = LabelEncoderExt() label_encoder.fit(country_list) print(label_encoder.classes_) # you can see new class called Unknown print(label_encoder.transform(country_list)) new_country_list = ['Canada', 'France', 'Italy', 'Spain', 'US', 'India', 'Pakistan', 'South Africa'] print(label_encoder.transform(new_country_list))
quelle
Ich habe den Eindruck, dass das, was Sie getan haben, dem sehr ähnlich ist, was andere Menschen in dieser Situation tun.
Es wurden einige Anstrengungen unternommen, um die Möglichkeit zum Codieren nicht sichtbarer Beschriftungen zum LabelEncoder hinzuzufügen (siehe insbesondere https://github.com/scikit-learn/scikit-learn/pull/3483 und https://github.com/scikit-learn/). scikit-learn / pull / 3599 ), aber das bestehende Verhalten zu ändern ist tatsächlich schwieriger, als es auf den ersten Blick scheint.
Im Moment sieht es so aus, als ob der Umgang mit "Out-of-Vocabulary" -Labels einzelnen Benutzern von Scikit-Learn überlassen bleibt.
quelle
Ich bin kürzlich auf dieses Problem gestoßen und konnte eine ziemlich schnelle Lösung für das Problem finden. Meine Antwort löst ein wenig mehr als nur dieses Problem, aber es wird auch für Ihr Problem leicht funktionieren. (Ich finde es ziemlich cool)
Ich arbeite mit Pandas-Datenrahmen und habe ursprünglich den sklearns labelencoder () verwendet, um meine Daten zu codieren, die ich dann für andere Module in meinem Programm verwenden würde.
Der Label-Encoder in der Vorverarbeitung von sklearn kann dem Codierungsalgorithmus jedoch keine neuen Werte hinzufügen. Ich habe das Problem des Codierens mehrerer Werte und des Speicherns der Zuordnungswerte sowie des Hinzufügens neuer Werte zum Encoder durch gelöst (hier eine grobe Übersicht über meine Arbeit):
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))
Anschließend können Sie das Wörterbuch einfach in einer JSON-Datei speichern und es abrufen und einen beliebigen Wert hinzufügen, indem Sie einen neuen Wert und den entsprechenden ganzzahligen Wert hinzufügen.
Ich werde einige Gründe für die Verwendung von map () anstelle von replace () erläutern. Ich fand heraus, dass die Verwendung der pandas replace () -Funktion über eine Minute dauerte, um ungefähr 117.000 Codezeilen zu durchlaufen. Die Verwendung der Karte brachte diese Zeit auf etwas mehr als 100 ms.
TLDR: Anstatt die Vorverarbeitung von sklearns zu verwenden, arbeiten Sie einfach mit Ihrem Datenrahmen, indem Sie ein Zuordnungswörterbuch erstellen und die Werte selbst zuordnen.
quelle
Ich kenne zwei Entwickler, die daran arbeiten, Wrapper um Transformatoren und Sklearn-Pipelines zu bauen. Sie verfügen über 2 robuste Encodertransformatoren (einen Dummy- und einen Label-Encoder), die unsichtbare Werte verarbeiten können. Hier ist die Dokumentation zu ihrer Skutil-Bibliothek. Suche nach
skutil.preprocessing.OneHotCategoricalEncoder
oderskutil.preprocessing.SafeLabelEncoder
. In ihrenSafeLabelEncoder()
werden unsichtbare Werte automatisch auf 999999 codiert.quelle
sklearn
selbst zu unterwerfen ? Dies ist ein universelles Problem. Offensichtlich parametrisieren wir den default_label_value.Ich habe versucht, dieses Problem zu lösen, und zwei praktische Möglichkeiten gefunden, kategoriale Daten aus Zug- und Testsätzen mit und ohne LabelEncoder zu codieren. Neue Kategorien werden mit einer bekannten Kategorie "c" (wie "andere" oder "fehlende") gefüllt. Die erste Methode scheint schneller zu funktionieren. Hoffe das wird dir helfen.
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)
quelle
#pandas category
Ansatz gibt die Zeiledf['f']=df['b'].astype('category',categories=dic)........
diesen Fehler aus:TypeError: astype() got an unexpected keyword argument 'categories'
Hier ist mit der Verwendung der relativ neuen Funktion von Pandas. Die Hauptmotivation ist, dass maschinelle Lernpakete wie 'lightgbm' Pandas-Kategorien als Feature-Spalten akzeptieren können und in einigen Situationen besser sind als die Verwendung einer Onehoten-Codierung. In diesem Beispiel gibt der Transformator eine Ganzzahl zurück, kann aber auch den Datumstyp ändern und durch die nicht sichtbaren kategorialen Werte durch -1 ersetzen.
from collections import defaultdict from sklearn.base import BaseEstimator,TransformerMixin from pandas.api.types import CategoricalDtype import pandas as pd import numpy as np class PandasLabelEncoder(BaseEstimator,TransformerMixin): def __init__(self): self.label_dict = defaultdict(list) def fit(self, X): X = X.astype('category') cols = X.columns values = list(map(lambda col: X[col].cat.categories, cols)) self.label_dict = dict(zip(cols,values)) # return as category for xgboost or lightgbm return self def transform(self,X): # check missing columns missing_col=set(X.columns)-set(self.label_dict.keys()) if missing_col: raise ValueError('the column named {} is not in the label dictionary. Check your fitting data.'.format(missing_col)) return X.apply(lambda x: x.astype('category').cat.set_categories(self.label_dict[x.name]).cat.codes.astype('category').cat.set_categories(np.arange(len(self.label_dict[x.name])))) def inverse_transform(self,X): return X.apply(lambda x: pd.Categorical.from_codes(codes=x.values, categories=self.label_dict[x.name])) dff1 = pd.DataFrame({'One': list('ABCC'), 'Two': list('bccd')}) dff2 = pd.DataFrame({'One': list('ABCDE'), 'Two': list('debca')}) enc=PandasLabelEncoder() enc.fit_transform(dff1)
One Two 0 0 0 1 1 1 2 2 1 3 2 2
One Two 0 0 2 1 1 -1 2 2 0 3 -1 1 4 -1 -1
One Two 0 A d 1 B NaN 2 C b 3 NaN c 4 NaN NaN
quelle
Ich habe das gleiche Problem und stellte fest, dass mein Encoder irgendwie Werte in meinem Spalten-Datenrahmen mischte. Nehmen wir an, Sie führen Ihren Encoder für mehrere Spalten aus. Wenn Sie Beschriftungen Nummern zuweisen, schreibt der Encoder automatisch Zahlen darauf und stellt manchmal fest, dass Sie zwei verschiedene Spalten mit ähnlichen Werten haben. Um das Problem zu lösen, habe ich für jede Spalte in meinem Pandas DataFrame eine Instanz von LabelEncoder () erstellt, und ich habe ein schönes Ergebnis.
encoder1 = LabelEncoder() encoder2 = LabelEncoder() encoder3 = LabelEncoder() df['col1'] = encoder1.fit_transform(list(df['col1'].values)) df['col2'] = encoder2.fit_transform(list(df['col2'].values)) df['col3'] = encoder3.fit_transform(list(df['col3'].values))
Grüße!!
quelle
LabelEncoder () sollte nur für die Codierung von Zieletiketten verwendet werden. Verwenden Sie zum Codieren kategorialer Features OneHotEncoder (), das unsichtbare Werte verarbeiten kann: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html#sklearn.preprocessing.OneHotEncoder
quelle
Wenn noch jemand danach sucht, ist hier mein Fix.
Angenommen, Sie haben
enc_list
: Liste der bereits codierten Variablennamenenc_map
: das Wörterbuch mit Variablen ausenc_list
und die entsprechende codierte Zuordnungdf
: Datenrahmen mit Werten einer Variablen, die nicht in vorhanden sindenc_map
Dies funktioniert unter der Annahme, dass Sie in den codierten Werten bereits die Kategorie "NA" oder "Unbekannt" haben
for l in enc_list: old_list = enc_map[l].classes_ new_list = df[l].unique() na = [j for j in new_list if j not in old_list] df[l] = df[l].replace(na,'NA')
quelle
Wenn es nur darum geht, ein Modell zu trainieren und zu testen, warum nicht einfach den gesamten Datensatz beschriften? Verwenden Sie dann die generierten Klassen aus dem Encoder-Objekt.
encoder = LabelEncoder() encoder.fit_transform(df["label"]) train_y = encoder.transform(train_y) test_y = encoder.transform(test_y)
quelle