Sklearn.LabelEncoder с невидимыми ранее значениями
Если sklearn.LabelEncoder
установлен на обучающем наборе, он может сломаться, если он встретит новые значения при использовании в тестовом наборе.
Единственное решение, которое я мог бы предложить для этого, - сопоставить все новое в тестовом наборе (т.е. Не принадлежащее к существующему классу) до "<unknown>"
, а затем явно добавить соответствующий класс в LabelEncoder
:
# train and test are pandas.DataFrame 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])
Это работает, но есть ли лучшее решение?
Обновить
Как замечает @sapo_cosmico в комментарии, кажется, что выше не работает больше, учитывая то, что я предполагаю, это изменение реализации в LabelEncoder.transform
, которое теперь, похоже, использует np.searchsorted
(я не знаю, было ли это случай раньше). Поэтому вместо добавления класса <unknown>
в список уже выделенных классов LabelEncoder
его необходимо вставить в отсортированном порядке:
import bisect
le_classes = le.classes_.tolist()
bisect.insort_left(le_classes, '<unknown>')
le.classes_ = le_classes
Однако, поскольку это кажется довольно неуклюжим в целом, я уверен, что для этого есть лучший подход.
Ответы
Ответ 1
В результате этой проблемы с невидимыми данными я перешел на Pandas 'get_dummies.
- создать манекены на обучающих данных
dummy_train = pd.get_dummies(train)
- создать манекены в новых (невидимых данных)
dummy_new = pd.get_dummies(new_data)
- переиндексировать новые данные в столбцы данных обучения, заполняя недостающие значения 0
dummy_new.reindex(columns = dummy_train.columns, fill_value=0)
Фактически любые новые функции, которые являются категориальными, не будут входить в классификатор, но я думаю, что это не должно вызывать проблем, поскольку он не знал бы, что с ними делать.
Ответ 2
LabelEncoder - это в основном словарь. Вы можете извлечь и использовать его для будущего кодирования:
from sklearn.preprocessing import LabelEncoder
le = preprocessing.LabelEncoder()
le.fit(X)
le_dict = dict(zip(le.classes_, le.transform(le.classes_)))
Получить метку для одного нового элемента, если элемент отсутствует, установить значение как неизвестное
le_dict.get(new_item, '<Unknown>')
Получить метки для столбца Dataframe:
df[your_col].apply(lambda x: le_dict.get(x, <unknown_value>))
Ответ 3
У меня создается впечатление, что то, что вы сделали, очень похоже на то, что делают другие люди, столкнувшись с этой ситуацией.
Было несколько попыток добавить возможность кодирования невидимых меток LabelEncoder (особенно особенно https://github.com/scikit-learn/scikit-learn/pull/3483 и https://github.com/scikit-learn/scikit-learn/pull/3599), но изменение существующего поведения на самом деле сложнее, чем кажется на первый взгляд.
На данный момент похоже, что обработка надписей "вне словарного запаса" предоставляется отдельным пользователям scikit-learn.
Ответ 4
Я создал класс для поддержки этого. Если у вас есть новый ярлык, он будет назначен как неизвестный класс.
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)
Пример использования:
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))
Ответ 5
Я знаю двух разработчиков, которые работают над созданием оберток вокруг трансформаторов и конвейеров Sklearn. Они имеют 2 надежных преобразователя кодера (один фиктивный и один кодер ярлыков), которые могут обрабатывать невидимые значения. Вот документация к их библиотеке skutil. Найдите skutil.preprocessing.OneHotCategoricalEncoder
или skutil.preprocessing.SafeLabelEncoder
. В их SafeLabelEncoder()
невидимые значения автоматически закодированы до 999999.
Ответ 6
Я пытался решить эту проблему и нашел два удобных способа кодирования категориальных данных из наборов поездов и тестов с использованием и без использования LabelEncoder. Новые категории заполнены некоторыми известными cetegory "c" (например, "другие" или "отсутствующие"). Первый метод работает быстрее. Надеюсь, это вам поможет.
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)
Ответ 7
Недавно я столкнулся с этой проблемой и смог придумать довольно быстрое решение этой проблемы. Мой ответ решает немного больше, чем просто эта проблема, но она легко будет работать и для вашей проблемы. (Я думаю, что это довольно круто)
Я работаю с кадрами данных pandas и изначально использовал sklearns labelencoder() для кодирования моих данных, которые я бы затем разборки, чтобы использовать в других модулях моей программы.
Однако кодер метки в предварительной обработке sklearn не имеет возможности добавлять новые значения в алгоритм кодирования. Я решил проблему кодирования нескольких значений и сохранения значений отображения AS WELL как возможность добавлять новые значения в кодировщик (здесь грубая схема того, что я сделал):
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))
Затем вы можете просто сохранить словарь в файле JSON и сможете его вытащить и добавить любое значение, добавив новое значение и соответствующее целочисленное значение.
Я объясню некоторые соображения по поводу использования map() вместо replace(). Я обнаружил, что использование функции pandas replace() заняло более минуты, чтобы пронести около 117 000 строк кода. Использование карты принесло это время чуть более 100 мс.
TL;DR: вместо предварительной обработки sklearns просто работайте с вашим фреймворком данных, создавая картографический словарь и сами определяя значения.
Ответ 8
Я столкнулся с той же проблемой и понял, что мой кодировщик как-то смешивает значения в моем столбце данных. Допустим, вы запускаете кодировщик для нескольких столбцов, и при назначении номеров меткам кодировщик автоматически записывает в него цифры, а иногда оказывается, что у вас есть два разных столбца с одинаковыми значениями. Чтобы решить эту проблему, я создал экземпляр LabelEncoder() для каждого столбца в моем панде DataFrame, и у меня получился хороший результат.
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))
Привет !!
Ответ 9
Если речь идет только о подготовке и тестировании модели, почему бы не просто labelencode на весь набор данных. А затем используйте сгенерированные классы из объекта encoder.
encoder = LabelEncoder()
encoder.fit_transform(df["label"])
train_y = encoder.transform(train_y)
test_y = encoder.transform(test_y)