sklearn.LabelEncoder以前从未见过的值

cja*_*vin 57 python scikit-learn

如果a sklearn.LabelEncoder已安装在训练集上,如果在测试集上使用时遇到新值,则可能会中断.

我能想到的唯一解决方案是将测试集中的所有新内容(即不属于任何现有类)映射到"<unknown>",然后在后面显式添加相应的类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])
Run Code Online (Sandbox Code Playgroud)

这有效,但有更好的解决方案吗?

更新

正如@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
Run Code Online (Sandbox Code Playgroud)

然而,总而言之,这感觉非常笨重,我确信有更好的方法.

sap*_*ico 37

由于这个看不见的数据问题,我最终切换到了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)

实际上,任何分类的新功能都不会进入分类器,但我认为这不会引起问题,因为它不知道如何处理它们.

  • 而不是`dummies.columns`,你的意思是'dummy_train.columns`? (2认同)
  • @matthiash一般我会在管道对象中使用它.我不能说我对酸洗有足够的了解,我通常会避免它,但是冒险猜测管道中的状态应该保持并保留这些列 (2认同)

Ran*_*ani 13

LabelEncoder基本上是字典。您可以提取并将其用于将来的编码:

from sklearn.preprocessing import LabelEncoder

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

le_dict = dict(zip(le.classes_, le.transform(le.classes_)))
Run Code Online (Sandbox Code Playgroud)

检索单个新项目的标签,如果缺少项目,则将值设置为unknown

le_dict.get(new_item, '<Unknown>')
Run Code Online (Sandbox Code Playgroud)

检索数据框列的标签:

df[your_col].apply(lambda x: le_dict.get(x, <unknown_value>))
Run Code Online (Sandbox Code Playgroud)


blu*_*ers 13

scikit-learn0.24.0 开始,您不应该LabelEncoder在您的功能上使用(并且应该使用OrdinalEncoder),因此它的名称为LabelEncoder

由于模型永远不会预测在其训练数据中未出现的标签,因此LabelEncoder不应支持未知标签。

但是,对于特征,它是不同的,因为显然您可能会遇到训练集中从未见过的不同类别。在 0.24.0 版本中,向scikit-learn提供了两个新参数OrdinalEncoder,允许它对未知类别进行编码。

OrdinalEncoder对特征进行编码并将未知类别转换为值的示例用法-1

from sklearn.preprocessing import OrdinalEncoder

# Create encoder
ordinal_encoder = OrdinalEncoder(handle_unknown='use_encoded_value',
                                 unknown_value=-1)

# Fit on training data
ordinal_encoder.fit(np.array([1,2,3,4,5]).reshape(-1, 1))

# Transform, notice that 0 and 6 are values that were never seen before
ordinal_encoder.transform(np.array([0,1,2,3,4,5,6]).reshape(-1, 1))
Run Code Online (Sandbox Code Playgroud)

输出:

array([[-1.],
       [ 0.],
       [ 1.],
       [ 2.],
       [ 3.],
       [ 4.],
       [-1.]])
Run Code Online (Sandbox Code Playgroud)

  • 这是这个问题的实际答案。 (5认同)

Eth*_*lla 9

我最近遇到了这个问题,并且能够想出一个非常快速的解决方案。我的答案解决的不仅仅是这个问题,但它也很容易解决您的问题。(我觉得挺好看的)

我正在使用 Pandas 数据帧,最初使用 sklearns labelencoder() 对我的数据进行编码,然后我将在程序中的其他模块中使用pickle。

但是,sklearn 的预处理中的标签编码器不具备向编码算法添加新值的能力。我解决了编码多个值并将映射值保存为能够通过以下方式向编码器添加新值的问题(这是我所做的粗略概述):

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))
Run Code Online (Sandbox Code Playgroud)

然后,您可以简单地将字典保存到 JSON 文件中,并能够通过添加新值和相应的整数值来提取它并添加您想要的任何值。

我将解释使用 map() 而不是 replace() 背后的一些原因。我发现使用 pandas replace() 函数需要花费一分钟的时间来遍历大约 117,000 行代码。使用地图使该时间刚刚超过 100 毫秒。

TLDR:不是使用 sklearns 预处理,而是通过制作映射字典并自己映射值来处理您的数据框。


lmj*_*ns3 7

我得到的印象是,你所做的与其他人面对这种情况时所做的非常相似.

已经做了一些努力来添加将看不见的标签编码到LabelEncoder的能力(特别参见https://github.com/scikit-learn/scikit-learn/pull/3483https://github.com/scikit-learn/ scikit-learn/pull/3599),但改变现有行为实际上比初看起来更难.

现在看起来处理"词汇外"标签似乎留给了scikit-learn的个人用户.


Jas*_*son 6

我认识两个开发人员正在致力于围绕 Transformer 和 Sklearn 管道构建包装器。它们有 2 个强大的编码器变压器(一个虚拟编码器和一个标签编码器),可以处理看不见的值。这是他们的 skutil 库的文档。搜索skutil.preprocessing.OneHotCategoricalEncoderskutil.preprocessing.SafeLabelEncoder。在它们中SafeLabelEncoder(),看不见的值被自动编码为 999999。

  • 他们没有尝试提交“sklearn”本身吗?这是一个普遍问题。显然我们参数化了default_label_value。 (2认同)

Vin*_*san 6

我创建了一个类来支持这一点。如果您有一个新标签,它将把它分配为未知类。

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)
Run Code Online (Sandbox Code Playgroud)

样本用法:

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))
Run Code Online (Sandbox Code Playgroud)