scikit-learn 数据互操作性完全指南:set_output API、稀疏数据与 Array API(GPU)支持
【免费下载链接】scikit-learnscikit-learn: machine learning in Python项目地址: https://gitcode.com/gh_mirrors/sc/scikit-learn
scikit-learn 的fit(X, y)、transform(X)等核心方法会面对多种形态的输入数据:普通的 array-like 对象、稀疏矩阵与稀疏数组、pandas/polars 数据表,以及符合 Array API 规范的数组(包括 GPU 上的张量)。本文以 doc/data_interoperability.rst 为骨架,结合 sklearn/utils/_set_output.py 与 sklearn/utils/_array_api.py 等源码实现,系统讲解这四类数据的处理机制,并给出set_output与array_api_dispatch两大配置的实际用法。读完本文,你将掌握:如何让 transformer 输出 pandas/polars DataFrame(含特征名传播)、如何控制稀疏输出的矩阵/数组形态,以及如何在 GPU 上运行支持 Array API 的估计器。
scikit-learn 处理的四类输入数据
根据 doc/data_interoperability.rst,scikit-learn 为fit(X, y)、fit(X)、fit_transform(X)、transform(X)中的X,以及transform(X)、fit_transform(X)返回的Xt,统一支持以下四类数据:
| 数据类型 | 说明 |
|---|---|
| array-like 对象 | 在fit(X)与transform(X)中,通过numpy.asarray转换为 numpy ndarray;返回的Xt为 ndarray,或稀疏矩阵/稀疏数组 |
| 稀疏矩阵(sparse matrices)与稀疏数组 | 多数估计器支持稀疏X,部分不支持并会抛出错误 |
| 表格数据(tabular data) | pandas 与 polars DataFrame,详见 doc/modules/df_output_transform.rst |
| Array API 兼容数组 | 包括 GPU 上的数组,详见 doc/modules/array_api.rst |
从 sklearn/_config.py 的全局配置默认值可以看到这三类行为对应的开关:transform_output="default"(表格输出开关)、array_api_dispatch=False(Array API 开关)、sparse_interface="spmatrix"(稀疏接口形态开关)。下面逐一展开。
第一类:array-like 对象与 numpy 转换
array-like 是最基础的数据形态,包括列表、元组、numpy 数组等。scikit-learn 在内部统一调用numpy.asarray(或numpy.array)将其转换为 numpy ndarray 后再进行计算,transform与fit_transform的返回结果Xt同样是 ndarray(或稀疏矩阵/稀疏数组,见下一节)。
值得注意的是,当array_api_dispatch=False(默认值)时,即使是 Array API 兼容输入(例如 JAX 数组)也会被转换为 numpy 数组。某些输入无法被成功转换,例如分配在 GPU 上的 torch 张量——这正是 doc/modules/array_api.rst 建议在使用 Array API 输入时显式打开array_api_dispatch=True的原因(详见本文 Array API 章节)。
第二类:稀疏矩阵与稀疏数组
许多估计器可以处理稀疏X,也有一些无法处理并会直接抛出错误。文档给出的两个典型例子是:
linear_model.LogisticRegression:可以在稀疏X上拟合;isotonic.IsotonicRegression:不可以。
部分 transformer 的transform/fit_transform会返回稀疏Xt。是否返回稀疏结果通常由一个sparse_output参数控制,例如preprocessing.SplineTransformer就带有该参数。
用 sparse_interface 控制稀疏形态
返回的是scipy.sparse的稀疏矩阵(csr_matrix / csc_matrix 等)还是稀疏数组,由配置项sparse_interface决定,可通过两个入口设置:
config_context(sparse_interface=...):上下文管理器,局部生效;set_config(sparse_interface=...):全局设置。
默认值为"spmatrix"(稀疏矩阵)。该配置同时控制拟合后稀疏类型的属性(如某些估计器的coef_、components_等)是稀疏矩阵还是稀疏数组。这一开关的实际消费逻辑位于 sklearn/_config.py 的全局配置字典与config_context/set_config的参数解析中。
第三类:表格数据与 set_output API
这是数据互操作性中与日常建模最相关的一部分,完整用户指南见 doc/modules/df_output_transform.rst,配套可运行示例见 examples/miscellaneous/plot_set_output.py。
特征名传播:从 numpy 数组到 DataFrame
默认情况下,scikit-learn 的 transformer 返回 numpy 数组(有时是稀疏数组)。由于 numpy 数组无法为轴的索引提供名称,在 1.0 版本之前pipeline.Pipeline无法传播特征名:
- 单步估计器不知道如何处理传入的特征名;
- pipeline 不知道如何在步骤之间传递特征名。
实际工作中大量用例始于带列名/特征名的表格数据(pandas DataFrame 或 polars DataFrame),因此这一能力至关重要。scikit-learn 支持表格数据的历程有两个里程碑:
compose.ColumnTransformer在版本 0.20 引入,作为在不同特征上应用不同估计器的"网关",且能理解传入的特征名;- 特征名问题最终由
SLEP007(Feature names, their generation and the API)在版本 1.1 完整解决。此后,估计器在fit阶段接收到 DataFrame 时,会设置一个feature_names_in_属性保存特征名;它既能理解 pandas DataFrame,也能理解实现了 Python DataFrame interchange protocol(__dataframe__方法)的 DataFrame。同时,已拟合的估计器拥有get_feature_names_out方法,transformer 的该方法返回的就是transform输出对应的特征名。
set_output API 的引入
进一步支持"DataFrame 进、DataFrame 出"的关键是SLEP018,其 pandas 支持在版本 1.2 实现,polars 支持在版本 1.4 实现。它引入了set_outputAPI,用于配置 transformer 输出 pandas 或 polars DataFrame:
- 按估计器配置:调用该估计器的
set_output方法; - 全局配置:调用
set_config(transform_output="pandas");若希望输出 polars DataFrame,则把"pandas"换成"polars"。
完整用法示例
以下是文档中的标准用法(默认 numpy 输出):
>>> import numpy as np >>> import pandas as pd >>> from sklearn.compose import ColumnTransformer >>> from sklearn.pipeline import make_pipeline >>> from sklearn.preprocessing import OneHotEncoder >>> from sklearn.linear_model import LinearRegression >>> X = pd.DataFrame( ... {"animals": ["cat", "cat", "dog", "dog"], "numeric": np.linspace(-1, 1, 4)} ... ) >>> y = np.array([-1.5, 0, 0.1, 1.0]) >>> ct = ColumnTransformer( ... [("categorical", OneHotEncoder(sparse_output=False), ["animals"])], ... remainder="passthrough", ... ) >>> model = make_pipeline(ct, LinearRegression()).fit(X, y) >>> model.feature_names_in_ array(['animals', 'numeric'], dtype=object) >>> model[0].get_feature_names_out() array(['categorical__animals_cat', 'categorical__animals_dog', 'remainder__numeric'], dtype=object) >>> model[0].transform(X) array([[ 1. , 0. , -1. ], [ 1. , 0. , -0.33333333], [ 0. , 1. , 0.33333333], [ 0. , 1. , 1. ]])同样的流程,开启 pandas 输出:
>>> from sklearn import set_config >>> set_config(transform_output="pandas") >>> model[0].transform(X)此时transform返回一个 pandas DataFrame,列名与get_feature_names_out()一致:
| 索引 | categorical__animals_cat | categorical__animals_dog | remainder__numeric |
|---|---|---|---|
| 0 | 1.0 | 0.0 | -1.000000 |
| 1 | 1.0 | 0.0 | -0.333333 |
| 2 | 0.0 | 1.0 | 0.333333 |
| 3 | 0.0 | 1.0 | 1.000000 |
恢复默认 numpy 输出:
>>> set_config(transform_output="default")从示例中可以看到关键细节:set_output配置生效的前提是估计器实现了get_feature_names_out,pandas 输出的列名正是由该方法的返回值决定(如categorical__animals_cat、remainder__numeric这种带步骤前缀的命名)。
set_output 的源码实现
set_output的底层实现在 sklearn/utils/_set_output.py 中,核心组件是"容器适配器"机制:
PandasAdapter(sklearn/utils/_set_output.py)与PolarsAdapter(sklearn/utils/_set_output.py)实现了统一的ContainerAdapterProtocol,负责create_container(把 numpy 结果包装成 DataFrame)、is_supported_container、rename_columns、hstack等操作。两个适配器通过ADAPTERS_MANAGER注册(sklearn/utils/_set_output.py),因此扩展新容器类型只需注册新适配器。_get_output_config(sklearn/utils/_set_output.py)决定输出容器:优先读取估计器自身的_sklearn_output_config(由set_output写入),否则回退到全局配置transform_output;若配置值不在supported_outputs({"default"} | adapters)中会抛出ValueError。_wrap_method_output(sklearn/utils/_set_output.py)自动包装transform/fit_transform方法的返回值,使配置后的输出被包装为目标容器。- 适配器要求对应库(pandas / polars)已安装,否则
check_library_installed会抛出带提示的ImportError(sklearn/utils/_set_output.py)。
从_wrap_data_with_container(sklearn/utils/_set_output.py)可以看到一个重要限制:当 transformer 输出的是 scipy 稀疏矩阵时,无法直接包装为 DataFrame,此时会抛出ValueError并建议"把 transformer 输出改为稠密数组,或通过set_output(transform='default')关闭 pandas 输出"。因此OneHotEncoder等默认输出稀疏结果的 transformer 若要配合set_output使用,需要显式设置sparse_output=False(示例代码中正是如此)。
set_output方法本身(sklearn/utils/_set_output.py)接受transform={"default", "pandas", "polars", None},其中"polars"选项在 1.4 版本加入,None表示不做任何修改。
元估计器中的传播
在Pipeline等元估计器中调用set_output(transform="pandas")会作用于所有步骤;底层通过_safe_set_output(sklearn/utils/_set_output.py)把输出配置安全地下发给各个子估计器——若子估计器没有transform方法(如纯预测型模型)则跳过,若有transform但缺少set_output则抛出ValueError提示无法配置输出。
第四类:Array API 支持(实验性)
Array API 规范(https://data-apis.org/array-api/latest/)定义了所有具备 NumPy 类似 API 的数组库的统一标准接口。部分主要依赖 NumPy(而非 Cython)实现fit/predict/transform算法逻辑的 scikit-learn 估计器,可以配置为接受任何 Array API 兼容的输入数据结构,并把运算自动分发到对应的数组库命名空间,而不是依赖 NumPy。完整文档见 doc/modules/array_api.rst。
重要:此支持目前被视为实验性,必须显式开启
array_api_dispatch配置,并且假设安装了最新版本的依赖库。实验性期间不保证向后兼容:当新版本依赖修复 bug 时,scikit-learn 不会为旧版本做 backport 或维护兼容。
受支持且定期跑合规测试的数组库
scikit-learn 会定期对下表列出的库和硬件运行自动化合规测试(其他符合 Array API 的库和设备也可能开箱即用,覆盖面会随时间增长):
| Library | 安装 | 支持设备 | 备注 |
|---|---|---|---|
| PyTorch | 官方安装指南 | CPU、NVIDIA GPU (CUDA)、Apple GPU (MPS)、Intel GPU (XPU) | 见 MPS 支持、Intel GPU (XPU) 支持、float64 设备支持说明 |
| CuPy | 官方安装指南 | CUDA | — |
| dpnp | 官方安装指南 | CPU、Intel GPU (SYCL) | 驱动安装见安装链接;见 XPU 支持与 float64 设备支持说明 |
如何启用 Array API 支持
需要把配置参数array_api_dispatch设为True。官方建议全局设置以保证行为一致、防止不同数组命名空间被意外混用;文档示例中使用上下文管理器config_context则只是为了在代码片段结束后不必手动复位为False。
开启前必须设置环境变量SCIPY_ARRAY_API=1(需在导入 scipy 与 scikit-learn 之前):
export SCIPY_ARRAY_API=1该环境变量是临时性的,详细说明见 SciPy 的 Array API 文档。源码层面的校验在_check_array_api_dispatch(sklearn/utils/_array_api.py):开启array_api_dispatch时,若 scipy 版本低于 1.14.0 会抛出ImportError;若未设置SCIPY_ARRAY_API=1则抛出RuntimeError。
启用后行为变化:
- 当
array_api_dispatch=False时,Array API 输入(如 JAX 数组)会被numpy.asarray/numpy.array转换为 NumPy,输出数组也总是 NumPy;转换可能失败(例如分配在 GPU 上的 torch 张量); - 当
array_api_dispatch=True时,输出数组的类型取决于输入数组(详见下文"输入与输出数组类型处理")。
示例:PyTorch 在 CUDA GPU 上运行 LDA
文档给出的示例用 PyTorch 在 CUDA GPU 上运行LinearDiscriminantAnalysis:
>>> from sklearn.datasets import make_classification >>> from sklearn import config_context >>> from sklearn.discriminant_analysis import LinearDiscriminantAnalysis >>> import torch >>> X_np, y_np = make_classification(random_state=0) >>> X_torch = torch.asarray(X_np, device="cuda", dtype=torch.float32) >>> y_torch = torch.asarray(y_np, device="cuda", dtype=torch.float32) >>> with config_context(array_api_dispatch=True): ... lda = LinearDiscriminantAnalysis() ... X_trans = lda.fit_transform(X_torch, y_torch) >>> type(X_trans) <class 'torch.Tensor'> >>> X_trans.device.type 'cuda'这一模式对任何受支持的数组库都适用:把torch.asarray(..., device="cuda")换成cupy.asarray(...)(CuPy)或dpnp.asarray(...)(dpnp)即可;在 PyTorch 内也可以通过修改device=参数(如"cpu"、"xpu"、"mps")切换不同设备。
训练完成后,估计器的拟合属性(数组类型)与训练数据来自同一个 Array API 命名空间、存放在同一设备上。向transform/predict传入不同命名空间或同一命名空间内不同设备的数据会报错:
>>> with config_context(array_api_dispatch=True): ... lda.transform(X_np) Traceback (most recent call last): ... ValueError: Inputs passed to LinearDiscriminantAnalysis.transform() must use the same namespace and the same device as those passed to fit()...该错误信息的生成逻辑在check_same_namespace(sklearn/utils/_array_api.py):它会对比X与拟合属性(如coef_)的命名空间和设备,不一致时抛出ValueError,并提示用get_namespace_and_device与move_estimator_to把估计器迁移过去。
在设备/命名空间之间移动估计器
move_estimator_to(sklearn/utils/_array_api.py)可以把估计器的数组属性迁移到不同的命名空间和设备(非数组属性保持不变):
>>> from sklearn.utils._array_api import move_estimator_to >>> import numpy as np >>> lda_np = move_estimator_to(lda, np, device="cpu") >>> with config_context(array_api_dispatch=True): ... X_trans = lda_np.transform(X_np) >>> type(X_trans) <class 'numpy.ndarray'>支持 Array API 的组件清单
下列组件支持 Array API 兼容输入。
估计器:
covariance.LedoitWolf、covariance.OAS(见 float64 设备支持说明)decomposition.PCA(svd_solver="full"、svd_solver="covariance_eigh"或svd_solver="randomized";randomized仅当power_iteration_normalizer="QR")kernel_approximation.Nystroemlinear_model.LogisticRegression(solver="lbfgs"与solver="newton-cg")linear_model.LogisticRegressionCV(solver="lbfgs"与solver="newton-cg")linear_model.PoissonRegressor(solver="lbfgs")linear_model.Ridge(solver="svd")linear_model.RidgeCV(见 float64 设备支持说明)linear_model.RidgeClassifier(solver="svd")linear_model.RidgeClassifierCV(见 float64 设备支持说明)discriminant_analysis.LinearDiscriminantAnalysis(solver="svd")naive_bayes.GaussianNBpreprocessing.Binarizer、KernelCenterer、LabelBinarizer(sparse_output=False)、LabelEncoder、MaxAbsScaler、MinMaxScaler、Normalizer、PolynomialFeatures、StandardScaler(见 float64 设备支持说明)mixture.GaussianMixture(init_params="random"或init_params="random_from_data",且warm_start=False)
元估计器(在基估计器也支持 Array API 的前提下接受 Array API 输入):
calibration.CalibratedClassifierCV(method="temperature")pipeline.FeatureUnionmodel_selection.GridSearchCV、RandomizedSearchCV、HalvingGridSearchCV、HalvingRandomSearchCV
指标:
- 分类:
accuracy_score、average_precision_score、balanced_accuracy_score、brier_score_loss、cohen_kappa_score、confusion_matrix、f1_score、fbeta_score、hamming_loss、jaccard_score、log_loss、matthews_corrcoef(见 float64 设备支持说明)、multilabel_confusion_matrix、precision_score、precision_recall_curve、precision_recall_fscore_support、recall_score、roc_curve、zero_one_loss - 回归:
d2_absolute_error_score、d2_brier_score、d2_log_loss_score、d2_pinball_score、d2_tweedie_score、explained_variance_score、max_error、mean_absolute_error、mean_absolute_percentage_error、mean_gamma_deviance、mean_pinball_loss、mean_poisson_deviance、mean_squared_error、mean_squared_log_error、mean_tweedie_deviance、median_absolute_error、r2_score、root_mean_squared_error、root_mean_squared_log_error - 聚类与核函数:
calinski_harabasz_score、cluster.contingency_matrix、det_curve、pairwise.additive_chi2_kernel、pairwise.chi2_kernel、pairwise.cosine_similarity、pairwise.cosine_distances、pairwise_distances(仅支持"cosine"、"euclidean"、"manhattan"与"l2")、pairwise_distances_argmin、pairwise.euclidean_distances(见 float64 设备支持说明)、pairwise.laplacian_kernel、pairwise.linear_kernel、pairwise.manhattan_distances、pairwise.paired_cosine_distances、pairwise.paired_euclidean_distances、pairwise.paired_manhattan_distances、pairwise_kernels、pairwise.polynomial_kernel、pairwise.rbf_kernel(见 float64 设备支持说明)、pairwise.sigmoid_kernel
工具:
preprocessing.label_binarize(sparse_output=False)model_selection.cross_val_predict、model_selection.train_test_splitutils.check_consistent_length
输入与输出数组类型处理
当array_api_dispatch=True时,估计器和评分函数可以接受来自不同数组库/不同设备的混合输入,scikit-learn 会按需转换数组使之一致。规则如下:
- 估计器:一切跟随
X。混合输入会被转换为与X相同的数组库和设备。这包括构造参数(如y、sample_weight);当X是 Array API 兼容输入时,其他数组输入也会被转换(如果尚未匹配)。该行为使得在 pipeline 内的任意环节都可以从 CPU 切换到 GPU——无需显式移动y(注意:scikit-learn 的 pipeline 不允许转换y,以避免数据泄漏)。 - 评分函数:一切跟随
y_pred。混合输入(如y_true、sample_weight)会被转换为与y_pred相同的数组库和设备。这使得评分函数可被用于会在设备之间移动输入数组的元估计器中。输出类型取决于输出值的数量:返回标量时是 Python 标量(通常为float实例);支持多分类或多输出的评分函数在需要输出多个值时,会返回与y_pred同库同设备的数组。
两个特殊处理点:
y为字符串数组(NumPy)而其他输入为任意容器的数值数组时也支持:由于 Array API 规范不覆盖字符串数组,scikit-learn 会把y内部转换为数值表示(如 one-hot 编码或序数编码),并迁移到其他输入的命名空间和设备;- 估计器的
classes_属性始终保留在原始y所在数组库中,以支持字符串类别标签。
文档给出的经典场景示例:一个 pipeline 中X和y都在 CPU 上,经过TargetEncoder(需要y,因此X、y都必须在 CPU)、FunctionTransformer(func=partial(torch.asarray, device="cuda"))(把X移到 GPU)、Ridge(在 GPU 上做大规模矩阵运算性能更佳)三个步骤;由于Ridge接受混合输入,即使y无法被 pipeline 转换,整体流程依然可以运行。
开发者:array_api_support 标签与合规测试
为估计器添加array_api_supporttag 即可启用通用测试(common tests)中的专项检查,验证使用纯 NumPy 与 Array API 输入时结果一致。运行这些检查需要安装array-api-strict(无需 GPU 即可模拟非 CPU 设备,便于快速迭代调试):
pip install array-api-strict # 以及其它需要的库 pytest -k "array_api" -v注意用-v查看哪些检查因缺少依赖而被自动跳过。要在真实 GPU 上运行完整测试,还需安装 PyTorch、CuPy 与/或 dpnp 并具备相应 GPU 硬件;由于 CUDA、MPS、Intel GPU 后端需要不同硬件,完整 GPU 覆盖通常分散在多台机器上。
注意事项
MPS 设备支持(macOS):PyTorch 可通过 Metal Performance Shaders (MPS) 使用 M1/M2 芯片的内部 GPU,但 MPS 设备支持尚不完整。运行测试前设置
PYTORCH_ENABLE_MPS_FALLBACK=1:PYTORCH_ENABLE_MPS_FALLBACK=1 pytest -k "array_api" -v当前所有 scikit-learn 测试应能通过,但计算速度不一定优于 CPU。
float64 设备支持:scikit-learn 中某些操作会自动使用
float64精度以防溢出并保证正确性(如metrics.pairwise.euclidean_distances、preprocessing.StandardScaler)。但部分命名空间与设备的组合不支持float64(如 PyTorch on MPS、部分 Intel GPU 设备上的 PyTorch XPU 或 dpnp),此时 scikit-learn 会回退到float32,可能导致与不使用 Array API 分发(或使用支持float64的设备)时行为不同(通常表现为数值不稳定)。Intel GPU 支持:PyTorch XPU 支持仅适用于 PyTorch >= 2.12。
小结:如何选择合适的数据通路
回到 doc/data_interoperability.rst 的四类数据划分,实际选型可以遵循以下原则:
- 普通 numpy 工作流:保持默认配置即可,array-like 输入会被
numpy.asarray规范化; - 带稀疏特征的场景:优先使用支持稀疏输入的估计器,并通过 transformer 的
sparse_output参数控制是否返回稀疏结果,再用sparse_interface配置决定稀疏矩阵/稀疏数组形态; - 需要保留列名的表格数据场景:通过
set_output(transform="pandas")(或全局set_config(transform_output="pandas"))让 pipeline 输出带特征名的 DataFrame,结合feature_names_in_与get_feature_names_out实现端到端的特征名传播; - 需要 GPU 加速或希望接入非 NumPy 数组生态(PyTorch、CuPy、dpnp)的场景:先设置
SCIPY_ARRAY_API=1环境变量,再开启array_api_dispatch=True,并核对所选估计器/指标/工具是否在支持清单内。
这三套机制(numpy 转换、稀疏接口配置、set_output与array_api_dispatch)共享 sklearn/_config.py 中的同一套全局配置体系,均可通过set_config、config_context或估计器自身方法按需切换,让 scikit-learn 在 numpy、稀疏、pandas/polars 与 GPU 数组之间保持高度互操作。
【免费下载链接】scikit-learnscikit-learn: machine learning in Python项目地址: https://gitcode.com/gh_mirrors/sc/scikit-learn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考