TargetEncoder 的内部交叉拟合#

TargetEncoder 将分类特征的每个类别替换为该类别目标变量的收缩均值(shrunk mean)。当分类特征与目标变量之间存在强相关性时,此方法非常有效。为了防止过拟合,TargetEncoder.fit_transform 使用内部的 交叉拟合 (cross fitting) 方案来对下游模型使用的训练数据进行编码。该方案涉及将数据拆分为 k 个折叠(fold),并使用从 其他 k-1 个折叠中学到的编码来对当前折叠进行编码。在此示例中,我们将演示交叉拟合程序对于防止过拟合的重要性。

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

创建合成数据集#

对于此示例,我们构建了一个包含三个分类特征的数据集:

  • 一个具有中等基数(cardinality)的信息性特征(“informative”);

  • 一个具有中等基数的非信息性特征(“shuffled”);

  • 一个具有高基数的非信息性特征(“near_unique”)。

首先,我们生成信息性特征。

import numpy as np

from sklearn.preprocessing import KBinsDiscretizer

n_samples = 50_000

rng = np.random.RandomState(42)
y = rng.randn(n_samples)
noise = 0.5 * rng.randn(n_samples)
n_categories = 100

kbins = KBinsDiscretizer(
    n_bins=n_categories,
    encode="ordinal",
    strategy="uniform",
    random_state=rng,
    subsample=None,
)
X_informative = kbins.fit_transform((y + noise).reshape(-1, 1))

# Remove the linear relationship between y and the bin index by permuting the
# values of X_informative:
permuted_categories = rng.permutation(n_categories)
X_informative = permuted_categories[X_informative.astype(np.int32)]

具有中等基数的非信息性特征是通过对信息性特征进行排列并消除其与目标变量之间的关系而生成的。

X_shuffled = rng.permutation(X_informative)

具有高基数的非信息性特征的生成方式使其与目标变量无关。我们将展示,如果不使用 交叉拟合 进行目标编码,将会导致下游回归模型出现严重的过拟合。这些高基数特征基本上是样本的唯一标识符,通常应从机器学习数据集中剔除。在此示例中,我们生成它们是为了展示 TargetEncoder 的默认 交叉拟合 行为如何自动减轻过拟合问题。

X_near_unique_categories = rng.choice(
    int(0.9 * n_samples), size=n_samples, replace=True
).reshape(-1, 1)

最后,我们汇总数据集并执行训练集与测试集的划分。

import pandas as pd

from sklearn.model_selection import train_test_split

X = pd.DataFrame(
    np.concatenate(
        [X_informative, X_shuffled, X_near_unique_categories],
        axis=1,
    ),
    columns=["informative", "shuffled", "near_unique"],
)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

训练 Ridge 回归模型#

在本节中,我们在有无编码的情况下对数据集训练 Ridge 回归模型,并探讨使用与不使用内部 交叉拟合 的目标编码器的影响。首先,我们看到在原始特征上训练的 Ridge 模型性能较低。这是因为我们对信息性特征的顺序进行了置换,这意味着 X_informative 在原始状态下不具备信息性。

import sklearn
from sklearn.linear_model import Ridge

# Configure transformers to always output DataFrames
sklearn.set_config(transform_output="pandas")

ridge = Ridge(alpha=1e-6, solver="lsqr", fit_intercept=False)

raw_model = ridge.fit(X_train, y_train)
print("Raw Model score on training set: ", raw_model.score(X_train, y_train))
print("Raw Model score on test set: ", raw_model.score(X_test, y_test))
Raw Model score on training set:  0.0049896314219659565
Raw Model score on test set:  0.004577621581464908

接下来,我们创建一个包含目标编码器和 Ridge 模型的流水线(pipeline)。该流水线使用 TargetEncoder.fit_transform,它应用了 交叉拟合。我们看到模型很好地拟合了数据并泛化到了测试集。

from sklearn.model_selection import KFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import TargetEncoder

model_with_cf = make_pipeline(
    TargetEncoder(cv=KFold(shuffle=True, random_state=0)), ridge
)
model_with_cf.fit(X_train, y_train)
print("Model with CF on train set: ", model_with_cf.score(X_train, y_train))
print("Model with CF on test set: ", model_with_cf.score(X_test, y_test))
Model with CF on train set:  0.8000184677460299
Model with CF on test set:  0.7927845601690924

线性模型的系数显示,大部分权重集中在列索引为 0 的特征上,即信息性特征。

import matplotlib.pyplot as plt
import pandas as pd

plt.rcParams["figure.constrained_layout.use"] = True

coefs_cf = pd.Series(
    model_with_cf[-1].coef_, index=model_with_cf[-1].feature_names_in_
).sort_values()
ax = coefs_cf.plot(kind="barh")
_ = ax.set(
    title="Target encoded with cross fitting",
    xlabel="Ridge coefficient",
    ylabel="Feature",
)
Target encoded with cross fitting

虽然 TargetEncoder.fit_transform 使用内部的 交叉拟合 方案来学习训练集的编码,但先调用 TargetEncoder.fit 再调用 TargetEncoder.transform 则不然。它使用完整的训练集来学习编码并转换分类特征。因此,我们可以使用 TargetEncoder.fit 后跟 TargetEncoder.transform 来禁用 交叉拟合。然后,该编码被传递给 Ridge 模型。

target_encoder = TargetEncoder(random_state=0)
target_encoder.fit(X_train, y_train)
X_train_no_cf_encoding = target_encoder.transform(X_train)
X_test_no_cf_encoding = target_encoder.transform(X_test)

model_no_cf = ridge.fit(X_train_no_cf_encoding, y_train)

我们评估了在编码时未使用 交叉拟合 的模型,发现其出现了过拟合。

print(
    "Model without CF on training set: ",
    model_no_cf.score(X_train_no_cf_encoding, y_train),
)
print(
    "Model without CF on test set: ",
    model_no_cf.score(
        X_test_no_cf_encoding,
        y_test,
    ),
)
Model without CF on training set:  0.858486250088675
Model without CF on test set:  0.6338211367110548

Ridge 模型过拟合的原因是,与使用 交叉拟合 对特征进行编码的情况相比,它为非信息性的极高基数(“near_unique”)和中等基数(“shuffled”)特征分配了更多的权重。

coefs_no_cf = pd.Series(
    model_no_cf.coef_, index=model_no_cf.feature_names_in_
).sort_values()
ax = coefs_no_cf.plot(kind="barh")
_ = ax.set(
    title="Target encoded without cross fitting",
    xlabel="Ridge coefficient",
    ylabel="Feature",
)
Target encoded without cross fitting

结论#

此示例展示了 TargetEncoder 内部 交叉拟合 的重要性。在将训练数据传递给机器学习模型之前,使用 TargetEncoder.fit_transform 进行编码非常重要。当 TargetEncoder 成为 Pipeline 的一部分且流水线被拟合时,流水线会正确地调用 TargetEncoder.fit_transform,并在编码训练数据时使用 交叉拟合

脚本总运行时间:(0 分 0.359 秒)

相关示例

比较目标编码器与其他编码器

比较目标编码器与其他编码器

梯度提升中的分类特征支持

梯度提升中的分类特征支持

HuberRegressor vs Ridge 在具有强离群点的数据集上的比较

HuberRegressor vs Ridge 在具有强离群点的数据集上的比较

普通最小二乘法和岭回归

普通最小二乘法和岭回归

由 Sphinx-Gallery 生成的图库