# 5.3 预处理数据 校验者:         [@if only](https://github.com/apachecn/scikit-learn-doc-zh) 翻译者:         [@Trembleguy](https://github.com/apachecn/scikit-learn-doc-zh) `sklearn.preprocessing` 包提供了几个常见的实用功能和变换器类型,用来将原始特征向量更改为更适合机器学习模型的形式。 一般来说,机器学习算法受益于数据集的标准化。如果数据集中存在一些离群值,那么稳定的缩放或转换更合适。不同缩放、转换以及归一在一个包含边缘离群值的数据集中的表现在 [Compare the effect of different scalers on data with outliers](https://scikit-learn.org/stable/auto_examples/preprocessing/plot_all_scaling.html#sphx-glr-auto-examples-preprocessing-plot-all-scaling-py) 中有着重说明。 ## 5.3.1 标准化,也称去均值和方差按比例缩放 数据集的 **标准化** 对scikit-learn中实现的大多数机器学习算法来说是 **常见的要求** 。如果个别特征或多或少看起来不是很像标准正态分布(**具有零均值和单位方差**),那么它们的表现力可能会较差。 在实际情况中,我们经常忽略特征的分布形状,直接经过去均值来对某个特征进行中心化,再通过除以非常量特征(non-constant features)的标准差进行缩放。 例如,在机器学习算法的目标函数(例如SVM的RBF内核或线性模型的l1和l2正则化),许多学习算法中目标函数的基础都是假设所有的特征都是零均值并且具有同一阶数上的方差。如果某个特征的方差比其他特征大几个数量级,那么它就会在学习算法中占据主导位置,导致学习器并不能像我们说期望的那样,从其他特征中学习。 函数 [`scale`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.scale.html#sklearn.preprocessing.scale "sklearn.preprocessing.scale") 为数组形状的数据集的标准化提供了一个快捷实现: ```py >>> import numpy as np >>> X_train = np.array([[ 1., -1., 2.], ... [ 2., 0., 0.], ... [ 0., 1., -1.]]) >>> X_scaled = preprocessing.scale(X_train) >>> X_scaled array([[ 0. ..., -1.22..., 1.33...], [ 1.22..., 0. ..., -0.26...], [-1.22..., 1.22..., -1.06...]]) 经过缩放后的数据具有零均值以及标准方差: ```py >>> X_scaled.mean(axis=0) array([ 0., 0., 0.]) >>> X_scaled.std(axis=0) array([ 1., 1., 1.]) ``` `预处理` 模块还提供了一个实用类 [`StandardScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html#sklearn.preprocessing.StandardScaler "sklearn.preprocessing.StandardScaler") ,它实现了转化器的API来计算训练集上的平均值和标准偏差,以便以后能够在测试集上重新应用相同的变换。因此,这个类适用于 [`sklearn.pipeline.Pipeline`](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html#sklearn.pipeline.Pipeline "sklearn.pipeline.Pipeline") 的早期步骤: ```py >>> scaler = preprocessing.StandardScaler().fit(X_train) >>> scaler StandardScaler(copy=True, with_mean=True, with_std=True) >>> scaler.mean_ array([ 1. ..., 0. ..., 0.33...]) >>> scaler.scale_ array([ 0.81..., 0.81..., 1.24...]) >>> scaler.transform(X_train) array([[ 0. ..., -1.22..., 1.33...], [ 1.22..., 0. ..., -0.26...], [-1.22..., 1.22..., -1.06...]]) ``` 缩放类对象可以在新的数据上实现和训练集相同缩放操作: ```py >>> X_test = [[-1., 1., 0.]] >>> scaler.transform(X_test) array([[-2.44..., 1.22..., -0.26...]]) ``` 你也可以通过在构造函数 :class:StandardScaler 中传入参数 `with_mean=False` 或者`with_std=False` 来取消中心化或缩放操作。 ### 5.3.1.1 将特征缩放至特定范围内 一种标准化是将特征缩放到给定的最小值和最大值之间,通常在零和一之间,或者也可以将每个特征的最大绝对值转换至单位大小。可以分别使用 [`MinMaxScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html#sklearn.preprocessing.MinMaxScaler "sklearn.preprocessing.MinMaxScaler") 和 [`MaxAbsScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MaxAbsScaler.html#sklearn.preprocessing.MaxAbsScaler "sklearn.preprocessing.MaxAbsScaler") 实现。 使用这种缩放的目的包括实现特征极小方差的鲁棒性以及在稀疏矩阵中保留零元素。 以下是一个将简单的数据矩阵缩放到``[0, 1]``的示例: ```py >>> 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. ]]) ``` 同样的转换实例可以被用与在训练过程中不可见的测试数据:实现和训练数据一致的缩放和移位操作: ```py >>> 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]]) ``` 可以检查缩放器(scaler)属性,来观察在训练集中学习到的转换操作的基本性质: ```py >>> min_max_scaler.scale_ array([ 0.5 , 0.5 , 0.33...]) >>> min_max_scaler.min_ array([ 0. , 0.5 , 0.33...]) ``` 如果给 [`MinMaxScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MinMaxScaler.html#sklearn.preprocessing.MinMaxScaler "sklearn.preprocessing.MinMaxScaler") 提供一个明确的 `feature_range=(min, max)` ,完整的公式是: ```py X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0)) X_scaled = X_std * (max - min) + min ``` 类 [`MaxAbsScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MaxAbsScaler.html#sklearn.preprocessing.MaxAbsScaler "sklearn.preprocessing.MaxAbsScaler") 的工作原理非常相似,但是它只通过除以每个特征的最大值将训练数据特征缩放至 `[-1, 1]` 范围内,这就意味着,训练数据应该是已经零中心化或者是稀疏数据。 示例::用先前示例的数据实现最大绝对值缩放操作。 以下是使用上例中数据运用这个缩放器的示例: ```py >>> 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 # doctest +NORMALIZE_WHITESPACE^ 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.]) ``` 在 [`scale`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.scale.html#sklearn.preprocessing.scale "sklearn.preprocessing.scale") 模块中进一步提供了方便的功能。当你不想创建对象时,可以使用如 [`minmax_scale`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.minmax_scale.html#sklearn.preprocessing.minmax_scale "sklearn.preprocessing.minmax_scale") 以及 [`maxabs_scale`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.maxabs_scale.html#sklearn.preprocessing.maxabs_scale "sklearn.preprocessing.maxabs_scale") 。 ### 5.3.1.2 缩放稀疏(矩阵)数据 中心化稀疏(矩阵)数据会破坏数据的稀疏结构,因此很少有一个比较明智的实现方式。但是缩放稀疏输入是有意义的,尤其是当几个特征在不同的量级范围时。 [`MaxAbsScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MaxAbsScaler.html#sklearn.preprocessing.MaxAbsScaler "sklearn.preprocessing.MaxAbsScaler") 以及 [`maxabs_scale`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.maxabs_scale.html#sklearn.preprocessing.maxabs_scale "sklearn.preprocessing.maxabs_scale") 是专为缩放数据而设计的,并且是缩放数据的推荐方法。但是, [`scale`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.scale.html#sklearn.preprocessing.scale "sklearn.preprocessing.scale") 和 [`StandardScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html#sklearn.preprocessing.StandardScaler "sklearn.preprocessing.StandardScaler") 也能够接受 `scipy.sparse` 作为输入,只要参数 `with_mean=False` 被准确传入它的构造器。否则会出现 `ValueError` 的错误,因为默认的中心化会破坏稀疏性,并且经常会因为分配过多的内存而使执行崩溃。 [`RobustScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.RobustScaler.html#sklearn.preprocessing.RobustScaler "sklearn.preprocessing.RobustScaler") 不能适应稀疏输入,但你可以在稀疏输入使用 `transform` 方法。 注意,缩放器同时接受压缩的稀疏行和稀疏列(参见 `scipy.sparse.csr_matrix` 以及 `scipy.sparse.csc_matrix` )。任何其他稀疏输入将会 **转化为压缩稀疏行表示** 。为了避免不必要的内存复制,建议在上游(早期)选择CSR或CSC表示。 最后,如果已经中心化的数据并不是很大,使用 `toarray` 方法将输入的稀疏矩阵显式转换为数组是另一种选择。 ### 5.3.1.3 缩放有离群值的数据 如果你的数据包含许多异常值,使用均值和方差缩放可能并不是一个很好的选择。这种情况下,你可以使用 [`robust_scale`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.robust_scale.html#sklearn.preprocessing.robust_scale "sklearn.preprocessing.robust_scale") 以及 [`RobustScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.RobustScaler.html#sklearn.preprocessing.RobustScaler "sklearn.preprocessing.RobustScaler") 作为替代品。它们对你的数据的中心和范围使用更有鲁棒性的估计。 > **参考资料**: > >更多关于中心化和缩放数据的重要性讨论在此FAQ中提及: [Should I normalize/standardize/rescale the data?](http://www.faqs.org/faqs/ai-faq/neural-nets/part2/section-16.html) >**Scaling vs Whitening** > >有时候独立地中心化和缩放数据是不够的,因为下游的机器学习模型能够对特征之间的线性依赖做出一些假设(这对模型的学习过程来说是不利的)。 > >要解决这个问题,你可以使用 [`sklearn.decomposition.PCA`](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html#sklearn.decomposition.PCA "sklearn.decomposition.PCA")并指定参数 `whiten=True` 来更多移除特征间的线性关联。 >**在回归中缩放目标变量** > >上面介绍过的所有函数(`scale`, `minmax_scale`, `maxabs_scale`, 和 `robust_scale`) 都可以直接处理一维数组。 ### 5.3.1.4 核矩阵的中心化 如果你有一个核矩阵 ![K](img/e279b8169ddd6581c5606c868ba52fae.jpg) ,它计算由函数 ![phi](img/a8e210a94f6eac6c32bc219dbc049288.jpg) 定义的特征空间的点积,那么一个 [`KernelCenterer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.KernelCenterer.html#sklearn.preprocessing.KernelCenterer "sklearn.preprocessing.KernelCenterer") 类能够转化这个核矩阵,通过移除特征空间的平均值,使它包含由函数 ![phi](img/a8e210a94f6eac6c32bc219dbc049288.jpg) 定义的内部产物。 ## 5.3.2 非线性转换 有两种类型的转换是可用的:分位数转换和幂函数转换。分位数和幂变换都基于特征的单调变换,从而保持了每个特征值的秩。 分位数变换根据公式G-1(F(X))将所有特征置于相同的期望分布中,其中F是特征的累积分布函数,G-1是期望输出值分布G的[分位数函数](https://en.wikipedia.org/wiki/Quantile_function).这个公式基于以下两个事实: 1. 如果X是具有连续累积分布函数F的随机变量,那么F(X)均匀分布在[0,1] 2. 如果U是在[0,1]上的随机分布,那么G-1(U)有分布G. 通过执行秩变换,分位数变换平滑了异常分布,并且比缩放方法受异常值的影响更小。但是它的确使特征间及特征内的关联和距离失真了。 幂变换则是一组参数变换,其目的是将数据从任意分布映射到接近高斯分布的位置。 ### 5.3.2.1 映射到均匀分布 [`QuantileTransformer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.QuantileTransformer.html#sklearn.preprocessing.QuantileTransformer "sklearn.preprocessing.QuantileTransformer") 类以及 [`quantile_transform`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.quantile_transform.html#sklearn.preprocessing.quantile_transform "sklearn.preprocessing.quantile_transform") 函数提供了一个基于分位数函数的无参数转换,将数据映射到了零到一的均匀分布上: ```py >>> from sklearn.datasets import load_iris >>> from sklearn.model_selection import train_test_split >>> iris = load_iris() >>> X, y = iris.data, iris.target >>> 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]) array([ 4.3, 5.1, 5.8, 6.5, 7.9]) ``` 这个特征是萼片的厘米单位的长度。一旦应用分位数转换,这些元素就接近于之前定义的百分位数: ```py >>> np.percentile(X_train_trans[:, 0], [0, 25, 50, 75, 100]) ... array([ 0.00... , 0.24..., 0.49..., 0.73..., 0.99... ]) ``` 这可以在具有类似形式的独立测试集上确认: ```py >>> np.percentile(X_test[:, 0], [0, 25, 50, 75, 100]) ... array([ 4.4 , 5.125, 5.75 , 6.175, 7.3 ]) >>> np.percentile(X_test_trans[:, 0], [0, 25, 50, 75, 100]) ... array([ 0.01..., 0.25..., 0.46..., 0.60... , 0.94...]) ``` ### 5.3.2.2 映射到高斯分布 在许多建模场景中,需要数据集中的特征的正态化。幂变换是一类参数化的单调变换, 其目的是将数据从任何分布映射到尽可能接近高斯分布,以便稳定方差和最小化偏斜。 类 [PowerTransformer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PowerTransformer.html#sklearn.preprocessing.PowerTransformer) 目前提供两个这样的幂变换,`Yeo-Johnson transform` 和 `the Box-Cox transform`。 Yeo-Johnson transform: ![\begin{split}x_i^{(\lambda)} =\begin{cases} [(x_i + 1)^\lambda - 1] / \lambda & \text{if } \lambda \neq 0, x_i \geq 0, \\[8pt]\ln{(x_i) + 1} & \text{if } \lambda = 0, x_i \geq 0 \\[8pt]-[(-x_i + 1)^{2 - \lambda} - 1] / (2 - \lambda) & \text{if } \lambda \neq 2, x_i < 0, \\[8pt] - \ln (- x_i + 1) & \text{if } \lambda = 2, x_i < 0\end{cases}\end{split}](img/preprocessing001.png) Box-Cox transform: ![\begin{split}x_i^{(\lambda)} =\begin{cases}\dfrac{x_i^\lambda - 1}{\lambda} & \text{if } \lambda \neq 0, \\[8pt]\ln{(x_i)} & \text{if } \lambda = 0,\end{cases}\end{split}](img/preprocessing002.png) Box-Cox只能应用于严格的正数据。在这两种方法中,变换都是参数化的,通过极大似然估计来确定。下面是一个使用Box-Cox将样本从对数正态分布映射到正态分布的示例: ```py >>> 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...]]) ``` 上述示例设置了参数`standardize`的选项为 False 。 但是,默认情况下,类[PowerTransformer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PowerTransformer.html#sklearn.preprocessing.PowerTransformer)将会应用`zero-mean`,`unit-variance normalization`到变换出的输出上。 下面的示例中 将 Box-Cox 和 Yeo-Johnson 应用到各种不同的概率分布上。 请注意 当把这些方法用到某个分布上的时候, 幂变换得到的分布非常像高斯分布。但是对其他的一些分布,结果却不太有效。这更加强调了在幂变换前后对数据进行可视化的重要性。 [![sphx_glr_plot_map_data_to_normal_0011.png](img/sphx_glr_plot_map_data_to_normal_0011.png)](https://scikit-learn.org/stable/auto_examples/preprocessing/plot_map_data_to_normal.html) 我们也可以 使用类 [QuantileTransformer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.QuantileTransformer.html#sklearn.preprocessing.QuantileTransformer) (通过设置 `output_distribution`='normal')把数据变换成一个正态分布。下面是将其应用到iris dataset上的结果: ```py >>> quantile_transformer = preprocessing.QuantileTransformer( ... output_distribution='normal', random_state=0) >>> X_trans = quantile_transformer.fit_transform(X) >>> quantile_transformer.quantiles_ array([[4.3, 2. , 1. , 0.1], [4.4, 2.2, 1.1, 0.1], [4.4, 2.2, 1.2, 0.1], ..., [7.7, 4.1, 6.7, 2.5], [7.7, 4.2, 6.7, 2.5], [7.9, 4.4, 6.9, 2.5]]) ``` 因此,输入的中值变成了输出的均值,以0为中心。正态输出被裁剪以便输入的最大最小值(分别对应于1e-7和1-1e-7)不会在变换之下变成无穷。 ## 5.3.3 归一化 **归一化** 是 **缩放单个样本以具有单位范数** 的过程。如果你计划使用二次形式(如点积或任何其他核函数)来量化任何样本间的相似度,则此过程将非常有用。 这个观点基于 [向量空间模型(Vector Space Model)](https://en.wikipedia.org/wiki/Vector_Space_Model) ,经常在文本分类和内容聚类中使用. 函数 [`normalize`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.normalize.html#sklearn.preprocessing.normalize "sklearn.preprocessing.normalize") 提供了一个快速简单的方法在类似数组的数据集上执行操作,使用 `l1` 或 `l2` 范式: ```py >>> X = [[ 1., -1., 2.], ... [ 2., 0., 0.], ... [ 0., 1., -1.]] >>> X_normalized = preprocessing.normalize(X, norm='l2') >>> X_normalized array([[ 0.40..., -0.40..., 0.81...], [ 1. ..., 0. ..., 0. ...], [ 0. ..., 0.70..., -0.70...]]) ``` `preprocessing` 预处理模块提供的 [`Normalizer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html#sklearn.preprocessing.Normalizer "sklearn.preprocessing.Normalizer") 工具类使用 `Transformer` API 实现了相同的操作(即使在这种情况下, `fit` 方法是无用的:该类是无状态的,因为该操作独立对待样本). 因此这个类适用于 [`sklearn.pipeline.Pipeline`](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html#sklearn.pipeline.Pipeline "sklearn.pipeline.Pipeline") 的早期步骤: ```py >>> normalizer = preprocessing.Normalizer().fit(X) # fit does nothing >>> normalizer Normalizer(copy=True, norm='l2') ``` 在这之后归一化实例可以被使用在样本向量中,像任何其他转换器一样: ```py >>> normalizer.transform(X) array([[ 0.40..., -0.40..., 0.81...], [ 1. ..., 0. ..., 0. ...], [ 0. ..., 0.70..., -0.70...]]) >>> normalizer.transform([[-1., 1., 0.]]) array([[-0.70..., 0.70..., 0. ...]]) ``` >**稀疏(数据)输入** > >函数 [`normalize`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.normalize.html#sklearn.preprocessing.normalize "sklearn.preprocessing.normalize") 以及类 [`Normalizer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html#sklearn.preprocessing.Normalizer "sklearn.preprocessing.Normalizer") 接收 **来自scipy.sparse的密集类数组数据和稀疏矩阵** 作为输入。 > >对于稀疏输入,在被提交给高效Cython例程前,数据被 **转化为压缩的稀疏行形式** (参见 `scipy.sparse.csr_matrix` )。为了避免不必要的内存复制,推荐在上游选择CSR表示。 ## 5.3.4 类别特征编码 在机器学习中,特征经常不是连续的数值型的而是标称型的(categorical)。举个示例,一个人的样本具有特征`["male", "female"]`, `["from Europe", "from US", "from Asia"]`, `["uses Firefox", "uses Chrome", "uses Safari", "uses Internet Explorer"]` 等。 这些特征能够被有效地编码成整数,比如 `["male", "from US", "uses Internet Explorer"]` 可以被表示为 `[0, 1, 3]`,`["female", "from Asia", "uses Chrome"]` 表示为 `[1, 2, 1]` 。 要把标称型特征(categorical features) 转换为这样的整数编码(integer codes), 我们可以使用 [OrdinalEncoder](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OrdinalEncoder.html#sklearn.preprocessing.OrdinalEncoder) 。 这个估计器把每一个categorical feature变换成 一个新的整数数字特征 (0 到 n_categories - 1): ```py >>> enc = preprocessing.OrdinalEncoder() >>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']] >>> enc.fit(X) OrdinalEncoder(categories='auto', dtype=<... 'numpy.float64'>) >>> enc.transform([['female', 'from US', 'uses Safari']]) array([[0., 1., 1.]]) ``` 这样的整数特征表示并不能在scikit-learn的估计器中直接使用,因为这样的连续输入,估计器会认为类别之间是有序的,但实际却是无序的。(例如:浏览器的类别数据是任意排序的)。 另外一种将标称型特征转换为能够被scikit-learn中模型使用的编码是one-of-K, 又称为 独热码或dummy encoding。 这种编码类型已经在类[OneHotEncoder](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html#sklearn.preprocessing.OneHotEncoder)中实现。该类把每一个具有n_categories个可能取值的categorical特征变换为长度为n_categories的二进制特征向量,里面只有一个地方是1,其余位置都是0。 继续我们上面的示例: ```py >>> >>> enc = preprocessing.OneHotEncoder() >>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']] >>> enc.fit(X) OneHotEncoder(categorical_features=None, categories=None, dtype=<... 'numpy.float64'>, handle_unknown='error', n_values=None, sparse=True) >>> enc.transform([['female', 'from US', 'uses Safari'], ... ['male', 'from Europe', 'uses Safari']]).toarray() array([[1., 0., 0., 1., 0., 1.], [0., 1., 1., 0., 0., 1.]]) ``` 默认情况下,每个特征使用几维的数值可以从数据集自动推断。而且也可以在属性`categories_`中找到: ```py >>> >>> enc.categories_ [array(['female', 'male'], dtype=object), array(['from Europe', 'from US'], dtype=object), array(['uses Firefox', 'uses Safari'], dtype=object)] ``` 可以使用参数`categories_`显式地指定这一点。我们的数据集中有两种性别、四种可能的大陆和四种web浏览器: ```py >>> genders = ['female', 'male'] >>> locations = ['from Africa', 'from Asia', 'from Europe', 'from US'] >>> browsers = ['uses Chrome', 'uses Firefox', 'uses IE', 'uses Safari'] >>> enc = preprocessing.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) OneHotEncoder(categorical_features=None, categories=[...], drop=None, dtype=<... 'numpy.float64'>, handle_unknown='error', n_values=None, sparse=True) >>> enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray() array([[1., 0., 0., 1., 0., 0., 1., 0., 0., 0.]]) ``` 如果训练数据可能缺少分类特性,通常最好指定`handle_unknown`='ignore',而不是像上面那样手动设置类别。当指定handle_unknown='ignore',并且在转换过程中遇到未知类别时,不会产生错误,但是为该特性生成的一热编码列将全部为零(handle_unknown='ignore'只支持一热编码): 如果训练数据中可能含有缺失的标称型特征, 通过指定`handle_unknown`='ignore'比像上面代码那样手动设置`categories`更好。 当`handle_unknown`='ignore' 被指定并在变换过程中真的碰到了未知的 categories, 则不会抛出任何错误,但是由此产生的该特征的one-hot编码列将会全部变成 0 。(这个参数设置选项 `handle_unknown`='ignore' 仅仅在 one-hot encoding的时候有效): ```py >>> enc = preprocessing.OneHotEncoder(handle_unknown='ignore') >>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']] >>> enc.fit(X) OneHotEncoder(categorical_features=None, categories=None, drop=None, dtype=<... 'numpy.float64'>, handle_unknown='ignore', n_values=None, sparse=True) >>> enc.transform([['female', 'from Asia', 'uses Chrome']]).toarray() array([[1., 0., 0., 0., 0., 0.]]) ``` 还可以使用`drop`参数将每个列编码为n_categories-1列,而不是n_categories列。此参数允许用户为要删除的每个特征指定类别。这对于避免某些分类器中输入矩阵的共线性是有用的。例如,当使用非正则化回归([线性回归](2))时,这种功能是有用的,因为共线性会导致协方差矩阵是不可逆的。当这个参数不是None时,`handle_unknown`必须设置为error: ```py >>> X = [['male', 'from US', 'uses Safari'], ['female', 'from Europe', 'uses Firefox']] >>> drop_enc = preprocessing.OneHotEncoder(drop='first').fit(X) >>> drop_enc.categories_ [array(['female', 'male'], dtype=object), array(['from Europe', 'from US'], dtype=object), array(['uses Firefox', 'uses Safari'], dtype=object)] >>> drop_enc.transform(X).toarray() array([[1., 1., 1.], [0., 0., 0.]]) ``` 标称型特征有时是用字典来表示的,而不是标量,具体请参阅[从字典中加载特征](39#521-从字典类型加载特征)。 ## 5.3.5 离散化 [离散化 (Discretization)](https://en.wikipedia.org/wiki/Discretization_of_continuous_features) (有些时候叫 量化(quantization) 或 装箱(binning)) 提供了将连续特征划分为离散特征值的方法。 某些具有连续特征的数据集会受益于离散化,因为 离散化可以把具有连续属性的数据集变换成只有名义属性(nominal attributes)的数据集。 (译者注: nominal attributes 其实就是 categorical features, 可以译为 名称属性,名义属性,符号属性,离散属性 等) One-hot 编码的离散化特征 可以使得一个模型更加的有表现力(expressive),同时还能保留其可解释性(interpretability)。 比如,用离散化器进行预处理可以给线性模型引入非线性。 ### 5.3.5.1 K-bins 离散化 [KBinsDiscretizer](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.KBinsDiscretizer.html#sklearn.preprocessing.KBinsDiscretizer) 类使用k个等宽的bins把特征离散化 ```py >>> X = np.array([[ -3., 5., 15 ], ... [ 0., 6., 14 ], ... [ 6., 3., 11 ]]) >>> est = preprocessing.KBinsDiscretizer(n_bins=[3, 2, 2], encode='ordinal').fit(X) ``` 默认情况下,输出是被 one-hot 编码到一个稀疏矩阵。(请看[类别特征编码](#534-类别特征编码))。 而且可以使用参数`encode`进行配置。对每一个特征, bin的边界以及总数目在 `fit`过程中被计算出来,它们将用来定义区间。 因此,对现在的示例,这些区间间隔被定义如下: - 特征 1:`[-∞,-1],[-1,2),[2,∞)` - 特征 2:`[-∞,5),[5,∞)` - 特征 3:`[-∞,14],[14,∞)` 基于这些 bin 区间, X 就被变换成下面这样: ```py >>> est.transform(X) array([[ 0., 1., 1.], [ 1., 1., 1.], [ 2., 0., 0.]]) ``` 由此产生的数据集包含了有序属性(ordinal attributes),可以被进一步用在类 [sklearn.pipeline.Pipeline](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html#sklearn.pipeline.Pipeline) 中。 离散化(Discretization)类似于为连续数据构建直方图(histograms)。 然而,直方图聚焦于统计特征落在特定的bins里面的数量,而离散化聚焦于给这些bins分配特征取值。 [`KBinsDiscretizer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.KBinsDiscretizer.html#sklearn.preprocessing.KBinsDiscretizer)类实现了不同的 binning策略,可以通过参数`strategy`进行选择。 ‘uniform’ 策略使用固定宽度的bins。 ‘quantile’ 策略在每个特征上使用分位数(quantiles)值以便具有相同填充的bins。 ‘kmeans’ 策略基于在每个特征上独立执行的k-means聚类过程定义bins。 > **示例** >* [Using KBinsDiscretizer to discretize continuous features](https://scikit-learn.org/stable/auto_examples/preprocessing/plot_discretization.html#sphx-glr-auto-examples-preprocessing-plot-discretization-py) >* [Feature discretization](https://scikit-learn.org/stable/auto_examples/preprocessing/plot_discretization_classification.html#sphx-glr-auto-examples-preprocessing-plot-discretization-classification-py) >* [Demonstrating the different strategies of KBinsDiscretizer](https://scikit-learn.org/stable/auto_examples/preprocessing/plot_discretization_strategies.html#sphx-glr-auto-examples-preprocessing-plot-discretization-strategies-py) ### 5.3.5.2 特征二值化 **特征二值化** 是 **将数值特征用阈值过滤得到布尔值** 的过程。这对于下游的概率型模型是有用的,它们假设输入数据是多值 [伯努利分布(Bernoulli distribution)](https://en.wikipedia.org/wiki/Bernoulli_distribution) 。例如这个示例 [`sklearn.neural_network.BernoulliRBM`](https://scikit-learn.org/stable/modules/generated/sklearn.neural_network.BernoulliRBM.html#sklearn.neural_network.BernoulliRBM "sklearn.neural_network.BernoulliRBM") 。 即使归一化计数(又名术语频率)和TF-IDF值特征在实践中表现稍好一些,文本处理团队也常常使用二值化特征值(这可能会简化概率估计)。 相比于 [`Normalizer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html#sklearn.preprocessing.Normalizer "sklearn.preprocessing.Normalizer") ,实用程序类 [`Binarizer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Binarizer.html#sklearn.preprocessing.Binarizer "sklearn.preprocessing.Binarizer") 也被用于 [`sklearn.pipeline.Pipeline`](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html#sklearn.pipeline.Pipeline "sklearn.pipeline.Pipeline") 的早期步骤中。因为每个样本被当做是独立于其他样本的,所以 `fit` 方法是无用的: ```py >>> X = [[ 1., -1., 2.], ... [ 2., 0., 0.], ... [ 0., 1., -1.]] >>> binarizer = preprocessing.Binarizer().fit(X) # fit does nothing >>> binarizer Binarizer(copy=True, threshold=0.0) >>> binarizer.transform(X) array([[ 1., 0., 1.], [ 1., 0., 0.], [ 0., 1., 0.]]) ``` 也可以为二值化器赋一个阈值: ```py >>> binarizer = preprocessing.Binarizer(threshold=1.1) >>> binarizer.transform(X) array([[ 0., 0., 1.], [ 1., 0., 0.], [ 0., 0., 0.]]) ``` 相比于 [`StandardScaler`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html#sklearn.preprocessing.StandardScaler "sklearn.preprocessing.StandardScaler") 和 [`Normalizer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Normalizer.html#sklearn.preprocessing.Normalizer "sklearn.preprocessing.Normalizer") 类的情况,预处理模块提供了一个相似的函数 [`binarize`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.binarize.html#sklearn.preprocessing.binarize "sklearn.preprocessing.binarize") ,以便不需要转换接口时使用。 >**稀疏输入** > >[`binarize`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.binarize.html#sklearn.preprocessing.binarize "sklearn.preprocessing.binarize") 以及 [`Binarizer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.Binarizer.html#sklearn.preprocessing.Binarizer "sklearn.preprocessing.Binarizer") 接收 **来自scipy.sparse的密集类数组数据以及稀疏矩阵作为输入** 。 > >对于稀疏输入,数据被 **转化为压缩的稀疏行形式** (参见 `scipy.sparse.csr_matrix` )。为了避免不必要的内存复制,推荐在上游选择CSR表示。 ## 5.3.6 缺失值补全 关于缺失值补全的方法和工具的讨论,请看章节: [缺失值处理](41)。 ## 5.3.7 生成多项式特征 在机器学习中,通过增加一些输入数据的非线性特征来增加模型的复杂度通常是有效的。一个简单通用的办法是使用多项式特征,这可以获得特征的更高维度和互相间关系的项。这在 [`PolynomialFeatures`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.PolynomialFeatures.html#sklearn.preprocessing.PolynomialFeatures "sklearn.preprocessing.PolynomialFeatures") 中实现: ```py >>> 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.]]) ``` X 的特征已经从 ![(X_1, X_2)](img/23bd4397a3e30a81d2ee26977f708e63.jpg) 转换为 ![(1, X_1, X_2, X_1^2, X_1X_2, X_2^2)](img/77ee769c7c80ba4738fa4b34ff922e25.jpg) 。 在一些情况下,只需要特征间的交互项,这可以通过设置 `interaction_only=True` 来得到: ```py >>> 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.]]) ``` X的特征已经从 ![(X_1, X_2, X_3)](img/300d1995dc6050bbfd575b2c14ec81ae.jpg) 转换为 ![(1, X_1, X_2, X_3, X_1X_2, X_1X_3, X_2X_3, X_1X_2X_3)](img/f4c7787828dd90f6b47e1677bbc806da.jpg) 。 注意,当使用多项的 [`Kernel functions`](https://scikit-learn.org/stable/modules/svm.html#svm-kernels) 时 ,多项式特征被隐式地在[核函数](https://en.wikipedia.org/wiki/Kernel_method)中被调用(比如, [`sklearn.svm.SVC`](https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html#sklearn.svm.SVC "sklearn.svm.SVC") , [`sklearn.decomposition.KernelPCA`](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.KernelPCA.html#sklearn.decomposition.KernelPCA "sklearn.decomposition.KernelPCA") )。 创建并使用多项式特征的岭回归实例请见 [Polynomial interpolation](https://scikit-learn.org/stable/auto_examples/linear_model/plot_polynomial_interpolation.html#sphx-glr-auto-examples-linear-model-plot-polynomial-interpolation-py) 。 ## 5.3.8 自定义转换器 在机器学习中,想要将一个已有的 Python 函数转化为一个转换器来协助数据清理或处理。可以使用 [`FunctionTransformer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.FunctionTransformer.html#sklearn.preprocessing.FunctionTransformer "sklearn.preprocessing.FunctionTransformer") 从任意函数中实现一个转换器。例如,在一个管道中构建一个实现日志转换的转化器,这样做: ```py >>> 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`,可以确保`func`和`inverse_func`是彼此的拟过程。请注意,请注意一个`warning`会被抛出,并且可以使用`filterwarnings`将其转为一个`error` 使用一个 [`FunctionTransformer`](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.FunctionTransformer.html#sklearn.preprocessing.FunctionTransformer "sklearn.preprocessing.FunctionTransformer") 类来做定制化特征选择的示例,请见 [Using FunctionTransformer to select columns](https://scikit-learn.org/stable/auto_examples/preprocessing/plot_function_transformer.html#sphx-glr-auto-examples-preprocessing-plot-function-transformer-py) 。