python - 用于分类功能的LabelEncoder?

标签 python machine-learning scikit-learn correlation feature-engineering

这可能是一个初学者的问题,但是我已经看到很多人使用LabelEncoder()用常规替换类别变量。很多人一次通过传递多列来使用此功能,但是我对某些功能中的错误序数及其对模型的影响会产生疑问。这是一个例子:

输入

import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder

a = pd.DataFrame(['High','Low','Low','Medium'])
le = LabelEncoder()
le.fit_transform(a)

输出
array([0, 1, 1, 2], dtype=int64)

如您所见,序数值未正确映射,因为我的LabelEncoder仅关心列/数组中的顺序(应该为High = 1,Med = 2,Low = 3,反之亦然)。错误的严重映射会如何影响模型?除了OrdinalEncoder()之外,还有其他简便的方法可以正确映射这些值吗?

最佳答案

TL; DR :使用 LabelEncoder 对任何类型的特征进行序数编码是一个坏主意!

实际上,这在文档中有明确说明,其中提到,顾名思义,该编码方法旨在对标签进行编码:

This transformer should be used to encode target values, i.e. y, and not the input X.


正如您在问题中正确指出的那样,将ordinal feature的固有序数映射到错误的比例将对模型的性能产生非常负面的影响(即与特征的相关性成比例)。同样,categorical feature也是如此,只是原始特征没有序数。
一种思考的直观方式是decision tree设置其边界。在训练过程中,决策树将学习要在每个节点上设置的最佳功能以及最佳阈值,根据这些值,看不见的样本将沿着一个分支或另一个分支前进。
如果我们使用简单的LabelEncoder编码序数特征,则可能会导致某个特征说1表示温暖,2可能转换为热,而0表示沸腾。在这种情况下,结果最终将是一棵不必要的大量拆分树,因此,对于更简单的建模而言,其复杂性将大大提高。
相反,正确的方法是使用 OrdinalEncoder ,并为序数特征定义适当的映射方案。或在具有分类功能的情况下,我们应该查看 OneHotEncoder Category Encoders中可用的各种编码器。

尽管实际上了解到为什么这是一个坏主意,将比仅凭文字更直观。
让我们使用一个简单的示例来说明上述内容,该示例由两个序数功能组成,其中包含一个范围,该范围包括学生准备考试的时数和所有以前作业的平均成绩,以及一个目标变量,指示考试是否已过或不。我已经将数据框的列定义为pd.Categorical:
df = pd.DataFrame(
        {'Hours of dedication': pd.Categorical(
              values =  ['25-30', '20-25', '5-10', '5-10', '40-45', 
                         '0-5', '15-20', '20-25', '30-35', '5-10',
                         '10-15', '45-50', '20-25'],
              categories=['0-5', '5-10', '10-15', '15-20', 
                          '20-25', '25-30','30-35','40-45', '45-50']),

         'Assignments avg grade': pd.Categorical(
             values =  ['B', 'C', 'F', 'C', 'B', 
                        'D', 'C', 'A', 'B', 'B', 
                        'B', 'A', 'D'],
             categories=['F', 'D', 'C', 'B','A']),

         'Result': pd.Categorical(
             values = ['Pass', 'Pass', 'Fail', 'Fail', 'Pass', 
                       'Fail', 'Fail','Pass','Pass', 'Fail', 
                       'Fail', 'Pass', 'Pass'], 
             categories=['Fail', 'Pass'])
        }
    )
如前所述,将类别列定义为 Pandas 的类别的好处是我们可以在其类别之间建立顺序。这允许基于已建立的顺序而不是词法排序更快地进行排序。它也可以用作一种简单的方法来根据其顺序获取不同类别的代码。
因此,我们将使用的数据帧如下所示:
print(df.head())

  Hours_of_dedication   Assignments_avg_grade   Result
0               20-25                       B     Pass
1               20-25                       C     Pass
2                5-10                       F     Fail
3                5-10                       C     Fail
4               40-45                       B     Pass
5                 0-5                       D     Fail
6               15-20                       C     Fail
7               20-25                       A     Pass
8               30-35                       B     Pass
9                5-10                       B     Fail
可以通过以下方式获得相应的类别代码:
X = df.apply(lambda x: x.cat.codes)
X.head()

   Hours_of_dedication   Assignments_avg_grade   Result
0                    4                       3        1
1                    4                       2        1
2                    1                       0        0
3                    1                       2        0
4                    7                       3        1
5                    0                       1        0
6                    3                       2        0
7                    4                       4        1
8                    6                       3        1
9                    1                       3        0
现在,让我们拟合 DecisionTreeClassifier ,看看树如何定义拆分:
from sklearn import tree

dt = tree.DecisionTreeClassifier()
y = X.pop('Result')
dt.fit(X, y)
我们可以使用 plot_tree 可视化树结构:
t = tree.plot_tree(dt, 
                   feature_names = X.columns,
                   class_names=["Fail", "Pass"],
                   filled = True,
                   label='all',
                   rounded=True)
enter image description here
这就是全部??好吧... 是的! 我实际上已经以某种方式设置功能,使得“奉献时数”功能与是否通过考试之间存在这种简单而明显的关系,这清楚地表明,该问题应该非常容易建模。

现在,让我们尝试通过使用我们可以通过LabelEncoder获得的编码方案直接编码所有特征来进行相同操作,因此不考虑特征的实际顺序,而只是随机分配一个值:
df_wrong = df.copy()
df_wrong['Hours_of_dedication'].cat.set_categories(
             ['0-5','40-45', '25-30', '10-15', '5-10', '45-50','15-20', 
              '20-25','30-35'], inplace=True)
df_wrong['Assignments_avg_grade'].cat.set_categories(
             ['A', 'C', 'F', 'D', 'B'], inplace=True)
rcParams['figure.figsize'] = 14,18
X_wrong = df_wrong.drop(['Result'],1).apply(lambda x: x.cat.codes)
y = df_wrong.Result

dt_wrong = tree.DecisionTreeClassifier()
dt_wrong.fit(X_wrong, y)

t = tree.plot_tree(dt_wrong, 
                   feature_names = X_wrong.columns,
                   class_names=["Fail", "Pass"],
                   filled = True,
                   label='all',
                   rounded=True)
enter image description here
不出所料,树结构比我们要建模的简单问题复杂得多。为了使树正确地预测所有训练样本,它已扩展到单个节点就足够的4深度为止。
这意味着分类器可能会过拟合,因为我们正在极大地增加复杂性。通过修剪树并调整必要的参数以防止过度拟合,我们也无法解决问题,因为我们通过错误地编码特征而添加了过多噪声。
因此,总而言之,一旦对特征进行编码,保留特征的普遍性至关重要,否则如本例所示,我们将失去其所有可预测的功能,而只会给模型增加噪声。

关于python - 用于分类功能的LabelEncoder?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61217713/

相关文章:

python - 在这个例子中如何通过tensorflow进行回归?

r - 在 R 中绘制决策树(插入符号)

python - 使用具有稀疏和密集矩阵的 sklearn RandomizedPCA 时的不同结果

python - sciklearn ValueError : operands could not be broadcast together with shapes 的预测问题

python - 意外的打印行为 Python 3

python - 如何使用 tf.case 转换 tensorflow 中的一组值?

python - 执行命令时路径未添加到 $PATH 变量

r - R 中 SVM 调优错误

apache-spark - Spark 线性回归特征哈希

python - 为什么 sklearn 的套索系数不等于线性回归系数?