预处理

dask_ml.preprocessing 包含一些 scikit-learn 风格的转换器,可以在 Pipelines 中使用,以作为模型拟合过程的一部分执行各种数据转换。这些转换器将在 dask 集合(dask.arraydask.dataframe)、NumPy 数组或 pandas 数据帧上表现良好。它们将并行拟合和转换。

Scikit-Learn 克隆

一些转换器是(大部分)scikit-learn 对应物的直接替换。

MinMaxScaler([feature_range, copy, clip])

通过将每个特征缩放到给定范围来转换特征。

QuantileTransformer(*[, n_quantiles, ...])

使用分位数信息转换特征。

RobustScaler(*[, with_centering, ...])

使用对异常值稳健的统计数据来缩放特征。

StandardScaler(*[, copy, with_mean, with_std])

通过去除均值并缩放到单位方差来标准化特征。

LabelEncoder([use_categorical])

将标签编码为介于 0 和 n_classes-1 之间的值。

OneHotEncoder(n_values, ...)

将分类整数特征编码为独热数值数组。

PolynomialFeatures([degree, ...])

生成多项式和交互特征。

这些可以像 scikit-learn 版本一样使用,除了以下几点:

  1. 它们在 dask 集合上并行操作

  2. .transform 在输入为 dask 集合时将返回 dask.arraydask.dataframe

有关任何特定转换器的更多信息,请参阅 sklearn.preprocessing 。Scikit-learn 确实有一些转换器是 Dask 服务于大内存任务的替代方案。这些包括 FeatureHasherDictVectorizerCountVectorizer 的良好替代品)和 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.LabelEncoderdask_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。对于大型数据集,使用分类数据类型对于实现性能至关重要。

这将对学习到的属性和转换后的值产生一些影响。

  1. 学习到的 categories_ 可能不同。Scikit-Learn 要求类别必须排序。使用 CategoricalDtype 时,类别不需要排序。

  2. 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 的。

Categorizer([categories, columns])

将 DataFrame 的列转换为分类数据类型。

DummyEncoder([columns, drop_first])

对分类列进行虚拟(独热)编码。

OrdinalEncoder([columns])

序数(整数)编码分类列。

无论是 dask_ml.preprocessing.Categorizer 还是 dask_ml.preprocessing.DummyEncoder 都处理将非数值数据转换为数值数据。它们在管道中的预处理步骤中非常有用,其中你从异构数据(数值和非数值的混合)开始,但估计器需要所有数值数据。

在这个简单的例子中,我们使用一个包含两列的数据集。'A' 是数值型数据,而 'B' 包含文本数据。我们创建一个小型管道来

  1. 对文本数据进行分类

  2. 对分类数据进行虚拟编码

  3. 拟合线性回归

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,这些类别在所有子集中都是相同的。