我们在本课程中看到的大多数技术都是针对数值特征的。而我们将在本课中看到的技术,target encoding,则是针对分类特征的。它是一种将类别编码为数字的方法,类似于独热编码或标签编码,区别在于它还使用 target 来创建编码。这使它成为我们所称的监督特征工程(supervised feature engineering)技术。

目标编码(target encoding)

目标编码是任何一种用从目标派生的某个数字替换特征类别的编码。

一个简单有效的版本是应用第3课中的分组聚合,如均值。使用 Automobiles 数据集,它计算每种车辆品牌的平均价格:

1
2
3
4
# 每个品牌的所有车型都会被替换为该品牌所有车型的平均价格
autos["make_encoded"] = autos.groupby("make")["price"].transform("mean")

autos[["make", "price", "make_encoded"]].head(5)
make price make_encoded
0 alfa-romero 13495 15498.33333
1 alfa-romero 16500 15498.33333
2 alfa-romero 16500 15498.33333
3 audi 13950 17859.16667
4 audi 17450 17859.16667

This kind of target encoding is sometimes called a mean encoding. Applied to a binary target, it’s also called bin counting. (Other names you might come across include: likelihood encoding, impact encoding, and leave-one-out encoding.)

这种目标编码有时被称为均值编码。应用于二元目标时,也称为箱计数。(您可能遇到的其他名称包括:似然编码、影响编码和留一法编码。)

平滑处理(Smoothing)

然而,这种编码存在几个问题。首先是未知类别。目标编码会产生过拟合的特殊风险,这意味着它们需要在独立的”编码”拆分上进行训练。当您将编码连接到未来的拆分时,Pandas将为编码拆分中不存在的任何类别填充缺失值。这些缺失值您需要以某种方式进行插补。

第二个是稀有类别。当一个类别在数据集中只出现几次时,对其组计算的任何统计数据都不太可能非常准确。在Automobiles数据集中,mercury品牌只出现一次。我们计算的”平均”价格只是那一辆车的价格,可能不太能代表我们未来可能看到的任何Mercury车型。目标编码稀有类别可能会增加过拟合的可能性。

这些问题的解决方案是添加平滑处理。想法是将类内平均值与整体平均值混合。稀有类别在其类别平均值上获得较少权重,而缺失类别仅获得整体平均值。

伪代码:

1
encoding = weight * in_category + (1 - weight) * overall

其中 weight 是根据类别频率计算的0到1之间的值。

确定 weight 值的一种简单方法是计算m-估计:

1
weight = n / (n + m)

其中 n 是该类别在数据中出现的总次数。参数 m 确定”平滑因子”。较大的 m 值使得整体估计获得更多权重。

在Automobiles数据集中有三辆chevrolet品牌的汽车。如果您选择m=2.0,那么chevrolet类别将被编码为60%的平均Chevrolet价格加上40%的整体平均价格。

1
chevrolet = 0.6 * 6000.00 + 0.4 * 13285.03

在选择m值时,考虑您预期类别会有多嘈杂。车辆价格在每个品牌内变化很大吗?您需要大量数据才能获得良好的估计吗?如果是这样,选择更大的m值可能更好;如果每个品牌的平均价格相对稳定,较小的值可能就足够了。

译者注:m越大,模型更偏信数据整体的平均值;m越小,模型更偏信各个类别自己的平均值。

最佳实践

目标编码非常适合:

  • 高基数特征:具有大量类别的特征在编码时可能会很麻烦:独热编码会生成太多特征,而标签编码等替代方法可能不适合该特征。目标编码使用特征最重要的属性为类别派生数字:其与目标的关系。
  • 领域驱动特征:从先验经验来看,您可能会怀疑某个分类特征应该很重要,即使它在特征度量方面表现不佳。目标编码可以帮助揭示特征的真正信息量。

示例 - MovieLens1M

MovieLens1M数据集包含由MovieLens网站用户提供的一百万个电影评分,以及描述每个用户和电影的特征。

1
Number of Unique Zipcodes: 3439

有超过3000个类别,Zipcode特征是目标编码的良好候选,而且这个数据集的规模(超过一百万行)意味着我们可以抽出一些数据来创建编码。

译者注:根据先验经验,我们猜测来自相同地区(相同zipcode)的人可能有相似的电影评分偏好。因此,我们target encoding(目标编码)是为了将这个高基数的分类变量(3439个不同值的zipcode)转换为有用的数值特征。

我们将首先创建一个25%的拆分来训练目标编码器。

1
2
3
4
5
6
7
X = df.copy()
y = X.pop('Rating')

X_encode = X.sample(frac=0.25)
y_encode = y[X_encode.index]
X_pretrain = X.drop(X_encode.index)
y_train = y[X_pretrain.index]

scikit-learn-contrib中的category_encoders包实现了一个m-估计编码器,我们将用它来编码我们的Zipcode特征。

1
2
3
4
5
6
7
8
9
10
from category_encoders import MEstimateEncoder

# 创建编码器实例。选择m来控制噪声。
encoder = MEstimateEncoder(cols=["Zipcode"], m=5.0)

# 在编码拆分上拟合编码器。
encoder.fit(X_encode, y_encode)

# 编码Zipcode列以创建最终训练数据
X_train = encoder.transform(X_pretrain)

让我们将编码值与目标进行比较,看看我们的编码可能有多大信息量。

1
2
3
4
5
plt.figure(dpi=90)
ax = sns.distplot(y, kde=False, norm_hist=True)
ax = sns.kdeplot(X_train.Zipcode, color='r', ax=ax)
ax.set_xlabel("Rating")
ax.legend(labels=['Zipcode', 'Rating']);

编码后的Zipcode特征分布大致遵循实际评分的分布,这意味着不同邮政编码的电影观众在评分上有足够的差异,使得我们的目标编码能够捕获有用的信息。

译者注:使用target encoding后,每个zipcode被替换为该zipcode地区用户的平均电影评分。

为了避免过拟合,示例使用了以下步骤:

  • 将数据分成75%训练集和25%编码集
  • 使用编码集来训练MEstimateEncoder(使用m=5.0进行平滑处理)
  • 然后将编码器应用到训练集

最后一张图显示了编码后的zipcode值(红线)与实际评分分布(蓝色柱状图)对比。红线大致跟随评分分布,表明邮政编码确实包含了一些与用户评分行为相关的信息。