记不清第几次帮人排查模型效果问题时,最后定位到的根因不是数据量不够、不是调参不到位,而是最开始那步“把类别变成数字”的编码方式选错了。前两天还有个朋友把城市名直接塞进LabelEncoder,喂给线性模型做预测,结果训练集上看着还行,一上线预测值出现了一堆负数,客户当场炸毛。这种情况在特征工程里特别典型:编码方式看起来只是数据格式转换,但它直接决定了模型能从特征里学到什么结构、学不到什么结构。
今天这篇就把One-Hot Encoding和LabelEncoder这两个最基础的编码方式彻底讲透:各自到底做了什么、为什么选错会影响模型效果、实际项目中怎么又快又准地做出选择。文章主要面向刚入坑特征工程的算法工程师,以及那些已经跑通模型但一直说不清“为什么换一种编码方式效果差这么多”的同学。
1. 一个“编码翻车”现场:血型当数值用,模型预测全是异常值
先说一个我实际遇到过的案例,这样后面讲原理时你能有个具体印象。
当时做的是医疗健康相关的用户画像模型,特征里有血型(A/B/AB/O)、居住城市、职业等十几个类别型字段。数据预处理的同学图省事,对所有类别列统一用了LabelEncoder,一个循环把全部非数值列都编码成 0、1、2、3……模型用的还是线性回归。结果训练完一测试,输出值里出现大量负数,甚至有些样本预测出的“健康评分”是 -800%。
当时我第一反应是看数据分布,结果发现问题的核心就在编码方式上。A、B、AB、O 四种血型被映射成了 0、1、2、3,线性模型拿到这四个数以后,会当真把它当成“血型数值”:AB型(编码为 2)和 O 型(编码为 3)之间的差值被解释成“血型每升高一个单位,健康评分下降多少”。这完全是无稽之谈。
而One-Hot Encoding就不会犯这个错。它会把这一个“血型”列拆成四个二值特征:is_A、is_B、is_AB、is_O,每个样本只有其中一个是 1,其余是 0。这样线性模型看到的是四个互斥的开关,而不是一条没有意义的数字轴。
1.1 编码前先想想:模型会怎么“理解”这个特征
很多人做特征工程会忽略一件事:同样的数据,线性模型和树模型对特征数值的“理解方式”完全不同。
线性模型的预测公式是 y = w1*x1 + w2*x2 + ... + b,它天然假设特征和输出之间存在“单调的、可加减的”关系。x1从 1 变成 2 带来的影响,就是让预测值增加一个w1。如果你把一个无序类别强行编码成1、2、3,模型就不得不强行学习这种“数字大小”带来的单调影响。血型从 0 变到 3,在所有样本里对应的其实是完全不同的、没有大小关系的四种类别,线性模型再怎么学,也只能学到一组“按数字大小排序”的权重,预测自然荒谬。
而树模型(决策树、随机森林、XGBoost、LightGBM)分裂时只看阈值:比如“x1 <= 0.5”“x1 <= 1.5”,本质上是在尝试把类别组合切开。它不像线性模型那样必须强行接受数字的大小含义,但问题是:LabelEncoder给类别分配的序号是随机的(完全取决于类别在数据里出现的先后顺序),这种随机顺序会直接影响树模型先切在哪里、后切在哪里。
1.2 一个简单的反事实实验
我做个快速模拟,你感受一下差距有多大。假设有 5000 个样本,特征是“颜色”,取值红、黄、蓝、绿四类,目标y是二分类,真实的规则是:红色和蓝色有 80% 概率为 1,黄色和绿色有 80% 概率为 0。
用One-Hot编码后训练一个逻辑回归,准确率大概能到 78%~80%。但如果用LabelEncoder把红黄蓝绿按出现顺序映射成 0~3,直接训练逻辑回归,准确率会掉到 60% 左右,甚至更差,因为模型根本学不到“红蓝是一伙、黄绿是一伙”这种非单调的组合关系。
换成树模型呢?One-Hot依然是稳定发挥。LabelEncoder如果碰巧把红和黄编码成相邻的0、1,树模型就容易先按“红色是否属于前两类”来切,效果会打折,而且不同的编码顺序会得到不同的结果,这本身就说明这个编码方式是靠运气在吃饭。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. LabelEncoder 和 One-Hot 各自做了什么:从数据表象到模型视角
先不急着给结论,我们把两个编码器的原理拆开看。
2.1 LabelEncoder:只是给类别“编号”,还是强行定义了顺序
LabelEncoder做的事很简单:给每个类别分配一个从 0 开始的整数。比如“猫”“狗”“鸟”三个类别,它可能会映射成猫=0, 狗=1, 鸟=2。
这里有个细节值得注意:官方文档里明确写了,LabelEncoder主要用来编码目标变量 y,而不是用来编码特征 X。很多人拿它直接处理所有特征列,其实是用错了地方。但为什么大家还是习惯这么用?因为写起来方便,一行代码就能把所有文本列变成数值列。
但问题在于:类别本身没有大小关系,编码后却被赋予了“大小关系”。0 < 1 < 2 这个数学事实,在数值特征里是有意义的,在无序类别里是虚构的。
2.2 One-Hot Encoding:用“标准基向量”表达类别之间的异同
One-Hot Encoding的思路完全不同。假设血型有四种,它会把每一条样本的血型变成一个 4 维向量:A 型是[1,0,0,0],B 型是[0,1,0,0],AB 型是[0,0,1,0],O 型是[0,0,0,1]。
你可以把它理解成:每个类别独立占一个维度,特征之间没有大小关系,只有“是或不是”。模型不需要解码这个数字到底代表什么,“是不是 A 型”这个信息是直接暴露给模型的。
One-Hot在数学里的名字叫“标准基向量”,包含一个重要性质:任意两个不同类别的向量内积为 0,距离相等。这意味着模型不会天然觉得“A 型和 B 型比 A 型和 O 型更接近”,所有类别都被平等对待。
2.3 还有两个经常被搞混的兄弟:OrdinalEncoder 和 get_dummies
实际项目中,除了LabelEncoder和OneHotEncoder,还会遇到另外两个高频工具:
OrdinalEncoder:和LabelEncoder生成的数字一样,但它是专门用来编码特征 X 的,而且允许你自定义类别的顺序(比如学历: 小学=0, 初中=1, 高中=2, 本科=3, 硕士=4)。对有序类别来说,这才是正确用法。pandas.get_dummies():做One-Hot最偷懒的方式,但默认会生成 K 列(类别数 K),而sklearn.preprocessing.OneHotEncoder默认会生成 K-1 列,因为它默认会“drop 掉第一列”来避免多重共线性。两者行为不太一样,后面我会专门讲这个坑。
两个编码器的核心对比,我整理成了一张表,方便你平时翻:
| 对比维度 | LabelEncoder | One-Hot Encoding |
|---|---|---|
| 输出维度 | 1 列整数(0~K-1) | K 列二值特征(或 K-1 列) |
| 是否引入顺序 | 是,且顺序随机 | 否,所有类别平权 |
| 是否适合线性模型 | 不适合无序类别 | 适合 |
| 是否适合树模型 | 勉强可用,但有隐性代价 | 适合,也符合树模型的切分直觉 |
| 可解释性 | 数字没有业务含义 | 每个二值列对应一个类别,可解释性强 |
| 内存占用 | 低 | 高(类别多时非常明显) |
| 类别很多时的稳定性 | 不受影响 | 可能维度爆炸,且罕见类别过拟合风险高 |
| 官方设计用途 | 编码目标变量 y | 编码特征 X |
3. 选错编码为什么会影响这么多:树模型和线性模型各自的“解读方式”
这一章是整个问题的核心。为什么一个看起来只是“数字怎么标”的选择,能对模型效果产生这么大的影响?关键在于模型会用它自己的方式“解读”你给的特征数字。
3.1 线性模型:会把整数当真·数值
还是回到血型的例子。血型被LabelEncoder编码成 0~3 之后,逻辑回归或线性回归会认为“血型”这个特征是一个连续变量,每增加 1 个单位,输出的对数值就变化w。如果 A 型(0)和 B 型(1)之间的差异被赋予权重,那么模型也会同样对待 B 型(1)和 AB 型(2)之间的差异。这纯属历史偶然(编码顺序取决于数据中出现顺序),模型却把它当成客观规律来学。
打个比方:给三种商品 A、B、C 分别标价 0 元、5 元、100 元。你本来只想给它们分个类,但旁边的人看到这三个数字后,会默认认为“C 比 B 贵 20 倍,B 比 A 贵无穷多倍”。但如果你用 One-Hot,把它们换成三个独立的“是否是该商品”开关标签,就不会产生这种误会了。
3.2 树模型:受编码顺序影响的“先切哪一刀”
树模型的分裂逻辑是:找到一个特征的一个阈值,让分裂后的两个节点的纯度提升最大。对LabelEncoder编码后的特征,候选切分点通常落在编码整数之间,比如 0.5、1.5、2.5。
有一句流传很广的说法:“树模型用 LabelEncoder 也行,反正树可以自己学。”这话只对了一半。
如果你所有特征本身就是高基数(比如 ID 类特征)或者样本量足够大、树足够深,那树确实可以通过多次切分近似还原出One-Hot的效果。但前提是样本量足够支撑这种反复切分,否则就是白白浪费分裂次数。更麻烦的是,树模型第一次切分时只会选择编码空间里某个阈值,这等于优先考虑了某些类别组合,把类别之间的“随机顺序”带进来当先验。一次两次不影响大局,但如果几棵树都沿着同样的路径走,模型偏差就出来了。
用一个具体的数据推演一下。假设城市这个特征有 10 个取值,随机森林训练时:
One-Hot下,每次分裂都是在问“是不是北京”“是不是上海”这种明确的问题;LabelEncoder下,第一次分裂可能是在问“这个城市的编码是否小于等于 3”,也就是“是不是落在编码 0~3 的城市组里”。
哪组城市被分到 0~3,完全取决于它们首次出现在数据里的顺序。如果碰巧把相关性强但实际无关的城市分在一组,树模型就得绕很大圈子才能纠正这个偏差。
3.3 维度灾难和内存爆炸:One-Hot 的另一面
说了这么多 One-Hot 的好话,它也不是没有代价。最大的问题就是:类别一多,维度就炸。
假设你有个特征叫“用户 ID”,100 万个用户就是 100 万列,每列只有 0 和 1。这个矩阵稀疏得吓人,训练速度直线下降,内存占用直接爆表。所以在实际工程里,“用不用 One-Hot”这个问题,很大程度上是在问“这个特征的类别数 K 到底有多大”。
- K 很小(比如 2~20 个类别):无脑
One-Hot,效果稳定,可解释性强。 - K 中等(20~100 个类别):
One-Hot还能用,但要想办法处理低频类别,比如合并成“其他”。 - K 非常大(成百上千甚至上万):放弃
One-Hot,改用目标编码、频数编码或者 embedding。
另外,One-Hot 还有一个隐藏的过拟合风险:如果你的某个类别在训练集里只出现过几次,One-Hot会为它单独设一个开关特征,模型很容易把这个开关当成“该类别出现过”来记忆,而不是泛化。所以实践中我一般会先做低频类别合并,再考虑One-Hot。
4. 实战选型流程:先把业务语义搞清楚,再动手写代码
讲了这么多理论,接下来直接给出一套可以“抄作业”的选型流程。我做了无数个特征工程方案后,总结了一个核心原则:先看特征有没有内在顺序,再看类别数量,最后看模型类型。
4.1 有序特征和无序特征:第一层决策
拿到任何一个类别特征,先问一个问题:这个类别的取值之间有没有天然的顺序或等级?
有顺序的,比如:
- 学历(小学 < 初中 < 高中 < 本科 < 硕士 < 博士)
- 收入档位(低 < 中低 < 中 < 中高 < 高)
- 满意度评分(非常不满意 < 不满意 < 一般 < 满意 < 非常满意)
- 星期几(周一 < 周二 < ... < 周日,注意循环序列要另做处理)
这些特征应该用OrdinalEncoder,并且显式传入类别顺序。注意,不是LabelEncoder那种随机分配顺序,而是要告诉模型真实的顺序关系。
没有顺序的,比如:
- 血型(A/B/AB/O)
- 城市(北京、上海、广州)
- 职业(医生、老师、程序员)
- 颜色(红、黄、蓝、绿)
这些就是无序类别,继续往下走。
4.2 类别数量:第二层决策
对无序类别,下一步看类别数 K。
- K 较小(通常 ≤ 20):直接用
One-Hot。简单、稳定、可解释。 - K 中等(20 ~ 100):先做低频合并,再
One-Hot。低频合并的阈值建议根据样本量来定,比如样本量为 10 万时,出现次数低于 50 次的类别可以统一合并成“其他”。 - K 较大(> 100):考虑频数编码(Count Encoding)、目标编码(Target Encoding)或者 embedding。把类别转成统计量或稠密向量,避免维度爆炸。
4.3 模型类型:第三层决策
同样的特征,在不同模型下最优编码可能不同:
- 线性模型(线性回归、逻辑回归、SVM):无序类别直接用
One-Hot,这是最安全的选择。如果特征本身有序,用OrdinalEncoder。 - 树模型(决策树、随机森林、XGBoost、LightGBM):两者都能用,但
One-Hot更稳妥(代价是训练速度变慢)。如果类别特别多,可以先用LabelEncoder临时跑通 baseline,再评估是否值得换One-Hot。 - 神经网络(MLP、embedding 类模型):最推荐的方式是 embedding 层,让网络自己学类别的向量表示。如果强行
One-Hot,输入维度会很夸张,训练也不稳定。
4.4 一个完整示例:从原始数据到最终编码
下面是一个完整可运行的示例,包含低频合并、One-Hot、序数编码三个步骤:
python复制import pandas as pd
import numpy as np
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
# 模拟数据
df = pd.DataFrame({
'city': np.random.choice(['北京', '上海', '广州', '深圳', '杭州', '成都'], size=5000),
'education': np.random.choice(['初中', '高中', '本科', '硕士'], size=5000),
'blood_type': np.random.choice(['A', 'B', 'AB', 'O'], size=5000),
'y': np.random.randint(0, 2, size=5000)
})
# 低频类别合并:让出现次数少于 100 的类目都变成 "other"
low_freq_cities = df['city'].value_counts()
valid_cities = low_freq_cities[low_freq_cities >= 100].index
df['city'] = df['city'].where(df['city'].isin(valid_cities), 'other')
# 定义有序特征的类别顺序
edu_categories = ['初中', '高中', '本科', '硕士']
# 用 ColumnTransformer 同时处理不同列
preprocessor = ColumnTransformer(
transformers=[
# 无序类别做 One-Hot
('onehot', OneHotEncoder(handle_unknown='ignore'), ['city', 'blood_type']),
# 有序类别做序数编码
('ordinal', OrdinalEncoder(categories=[edu_categories]), ['education'])
],
remainder='passthrough'
)
# 塞进 Pipeline 里,交叉验证时不会泄漏
pipe = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(n_estimators=100, random_state=42))
])
pipe.fit(df.drop('y', axis=1), df['y'])
这段代码把选型决策直接固化成了预处理流程:无序的走OneHotEncoder,有序的走OrdinalEncoder,低频类别先合并,所有步骤包在Pipeline里防止交叉验证泄漏。注意handle_unknown='ignore'这个参数,它能让模型在遇到训练集没见过的类别时不报错,后面会详细讲。
4.5 两个高频“伪需求”:不要被网上说法带偏
网上经常有人争论“树模型到底能不能用 LabelEncoder”,其实很多讨论都把场景搞混了。我实际观察到的是:
- 如果特征是低基数(比如性别、星期几),
One-Hot和LabelEncoder对树模型的影响很小,因为树的分裂能力很强,几个类别很快就能分离。 - 如果特征是中等基数(比如 50 个城市),
LabelEncoder的随机顺序影响就开始显现了,不同随机种子可能得到差异明显的结果。 - 如果特征是 ID 类(上万基数),
LabelEncoder通常能省下巨大内存,配合树模型反而比One-Hot更有工程价值。
所以不要一听“LabelEncoder 有坑”就完全不用,关键还是看你的特征属于哪种情况。工程上没有绝对正确,只有代价和收益的权衡。
5. 预处理管线里的坑:训练/测试不一致、稀疏矩阵、数据泄漏
编码方式选对了,不代表后面没有坑。下面这几个都是在实际项目里踩过、并且排查过很久的问题。
5.1 训练集和测试集类别不一致:handle_unknown 和 min_frequency
现实里经常出现这种情况:训练集的“城市”列里有北京、上海、广州,测试集里突然冒出一个“深圳”,而训练时OneHotEncoder没见过这个类别。默认情况下,OneHotEncoder会直接报错,导致整个流程崩掉。
解决办法有两个:
- 设置
handle_unknown='ignore',让未出现的类别在One-Hot后所有列都是 0。 - 设置
min_frequency和max_categories参数,让低频类别自动合并成“其他”类。
但要注意,handle_unknown='ignore'有个副作用:新类别会变成全 0 向量,和“缺失值”在编码上完全一样,模型无法区分“这个城市我从来没见过”和“这个样本缺失了城市信息”。如果这两种情况需要区别对待,可以手动加一列“是否已知类别”的指示特征。
5.2 One-Hot 的 K 列还是 K-1 列:多重共线性问题
pandas.get_dummies()默认输出 K 列,sklearn.preprocessing.OneHotEncoder默认输出 K-1 列(使用drop='first'会丢掉第一列)。两者差别在于,K 列时,特征之间存在完全的多重共线性(知道 K-1 列的值,第 K 列就确定了)。对线性模型来说,这种共线性会导致权重无法唯一确定,训练不稳定,可解释性变差。对树模型来说问题不大。
我的建议是:用线性模型时,显式开启drop='first'或drop='if_binary';用树模型时,K 列 K-1 列影响不大,按习惯用就行。
5.3 编码器要放进 Pipeline,不能先编码再切分
这是一个非常经典的错误。很多人习惯先做特征工程、再切训练集测试集:
python复制# 错误示范
df_encoded = pd.get_dummies(df)
X_train, X_test, y_train, y_test = train_test_split(df_encoded, y)
这会导致数据泄漏:get_dummies默认是先把全量数据的类目统计出来,再做切分,意味着测试集的信息在训练时已经被“偷看”了。如果数据里有类目极其稀少的样本,测试集只有一条且被单独开了一列,模型在训练时就会见过一个“测试集专属开关”,这等于部分数据泄漏。
正确做法是把编码器放进Pipeline里,每次交叉验证时只在训练折上拟合编码器,再用同一套映射去转换验证折。Pipeline天然保证这一点,这也是上面示例为什么要用ColumnTransformer的原因。
5.4 编码顺序依赖数据顺序导致的不稳定性
LabelEncoder和OneHotEncoder在拟合时,类别顺序默认是按照数据中出现顺序排序的(sklearn里默认lexical order还是出现顺序取决于版本和参数)。这就带来一个隐患:如果数据顺序变了一下,编码结果就全变了,模型训练结果也随之变化。
在需要复现结果的场景下,一定记得固定random_state,并且尽量不要在迭代过程中频繁改变数据的行顺序。如果你在做特征重要度分析,更需要保证编码的稳定性,否则连特征重要度的排名都可能飘。
5.5 编码后的可逆性:线上预测时怎么还原
训练时做了One-Hot,模型上线后遇到新样本,同样要做一模一样的编码。最容易出问题的是:业务方传过来的特征值,和训练时某个类别的写法稍有不同(比如训练时是“北京”和“上海”,线上传的是“北京市”“上海市”),编码器就会把它们当成新类别,直接忽略处理,模型结果自然不对劲。
建议:将训练好的预处理对象保存成文件(例如用joblib导出),在线服务启动时加载,保证线上线下的编码逻辑完全一致。这是最稳妥的。
python复制import joblib
joblib.dump(pipe.named_steps['preprocessor'], 'preprocessor.pkl')
# 线上加载
preprocessor = joblib.load('preprocessor.pkl')
6. 编码只是起点:频数编码、目标编码与 embedding 的进阶玩法
聊完基础编码,最后分享几个在真实项目里能明显提升效果的进阶方案。当你的类别特征足够多、类别数足够大时,One-Hot就不是最好的选择了。
6.1 频数编码:用“出现次数”当特征
把类别映射成它在训练集里出现的次数,这就是频数编码(Count Encoding)。比如“城市”列里北京出现 3000 次、上海出现 2500 次,城市这个特征就变成了[3000, 2500, ...]。
好处是只有一个维度,内存友好,还能捕捉“常见类别 vs 罕见类别”的信息。缺点是会把“数量”当成一种信号,有时会产生误导:一个类别出现次数的多少和预测目标之间往往没有固有的因果关系。
不过,把频数编码和One-Hot结合起来用,往往能获得意想不到的效果。比如:
- 对基数大的类别,先排序截断保留出现次数最多的 Top 20 类,对这些类做 One-Hot,其余全部合并为“其他”。
- 单独加一列“该类别的出现次数”作为连续特征,让模型知道这个类别的普遍程度。
这种组合方案,我在不少 Kaggle 竞赛里都见到过,优点是可解释性强,又不至于让维度失控。
6.2 目标编码:用目标变量的均值来编码,但必须防泄漏
目标编码(Target Encoding)的逻辑是:把类别映射成“该类别下目标变量的均值”。比如,在二分类任务中,某种职业的样本中有 70% 是正样本,那么这个职业的编码值就是 0.7。
这个方法对高基数类别特别有效,一个类别一行数字,直接把“类别与目标的关系”暴露给模型,信息量比One-Hot大得多。但它有个致命问题:极其容易过拟合。如果某个类别在训练集里只出现了一次,它的目标均值只能是 0 或 1,模型会把这个极端值当作规律学进去,泛化性能会很差。
常见的解决办法是加平滑系数(smoothing),公式大概长这样:
text复制encoding = (count * category_mean + prior_mean * m) / (count + m)
其中prior_mean是全体样本的目标均值,count是这个类别的样本数,m是平滑强度。当类别样本量很小的时候,编码值会向全体均值收缩;样本量大时,编码值趋近于类别的真实均值。
另外,目标编码必须使用交叉验证的方式计算。简单来说:对每一折,只利用其他折的数据计算类别的目标均值,再对当前折做编码,避免数据泄漏。如果直接全量计算再切分,特征里会带进去目标的信息,测试集上看着很好,一旦上线就崩。
6.3 Embedding 编码:让模型自己学类别向量
如果你用的是神经网络,可以考虑把类别特征送入 embedding 层。它的本质是把每个类别映射为一个可学习的稠密向量(比如 32 维或 64 维),向量之间的距离关系是模型在训练过程中自己学出来的,能捕捉到相似类别的语义。
这和One-Hot的关系是:embedding 层的输入依然来自One-Hot向量,只是中间多了一个稠密映射层。如果你的项目已经在用神经网络,把高基数类别直接接到 embedding 层,往往比One-Hot更节省参数、效果更好。
6.4 哈希编码:当类别数大到无法枚举时
哈希编码(Hashing Encoding)把类别通过哈希函数映射到一个固定大小的整数空间,比如总是映射到 0~9999。它的最大优点是:不占额外存储、无需维护类别映射表、能处理无限增长的类别。缺点是不同的类别可能哈希到同一个桶里(碰撞),会损失一些区分度。
比较适合用于文本类别、URL、商品 ID 这类高基数且持续增长的场景。在实际使用时,建议把哈希空间设大一些,比如目标列数的 2~4 倍,把碰撞率控制在可接受范围。
6.5 我个人的“组合拳”实践
最后分享一个我自己在业务模型里反复使用的组合方案,不是什么高深的东西,但很实用:
- 先把分类别特征分成“高基数”“中低基数”两组;
- 中低基数组:先做低频合并(合并成“其他”),再
One-Hot; - 高基数组:用目标编码(交叉验证方式计算,加平滑),再加一个原始出现次数做频数特征;
- 如果特征本身就是 ID 类(比如用户 ID、设备 ID),直接砍掉,不编码。ID 类特征要么没有泛化能力,要么就是要用专门的手段处理(比如负采样、embeddding),常规编码只会带来灾难性的过拟合。
这套组合下来,既保留了低基数特征的可解释性,又避免了高基数特征的维度爆炸,同时用目标编码把类别和目标的关联信息补了进来,模型效果和稳定性能兼顾。
回到最开始那个问题:为什么选对编码方式,模型效果能提升这么多?其实就是因为编码方式决定了模型能看到什么信息、学到什么结构。One-Hot保留了无序类别的“互斥关系”,OrdinalEncoder保留了有序类别的“顺序关系”,而LabelEncoder如果不小心用于无序类别,就是在往数据里塞虚构的“大小关系”。
我个人踩了这么多次坑以后的体会是:遇到类别特征,先别急着写LabelEncoder,先问自己三个问题——这个特征有顺序吗?类别数量多不多?下游模型是什么?三个问题想清楚,编码方案基本上就定了。剩下的,是在工程细节上把低频合并、防泄漏、测试集对齐这些事做到位。特征工程很多时候看着不起眼,但恰恰是这些“一行的差别”,决定了模型在真实场景里是能扛住线上波动,还是一上线就崩。
