python - LabelEncoder 用于分类特征?
问题描述
这可能是一个初学者的问题,但我看到很多人使用 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:使用 a对任何类型的特征进行序数LabelEncoder
编码都是一个坏主意!
这实际上在文档中明确说明,其中提到顾名思义,这种编码方法旨在对标签进行编码:
这个转换器应该用于编码目标值,即
y
,而不是输入X
。
正如您在问题中正确指出的那样,将序数特征的固有序数映射到错误的比例将对模型的性能产生非常负面的影响(即与特征的相关性成正比)。这同样适用于分类特征,只是原始特征没有序数。
一种直观的思考方式是决策树设置边界的方式。在训练期间,决策树将学习在每个节点上设置的最佳特征,以及一个最佳阈值,根据这些值,看不见的样本将跟随一个分支或另一个分支。
如果我们使用 simple 对序数特征进行编码LabelEncoder
,则可能导致特征表示1
表示暖,2
这可能会转换为热,而0
表示沸腾。在这种情况下,结果最终将是一棵具有不必要的大量分裂的树,因此对于应该更简单建模的东西来说复杂性要高得多。
相反,正确的方法是使用OrdinalEncoder
, 并为序数特征定义适当的映射方案。或者在具有分类特征的情况下,我们应该查看Category EncodersOneHotEncoder
中可用的各种编码器。
尽管实际了解为什么这是一个坏主意将比仅凭文字更直观。
让我们用一个简单的例子来说明上述情况,由两个序数特征组成,其中包含一个范围,即学生准备考试所花费的小时数和所有先前作业的平均成绩,以及一个指示考试是否已通过的目标变量或不。我已将数据框的列定义为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
现在让我们拟合 a 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)
这就是全部??嗯……是的!实际上,我已经以这样一种方式设置了特征,即在奉献时间特征与考试是否通过之间存在这种简单而明显的关系,从而清楚地表明问题应该很容易建模。
现在让我们尝试通过使用我们可以通过例如 a 获得的编码方案直接编码所有特征来做同样的事情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)
正如预期的那样,对于我们试图建模的简单问题,树结构比必要的复杂得多。为了让树正确地预测所有训练样本,它已经扩展直到深度为4
,此时单个节点就足够了。
这意味着分类器可能会过度拟合,因为我们正在大幅增加复杂性。通过修剪树和调整必要的参数来防止过度拟合,我们也没有解决问题,因为我们通过错误地编码特征添加了太多噪声。
因此,总而言之,在对特征进行编码后保持特征的顺序是至关重要的,否则正如这个例子所表明的那样,我们将失去所有可预测的能力,只会给我们的模型添加噪声。
推荐阅读
- python - 执行非 Python (MQL5) 文件的 Python 命令?
- android - 单击并按住底部导航视图上的按钮会在其周围创建一个奇怪的“阴影”并显示一个黑框
- ios - Swift:查找和绘制两个日期之间的小时数
- apache-kafka - 当前不支持密钥格式“AVRO”
- java - 为什么当我在未显示的谷歌地图片段上插入标记时?
- mysql - 为什么 INSERT IGNORE 插入具有相同主键的行?
- nginx - 如何从 nginx 反向代理转发 docker 容器之间的请求以响应 nginx 中的路由?
- java - 在 Wildfly 服务器组中完成繁重的进程后,Java 堆未收集垃圾
- c++ - 浮点精度在测试变量是否达到界限时的影响
- azure - 504 网关超时错误 Keycloak 与 Microsoft SSO