3. 特征编码技术

做机器学习,有个绕不开的坎——模型只认数字。你手里的数据,往往是「男/女」、「北京/上海」、「高/中/低」这类文本。怎么把它们变成数字?这就是特征编码要干的事。

我刚开始做特征工程时,觉得编码不就是把文字映射成数字嘛,简单。结果踩了不少坑。今天我把四种最常用的编码技术掰开揉碎讲清楚,每种我都会告诉你:什么时候用、怎么用、有什么坑。

一句话总结:标签编码省空间但会引入顺序假象;独热编码无偏但维度爆炸;目标编码和信息量挂钩但容易过拟合;WOE编码是金融风控的标配,自带单调性约束。

特征编码技术全景图 原始类别特征 标签编码 Label Encoding 独热编码 One-Hot Encoding 目标编码 Target Encoding WOE编码 Weight of Evidence 选择依据:特征基数、模型类型、是否有序、是否与目标变量相关

3.1 标签编码:简单粗暴,但有陷阱

标签编码是最直观的做法。把每个类别映射成一个整数,比如「红=0, 黄=1, 蓝=2」。说白了就是给每个类别发个编号。

from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
colors = ['红', '黄', '蓝', '红', '蓝']
encoded = le.fit_transform(colors)
print(encoded)  # [0 1 2 0 2]

什么时候用?类别之间有明显的顺序关系。比如「小=0, 中=1, 大=2」,模型能理解这个大小关系。再比如学历「小学=0, 中学=1, 大学=2, 研究生=3」——这个顺序是合理的。

我曾经踩过的坑:有一次我把「北京=0, 上海=1, 深圳=2」做标签编码,扔进线性模型。结果模型学出来「深圳 ≈ 2 × 北京」这种荒谬关系。因为线性模型会认为数字大小代表真实距离。城市之间没有大小关系,用标签编码就是给自己挖坑。

避坑指南:

  • 树模型(决策树、随机森林、XGBoost)对标签编码相对鲁棒,因为它们做的是条件分裂,不是线性加权
  • 线性模型、神经网络千万别乱用标签编码处理无序类别
  • 类别数量超过几十个时,标签编码比独热编码更省内存

3.2 独热编码:无偏但费维度

独热编码的思路是:每个类别单独开一列,属于这个类别就填1,否则填0。比如颜色有红黄蓝三色,就变成三列:is_red、is_yellow、is_blue。

from sklearn.preprocessing import OneHotEncoder
import pandas as pd

data = pd.DataFrame({'color': ['红', '黄', '蓝', '红']})
encoder = OneHotEncoder(sparse_output=False)
encoded = encoder.fit_transform(data[['color']])
print(encoded)
# [[1. 0. 0.]
#  [0. 1. 0.]
#  [0. 0. 1.]
#  [1. 0. 0.]]

独热编码的好处是:不引入任何顺序假象。每个类别都是平等的,模型不会觉得「红」和「蓝」之间的距离比「红」和「黄」更近。

但问题也很明显:

  • 类别越多,维度膨胀越厉害。100个城市就多100列
  • 高维稀疏数据对内存不友好,很多模型训练也会变慢
  • 如果某个类别在训练集出现次数极少,模型几乎学不到它的信息

我的建议:类别数小于10且没有顺序关系时,无脑用独热编码。类别数在10-50之间,可以结合业务做降维(比如把相似类别合并)。超过50个,我一般会考虑目标编码或者embedding。

3.3 目标编码:用标签信息反哺特征

目标编码的思路很巧妙:用目标变量的统计量来替换原始类别。比如做二分类,把「北京」这个城市替换成「北京地区用户的平均转化率」。

import pandas as pd

# 示例:目标编码
df = pd.DataFrame({
    'city': ['北京', '上海', '北京', '深圳', '上海', '北京'],
    'target': [1, 0, 1, 0, 1, 0]
})

# 计算每个城市的平均target
city_mean = df.groupby('city')['target'].mean()
df['city_encoded'] = df['city'].map(city_mean)
print(df)
#    city  target  city_encoded
# 0  北京       1      0.666667
# 1  上海       0      0.500000
# 2  北京       1      0.666667
# 3  深圳       0      0.000000
# 4  上海       1      0.500000
# 5  北京       0      0.666667

目标编码的好处是:把高基数类别压缩成一列,信息密度高。我在做用户画像特征时经常用,比如把「用户ID」编码成「历史购买率」,效果比独热编码好得多。

但这里有个大坑——数据泄露。如果你用全量数据计算均值,再用这个均值去预测同一条数据,模型会「偷看」到答案。我刚开始用目标编码时就犯过这个错,线上效果比线下差了一大截。

正确的做法:

  1. 在训练集上做K折交叉统计:每折只用其他折的数据计算均值
  2. 加入平滑项:encoding = (n * mean + m * global_mean) / (n + m),m是平滑参数
  3. 测试集用训练集的全局均值编码

经验值:平滑参数m一般取5-20。类别样本量越少,m应该越大。我习惯先设m=10,然后根据验证集效果微调。

3.4 WOE编码:风控领域的标配

WOE(Weight of Evidence,证据权重)编码在金融风控领域几乎是必用技术。它衡量的是:某个类别下「好样本」和「坏样本」的分布差异。

公式长这样:

WOE = ln( 好样本占比 / 坏样本占比 )

其中:
好样本占比 = 该类别中好样本数 / 全部好样本数
坏样本占比 = 该类别中坏样本数 / 全部坏样本数

举个例子,假设我们有1000个用户,其中300人逾期(坏样本),700人正常(好样本)。城市A有200人,其中50人逾期:

好样本占比 = (200-50)/700 = 0.214
坏样本占比 = 50/300 = 0.167
WOE = ln(0.214/0.167) = 0.248

WOE为正,说明城市A的好样本比例高于整体水平,风险较低。

WOE编码的几个特点:

  • 自带单调性约束——WOE值随坏样本率单调变化,逻辑回归很喜欢这个性质
  • 可以处理缺失值——把缺失单独作为一个类别计算WOE
  • 对异常值不敏感——因为用的是占比,不是绝对值

我个人的习惯:做风控模型时,先用WOE编码做一轮特征筛选。WOE绝对值小于0.1的类别,说明区分能力弱,可以考虑合并。WOE绝对值大于0.5的类别,区分能力强,要重点保留。

3.5 四种编码怎么选?一张表说清楚

编码方式 适用场景 优点 缺点 推荐模型
标签编码 有序类别、高基数 不增加维度、简单 引入顺序假象 树模型
独热编码 低基数无序类别 无偏、可解释 维度爆炸 线性模型、神经网络
目标编码 高基数、与目标相关 信息密度高 容易过拟合、数据泄露 树模型、线性模型
WOE编码 二分类、风控场景 单调性、可解释 只适用于二分类 逻辑回归

嗯,到这里四种编码技术就讲完了。你可能会问:有没有一种万能编码?说实话,没有。我做了这么多年特征工程,每次选编码方式都要先看数据、看业务、看模型。记住一个原则:编码的本质是在「信息保留」和「维度控制」之间找平衡。选对了,模型效果能提升一大截;选错了,再好的算法也救不回来。