4. 特征编码技术:标签编码、独热编码、目标编码、频率编码与WOE编码
咱们做机器学习,遇到的第一关往往就是:数据里全是文字,模型只认数字。这就得靠特征编码来搭桥。编码选得好,模型吃得饱;编码选得糙,调参调到老。今天我把五种最常用的编码方法掰开揉碎讲清楚。
4.1 标签编码
标签编码,说白了就是给每个类别分配一个整数。假设性别有男、女、未知,那就分别变成0、1、2。简单粗暴,一行代码搞定。
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
labels = ['男', '女', '未知']
le.fit(labels)
print(le.transform(labels)) # [0 1 2]
但我得提醒一句:标签编码隐含了顺序关系。比如“未知”被编码成2,模型可能会认为它比“男”(0)和“女”(1)都“大”。如果你的类别之间没有天然顺序,这就会引入错误偏见。
适合用标签编码的场景:只有两个类别(比如是否逾期)、或者类别确实有序(学历:小学<中学<大学)。
4.2 独热编码
独热编码是解决顺序问题的标准答案。每个类别变成一个独立的二进制列,只有当前样本属于该类别时对应位为1。比如性别男=[1,0],女=[0,1]。
import pandas as pd
df = pd.DataFrame({'城市': ['北京','上海','广州']})
pd.get_dummies(df, columns=['城市'])
结果长这样:
| 北京 | 上海 | 广州 |
|---|---|---|
| 1 | 0 | 0 |
| 0 | 1 | 0 |
| 0 | 0 | 1 |
它的代价是维度爆炸。如果城市有100个,那就新生成100列。我见过一个电商数据集,光商品类别就有5000个,用独热编码后内存直接爆掉。所以高基数类别千万不要无脑独热。
4.3 目标编码
目标编码是个狠角色。它用目标变量(比如点击率、转化率)的统计量来替换原始类别。具体来说,计算每个类别下目标值的均值,然后用这个均值作为编码值。
# 假数据:用户ID(高基数) -> 是否点击
mean_enc = df.groupby('user_id')['click'].mean()
df['user_encoded'] = df['user_id'].map(mean_enc)
我个人的习惯是:只要遇到高基数类别特征(上千个值),优先试目标编码。它天然解决了维度灾难,而且能捕捉类别与目标之间的关联。
但有一个大坑:过拟合。如果某个类别只出现一两次,它的均值就会非常极端。比如某个用户只出现一次且点击了,那编码值就是1.0,模型就会死记这个样本。
标准的做法是用K-fold目标编码:在每一折内只用训练集计算均值,避免数据泄露。也可以用平滑公式:
编码值 = (该类别目标值之和 + m * 全局均值) / (该类别样本数 + m)
其中m是平滑系数,m越大,编码值越接近全局均值。
4.4 频率编码
频率编码更简单:直接用每个类别出现的频次或频率替换原值。比如某个品牌出现1000次,编码成1000或者0.01(归一化后)。
freq = df['brand'].value_counts(normalize=True)
df['brand_freq'] = df['brand'].map(freq)
这种编码的潜台词是:频繁出现的类别通常更重要。我在处理用户行为数据时发现,用户点击的品牌频率往往与购买意愿正相关。
但你想想看,频率编码完全不依赖目标变量,所以不会有数据泄露问题,这是它最大的优势。缺点也很明显——它没办法区分两个出现次数相同但含义完全不同的类别。
| 编码方法 | 是否使用目标变量 | 处理高基数 | 过拟合风险 |
|---|---|---|---|
| 标签编码 | 否 | 好 | 低 |
| 独热编码 | 否 | 差 | 低 |
| 目标编码 | 是 | 好 | 高 |
| 频率编码 | 否 | 好 | 低 |
| WOE编码 | 是 | 好 | 中 |
4.5 WOE编码
WOE(Weight of Evidence)编码来自信用评分模型,算是目标编码的升级版。它不仅考虑目标均值,还考虑好坏样本的比例关系。
# 对二分类目标,计算每个类别的WOE
import numpy as np
# 假设坏样本为1,好样本为0
df_group = df.groupby('category').agg(
good=('target', lambda x: (x==0).sum()),
bad=('target', lambda x: (x==1).sum())
)
df_group['woe'] = np.log(
(df_group['good'] / df_group['good'].sum()) /
(df_group['bad'] / df_group['bad'].sum())
)
说白了,WOE衡量的是:这个类别里好样本的相对比例比整体样本高多少(正WOE)还是低多少(负WOE)。它在逻辑回归里特别好用,因为WOE与log-odds是线性关系。
我建议在信贷、保险、风控这类要求可解释性的场景中,优先使用WOE编码。它的系数可以直接解释为“该类别使风险上升/下降了多少”。
4.6 如何选择:一张决策图
说了这么多,到底用哪个?我整理了一个流程图帮你拍板。
总结一下我的经验:
- 类别数 ≤ 5 且无顺序 → 独热编码,别整花哨的
- 类别数 6~50 → 独热或目标编码都行,优先目标编码
- 类别数 > 50 → 目标编码/频率编码/WOE,绝对不要用独热
- 风控模型 → WOE编码是不二之选
- 怕过拟合 → 频率编码最安全
嗯,编码方法选对了,模型训练就成功了一半。剩下的交给调参和特征交叉。你根据自己手头的特征卡,对着上面的图选就对了。