Подтвердить что ты не робот

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

Однако, поскольку это кажется довольно неуклюжим в целом, я уверен, что для этого есть лучший подход.

4b9b3361

Ответ 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)