跳转至

11.9.特征处理

Linux 5.4.0-74-generic
Python 3.9.5 @ GCC 7.3.0
Latest build date 2021.06.22
sklearn version:  0.24.2
from toolkit import H
from sklearn.preprocessing import (StandardScaler, MinMaxScaler, MaxAbsScaler,
                                   RobustScaler, Normalizer, Binarizer,
                                   KBinsDiscretizer, OrdinalEncoder,
                                   OneHotEncoder, PolynomialFeatures,
                                   FunctionTransformer)

共同的方法

所有转换器都具有以下方法:

  • fit(X, y=None) :基于训练集 X 计算转换所需的参数,参数值会保存在转换器实例中,transform方法被调用时,参数值将被使用。
  • transform(X):对数据集X执行转换,并返还转换后的结果。
  • fit_transform(X, y=None[, fit_params]):一个快捷方式,先调用fit方法计算所需参数值,再调用transform方法返还转换结果。

大部分转换器都具有以下方法:

  • inverse_transform(X):将转换后的数据 X 还原成原始数据。

MaxAbsScalerMinMaxScalerStandardScaler转换器具有以下方法:

  • partial_fit(X, y=None[, sample_weight=None]):根据部分数据计算所需参数值,为后续的转换做准备。该方法支持批量学习(增量学习),这样对于内存更友好。这适用于样本量过大或者流式读取X的情况。

标准化 StandardScaler

数据集的标准化对 scikit-learn 中实现的大多数机器学习算法来说是常见的要求。如果个别特征或多或少看起来不是很像标准正态分布(具有零均值和单位方差),那么算法的性能可能会较差。

许多机习算法的目标函数中包含的元素(例如 SVM 的 RBF 内核或线性模型的 l1 和 l2 正则化项)都是假设所有特征都是零均值并且具有相同阶数的方差。如果某个特征的方差比其他特征大几个数量级,那么在算法求解最优参数的过程中,它会占据主导位置,导致学习器无法按预期从其他特征中正确学习。

在实践中,我们通常会忽略特征的分布形状,直接删除每个特征的平均值来实现特征数据中心化,然后除以非常量特征的标准差来缩放数据。

StandardScaler 实现了 z-score 标准化:

$$ z = \frac{x-\mu}{s} $$

其中,$\mu$ 是训练数据的均值;$s$ 是训练数据的标准差。

StandardScaler(copy=True, with_mean=True, with_std=True)
  • copy:是否拷贝数据。
  • with_mean:转换时是否执行中心化。若为True,则缩放之前先将特征数据减去均值。 如果数据是稀疏矩阵的格式,则不能指定with_mean=True
  • with_std:是否方差归一化。如果为True,则缩放每个特征到单位方差。

调用fit方法之后才存在以下属性

  • scale_:一个数组,给出了每个特征的缩放倍数的倒数。
  • mean_:一个数组,给出了原始数据每个特征的均值。
  • var_:一个数组,给出了原始数据每个特征的方差。
  • n_samples_seen_:一个整数,给出了当前已经处理的样本的数量(用于分批训练)。
from sklearn.preprocessing import StandardScaler

data = [[0, 0], [0, 0], [1, 1], [1, 1]]
scaler = StandardScaler()

scaler.fit(data)

print(scaler.mean_)
print(scaler.transform(data))
print(scaler.transform([[2, 2]]))
[0.5 0.5]
[[-1. -1.]
 [-1. -1.]
 [ 1.  1.]
 [ 1.  1.]]
[[3. 3.]]

归一化 MinMaxScaler

MinMaxScaler估计器分别缩放和转换每个特征,以使其处于训练集的给定范围内。该转换常常作为零均值、单位方差缩放的代替。

MinMaxScaler(feature_range=(0, 1), copy=True, clip=False)
  • feature_range:一个元组(min,max),指定了执行变换之后特征的取值范围。

属性

  • min_:一个数组,给出了每个特征的原始最小值的调整值。设特征 的原始最小值为 ,原始最大值为 。则特征 的原始最小值的调整值为: 。
  • scale_:一个数组,给出了每个特征的缩放倍数 。
  • data_min_:一个数组,给出了每个特征的原始最小值 。
  • data_max_:一个数组,给出了每个特征的原始最大值。
  • data_range_:一个数组,给出了每个特征的原始的范围(最大值减最小值)。

在 0 和 1 之间的缩放,转换公式如下:

$$ X_{std} = \frac{X - \min(X)}{\max(X)-\min(X)} \\ X_{scaled} = X_{std} \times (\max-\min) + \min $$

其中,min, max = feature_range

对于一般的情况,转换公式如下: $$ X_{scaled} = \frac{\max-\min}{\max(X)-\min(X)} \times X + \min - \frac{\max-\min}{\max(X)-\min(X)}\min(X) $$

from sklearn.preprocessing import MinMaxScaler
data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
scaler = MinMaxScaler()
scaler.fit(data)

print(scaler.data_max_)
print(scaler.transform(data))
print(scaler.transform([[2, 2]]))
[ 1. 18.]
[[0.   0.  ]
 [0.25 0.25]
 [0.5  0.5 ]
 [1.   1.  ]]
[[1.5 0. ]]

MaxAbsScaler

MaxAbsScaler 实现了max-abs 标准化:

MaxAbsScaler(copy=True)

属性

  • scale_:一个数组,给出了每个特征的缩放倍数的倒数。
  • max_abs_:一个数组,给出了每个特征的绝对值的最大值。
  • n_samples_seen_:一个整数,给出了当前已经处理的样本的数量(用于分批训练)。

MaxAbsScaler的工作原理与MinMaxScaler非常相似,但是它只通过除以每个特征的最大绝对值将训练数据特征缩放至 [-1, 1] 范围内:

$$ X_{scaled} = \frac{X}{\max(\text{abs}(X))} $$

MaxAbsScaler 不会移动或居中数据,因此不会破坏任何稀疏性。该缩放器可以应用于稀疏CSR或CSC矩阵。

from sklearn.preprocessing import MaxAbsScaler

X = [[1., -1., 2.],
     [2., 0., 0.],
     [0., 1., -1.]]
transformer = MaxAbsScaler().fit(X)
transformer.transform(X)
array([[ 0.5, -1. ,  1. ],
       [ 1. ,  0. ,  0. ],
       [ 0. ,  1. , -0.5]])

缩放存在离群值的数据 RobustScaler

如果数据包含许多异常值,使用均值和方差缩放可能并不是一个很好的选择。这种情况下,可以使用以及 RobustScaler 作为替代品。它对带有离群值的数据的中心和范围有鲁棒性的估计。

from sklearn.preprocessing import RobustScaler

缩放函数

scale(X, axis=0, with_mean=True, with_std=True, copy=True)
minmax_scale(X, feature_range=(0, 1), axis=0, copy=True)
maxabs_scale(X, axis=0, copy=True)
robust_scale(X, axis=0, with_centering=True, with_scaling=True, 
             quantile_range=(25.0, 75.0), copy=True)

scaleminmax_scalemaxabs_scalerobust_scale分别是StandardScalerMinMaxScalerMaxAbsScalerRobustScaler四个estimator的等效函数。如果你不想创建对象,或者不需要使用estimator,这四个函数提供了方便的功能。

缩放稀疏矩阵

中心化稀疏矩阵会破坏数据的稀疏结构,因此很少有一个比较好的实现方式。但是缩放稀疏矩阵是有意义的,尤其是当几个特征在不同的量级范围时。

MaxAbsScaler以及 maxabs_scale 是专为缩放数据而设计的,并且是缩放数据的推荐方法。使用这种缩放的目的包括实现特征极小方差的鲁棒性以及在稀疏矩阵中保留零元素。但是, scaleStandardScaler也能够接受scipy.sparse作为输入,只要设置参数with_mean=False。否则会出现 ValueError 的错误,因为默认的中心化会破坏稀疏性,并且经常会因为分配过多的内存而使执行崩溃。 RobustScaler 不能适应稀疏输入,但你可以在稀疏输入使用 transform 方法。

注意,缩放器同时接受压缩的稀疏行和稀疏列(参见 scipy.sparse.csr_matrix 以及 scipy.sparse.csc_matrix )。任何其他稀疏输入都会被转化为压缩稀疏行表示 。为了避免不必要的内存复制,建议将输入的数据表示为CSR或CSC的形式。

最后,如果稀疏矩阵中心化后的数据并不是很大,使用 toarray 方法将输入的稀疏矩阵显式转换为数组是另一种选择。

正则化

这里说的正则化缩放单个样本以具有单位范数的过程。如果你计划使用二次形式(如点积或任何其他核函数)来量化任何样本间的相似度,则此过程将非常有用。这个观点基于 向量空间模型(Vector Space Model) ,经常在文本分类和内容聚类中使用。

Normalizer类和normalizer函数实现了数据正则化:

Normalizer(norm='l2', copy=True)

normalizer(X, norm='l2', axis=1, copy=True, return_norm=False)
  • norm:一个字符串,指定正则化方法。可以为:

    • 'l1':采用 $L_1$ 范数正则化。

    • 'l2':采用 $L_2$ 范数正则化。

    • 'max':采用 $L_\infty$ 范数正则化。

Normalizer类的方法:

  • fit(X[, y]) :不做任何事情,主要用于为流水线Pipeline 提供接口。
  • transform(X[, y, copy]) :将每一个样本正则化为范数等于单位1。
  • fit_transform(X[, y]) :将每一个样本正则化为范数等于单位1。

函数 normalize 以及类 Normalizer 接收 来自scipy.sparse的密集类数组数据和稀疏矩阵 作为输入。对于稀疏输入,数据被 转化为压缩的稀疏行形式 。为了避免不必要的内存复制,推荐在上游选择CSR表示。

from sklearn.preprocessing import normalize, Normalizer

X = np.array([[1,2]])

print(normalize(X, norm='l1'))
print(normalize(X, norm='l2'))
[[0.33333333 0.66666667]]
[[0.4472136  0.89442719]]

数值特征离散化

二值化

Binarizer 根据阈值对数据进行二值化(将特征值设置为0或1):

Binarizer(threshold=0.0, copy=True)
  • threshold:转换的阈值:低于此阈值的值转换为0,高于此阈值的值转换为 1。

方法:

  • fit(X[, y]) :不作任何事情,主要用于为流水线Pipeline 提供接口。
  • transform(X[, copy]) :将每个样本的特征二元化。
  • fit_transform(X[, y]) :将每个样本的特征二元化。

预处理模块提供了一个相似的函数 binarize ,以便不需要转换接口时使用。

binarize 以及 Binarizer 都可以接收来自scipy.sparse的稠密矩阵或稀疏矩阵作为输入。#' 对于稀疏输入,数据被转化为压缩的稀疏行形式 (参见 scipy.sparse.csr_matrix )。为了避免不必要的内存复制,推荐在上游选择CSR表示。

from sklearn.preprocessing import Binarizer
import numpy as np

bins = Binarizer(threshold=0.5)
X = np.array([[1, 2], [0.4, 0.3]])

bins.transform(X)
array([[1., 1.],
       [0., 0.]])

K-bins 离散化

将连续数据分成K个间隔。

KBinsDiscretizer(n_bins=5, encode='onehot', strategy='quantile')
  • n_bins
  • encode:指定用于编码转换结果的方法。

    • onehot:onehot编码,返回稀疏矩阵。

    • onehot-dense:onehot编码,返回稠密矩阵。

    • ordinal:每个bin被编码成一个唯一的整数值。

  • strategy:指定不同的 bin 策略。

    • uniform:特征的bins具有相同的宽度。

    • quantile:特征的各个的bins具有相同数量的点。

    • kmeans:每个bin中的值具有一维k-means簇的最近中心。

KBinsDiscretizer使用k个等宽的bins把特征离散化

from sklearn.preprocessing import KBinsDiscretizer
import numpy as np

X = np.array([[ -3., 5., 15 ],
              [  0., 6., 14 ],
              [  6., 3., 11 ]])
KBinsDiscretizer(n_bins=[3, 2, 2], encode='ordinal').fit_transform(X)
array([[0., 1., 1.],
       [1., 1., 1.],
       [2., 0., 0.]])
KBinsDiscretizer(n_bins=[3, 2, 2], encode='onehot-dense').fit_transform(X)
array([[1., 0., 0., 0., 1., 0., 1.],
       [0., 1., 0., 0., 1., 0., 1.],
       [0., 0., 1., 1., 0., 1., 0.]])

这些区间间隔被定义如下:

  • 特征 1:[-∞,-1], [-1,2), [2,∞)
  • 特征 2:[-∞,5), [5,∞)
  • 特征 3:[-∞,14], [14,∞)

由此产生的数据集包含了有序属性(ordinal attributes),可以被进一步用在 sklearn.pipeline.Pipeline 中。

离散化 (Discretization) 类似于为连续数据构建直方图(histograms)。 然而,直方图聚焦于统计特征落在特定的bins里面的数量,而离散化聚焦于给这些bins分配特征取值。

更多示例

类别特征编码

整数编码

要把类别特征 (categorical features) 转换为整数编码 (integer codes),可以使用 OrdinalEncoder 。 这个估计器把每一个类别特征变换成 一个新的整数数字特征 (0 到 n_categories - 1):

OrdinalEncoder(categories='auto', dtype=<class 'numpy.float64'>)
from sklearn.preprocessing import OrdinalEncoder

enc = OrdinalEncoder()
X = [['male', 'from US', 'uses Safari'], 
     ['female', 'from Europe', 'uses Firefox']]

enc.fit(X)
enc.transform(X)
array([[1., 1., 1.],
       [0., 0., 0.]])

这样的整数特征表示并不能在scikit-learn的估计器中直接使用,因为这样的连续输入,估计器会认为类别之间是有序的,但实际却是无序的。(例如:浏览器的类别数据是任意排序的)。

onehot 编码

另外一种将类别特征转换为能够被scikit-learn模型使用的编码是 one-of-K,又称为独热编码或dummy encoding。 onehot 编码由OneHotEncoder 实现。该类把每一个具有n_categories个可能取值的categorical特征变换为长度为n_categories的二进制特征向量,里面只有一个地方是1,其余位置都是0。

OneHotEncoder(categories='auto', drop=None, sparse=True, 
              dtype=<class 'numpy.float64'>, 
              handle_unknown='error')
from sklearn.preprocessing import OneHotEncoder

enc = OneHotEncoder()
X = [['male', 'from US', 'uses Safari'], 
     ['female', 'from Europe', 'uses Firefox']]
enc.fit(X)
enc.transform(X).toarray()
array([[0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0.]])

默认情况下,每个特征使用几维的数值可以从数据集自动推断。而且也可以在属性categories_中找到:

enc.categories_
[array(['female', 'male'], dtype=object),
 array(['from Europe', 'from US'], dtype=object),
 array(['uses Firefox', 'uses Safari'], dtype=object)]

categories 参数

可以使用参数categories显式地指定这一点。下面的数据集中有两种性别、四个洲和四种web浏览器:

genders = ['female', 'male']
locations = ['from Africa', 'from Asia', 'from Europe', 'from US']
browsers = ['uses Chrome', 'uses Firefox', 'uses IE', 'uses Safari']
enc = OneHotEncoder(categories=[genders, locations, browsers])

# Note that for there are missing categorical values for the 2nd and 3rd
# feature
X = [['male', 'from US', 'uses Safari'], 
     ['female', 'from Europe', 'uses Firefox']]
enc.fit(X)

enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray()
array([[1., 0., 0., 1., 0., 0., 1., 0., 0., 0.]])

handle_unknown 参数

如果训练数据可能缺少类别特性,最好指定handle_unknown='ignore',而不是像上面那样手动设置类别。当指定handle_unknown='ignore',并且在转换过程中遇到未知类别时,不会产生错误,但是存在未知类别的特征生成的onehot编码列将全部为零(handle_unknown='ignore'只支持onehot编码):

enc = OneHotEncoder(handle_unknown='ignore')
X = [['male', 'from US', 'uses Safari'], 
     ['female', 'from Europe', 'uses Firefox']]
enc.fit(X)
enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray()
array([[1., 0., 0., 0., 0., 0.]])

drop 参数

还可以使用drop参数将每个列编码为n_categories-1列,而不是n_categories列。此参数允许用户为要删除的每个特征指定类别。这对于避免某些分类器中输入矩阵的共线性是有用的。例如,当使用非正则化回归(线性回归)时,这种功能是有用的,因为共线性会导致协方差矩阵是不可逆的。当这个参数不是None时,handle_unknown必须设置为error:

X = [['male', 'from US', 'uses Safari'], 
     ['female', 'from Europe', 'uses Firefox']]
drop_enc = OneHotEncoder(drop='first').fit(X)
print(drop_enc.categories_)

drop_enc.transform(X).toarray()
[array(['female', 'male'], dtype=object), array(['from Europe', 'from
US'], dtype=object), array(['uses Firefox', 'uses Safari'],
dtype=object)]
array([[1., 1., 1.],
       [0., 0., 0.]])
no_drop_enc = OneHotEncoder(drop=None).fit(X)
print(no_drop_enc.categories_)

no_drop_enc.transform(X).toarray()
[array(['female', 'male'], dtype=object), array(['from Europe', 'from
US'], dtype=object), array(['uses Firefox', 'uses Safari'],
dtype=object)]
array([[0., 1., 0., 1., 0., 1.],
       [1., 0., 1., 0., 1., 0.]])

类别特征有时是用字典来表示的,而不是标量,具体请参阅从字典中加载特征

特征生成

生成多项式特征

在机器学习中,通过增加一些输入数据的非线性特征来增加模型的复杂度通常是有效的。一个简单通用的办法是使用多项式特征,这可以获得特征的更高维度和交互项。这在 PolynomialFeatures 中实现:

PolynomialFeatures(degree=2, interaction_only=False, include_bias=True, 
                   order='C')
  • interaction_only:只保留交互项。
import numpy as np
from sklearn.preprocessing import PolynomialFeatures

X = np.arange(6).reshape(3, 2)
X                                            

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.]])

X 的特征已经从 $(X_1, X_2)$转换为$(1,X_1,X_2,X^2_1,X_1X_2, X^2_2)$。

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

poly = PolynomialFeatures(degree=2, interaction_only=True)
poly.fit_transform(X)
array([[ 1.,  0.,  1.,  0.],
       [ 1.,  2.,  3.,  6.],
       [ 1.,  4.,  5., 20.]])

X 的特征已经从 $(X_1, X_2)$转换为$(1,X_1,X_2,X_1X_2)$。

注意,当使用多项的 Kernel functions 时 ,多项式特征被隐式地在核函数中被调用(比如, sklearn.svm.SVCsklearn.decomposition.KernelPCA )。

创建并使用多项式特征的岭回归实例请见 Polynomial interpolation

自定义转换器

在机器学习中,想要将一个已有的 Python 函数转化为一个转换器来协助数据清理或处理。可以使用 FunctionTransformer 从任意函数中实现一个转换器。例如,在一个管道中构建一个实现日志转换的转化器,这样做:

import numpy as np
from sklearn.preprocessing import FunctionTransformer

transformer = FunctionTransformer(np.log1p, validate=True)
X = np.array([[0, 1], [2, 3]])
transformer.transform(X)
array([[0.        , 0.69314718],
       [1.09861229, 1.38629436]])

通过设置check_reverse=True并在转换之前调用fit,可以确保funcinverse_func是彼此的拟过程。请注意,请注意一个warning会被抛出,并且可以使用filterwarnings将其转为一个error

使用一个 FunctionTransformer 类来做定制化特征选择的示例,请见 Using FunctionTransformer to select columns