数据预处理
目录
数据预处理¶
dask_ml.preprocessing
包含一些 scikit-learn 风格的转换器(transformer),可用于 Pipelines
中,作为模型拟合过程的一部分执行各种数据转换。这些转换器可以很好地处理 dask 集合(dask.array
, dask.dataframe
)、NumPy 数组或 pandas 数据框。它们将并行进行拟合(fit)和转换(transform)。
Scikit-Learn 克隆¶
一些转换器(transformer)是其 scikit-learn 对应项的(大部分)直接替代品。
|
通过缩放每个特征到给定范围来转换特征。 |
|
使用分位数信息转换特征。 |
|
使用对异常值鲁棒的统计量来缩放特征。 |
|
通过移除均值并缩放到单位方差来标准化特征。 |
|
将标签编码为 0 到 n_classes-1 之间的值。 |
|
将类别整数特征编码为独热(one-hot)数值数组。 |
|
生成多项式和交互特征。 |
它们可以像 scikit-learn 版本一样使用,不同之处在于
它们并行处理 dask 集合
.transform
在输入是 dask 集合时将返回一个dask.array
或dask.dataframe
有关任何特定转换器的更多信息,请参阅 sklearn.preprocessing
。Scikit-learn 确实有一些转换器可以作为 Dask 处理大内存任务的替代方案。这些包括 FeatureHasher(是 DictVectorizer 和 CountVectorizer 的良好替代品)和 HashingVectorizer(最适合在文本处理中使用,优于 CountVectorizer)。它们是无状态的,这使得它们可以轻松地与 Dask 的 map_partitions
一起使用。
In [1]: import dask.bag as db
In [2]: from sklearn.feature_extraction import FeatureHasher
In [3]: D = [{'dog': 1, 'cat':2, 'elephant':4}, {'dog': 2, 'run': 5}]
In [4]: b = db.from_sequence(D)
In [5]: h = FeatureHasher()
In [6]: b.map_partitions(h.transform).compute()
Out[6]:
[<Compressed Sparse Row sparse matrix of dtype 'float64'
with 3 stored elements and shape (1, 1048576)>,
<Compressed Sparse Row sparse matrix of dtype 'float64'
with 2 stored elements and shape (1, 1048576)>]
注意
dask_ml.preprocessing.LabelEncoder
和 dask_ml.preprocessing.OneHotEncoder
将使用 pandas.api.types.CategoricalDtype 的 dask 或 pandas Series 的类别 dtype 信息。这提高了性能,但可能导致不同的编码,具体取决于类别。更多信息请参阅类文档字符串。
类别特征编码¶
dask_ml.preprocessing.OneHotEncoder
可用于对特征进行“独热”(或“虚拟变量”)编码。
有关完整讨论,请参阅 scikit-learn 文档。本节仅关注与 scikit-learn 的差异。
Dask-ML 支持 pandas 的 Categorical dtype¶
Dask-ML 支持并使用 pandas Categorical dtype 的类型信息。有关介绍,请参阅 https://pandas.ac.cn/pandas-docs/stable/categorical.html。对于大型数据集,使用类别 dtype 对于提高性能至关重要。
这将对学习到的属性和转换后的值产生一些影响。
学习到的
categories_
可能有所不同。Scikit-Learn 要求类别是排序的。使用CategoricalDtype
时,类别不需要排序。OneHotEncoder.transform()
的输出类型将与输入类型相同。传入 pandas DataFrame 将返回 pandas DataFrame,而不是 NumPy 数组。同样,Dask DataFrame 返回 Dask DataFrame。
Dask-ML 的稀疏数据支持¶
OneHotEncoder 的默认行为是返回稀疏数组。Scikit-Learn 对于传递给 transform
的 ndarrays 返回 SciPy 稀疏矩阵。
当传入 Dask Array 时,OneHotEncoder.transform()
返回一个 Dask Array,*其中每个块都是一个 scipy 稀疏矩阵*。SciPy 稀疏矩阵不支持与 NumPy ndarray 相同的 API,因此大多数方法对结果无效。即使是像 compute
这样的基本操作也会失败。为了解决这个问题,我们目前建议将稀疏矩阵转换为密集矩阵。
from dask_ml.preprocessing import OneHotEncoder
import dask.array as da
import numpy as np
enc = OneHotEncoder(sparse=True)
X = da.from_array(np.array([['A'], ['B'], ['A'], ['C']]), chunks=2)
enc = enc.fit(X)
result = enc.transform(X)
result
result
的每个块都是一个 scipy 稀疏矩阵
result.blocks[0].compute()
# This would fail!
# result.compute()
# Convert to, say, pydata/sparse COO matrices instead
from sparse import COO
result.map_blocks(COO.from_scipy_sparse, dtype=result.dtype).compute()
Dask-ML 对稀疏数据的支持目前正在变化中。如有任何问题,请联系我们。
附加的转换器¶
其他转换器是 dask-ml 特有的。
|
将 DataFrame 的列转换为 categorical dtype。 |
|
对类别列进行虚拟变量(独热)编码。 |
|
对类别列进行序号(整数)编码。 |
dask_ml.preprocessing.Categorizer
和 dask_ml.preprocessing.DummyEncoder
都处理将非数值数据转换为数值数据。它们在流水线中作为预处理步骤非常有用,特别是当你从异构数据(数值和非数值混合)开始,但估计器需要所有数值数据时。
在这个玩具示例中,我们使用一个包含两列的数据集。'A'
是数值类型,'B'
包含文本数据。我们构建一个小型流水线来:
对文本数据进行类别化
对类别数据进行虚拟变量编码
拟合线性回归
In [7]: from dask_ml.preprocessing import Categorizer, DummyEncoder
In [8]: from sklearn.linear_model import LogisticRegression
In [9]: from sklearn.pipeline import make_pipeline
In [10]: import pandas as pd
In [11]: import dask.dataframe as dd
In [12]: df = pd.DataFrame({"A": [1, 2, 1, 2], "B": ["a", "b", "c", "c"]})
In [13]: X = dd.from_pandas(df, npartitions=2)
In [14]: y = dd.from_pandas(pd.Series([0, 1, 1, 0]), npartitions=2)
In [15]: pipe = make_pipeline(
....: Categorizer(),
....: DummyEncoder(),
....: LogisticRegression(solver='lbfgs')
....: )
....:
In [16]: pipe.fit(X, y)
Out[16]:
Pipeline(steps=[('categorizer', Categorizer()),
('dummyencoder', DummyEncoder()),
('logisticregression', LogisticRegression())])
Categorizer
将把 X
中的一部分列转换为 categorical dtype(有关 pandas 如何处理类别数据,请参阅此处)。默认情况下,它会转换所有 object
dtype 的列。
DummyEncoder
将对数据集进行虚拟变量(或独热)编码。这将一个类别列替换为多个列,其中值为 0 或 1,取决于原始值。
In [17]: df['B']
Out[17]:
0 a
1 b
2 c
3 c
Name: B, dtype: object
In [18]: pd.get_dummies(df['B'])
Out[18]:
a b c
0 True False False
1 False True False
2 False False True
3 False False True
原始值为 'a'
的地方,转换后在 a
列中为 1
,在其他地方为 0
。
为什么 Categorizer
步骤是必要的?为什么我们不能直接操作 object
(字符串) dtype 列?这样做会很脆弱,尤其是在使用 dask.dataframe
时,因为*输出的形状将取决于存在的值*。例如,假设我们在训练数据集中只看到前两行,在测试数据集中只看到后两行。那么,在训练时,我们转换后的列将是
In [19]: pd.get_dummies(df.loc[[0, 1], 'B'])
Out[19]:
a b
0 True False
1 False True
而在测试数据集上,它们将是
In [20]: pd.get_dummies(df.loc[[2, 3], 'B'])
Out[20]:
c
2 True
3 True
这是不正确的!列不匹配。
当我们将数据类别化时,我们可以确信所有可能的值都已指定,这样输出的形状就不再取决于我们当前看到的任何数据子集中的值。相反,它取决于 categories
,这些类别在所有子集中都是相同的。