(一)独热编码、标签编码、目标编码、序数编码详解

独热编码(One-hot Encoding)、标签编码(Label Encoding)、目标编码(Target Encoding)、序数编码(Ordinal Enconding)是机器学习领域非常常见的四个编码方式,也是初学者感到困惑和混淆的内容。本文从原理出发、渗透Python应用,详细解释这四种编码方式。

独热编码(One-hot Encoding)

独热编码主要用于无序的分类特征,生成多列的0/1稀疏矩阵。我们可以从范例了认识它。

假如我们有以下比赛的数据,其中比赛分数 和比赛等级 是数据的特征,是否获得奖金是预测值(分类预测):

index 比赛分数 比赛等级 是否获得奖金
0 178 三等 否
1 192 一等 是
2 184 二等 是
3 191 一等 是
4 172 三等 否

对于这组数据,比赛等级 和是否获得奖金 这两列显然是无序的分类特征(一等、二等、三等无数学大小含义),我们可以使用独热编码来使得机器学习模型能够理解它们的特征。当我们对这组数据的比赛等级 和是否获得奖金这两列进行独热编码后,数据表将会变为如下的样式。

index 比赛分数 比赛等级_一等 比赛等级_二等 比赛等级_三等 是否获得奖金_是 是否获得奖金_否
0 178 0 0 1 0 1
1 192 1 0 0 1 0
2 184 0 1 0 1 0
3 191 1 0 0 1 0
4 172 0 0 1 0 1

基于这个例子,我们可以看到独热编码会将分类特征拆分为各单独的一列以0/1的形式作是否存在这一特征的编码。由此我们应该知道用于独热编码的分类特征应该是比较少的(如本例中只有三个类别一等、二等、三等,或者是/否两个类别)

所以可以得出独热编码的优缺点:

优点 缺点
完全消除类别虚假数值顺序,适配线性模型; 维度爆炸:若特征包含上百个类别,会新增上百列,数据极度稀疏,占用大量内存,训练速度大幅下降;
特征含义清晰,可解释性强。 不适用高基数特征(用户 ID、城市、商品编号等)。

使用独热编码

常用进行独热编码可以使用pandas或者sklearn的相关方法:

Python 复制代码
#pandas
import pandas as pd
df_encoded = pd.get_dummies(df,columns=['比赛等级','是否获得奖金'],drop_first=False)
Python 复制代码
#scikit-learn
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
preprocessor = ColumnTransformer(
	transformers=[
		('cat',OneHotEncoder(drop=None),['比赛等级','是否获得奖金'])
	],
	remainder='passthrough'
)
X_encoded = preprocessor.fit_transform(X)
'''
transformers 参数
	name:字符串,给这个转换器起个名字,便于调试或查看特征名(如 'cat')。
	transformer:要应用的转换器对象,这里是 OneHotEncoder(drop=None)。
	columns:要应用该转换器的列名列表,如 ['比赛等级','是否获得奖金']
remainder 参数
	控制未被 transformers 选中的列如何处理:
	'drop'(默认):丢弃未选中的列。
	'passthrough':保留未选中的列原样输出。
	也可以是一个转换器对象,对剩余列应用其他转换。
'''

标签编码(Label Encoding)

在独热编码的使用过程中会发现,当分类特征的种类过多时,数据会变得极度稀疏,这时的训练效率会变得非常低。于是,使用标签编码就可以解决这一痛点。

标签编码自动的将所有类别随机映射成连续整数0,1,2.....

我们来看以下例子:

index 比赛分数 比赛等级 是否获得奖金
0 178 三等 否
1 192 一等 是
2 184 二等 是
3 191 一等 是
4 172 三等 否

对于这组数据,对比赛等级和是否获得奖金两列进行标签编码会转换为如下样式:

index 比赛分数 比赛等级 是否获得奖金
0 178 0 0
1 192 2 1
2 184 1 1
3 191 2 1
4 172 0 0

但是,标签编码有一个明显的缺点 。对于没有数学意义上的大小关系的数据会引入虚假有序关系。如上面这个例子,它可能让模型误以为一等(2)>二等(1)>三等(0)。这样会让神经网络或者线性模型遭到误导,影响训练效果。

如果使用树模型,这种影响则偏小。

使用标签编码

Python 复制代码
from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
data["奖金标签"] = le.fit_transform(data["是否获得奖金"])
print(data["奖金标签"]) # [0,1,1,1,0]

目标编码(Target Encoding)

目标编码属于有监督编码,利用训练集目标 y 的统计均值替换分类类别,解决高基数特征维度爆炸问题。一般来说,为了保持特征的训练平衡,目标编码会使用有权重的均值(加权平均值)作为分类特征的编码。

\(加权平均值 = \frac{n \times 分项平均值 + m \times 总体平均值 }{n+m}\)

其中 \(n:分项平均值的权重(一般为数据行数)\)

\(m:总体平均值的权重(由用户自行设定)\)

index 比赛分数 比赛等级 是否获得奖金 奖金标签 y
0 178 三等 否 0
1 192 一等 是 1
2 184 二等 是 1
3 191 一等 是 1
4 172 三等 否 0

按照上面的例子,我们对比赛等级 这一列进行目标编码,将得到如下的效果:

首先先计算总体平均值:

\总体平均值 = \\frac{0+1+1+1+0}{5} = 0.6 \\

比赛等级 n 分项平均值 总体平均值 m 加权平均值
一等 2 1.0 0.6 1 \(\frac{2 \times 1.0 + 1 \times 0.6}{2+1}=0.867\)
二等 1 1.0 0.6 1 \(\frac{1 \times 1.0 + 1 \times 0.6}{1+1}=0.800\)
三等 2 0.0 0.6 1 \(\frac{2 \times 0.0 + 1 \times 0.6}{2+1}=0.200\)

用这些加权平均值替换原来的"比赛等级",得到目标编码后的数据:

index 比赛分数 比赛等级_目标编码 是否获得奖金
0 178 0.200 否
1 192 0.867 是
2 184 0.800 是
3 191 0.867 是
4 172 0.200 否

使用目标编码

使用 category_encoders

python 复制代码
# pip install category_encoders
from category_encoders import TargetEncoder

encoder = TargetEncoder(cols=["比赛等级"], smoothing=1.0)
data_encoded = encoder.fit_transform(data[["比赛等级"]], data["奖金标签"])

data["比赛等级_目标编码"] = data_encoded["比赛等级"]
print(data)

使用 scikit-learn

python 复制代码
# scikit-learn 1.3+ 支持 TargetEncoder
from sklearn.preprocessing import TargetEncoder

encoder = TargetEncoder(target_type="binary", smooth=1.0)
X_encoded = encoder.fit_transform(data[["比赛等级"]], data["奖金标签"])

print(X_encoded)

目标编码优缺点

优点 缺点
能处理高基数分类特征,不会像独热编码一样维度爆炸 容易造成目标泄漏,训练集内直接编码会过拟合
利用目标 y 的统计信息,特征表达能力强 小样本类别的统计量不稳定,需要平滑处理
适合树模型、线性模型、神经网络等多种模型 需要交叉验证、留一法或嵌套编码,流程更复杂
不增加特征维度,内存占用和训练速度通常优于独热编码 对训练集和测试集分布变化敏感,新类别需要回退到全局均值

序数编码(Ordinal Encoding)

序数编码用于有序分类特征。它会按照业务上明确的顺序,把类别映射成整数,从而保留类别之间的大小或顺序关系。

序数编码和标签编码看起来很像,都是把类别变成整数。区别在于:

  • 标签编码:通常只是自动或随机地把类别映射成 0、1、2......不保证整数顺序有业务含义;
  • 序数编码:由用户显式指定类别顺序,整数顺序具有明确含义。

例如,如果业务认为"比赛等级"存在如下顺序:

\三等 \< 二等 \< 一等 \\

那么可以使用序数编码:

index 比赛分数 比赛等级 是否获得奖金 比赛等级_序数
0 178 三等 否 0
1 192 一等 是 2
2 184 二等 是 1
3 191 一等 是 2
4 172 三等 否 0

这里:

  • 三等 → 0
  • 二等 → 1
  • 一等 → 2

这样模型就能知道"一等"比"二等"高,"二等"比"三等"高。但如果特征本身没有顺序,比如"颜色""城市""商品编号",就不应该使用序数编码,否则会引入虚假的顺序关系。

使用序数编码

使用 scikit-learn

python 复制代码
from sklearn.preprocessing import OrdinalEncoder

encoder = OrdinalEncoder(categories=[["三等", "二等", "一等"]])
data["比赛等级_序数"] = encoder.fit_transform(data[["比赛等级"]])

print(data["比赛等级_序数"].tolist())  # [0.0, 2.0, 1.0, 2.0, 0.0]

注意:categories 中的顺序就是编码顺序,第一个类别编码为 0,第二个类别编码为 1,以此类推。

使用 pandas 手动映射

python 复制代码
order_map = {
    "三等": 0,
    "二等": 1,
    "一等": 2
}

data["比赛等级_序数"] = data["比赛等级"].map(order_map)
print(data)

序数编码优缺点

优点 缺点
保留有序类别之间的顺序关系 只适用于确实存在顺序的分类特征
不增加特征维度,内存占用小 顺序必须由业务确定,错误顺序会误导模型
编码结果可解释,适合树模型和部分线性模型 默认假设类别之间等距,但实际间隔不一定相等
可以配合线性模型表达单调关系 对无序类别使用会引入虚假顺序

四种编码方式简单对比

编码方式 适用场景 是否保留顺序 是否增加维度 是否使用目标 y
独热编码 One-hot Encoding 无序、低基数分类特征 否 是 否
标签编码 Label Encoding 无序特征、树模型,或目标变量编码 否 否 否
目标编码 Target Encoding 高基数分类特征、有监督任务 不直接保留 否 是
序数编码 Ordinal Encoding 有序分类特征 是 否 否
相关推荐
用户7624117567051 小时前
第二章 Python语言基础
python
129Lab1 小时前
BMS 核心算法:SOC 估算从安时积分到 LSTM-UKF 串级融合的 30 年演进(附 Python 实现)
python·算法·lstm·python3.11·bms·soc估算
129Lab1 小时前
电池热管理仿真的AI加速:用Python+PINN物理信息神经网络替代传统CFD的可行性探索
pytorch·python·cfd·pinn·物理信息神经网络·仿真加速·电池热管理
用户298698530141 小时前
Python 中 HTML 转 PDF 的实现方法
python·html·api
答案是你2 小时前
YOLOE 开放词汇检测 + ONNX / TensorRT 推理,开源了!
python·深度学习·yolo·目标检测·计算机视觉·视觉检测
2601_962885722 小时前
AlphaFeed 支持哪些 K 线周期?period参数怎么传?
前端·数据库·python
老歌老听老掉牙2 小时前
基于 Python 的顺次文本拼接:以 MCD 数控文件为例
python·数控·mcd
朝朝辞暮i2 小时前
VLA 系统学习第 7 课:loss.backward() 到底做了什么?——从计算图到反向传播
人工智能·python·深度学习·神经网络·vla
外收内放2 小时前
Python基础语法练习题(53-54)
python·学习