当前位置:   article > 正文

Scikit-Learn 1.4使用指南:数据转换 数据预处理 Preprocessing data

Scikit-Learn 1.4使用指南:数据转换 数据预处理 Preprocessing data


sklearn.preprocessing

sklearn.preprocessing 包提供了几个常用的实用函数和转换器类,用于将原始特征向量转换为更适合下游估计器的表示形式。

一般来说,许多学习算法(如线性模型)受益于对数据集进行标准化处理(参见 sphx_glr_auto_examples_preprocessing_plot_scaling_importance.py)。如果数据集中存在一些异常值,则鲁棒缩放器或其他转换器可能更合适。不同缩放器、转换器和归一化器在包含边缘异常值的数据集上的行为在 sphx_glr_auto_examples_preprocessing_plot_all_scaling.py 中进行了重点介绍。

标准化,或均值移除和方差缩放

对于许多在 scikit-learn 中实现的机器学习估计器来说,对数据集进行标准化是一个常见的要求;如果各个特征不大像标准正态分布的数据(均值为零,方差为单位),它们可能会表现不佳。

实际上,我们经常忽略分布的形状,只是通过去除每个特征的均值来将数据居中,然后通过将非常数特征除以它们的标准差来进行缩放。

例如,许多学习算法的目标函数(如支持向量机的 RBF 核或线性模型的 l1 和 l2 正则化器)可能假设所有特征都围绕零居中或方差具有相同的顺序。如果某个特征的方差比其他特征大几个数量级,它可能会主导目标函数,并使估计器无法像预期的那样从其他特征中正确学习。

~sklearn.preprocessing 模块提供了 StandardScaler 实用类,它是在类似数组的数据集上执行以下操作的一种快速简便方法:

>>> from sklearn import preprocessing
>>> import numpy as np
>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
>>> scaler = preprocessing.StandardScaler().fit(X_train)
>>> scaler
StandardScaler()

>>> scaler.mean_
array([1. ..., 0. ..., 0.33...])

>>> scaler.scale_
array([0.81..., 0.81..., 1.24...])

>>> X_scaled = scaler.transform(X_train)
>>> X_scaled
array([[ 0.  ..., -1.22...,  1.33...],
       [ 1.22...,  0.  ..., -0.26...],
       [-1.22...,  1.22..., -1.06...]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

缩放后的数据具有零均值和单位方差:

>>> X_scaled.mean(axis=0)
array([0., 0., 0.])

>>> X_scaled.std(axis=0)
array([1., 1., 1.])
  • 1
  • 2
  • 3
  • 4
  • 5

该类实现了 Transformer API,以便能够在训练集上计算均值和标准差,从而能够在测试集上重新应用相同的转换。因此,该类适用于 ~sklearn.pipeline.Pipeline 的早期步骤:

>>> from sklearn.datasets import make_classification
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.model_selection import train_test_split
>>> from sklearn.pipeline import make_pipeline
>>> from sklearn.preprocessing import StandardScaler

>>> X, y = make_classification(random_state=42)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
>>> pipe = make_pipeline(StandardScaler(), LogisticRegression())
>>> pipe.fit(X_train, y_train)  # 在训练数据上应用缩放
Pipeline(steps=[('standardscaler', StandardScaler()),
                ('logisticregression', LogisticRegression())])

>>> pipe.score(X_test, y_test)  # 在测试数据上应用缩放,不泄露训练数据。
0.96
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

可以通过将 with_mean=Falsewith_std=False 传递给 StandardScaler 的构造函数来禁用居中或缩放。

将特征缩放到一定范围

另一种标准化的方法是将特征缩放到给定的最小值和最大值之间,通常在零和一之间,或者使每个特征的最大绝对值缩放为单位大小。可以分别使用 MinMaxScalerMaxAbsScaler 来实现这一目标。

使用这种缩放的动机包括对特征的非常小的标准差的鲁棒性和保留稀疏数据中的零条目。

下面是一个将玩具数据矩阵缩放到 [0, 1] 范围的示例:

>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
...
>>> min_max_scaler = preprocessing.MinMaxScaler()
>>> X_train_minmax = min_max_scaler.fit_transform(X_train)
>>> X_train_minmax
array([[0.5       , 0.        , 1.        ],
       [1.        , 0.5       , 0.33333333],
       [0.        , 1.        , 0.        ]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后,可以将相同的转换器实例应用于一些在拟合调用期间未见过的新测试数据:将应用相同的缩放和平移操作,以保持与在训练数据上执行的转换一致:

>>> X_test = np.array([[-3., -1.,  4.]])
>>> X_test_minmax = min_max_scaler.transform(X_test)
>>> X_test_minmax
array([[-1.5       ,  0.        ,  1.66666667]])
  • 1
  • 2
  • 3
  • 4

可以检查缩放器属性以了解在训练数据上学到的转换的确切性质:

>>> min_max_scaler.scale_
array([0.5       , 0.5       , 0.33...])

>>> min_max_scaler.min_
array([0.        , 0.5       , 0.33...])
  • 1
  • 2
  • 3
  • 4
  • 5

如果 MinMaxScaler 给定了显式的 feature_range=(min, max),则完整的公式为:

X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))

X_scaled = X_std * (max - min) + min
  • 1
  • 2
  • 3

MaxAbsScaler 的工作方式非常相似,但是通过将每个特征中的最大最大值除以最大值,以使训练数据位于 [-1, 1] 范围内进行缩放。它适用于已经以零为中心的数据或稀疏数据。

下面是如何使用先前示例中的玩具数据和此缩放器的示例:

>>> X_train = np.array([[ 1., -1.,  2.],
...                     [ 2.,  0.,  0.],
...                     [ 0.,  1., -1.]])
...
>>> max_abs_scaler = preprocessing.MaxAbsScaler()
>>> X_train_maxabs = max_abs_scaler.fit_transform(X_train)
>>> X_train_maxabs
array([[ 0.5, -1. ,  1. ],
       [ 1. ,  0. ,  0. ],
       [ 0. ,  1. , -0.5]])
>>> X_test = np.array([[ -3., -1.,  4.]])
>>> X_test_maxabs = max_abs_scaler.transform(X_test)
>>> X_test_maxabs
array([[-1.5, -1. ,  2. ]])
>>> max_abs_scaler.scale_
array([2.,  1.,  2.])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

缩放稀疏数据

对稀疏数据进行居中会破坏数据中的稀疏结构,因此很少是一个明智的做法。然而,对稀疏输入进行缩放可能是有意义的,特别是如果特征的尺度不同。

MaxAbsScaler 专门设计用于缩放稀疏数据,并且是推荐的方法。然而,StandardScaler 可以接受 scipy.sparse 矩阵作为输入,只要显式地传递 with_mean=False 给构造函数即可。否则,将引发 ValueError,因为静默地进行居中会破坏稀疏性,并且通常会通过意外分配过多的内存来导致执行崩溃。RobustScaler 不能适应稀疏输入,但可以在稀疏输入上使用 transform 方法。

请注意,缩放器接受压缩稀疏行和压缩稀疏列格式(参见 scipy.sparse.csr_matrixscipy.sparse.csc_matrix)。任何其他稀疏输入都将转换为压缩稀疏行表示。为了避免不必要的内存复制,建议在上游选择 CSR 或 CSC 表示。

带有异常值的数据缩放

如果您的数据包含许多异常值,使用数据的均值和方差进行缩放可能效果不佳。在这种情况下,您可以使用RobustScaler作为替代方案。它使用更健壮的估计值来计算数据的中心和范围。

参考文献:

关于中心化和缩放数据的重要性的进一步讨论,请参见此常见问题解答:我应该对数据进行归一化/标准化/缩放吗?

缩放 vs 白化

仅对特征进行中心化和缩放有时是不够的,因为下游模型可能进一步对特征的线性独立性做出一些假设。

为了解决这个问题,您可以使用~sklearn.decomposition.PCA并将whiten=True来进一步消除特征之间的线性相关性。

中心化核矩阵

如果您有一个核矩阵,该核矩阵由一个计算特征空间中的点积的核函数 K K K(可能是隐式定义的)定义,那么KernelCenterer可以将核矩阵转换为包含特征空间中点积的中心化矩阵。换句话说,KernelCenterer计算与正半定核 K K K相关联的中心化格拉姆矩阵。

数学公式

现在我们已经有了直观的理解,我们可以看一下数学公式。假设 K K K是一个形状为(n_samples,n_samples)的核矩阵,由形状为(n_samples,n_features)的数据矩阵 X X X在拟合步骤中计算得出。 K K K由以下公式定义:

K ( X , X ) = ϕ ( X ) . ϕ ( X ) T K(X, X) = \phi(X) . \phi(X)^{T} K(X,X)=ϕ(X).ϕ(X)T

ϕ ( X ) \phi(X) ϕ(X)是将 X X X映射到希尔伯特空间的函数。中心化的核 K ~ \tilde{K} K~定义为:

K ~ ( X , X ) = ϕ ~ ( X ) . ϕ ~ ( X ) T \tilde{K}(X, X) = \tilde{\phi}(X) . \tilde{\phi}(X)^{T} K~(X,X)=ϕ~(X).ϕ~(X)T

其中 ϕ ~ ( X ) \tilde{\phi}(X) ϕ~(X)是在希尔伯特空间中对 ϕ ( X ) \phi(X) ϕ(X)进行中心化后的结果。

因此,可以通过使用函数 ϕ ( ⋅ ) \phi(\cdot) ϕ() X X X映射并在这个新空间中对数据进行中心化来计算 K ~ \tilde{K} K~。然而,通常使用核函数是因为它们允许进行一些代数计算,避免了使用 ϕ ( ⋅ ) \phi(\cdot) ϕ()显式计算映射。实际上,可以如附录B中所示隐式地进行中心化计算【Scholkopf1998】

K ~ = K − 1 n s a m p l e s K − K 1 n s a m p l e s + 1 n s a m p l e s K 1 n s a m p l e s \tilde{K} = K - 1_{\text{n}_{samples}} K - K 1_{\text{n}_{samples}} + 1_{\text{n}_{samples}} K 1_{\text{n}_{samples}} K~=K1nsamplesKK1nsamples+1nsamplesK1nsamples

1 n s a m p l e s 1_{\text{n}_{samples}} 1nsamples是一个形状为(n_samples,n_samples)的矩阵,其中所有条目都等于 1 n s a m p l e s \frac{1}{\text{n}_{samples}} nsamples1。在转换步骤中,核变为 K t e s t ( X , Y ) K_{test}(X, Y) Ktest(X,Y),定义为:

K t e s t ( X , Y ) = ϕ ( Y ) . ϕ ( X ) T K_{test}(X, Y) = \phi(Y) . \phi(X)^{T} Ktest(X,Y)=ϕ(Y).ϕ(X)T

Y Y Y是形状为(n_samples_test,n_features)的测试数据集,因此 K t e s t K_{test} Ktest的形状为(n_samples_test,n_samples)。在这种情况下,中心化 K t e s t K_{test} Ktest的计算如下:

K ~ t e s t ( X , Y ) = K t e s t − 1 n s a m p l e s ′ K − K t e s t 1 n s a m p l e s + 1 n s a m p l e s ′ K 1 n s a m p l e s \tilde{K}_{test}(X, Y) = K_{test} - 1'_{\text{n}_{samples}} K - K_{test} 1_{\text{n}_{samples}} + 1'_{\text{n}_{samples}} K 1_{\text{n}_{samples}} K~test(X,Y)=Ktest1nsamplesKKtest1nsamples+1nsamplesK1nsamples

1 n s a m p l e s ′ 1'_{\text{n}_{samples}} 1nsamples是一个形状为(n_samples_test,n_samples)的矩阵,其中所有条目都等于 1 n s a m p l e s \frac{1}{\text{n}_{samples}} nsamples1

参考文献

非线性转换

有两种类型的转换可用:分位数转换和幂转换。分位数转换和幂转换都基于特征的单调转换,因此保持了每个特征值的排序。

分位数转换将所有特征值放入相同的目标分布中,该分布基于公式 G − 1 ( F ( X ) ) G^{-1}(F(X)) G1(F(X)),其中 F F F是特征的累积分布函数, G − 1 G^{-1} G1是所需输出分布 G G G分位数函数。这个公式使用了以下两个事实:(i)如果 X X X是具有连续累积分布函数 F F F的随机变量,则 F ( X ) F(X) F(X) [ 0 , 1 ] [0,1] [0,1]上均匀分布;(ii)如果 U U U是在 [ 0 , 1 ] [0,1] [0,1]上均匀分布的随机变量,则 G − 1 ( U ) G^{-1}(U) G1(U)的分布为 G G G。通过执行秩转换,分位数转换平滑了异常分布,并且不像缩放方法那样受到异常值的影响。然而,它会扭曲特征内部和特征之间的相关性和距离。

幂转换是一族参数化转换,旨在将数据从任何分布映射到尽可能接近高斯分布。

映射到均匀分布

QuantileTransformer提供了一种非参数化转换,将数据映射到介于0和1之间的均匀分布:

>>> from sklearn.datasets import load_iris
>>> from sklearn.model_selection import train_test_split
>>> X, y = load_iris(return_X_y=True)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
>>> quantile_transformer = preprocessing.QuantileTransformer(random_state=0)
>>> X_train_trans = quantile_transformer.fit_transform(X_train)
>>> X_test_trans = quantile_transformer.transform(X_test)
>>> np.percentile(X_train[:, 0], [0, 25, 50, 75, 100]) # doctest: +SKIP
array([ 4.3,  5.1,  5.8,  6.5,  7.9])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这个特征对应于以厘米为单位的花萼长度。应用分位数转换后,这些地标接近先前定义的百分位数:

>>> np.percentile(X_train_trans[:, 0], [0, 25, 50, 75, 100])
... # doctest: +SKIP
array([ 0.00... ,  0.24...,  0.49...,  0.73...,  0.99... ])
  • 1
  • 2
  • 3

在具有类似备注的独立测试集上可以确认这一点:

>>> np.percentile(X_test[:, 0], [0, 25, 50, 75, 100])
... # doctest: +SKIP
array([ 4.4  ,  5.125,  5.75 ,  6.175,  7.3  ])
>>> np.percentile(X_test_trans[:, 0], [0, 25, 50, 75, 100])
... # doctest: +SKIP
array([ 0.01...,  0.25...,  0.46...,  0.60... ,  0.94...])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

映射到高斯分布

在许多建模场景中,数据集中特征的正态性是可取的。幂转换是一族参数化、单调递增的转换,旨在将数据从任何分布映射到尽可能接近高斯分布,以稳定方差并最小化偏斜。

PowerTransformer目前提供了两种这样的幂转换,即Yeo-Johnson转换和Box-Cox转换。

Yeo-Johnson转换定义如下:

$$\begin{aligned}
x_i^{(\lambda)} =
\begin{cases}
[(x_i + 1)^\lambda - 1] / \lambda & \text{如果 } \lambda \neq 0, x_i \geq 0, \[8pt]
\ln{(x_i + 1)} & \text{如果 } \lambda = 0, x_i \geq 0 \[8pt]
-[(-x_i + 1)^{2 - \lambda} - 1] / (2 - \lambda) & \text{如果 } \lambda \neq 2, x_i < 0, \[8pt]

  • \ln (- x_i + 1) & \text{如果 } \lambda = 2, x_i < 0
    \end{cases}
    \end{aligned}$$

而Box-Cox转换定义如下:

x i ( λ ) = { x i λ − 1 λ 如果  λ ≠ 0 , ln ⁡ ( x i ) 如果  λ = 0 , x(λ)i={xλi1λ如果 λ0,ln(xi)如果 λ=0,

xi(λ)= λxiλ1ln(xi)如果 λ=0,如果 λ=0,

Box-Cox只能应用于严格正的数据。在这两种方法中,转换由参数 λ \lambda λ参数化,通过最大似然估计确定。下面是一个使用Box-Cox将从对数正态分布中抽取的样本映射到正态分布的示例:

>>> pt = preprocessing.PowerTransformer(method='box-cox', standardize=False)
>>> X_lognormal = np.random.RandomState(616).lognormal(size=(3, 3))
>>> X_lognormal
array([[1.28..., 1.18..., 0.84...],
       [0.94..., 1.60..., 0.38...],
       [1.35..., 0.21..., 1.09...]])
>>> pt.fit_transform(X_lognormal)
array([[ 0.49...,  0.17..., -0.15...],
       [-0.05...,  0.58..., -0.57...],
       [ 0.69..., -0.84...,  0.10...]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

尽管上面的示例将standardize选项设置为False,但PowerTransformer默认情况下会对转换后的输出应用零均值、单位方差的归一化。

使用 QuantileTransformer 并将 output_distribution 设置为 'normal',也可以将数据映射到正态分布。以前面介绍的鸢尾花数据集为例:

quantile_transformer = preprocessing.QuantileTransformer(
    output_distribution='normal', random_state=0)
X_trans = quantile_transformer.fit_transform(X)
quantile_transformer.quantiles_
  • 1
  • 2
  • 3
  • 4

因此,输入的中位数变为输出的均值,且输出以0为中心。正态输出被剪裁,以防止输入的最小值和最大值在转换过程中变为无穷大,分别对应于1e-7和1-1e-7的分位数。

标准化

标准化是将每个样本缩放为单位范数的过程。如果您计划使用二次形式(如点积)或任何其他核函数来量化任意一对样本的相似性,这个过程可能会很有用。

这个假设是文本分类和聚类环境中经常使用的向量空间模型的基础。

函数 normalize 提供了一种在单个类似数组的数据集上执行此操作的快速简便方法,可以使用 l1l2max 范数:

X = [[ 1., -1.,  2.],
     [ 2.,  0.,  0.],
     [ 0.,  1., -1.]]
X_normalized = preprocessing.normalize(X, norm='l2')

X_normalized
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

preprocessing 模块还提供了一个实用的类 Normalizer,它使用 Transformer API 实现了相同的操作(尽管在这种情况下,fit 方法是无用的:该类是无状态的,因为该操作独立地处理样本)。

因此,这个类适用于在 ~sklearn.pipeline.Pipeline 的早期步骤中使用:

normalizer = preprocessing.Normalizer().fit(X)  # fit 什么都不做
normalizer
  • 1
  • 2

然后,可以像对待任何转换器一样使用 normalizer 实例对样本向量进行处理:

normalizer.transform(X)
  • 1
normalizer.transform([[-1.,  1., 0.]])
  • 1

注意:L2 标准化也被称为空间符号预处理。

稀疏输入

normalizeNormalizer 都接受来自 scipy.sparse密集数组和稀疏矩阵作为输入。

对于稀疏输入,数据在传递给高效的 Cython 例程之前会被转换为压缩稀疏行表示(参见 scipy.sparse.csr_matrix)。为了避免不必要的内存复制,建议在上游选择 CSR 表示。

编码分类特征

通常,特征不是连续值,而是分类值。例如,一个人可能有特征 ["男性", "女性"]["来自欧洲", "来自美国", "来自亚洲"]["使用 Firefox", "使用 Chrome", "使用 Safari", "使用 Internet Explorer"]。这些特征可以高效地编码为整数,例如 ["男性", "来自美国", "使用 Internet Explorer"] 可以表示为 [0, 1, 3],而 ["女性", "来自亚洲", "使用 Chrome"] 则表示为 [1, 2, 1]

要将分类特征转换为这种整数编码,可以使用 OrdinalEncoder。该估计器将每个分类特征转换为一个整数(0 到 n_categories - 1)的新特征:

enc = preprocessing.OrdinalEncoder()
X = [['男性', '来自美国', '使用 Safari'], ['女性', '来自欧洲', '使用 Firefox']]
enc.fit(X)
enc.transform([['女性', '来自美国', '使用 Safari']])
  • 1
  • 2
  • 3
  • 4

这种整数表示法通常不能直接用于所有 scikit-learn 估计器,因为这些估计器期望连续输入,并且会将分类解释为有序的,这通常是不希望的(即浏览器集合是任意排序的)。

默认情况下,OrdinalEncoder 还会传递由 np.nan 指示的缺失值。

>>> enc = preprocessing.OrdinalEncoder()
>>> X = [['male'], ['female'], [np.nan], ['female']]
>>> enc.fit_transform(X)
array([[ 1.],
       [ 0.],
       [nan],
       [ 0.]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

OrdinalEncoder 提供了一个参数 encoded_missing_value,用于在不需要创建流水线并使用 ~sklearn.impute.SimpleImputer 的情况下编码缺失值。

>>> enc = preprocessing.OrdinalEncoder(encoded_missing_value=-1)
>>> X = [['male'], ['female'], [np.nan], ['female']]
>>> enc.fit_transform(X)
array([[ 1.],
       [ 0.],
       [-1.],
       [ 0.]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

上述处理等效于以下流水线:

from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
enc = Pipeline(steps=[
    ("encoder", preprocessing.OrdinalEncoder()),
    ("imputer", SimpleImputer(strategy="constant", fill_value=-1)),
])
enc.fit_transform(X)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

将分类特征转换为可用于 scikit-learn 估计器的特征的另一种可能性是使用一对 K 编码,也称为独热编码或虚拟编码。这种编码类型可以使用 OneHotEncoder 获得,它将具有 n_categories 可能值的每个分类特征转换为 n_categories 个二进制特征,其中一个为 1,其他全部为 0。

继续上面的例子:

enc = preprocessing.OneHotEncoder()
X = [['男性', '来自美国', '使用 Safari'], ['女性', '来自欧洲', '使用 Firefox']]
enc.fit(X)
enc.transform([['女性', '来自美国', '使用 Safari'],
               ['男性', '来自欧洲', '使用 Safari']]).toarray()
  • 1
  • 2
  • 3
  • 4
  • 5

默认情况下,每个特征可以取的值是从数据集中自动推断出来的,并且可以在 categories_ 属性中找到:

enc.categories_
  • 1

可以使用参数 categories 显式指定这一点。在我们的数据集中,有两个性别、四个可能的大陆和四个网络浏览器:

genders = ['女性', '男性']
locations = ['来自非洲', '来自亚洲', '来自欧洲', '来自美国']
browsers = ['使用 Chrome', '使用 Firefox', '使用 IE', '使用 Safari']
enc = preprocessing.OneHotEncoder(categories=[genders, locations, browsers])
# 注意,第二个和第三个特征有缺失的分类值
X = [['男性', '来自美国', '使用 Safari'], ['女性', '来自欧洲', '使用 Firefox']]
enc.fit(X)
enc.transform([['女性', '来自亚洲', '使用 Chrome']]).toarray()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
    >>> enc.transform(np.array([['dragon'], ['cat']]))
    array([[0., 0., 1.],
           [1., 0., 0.]])
  • 1
  • 2
  • 3

The unknown category ‘dragon’ is considered infrequent and encoded as the third feature ‘x0_infrequent_sklearn’. The known category ‘cat’ is encoded as the first feature ‘x0_cat’.

  1. 如果在训练过程中存在一个不常见的类别,未知类别将被视为不常见类别。在逆变换中,'infrequent_sklearn’将用于表示不常见类别。

也可以使用max_categories来配置不常见类别。在下面的示例中,我们将max_categories设置为2以限制输出中的特征数量。这将导致除了’cat’类别之外的所有类别都被视为不常见类别,从而得到两个特征,一个用于’cat’类别,一个用于所有其他不常见类别:

enc = preprocessing.OneHotEncoder(max_categories=2, sparse_output=False)
enc = enc.fit(X)
enc.transform([['dog'], ['cat'], ['rabbit'], ['snake']])
  • 1
  • 2
  • 3

输出结果为:

array([[0., 1.],
       [1., 0.],
       [0., 1.],
       [0., 1.]])
  • 1
  • 2
  • 3
  • 4

如果max_categories和min_frequency都是非默认值,则首先根据min_frequency选择类别,然后保留max_categories个类别。在下面的示例中,min_frequency=4只将’snake’视为不常见类别,但max_categories=3会强制将’dog’也视为不常见类别:

enc = preprocessing.OneHotEncoder(min_frequency=4, max_categories=3, sparse_output=False)
enc = enc.fit(X)
enc.transform([['dog'], ['cat'], ['rabbit'], ['snake']])
  • 1
  • 2
  • 3

输出结果为:

array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]])
  • 1
  • 2
  • 3
  • 4

如果在max_categories截断点处存在相同基数的不常见类别,则根据词典顺序选择前max_categories个类别。在下面的示例中,“b”、"c"和"d"具有相同的基数,并且max_categories=2,因此"b"和"c"被视为不常见类别,因为它们具有更高的词典顺序。

X = np.asarray([["a"] * 20 + ["b"] * 10 + ["c"] * 10 + ["d"] * 10], dtype=object).T
enc = preprocessing.OneHotEncoder(max_categories=3).fit(X)
[enc.infrequent_categories]()
  • 1
  • 2
  • 3

输出结果为:

array(['b', 'c'], dtype=object)
  • 1

目标编码

sklearn.preprocessing

TargetEncoder使用在给定分类特征条件下的目标均值来编码无序类别,即名义类别。这种编码方案对于具有高基数的分类特征非常有用,其中使用独热编码会使特征空间膨胀,导致下游模型处理更加昂贵。高基数类别的经典示例是基于位置的类别,例如邮政编码或地区。对于二分类目标,目标编码的计算公式如下:

S i = λ i n i Y n i + ( 1 − λ i ) n Y n S_i = \lambda_i\frac{n_{iY}}{n_i} + (1 - \lambda_i)\frac{n_Y}{n} Si=λininiY+(1λi)nnY

其中 S i S_i Si是类别 i i i的编码, n i Y n_{iY} niY是具有 Y = 1 Y=1 Y=1和类别 i i i的观测数量, n i n_i ni是具有类别 i i i的观测数量, n Y n_Y nY是具有 Y = 1 Y=1 Y=1的观测数量, n n n是观测数量, λ i \lambda_i λi是类别 i i i的收缩因子。收缩因子的计算公式如下:

λ i = n i m + n i \lambda_i = \frac{n_i}{m + n_i} λi=m+nini

其中 m m m是平滑因子,由TargetEncoder的smooth参数控制。较大的平滑因子将更多的权重放在全局均值上。当smooth="auto"时,平滑因子被计算为经验贝叶斯估计值: m = σ i 2 / τ 2 m=\sigma_i^2/\tau^2 m=σi2/τ2,其中 σ i 2 \sigma_i^2 σi2是具有类别 i i i的y的方差, τ 2 \tau^2 τ2是y的全局方差。

对于多类分类目标,公式与二分类类似:

S i j = λ i n i Y j n i + ( 1 − λ i ) n Y j n S_{ij} = \lambda_i\frac{n_{iY_j}}{n_i} + (1 - \lambda_i)\frac{n_{Y_j}}{n} Sij=λininiYj+(1λi)nnYj

其中 S i j S_{ij} Sij是类别 i i i和类别 j j j的编码, n i Y j n_{iY_j} niYj是具有 Y = j Y=j Y=j和类别 i i i的观测数量, n i n_i ni是具有类别 i i i的观测数量, n Y j n_{Y_j} nYj是具有 Y = j Y=j Y=j的观测数量, n n n是观测数量, λ i \lambda_i λi是类别 i i i的收缩因子。

对于连续目标,公式与二分类类似:

S i = λ i ∑ k ∈ L i Y k n i + ( 1 − λ i ) ∑ k = 1 n Y k n S_i = \lambda_i\frac{\sum_{k\in L_i}Y_k}{n_i} + (1 - \lambda_i)\frac{\sum_{k=1}^{n}Y_k}{n} Si=λinikLiYk+(1λi)nk=1nYk

其中 L i L_i Li是具有类别 i i i的观测集合, n i n_i ni是具有类别 i i i的观测数量。

~TargetEncoder.fit_transform内部依赖于cross fitting方案,以防止目标信息泄漏到训练时的表示中,特别是对于非信息性的高基数分类变量,并帮助防止下游模型过度拟合虚假相关性。请注意,因此,fit(X, y).transform(X)不等于fit_transform(X, y)。在~TargetEncoder.fit_transform中,训练数据被分成k折(由cv参数确定),并且使用其他k-1折学习的编码对每个折进行编码。下图显示了~TargetEncoder.fit_transform中的cross fitting方案,默认情况下cv=5

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

~TargetEncoder.fit_transform还使用整个训练集学习了一个’full data’编码。这在~TargetEncoder.fit_transform中不会使用,但保存在encodings_属性中,以便在调用~TargetEncoder.transform时使用。请注意,在cross fitting方案期间为每个折学习的编码不会保存到属性中。

~TargetEncoder.fit方法使用任何cross fitting方案,并在整个训练集上学习一个编码,该编码用于在~TargetEncoder.transform中对类别进行编码。这个编码与~TargetEncoder.fit_transform中学习的’full data’编码相同。

[!NOTE]
TargetEncoder将缺失值(如np.nan或None)视为另一类别,并像其他类别一样对其进行编码。在fit期间未见的类别将使用目标均值进行编码,即target_mean_。

示例:

  • sphx_glr_auto_examples_preprocessing_plot_target_encoder.py
  • sphx_glr_auto_examples_preprocessing_plot_target_encoder_cross_val.py

参考文献

离散化

离散化(也称为量化或分箱)提供了一种将连续特征划分为离散值的方法。某些具有连续特征的数据集可能会受益于离散化,因为离散化可以将连续属性的数据集转换为只具有名义属性的数据集。

独热编码的离散化特征可以使模型更具表现力,同时保持可解释性。例如,使用离散化器进行预处理可以为线性模型引入非线性。对于更高级的可能性,特别是平滑的可能性,请参见下面的generating_polynomial_features

K-bins离散化

KBinsDiscretizer将特征离散化为k个箱:

X = np.array([[ -3., 5., 15 ],
              [  0., 6., 14 ],
              [  6., 3., 11 ]])
est = preprocessing.KBinsDiscretizer(n_bins=[3, 2, 2], encode='ordinal').fit(X)
  • 1
  • 2
  • 3
  • 4

默认情况下,输出将被独热编码为稀疏矩阵(参见preprocessing_categorical_features),可以使用encode参数进行配置。对于每个特征,边界将在fit期间计算,并且与箱数一起定义间隔。因此,对于当前示例,这些间隔定义如下:

  • 特征1: [ − ∞ , − 1 ) , [ − 1 , 2 ) , [ 2 , ∞ ) {[-\infty, -1), [-1, 2), [2, \infty)} [,1),[1,2),[2,)
  • 特征2: [ − ∞ , 5 ) , [ 5 , ∞ ) {[-\infty, 5), [5, \infty)} [,5),[5,)
  • 特征3: [ − ∞ , 14 ) , [ 14 , ∞ ) {[-\infty, 14), [14, \infty)} [,14),[14,)

基于这些箱间隔,X被转换如下:

est.transform(X)
  • 1

输出结果为:

array([[ 0., 1., 1.],
       [ 1., 1., 1.],
       [ 2., 0., 0.]])
  • 1
  • 2
  • 3

生成的数据集包含有序属性,可以进一步在~sklearn.pipeline.Pipeline中使用。
KBinsDiscretizer 实现了不同的分箱策略,可以通过 strategy 参数进行选择。‘uniform’ 策略使用等宽的箱子。‘quantile’ 策略使用分位数值,在每个特征中有相同数量的样本。‘kmeans’ 策略基于对每个特征独立进行的 k-means 聚类过程来定义箱子。

需要注意的是,可以通过传递一个可调用对象来指定自定义的箱子,该对象定义了离散化策略,传递给 ~sklearn.preprocessing.FunctionTransformer。例如,我们可以使用 Pandas 的 pandas.cut 函数:

import pandas as pd
import numpy as np
bins = [0, 1, 13, 20, 60, np.inf]
labels = ['婴儿', '儿童', '青少年', '成年人', '老年人']
transformer = preprocessing.FunctionTransformer(
    pd.cut, kw_args={'bins': bins, 'labels': labels, 'retbins': False}
)
X = np.array([0.2, 2, 15, 25, 97])
transformer.fit_transform(X)
['婴儿', '儿童', '青少年', '成年人', '老年人']
Categories (5, object): ['婴儿' < '儿童' < '青少年' < '成年人' < '老年人']
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

示例:

  • sphx_glr_auto_examples_preprocessing_plot_discretization.py
  • sphx_glr_auto_examples_preprocessing_plot_discretization_classification.py
  • sphx_glr_auto_examples_preprocessing_plot_discretization_strategies.py

特征二值化

特征二值化是将数值特征阈值化为布尔值的过程。这对于下游的概率估计器非常有用,因为它们假设输入数据服从多变量伯努利分布。例如,~sklearn.neural_network.BernoulliRBM 就是这种情况。

即使在文本处理领域,使用二进制特征值也很常见(可能是为了简化概率推理),尽管在实践中,归一化计数(也称为词频)或 TF-IDF 值的特征通常表现稍好。

Normalizer 类似,实用类 Binarizer 旨在在 ~sklearn.pipeline.Pipeline 的早期阶段使用。fit 方法不执行任何操作,因为每个样本都是独立处理的:

X = [[ 1., -1.,  2.],
     [ 2.,  0.,  0.],
     [ 0.,  1., -1.]]

binarizer = preprocessing.Binarizer().fit(X)  # fit 不执行任何操作
binarizer
Binarizer()

binarizer.transform(X)
array([[1., 0., 1.],
       [1., 0., 0.],
       [0., 1., 0.]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

可以调整二值化器的阈值:

binarizer = preprocessing.Binarizer(threshold=1.1)
binarizer.transform(X)
array([[0., 0., 1.],
       [1., 0., 0.],
       [0., 0., 0.]])
  • 1
  • 2
  • 3
  • 4
  • 5

Normalizer 类一样,预处理模块提供了一个伴随函数 binarize,用于在不需要转换器 API 的情况下使用。

请注意,当 k = 2 且边界值为阈值时,BinarizerKBinsDiscretizer 类似。

稀疏输入

binarizeBinarizer 都接受来自 scipy.sparse密集数组和稀疏矩阵作为输入

对于稀疏输入,数据会被转换为压缩稀疏行表示(参见 scipy.sparse.csr_matrix)。为了避免不必要的内存复制,建议在上游选择 CSR 表示。

缺失值的填充

有关填充缺失值的工具在 impute 中讨论。

生成多项式特征

通常,通过考虑输入数据的非线性特征来为模型增加复杂性是有用的。我们展示了两种可能性,它们都基于多项式:第一种使用纯多项式,第二种使用样条函数,即分段多项式。

多项式特征

一个简单且常见的方法是使用多项式特征,可以获得特征的高阶和交互项。它在 PolynomialFeatures 中实现:

import numpy as np
from sklearn.preprocessing import PolynomialFeatures
X = np.arange(6).reshape(3, 2)
X
array([[0, 1],
       [2, 3],
       [4, 5]])
poly = PolynomialFeatures(2)
poly.fit_transform(X)
array([[ 1.,  0.,  1.,  0.,  0.,  1.],
       [ 1.,  2.,  3.,  4.,  6.,  9.],
       [ 1.,  4.,  5., 16., 20., 25.]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

X 的特征已从 ( X 1 , X 2 ) (X_1, X_2) (X1,X2) 转换为 ( 1 , X 1 , X 2 , X 1 2 , X 1 X 2 , X 2 2 ) (1, X_1, X_2, X_1^2, X_1X_2, X_2^2) (1,X1,X2,X12,X1X2,X22)

在某些情况下,只需要特征之间的交互项,可以通过设置 interaction_only=True 来获得:

X = np.arange(9).reshape(3, 3)
X
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
poly = PolynomialFeatures(degree=3, interaction_only=True)
poly.fit_transform(X)
array([[  1.,   0.,   1.,   2.,   0.,   0.,   2.,   0.],
       [  1.,   3.,   4.,   5.,  12.,  15.,  20.,  60.],
       [  1.,   6.,   7.,   8.,  42.,  48.,  56., 336.]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

X 的特征已从 ( X 1 , X 2 , X 3 ) (X_1, X_2, X_3) (X1,X2,X3) 转换为 ( 1 , X 1 , X 2 , X 3 , X 1 X 2 , X 1 X 3 , X 2 X 3 , X 1 X 2 X 3 ) (1, X_1, X_2, X_3, X_1X_2, X_1X_3, X_2X_3, X_1X_2X_3) (1,X1,X2,X3,X1X2,X1X3,X2X3,X1X2X3)

请注意,当使用多项式 svm_kernels 时,多项式特征会隐式地用于核方法(例如,~sklearn.svm.SVC~sklearn.decomposition.KernelPCA)。

有关使用创建的多项式特征进行 Ridge 回归的示例,请参见 sphx_glr_auto_examples_linear_model_plot_polynomial_interpolation.py

样条变换器

除了使用特征的纯多项式之外,还可以使用 SplineTransformer 为每个特征生成样条基函数。样条是分段多项式,其参数由多项式的阶数和节点的位置确定。SplineTransformer 实现了 B 样条基函数,请参阅下面的参考文献。

[!NOTE]
SplineTransformer 单独处理每个特征,即不会生成交互项。

与多项式相比,样条的一些优点包括:

  • 如果保持固定的较低阶数(通常为 3)并适当调整节点的数量,B 样条非常灵活且稳健。多项式需要更高的阶数,这会导致下一个点。
  • 在边界处,B 样条不会像多项式那样产生振荡行为(阶数越高,情况越糟)。这被称为朗格现象
  • B 样条提供了超出拟合值范围的外推选项。请查看 extrapolation 选项。
  • B 样条生成具有带状结构的特征矩阵。对于单个特征,每行只包含 degree + 1 个非零元素,这些元素连续出现且为正。这导致矩阵具有良好的数值特性,例如较低的条件数,与多项式矩阵形成鲜明对比,后者被称为范德蒙德矩阵。对于线性模型的稳定算法来说,低条件数非常重要。

以下代码片段展示了样条的使用:

import numpy as np
from sklearn.preprocessing import SplineTransformer
X = np.arange(5).reshape(5, 1)
X
array([[0],
       [1],
       [2],
       [3],
       [4]])
spline = SplineTransformer(degree=2, n_knots=3)
spline.fit_transform(X)
array([[0.5  , 0.5  , 0.   , 0.   ],
       [0.125, 0.75 , 0.125, 0.   ],
       [0.   , 0.5  , 0.5  , 0.   ],
       [0.   , 0.125, 0.75 , 0.125],
       [0.   , 0.   , 0.5  , 0.5  ]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

由于 X 是排序的,可以很容易地看到带状矩阵输出。对于 degree=2,只有三个中间对角线是非零的。阶数越高,样条重叠越多。

参考文献:

参考文献:

  • Eilers, P., & Marx, B. (1996). Flexible Smoothing with B-splines and Penalties <10.1214/ss/1038425655>. Statist. Sci. 11 (1996), no. 2, 89–121.
  • Perperoglou, A., Sauerbrei, W., Abrahamowicz, M. et al. A review of spline function procedures in R <10.1186/s12874-019-0666-3>. BMC Med Res Methodol 19, 46 (2019).

自定义转换器

通常,您希望将现有的 Python 函数转换为转换器,以帮助进行数据清洗或处理。您可以使用 FunctionTransformer 从任意函数实现一个转换器。例如,要在流水线中构建一个应用对数变换的转换器,可以执行以下操作:

>>> import numpy as np
>>> from sklearn.preprocessing import FunctionTransformer
>>> transformer = FunctionTransformer(np.log1p, validate=True)
>>> X = np.array([[0, 1], [2, 3]])
>>> # 由于 FunctionTransformer 在拟合过程中不起作用,我们可以直接调用 transform
>>> transformer.transform(X)
array([[0.        , 0.69314718],
       [1.09861229, 1.38629436]])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

您可以通过设置 check_inverse=True 并在 transform 之前调用 fit 来确保 funcinverse_func 互为反函数。请注意,会引发警告,可以通过 filterwarnings 将其转换为错误:

>>> import warnings
>>> warnings.filterwarnings("error", message=".*check_inverse*.",
...                         category=UserWarning, append=False)
  • 1
  • 2
  • 3

有一个完整的代码示例,演示了如何使用 FunctionTransformer 从文本数据中提取特征,请参见 sphx_glr_auto_examples_compose_plot_column_transformer.pysphx_glr_auto_examples_applications_plot_cyclical_feature_engineering.py

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/176431
推荐阅读
相关标签
  

闽ICP备14008679号