sklearn.LabelEncoder以前从未见过的值

时间:2014-01-11 01:54:34

标签: python scikit-learn

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

我能想到的唯一解决方案是将测试集中的所有新内容(即不属于任何现有类)映射到"<unknown>",然后将相应的类显式添加到{{1之后:

LabelEncoder

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

更新

正如@sapo_cosmico在评论中指出的那样,似乎上述内容不再适用,因为我假设# 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]) 中的实现更改,现在似乎使用LabelEncoder.transform(I不知道以前是否是这种情况)。因此,不是将np.searchsorted类附加到已经提取的类的<unknown>列表中,而是需要按排序顺序插入:

LabelEncoder

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

13 个答案:

答案 0 :(得分:32)

我最终转向了Pandas&#39; 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)

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

答案 1 :(得分:11)

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

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

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

使用 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))

输出:

array([[-1.],
       [ 0.],
       [ 1.],
       [ 2.],
       [ 3.],
       [ 4.],
       [-1.]])

答案 2 :(得分:7)

我的印象是,你所做的与其他人在遇到这种情况时所做的非常相似。

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

现在它看起来像处理&#34;词汇量不足&#34;标签留给了scikit-learn的个人用户。

答案 3 :(得分:4)

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>')

检索“数据框”列的标签:

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

答案 4 :(得分:3)

我知道两个开发人员正致力于围绕变压器和Sklearn管道构建包装器。它们有2个强大的编码器变压器(一个虚拟编码器和一个标签编码器),可以处理看不见的值。 Here is the documentation to their skutil library.搜索skutil.preprocessing.OneHotCategoricalEncoderskutil.preprocessing.SafeLabelEncoder。在SafeLabelEncoder()中,看不见的值会自动编码为999999。

答案 5 :(得分:2)

我试图解决这个问题,并找到了两种方便的方法来编码来自列车和测试集的分类数据,使用和不使用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)

答案 6 :(得分:2)

我最近遇到了这个问题,并且能够快速解决该问题。我的答案不仅仅解决了这个问题,还可以轻松解决您的问题。 (我认为它很酷)

我正在使用熊猫数据帧,并且最初使用sklearns labelencoder()编码我的数据,然后将其腌制以用于程序的其他模块中。

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

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行代码。使用map可以将时间缩短到100毫秒以上。

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

答案 7 :(得分:1)

如果仅用于训练和测试模型,为什么不对整个数据集进行标签编码。然后使用从编码器对象生成的类。

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

答案 8 :(得分:1)

我遇到了同样的问题,并意识到我的编码器以某种方式在列数据帧内混合了值。假设您将编码器运行了几列,并且为标签分配数字时,编码器会自动向其写入数字,有时还会发现您有两个具有相似值的不同列。我要解决的问题是为我的pandas DataFrame中的每一列创建一个LabelEncoder()实例,结果很不错。

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 :(得分:1)

此处使用了来自熊猫的相对较新的功能。这样做的主要动机是像“ lightgbm”这样的机器学习包可以将pandas类作为特征列,并且在某些情况下比使用onehotencoding更好。在此示例中,转换器返回一个整数,但也可以更改日期类型,并用-1替换为看不见的分类值。

ViewContext.RouteData.Values["controller"].ToString()
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
dff3=enc.transform(dff2)
dff3
    One Two
0   0   2
1   1   -1
2   2   0
3   -1  1
4   -1  -1
enc.inverse_transform(dff3)

答案 10 :(得分:0)

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

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))

答案 11 :(得分:0)

LabelEncoder()仅应用于目标标签编码。要编码分类特征,请使用OneHotEncoder(),它可以处理看不见的值: https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html#sklearn.preprocessing.OneHotEncoder

答案 12 :(得分:0)

如果有人仍在寻找它,这是我的解决方法。

说你拥有
enc_list:已经编码的变量名称列表
enc_map:字典包含enc_list中的变量和相应的编码映射
df:包含enc_map

中不存在的变量值的数据框

这将在您已在编码值中包含类别“ NA”或“未知”的情况下起作用

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')