预处理
内容
预处理¶
dask_ml.preprocessing
包含一些 scikit-learn 风格的转换器,可以在 Pipelines
中使用,以作为模型拟合过程的一部分执行各种数据转换。这些转换器将在 dask 集合(dask.array
、dask.dataframe
)、NumPy 数组或 pandas 数据帧上表现良好。它们将并行拟合和转换。
Scikit-Learn 克隆¶
一些转换器是(大部分)scikit-learn 对应物的直接替换。
|
通过将每个特征缩放到给定范围来转换特征。 |
|
使用分位数信息转换特征。 |
|
使用对异常值稳健的统计数据来缩放特征。 |
|
通过去除均值并缩放到单位方差来标准化特征。 |
|
将标签编码为介于 0 和 n_classes-1 之间的值。 |
|
将分类整数特征编码为独热数值数组。 |
|
生成多项式和交互特征。 |
这些可以像 scikit-learn 版本一样使用,除了以下几点:
它们在 dask 集合上并行操作
.transform
在输入为 dask 集合时将返回dask.array
或dask.dataframe
有关任何特定转换器的更多信息,请参阅 sklearn.preprocessing
。Scikit-learn 确实有一些转换器是 Dask 服务于大内存任务的替代方案。这些包括 FeatureHasher (DictVectorizer 和 CountVectorizer 的良好替代品)和 HashingVectorizer (最适合在文本中使用,优于 CountVectorizer)。它们是无状态的,这使得在使用 map_partitions
时可以轻松地与 Dask 一起使用:
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。这提高了性能,但可能会导致不同的编码,具体取决于类别。更多信息请参阅类文档字符串。
编码分类特征¶
dask_ml.preprocessing.OneHotEncoder
对于“独热”(或“虚拟”)编码特征非常有用。
有关完整讨论,请参阅 scikit-learn 文档。本节仅关注与 scikit-learn 的不同之处。
Dask-ML 支持 pandas 的 Categorical 数据类型¶
Dask-ML 支持并使用 pandas Categorical dtype 的类型信息。有关介绍,请参见 https://pandas.pydata.org/pandas-docs/stable/categorical.html。对于大型数据集,使用分类数据类型对于实现性能至关重要。
这将对学习到的属性和转换后的值产生一些影响。
学习到的
categories_
可能不同。Scikit-Learn 要求类别必须排序。使用CategoricalDtype
时,类别不需要排序。The output of
OneHotEncoder.transform()
will be the same type as the input. Passing a pandas DataFrame returns a pandas Dataframe, instead of a NumPy array. Likewise, a Dask DataFrame returns a Dask DataFrame.
Dask-ML 的稀疏支持¶
OneHotEncoder 的默认行为是返回一个稀疏数组。Scikit-Learn 对于传递给 transform
的 ndarray 返回一个 SciPy 稀疏矩阵。
当传递一个 Dask 数组时,OneHotEncoder.transform()
返回一个 Dask 数组 其中每个块是一个 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 的列转换为分类数据类型。 |
|
对分类列进行虚拟(独热)编码。 |
|
序数(整数)编码分类列。 |
无论是 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
中的部分列转换为分类数据类型(有关 pandas 如何处理分类数据的更多信息,请参见 这里)。默认情况下,它会转换所有 object
数据类型的列。
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
,这些类别在所有子集中都是相同的。