4、特征编码技术:Label Encoding、One-Hot Encoding、Target Encoding、自动化编码策略选择
特征编码,说白了就是把非数值型数据变成模型能吃的数字。这件事看起来简单,但坑特别多。我见过不少项目,模型效果上不去,最后发现是编码方式选错了。
今天咱们就把几种主流编码方式掰开揉碎讲清楚。我会结合自己的实战经验,告诉你什么时候该用哪种,以及怎么让整个过程自动化。
4.1 Label Encoding:最简单的映射
Label Encoding 就是把每个类别映射成一个整数。比如「红、黄、蓝」变成 0、1、2。
听起来很直接对吧?但这里有个大坑——模型会认为这些数字有大小关系。0 < 1 < 2,模型会以为「蓝」比「红」大。这在有序类别(比如「小、中、大」)上没问题,但在无序类别上就出事了。
什么时候用 Label Encoding?
- 类别本身有顺序(比如学历:小学 < 中学 < 大学)
- 类别数量特别多(几百个),One-Hot 会撑爆内存
- 树模型(决策树、随机森林)对顺序不敏感,可以凑合用
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
data['education_encoded'] = le.fit_transform(data['education'])
# 小学→0, 中学→1, 大学→2
4.2 One-Hot Encoding:最稳妥的选择
One-Hot 把每个类别变成一列 0/1 向量。比如颜色有红、黄、蓝三种,就生成三列:is_red、is_yellow、is_blue。
这样做的好处是——模型不会误解类别之间的关系。每个类别都是独立的,没有大小之分。
但代价也很明显:维度爆炸。如果某个特征有 1000 个类别,就会多出 1000 列。我做过一个用户画像项目,光「城市」这个特征就产生了 300 多列,训练集直接翻了三倍。
import pandas as pd
# 直接用 pandas 的 get_dummies
one_hot = pd.get_dummies(data['color'], prefix='color')
data = pd.concat([data, one_hot], axis=1)
4.3 Target Encoding:用标签信息编码
Target Encoding 是个「作弊」技巧——用目标变量的均值来编码类别。比如计算每个城市的平均房价,然后用这个均值代替城市名。
这样做的好处是:一个特征就能表达类别信息,不会维度爆炸。而且编码后的特征和目标变量直接相关,模型学起来很快。
但风险也很大——过拟合。如果某个类别只有几条样本,它的均值可能完全不准。我见过一个项目,某个「职业」类别只有 2 个人,Target Encoding 后模型直接记住了这两个人的标签,泛化能力一塌糊涂。
encoded = (n * group_mean + m * global_mean) / (n + m)
其中 n 是类别样本数,m 是平滑参数。
def target_encoding_smooth(df, feature, target, m=10):
global_mean = df[target].mean()
group_stats = df.groupby(feature)[target].agg(['mean', 'count'])
group_stats['encoded'] = (
group_stats['count'] * group_stats['mean'] +
m * global_mean
) / (group_stats['count'] + m)
return df[feature].map(group_stats['encoded'])
4.4 四种编码方式对比
| 编码方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Label Encoding | 有序类别、类别极多 | 简单、不增加维度 | 引入虚假顺序关系 |
| One-Hot Encoding | 无序类别、类别少 | 无顺序假设、模型友好 | 维度爆炸、稀疏 |
| Target Encoding | 高基数类别、有标签信息 | 维度低、信息密度高 | 容易过拟合 |
| Frequency Encoding | 类别分布差异大 | 简单、保留频次信息 | 丢失类别语义 |
4.5 自动化编码策略选择
手动选编码方式太累了。你想想看,一个数据集可能有几十个类别特征,每个都要人工判断?不现实。
我设计了一套自动化规则,直接写进 pipeline 里:
- 判断类别是否有顺序:如果有,用 Label Encoding
- 判断类别数量:< 10 用 One-Hot,10~50 看模型类型
- 判断模型类型:线性模型用 One-Hot,树模型用 Label
- 高基数(> 50):用 Target Encoding + 交叉验证
- 兜底方案:Frequency Encoding
def auto_encoder_selector(feature, n_categories, is_ordinal, model_type):
if is_ordinal:
return 'label_encoding'
if n_categories < 10:
return 'one_hot'
if 10 <= n_categories <= 50:
if model_type == 'linear':
return 'one_hot'
else:
return 'label_encoding'
if n_categories > 50:
return 'target_encoding'
return 'frequency_encoding'
4.6 知识体系总览
下面这张图把编码技术的选择逻辑串起来了。你可以把它当作决策地图,遇到新数据时照着走一遍。
4.7 实战中的坑与建议
最后分享几个我踩过的坑:
- 测试集和训练集要分开编码:尤其是 Target Encoding,必须用训练集计算均值,再映射到测试集。否则就是数据泄露。
- 新类别处理:线上预测时可能出现训练集没见过的类别。我习惯在编码时加一个「unknown」兜底。
- 不要迷信一种编码:同一个数据集,不同特征可以用不同编码。我做过一个项目,城市用 Target Encoding,性别用 One-Hot,学历用 Label,效果比统一编码好很多。