趋近智
独热编码对于类别较少的名义特征很有效,但当处理具有许多唯一值(高基数)的变量时,它可能导致高维特征空间。序数编码需要有意义的顺序,但这并非总是存在。目标编码,也称为均值编码,提供了一种替代方法,它直接使用目标变量的信息来为类别创建数值表示。
目标编码的基本原理是用与该类别关联的目标变量的平均值来替换特征中的每个类别。
考虑一个简单的二元分类数据集,其中包含一个类别特征 City 和一个二元目标 Purchased:
| 城市 | 已购买 |
|---|---|
| 伦敦 | 1 |
| 巴黎 | 0 |
| 伦敦 | 0 |
| 东京 | 1 |
| 巴黎 | 1 |
| 伦敦 | 1 |
| 东京 | 0 |
要计算 City 的目标编码:
City 列中的唯一值对数据进行分组。Purchased 列的均值。
编码后的特征如下所示:
| 城市(编码后) | 已购买 |
|---|---|
| 0.67 | 1 |
| 0.50 | 0 |
| 0.67 | 0 |
| 0.50 | 1 |
| 0.50 | 1 |
| 0.67 | 1 |
| 0.50 | 0 |
这个单一的数值特征现在包含了与每个城市相关的购买可能性信息,可能带来显著的预测能力,而不会大幅增加维度。
目标编码看起来很有用,但如上所示地简单操作会带来一个重大风险:目标泄露。
当您使用同一行的目标值来计算特定行的编码时,您就将目标信息泄露到了特征中。模型会学到一个直接关联:“当特定行的目标值为1时,编码值略高/略低。”这会导致训练期间出现过于乐观的性能,因为模型实际上获得了目标值的提示。然而,这种性能提升无法泛化到目标未知的新数据上,从而导致过拟合。
想象一下对第一行(伦敦,已购买=1)的 City 进行编码。计算出的均值(0.67)就包含了那个 已购买=1 的值。
要安全有效地使用目标编码,您必须实施策略来预防或最小化目标泄露。
样本量极少的类别可能导致均值估计不可靠。例如,如果某个城市只出现过一次并导致了购买,其目标编码将是1.0,这可能是一个极端且带有噪声的估计。平滑处理通过将该类别的均值与目标变量的整体全局均值进行混合来解决这个问题。
一种常用的平滑技术使用以下公式:
平滑均值=计数+m计数×类别均值+m×全局均值其中:
count 是属于该类别的样本数量。category_mean 是该类别的原始目标均值。global_mean 是整个数据集的目标均值。m 是平滑因子,一个超参数,用于决定平滑的“强度”。m 值越高,意味着样本量较小的类别的均值将被更强地拉向全局均值。直观上,对于 count 值较大的类别,该公式会更多地考虑 category_mean。而对于 count 值较小的类别,global_mean 会占据主导,从而提供一个更保守的估计。确定最佳 m 值通常需要进行交叉验证。
一种更可靠的方法是在交叉验证框架内计算编码。您不是在整个数据集上计算均值,而是在训练折叠上计算,并将其应用于相应的验证折叠。
以下是使用 K-折交叉验证的典型工作流程:
k(作为临时验证集):
k 内的数据点进行类别特征编码。这种方法确保了任何给定数据点的编码是在不使用其自身目标值的情况下计算的,从而有效地防止了训练集评估中的直接泄露。
import pandas as pd
from sklearn.model_selection import KFold
# 示例数据
data = {'City': ['London', 'Paris', 'London', 'Tokyo', 'Paris', 'London', 'Tokyo', 'Rome', 'Rome'],
'Purchased': [1, 0, 0, 1, 1, 1, 0, 0, 1]}
df = pd.DataFrame(data)
target = 'Purchased'
feature = 'City'
# 全局均值,用于平滑和后续填充NA值
global_mean = df[target].mean()
# 平滑因子
m = 1
# 设置 K折交叉验证
kf = KFold(n_splits=3, shuffle=True, random_state=42)
df[f'{feature}_encoded'] = 0.0 # 初始化编码列
# 应用交叉验证编码
for train_index, val_index in kf.split(df):
df_train, df_val = df.iloc[train_index], df.iloc[val_index]
# 在折叠的训练部分计算平滑均值
means = df_train.groupby(feature)[target].agg(['count', 'mean'])
smoothed_means = (means['count'] * means['mean'] + m * global_mean) / (means['count'] + m)
# 将均值应用于折叠的验证部分
# 使用 .map() 并用全局均值填充验证集中可能出现的新类别
df.loc[val_index, f'{feature}_encoded'] = df_val[feature].map(smoothed_means).fillna(global_mean)
print("带有交叉验证目标编码的原始DataFrame:")
print(df)
# 示例:编码新的数据点(使用完整训练集的均值)
full_train_means = df.groupby(feature)[target].agg(['count', 'mean'])
full_smoothed_means = (full_train_means['count'] * full_train_means['mean'] + m * global_mean) / (full_train_means['count'] + m)
new_data = pd.DataFrame({'City': ['Paris', 'Berlin']}) # Berlin是未见过的新类别
new_data[f'{feature}_encoded'] = new_data[feature].map(full_smoothed_means).fillna(global_mean)
print("\n编码新数据:")
print(new_data)
上述代码演示了带有平滑处理的 K-折目标编码。请注意每行的编码值是如何从其他折叠计算出的均值中得出的,以及如何使用全局均值处理未见过的类别(如
Berlin)。
虽然您可以像所示那样手动使用 Pandas 实现目标编码,但专业库通常提供优化后的实现,它们在内部处理平滑和交叉验证。category_encoders 库是一个受欢迎的选择:
# 使用 category_encoders 的示例(需要安装:pip install category_encoders)
# import category_encoders as ce
# 假设 df_train 和 df_test 是您的训练集和测试集
# target_encoder = ce.TargetEncoder(cols=[feature], smoothing=1.0) # 指定平滑因子
# 在训练数据上拟合(计算均值)
# target_encoder.fit(df_train[feature], df_train[target])
# 转换训练和测试数据
# df_train[f'{feature}_encoded'] = target_encoder.transform(df_train[feature])
# df_test[f'{feature}_encoded'] = target_encoder.transform(df_test[feature])
# 注意:category_encoders 的 TargetEncoder 会执行平滑处理,但可能不会默认实现
# 严格的 K折交叉验证编码;请查阅文档了解交叉验证策略。
# 为了严格防止信息泄露,通常更推荐手动实现 K折交叉验证或将其与 sklearn 管道集成。
优点:
缺点:
目标编码是一种很有用的方法,特别适用于:
始终在将数据拆分为训练集和测试集之后应用目标编码,并在您的训练流程中使用交叉验证编码和平滑等技术来构建可靠的模型。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造