常见问题#

在这里,我们试图回答一些经常出现在邮件列表中的问题。

关于项目#

项目名称是什么(很多人搞错了)?#

scikit-learn,而不是 scikit 或 SciKit,也不是 sci-kit learn。也不是以前使用的 scikits.learn 或 scikits-learn。

项目名称如何发音?#

sy-kit learn。sci 代表 science(科学)!

为什么叫 scikit?#

有多个 scikit,它们是围绕 SciPy 构建的科学工具箱。除了 scikit-learn,另一个流行的工具箱是 scikit-image

你们支持 PyPy 吗?#

由于维护人员资源有限且用户数量较少,scikit-learn 不正式支持与 PyPy(具有内置即时编译器的替代 Python 实现)一起使用。

我如何获得在我的作品中使用 scikit-learn 中图片的许可?#

scikit-learn 存储库中包含的图片以及 scikit-learn 文档中生成的图片可以通过 BSD 3-Clause License 用于您的作品。强烈鼓励并感谢引用 scikit-learn。请参阅 引用 scikit-learn

实现决策#

为什么不支持深度学习或强化学习?将来会支持吗?#

深度学习和强化学习都需要丰富的词汇来定义架构,深度学习还需要 GPU 才能高效计算。然而,这些都不符合 scikit-learn 的设计约束。因此,深度学习和强化学习目前不在 scikit-learn 寻求实现的目标范围内。

您可以在 你们会添加 GPU 支持吗? 中找到有关添加 GPU 支持的更多信息。

请注意,scikit-learn 目前在 sklearn.neural_network 中实现了一个简单的多层感知器。我们只接受此模块的错误修复。如果您想实现更复杂的深度学习模型,请转向流行的深度学习框架,例如 tensorflowkeraspytorch

你们会向 scikit-learn 添加图模型或序列预测吗?#

在可预见的将来不会。scikit-learn 试图为机器学习中的基本任务提供统一的 API,并使用管道和网格搜索等元算法将所有内容联系起来。结构化学习所需的概​​念、API、算法和专业知识与 scikit-learn 提供的不同。如果我们开始进行任意的结构化学习,我们将需要重新设计整个包,并且项目可能会因自身重量而崩溃。

有两个 API 类似于 scikit-learn 的项目进行结构化预测

  • pystruct 处理一般的结构化学习(侧重于任意图结构上的 SSVM,具有近似推理;将样本定义为图结构的一个实例)。

  • seqlearn 只处理序列(侧重于精确推理;有 HMM,但主要是为了完整性;将特征向量视为样本,并使用偏移编码来表示特征向量之间的依赖关系)。

为什么从 scikit-learn 中移除了 HMMs?#

请参阅 你们会向 scikit-learn 添加图模型或序列预测吗?

你们会添加 GPU 支持吗?#

默认添加 GPU 支持会引入繁重的特定硬件软件依赖项,并且现有算法需要重新实现。这将使普通用户更难安装 scikit-learn,也使开发人员更难维护代码。

然而,自 2023 年以来,如果输入数据以 PyTorch 或 CuPy 数组的形式提供,并且 scikit-learn 已配置为接受此类输入,如 Array API 支持(实验性) 中所述,有限但不断增长的 scikit-learn 估计器列表 已经可以在 GPU 上运行。这种 Array API 支持允许 scikit-learn 在 GPU 上运行,而无需在主包中引入繁重且特定于硬件的软件依赖项。

大多数依赖 NumPy 进行计算密集型操作的估计器都可以考虑使用 Array API 支持,因此也可以支持 GPU。

然而,并非所有 scikit-learn 估计器都适合通过 Array API 在 GPU 上高效运行,这有根本的算法原因。例如,目前在 scikit-learn 中使用 Cython 实现的基于树的模型本质上不是基于数组的算法。其他算法,如 k-means 或 k-nearest neighbors 依赖于基于数组的算法,但也使用 Cython 实现。Cython 用于手动交错连续的数组操作,以避免引入对大型中间数组的性能杀伤性内存访问:这种低级算法重写称为“内核融合”,在可预见的将来无法通过 Array API 来表达。

要为无法使用 Array API 高效实现的估计器添加高效的 GPU 支持,需要设计和采用更灵活的 scikit-learn 扩展系统。目前正在以下 GitHub issue(讨论中)中考虑这种可能性

为什么 scikit-learn 中的分类变量需要预处理,而其他工具不需要?#

大多数 scikit-learn 假定数据是单个数字 dtype 的 NumPy 数组或 SciPy 稀疏矩阵。目前,这些不明确表示分类变量。因此,与 R 的 data.framespandas.DataFrame 不同,我们需要将分类特征显式转换为数值,如 编码分类特征 中所述。另请参阅 具有混合类型的 Column Transformer,了解处理异构(例如分类和数值)数据的示例。

请注意,最近,HistGradientBoostingClassifierHistGradientBoostingRegressor 通过选项 categorical_features="from_dtype" 获得了对分类特征的原生支持。此选项依赖于根据 pandas.CategoricalDtypepolars.datatypes.Categorical dtypes 推断哪些数据列是分类的。

scikit-learn 是否原生支持各种类型的数据框?#

Scikit-learn 对 pandas.DataFramepolars.DataFrame 的支持有限。scikit-learn 估计器可以接受这两种数据框类型作为输入,scikit-learn 转换器可以使用 set_output API 输出数据框。有关更多详细信息,请参阅 Introducing the set_output API

然而,scikit-learn 估计器中的内部计算依赖于在齐性数据结构(例如 NumPy 数组或 SciPy 稀疏矩阵)上更高效执行的数值运算。因此,大多数 scikit-learn 估计器会在内部将数据框输入转换为这些齐性数据结构。同样,数据框输出也是从这些齐性数据结构生成的。

另请注意,ColumnTransformer 通过将按名称或 dtype 选择的数据框列的齐性子集映射到专用的 scikit-learn 转换器,方便地处理异构 pandas 数据框。因此,在处理异构数据框时,ColumnTransformer 通常用于 scikit-learn 管道的第一步(有关详细信息,请参阅 Pipeline: chaining estimators)。

另请参阅 具有混合类型的 Column Transformer,了解处理异构(例如分类和数值)数据的示例。

你们计划在管道中实现对目标 y 的转换吗?#

目前,转换仅适用于管道中的特征 X。关于无法在管道中转换 y 的讨论由来已久。请关注 GitHub issue #4143。同时,您可以查看 TransformedTargetRegressorpipegraphimbalanced-learn。请注意,scikit-learn 解决了在训练前对 y 应用可逆转换并在预测后反转的情况。scikit-learn 打算解决需要在训练时而不是测试时转换 y 的用例,例如重采样和类似用途,就像在 imbalanced-learn 中一样。通常,这些用例可以通过自定义元估计器而不是 Pipeline 来解决。

为什么线性模型有这么多不同的估计器?#

通常,每种模型类型有一个分类器和一个回归器,例如 GradientBoostingClassifierGradientBoostingRegressor。两者具有相似的选项,并且都具有参数 loss,这在回归情况下特别有用,因为它能够估计条件均值和条件分位数。

对于线性模型,有许多估计器类彼此非常接近。让我们看看

维护者视角: 它们原则上都做相同的事情,只是惩罚不同。然而,这对底层优化问题的解决方式有很大影响。最终,这涉及到使用不同的线性代数方法和技巧。一个特例是 SGDRegressor,它包含了前 4 个模型,并且在优化过程上有所不同。另一个副作用是不同的估计器支持不同的数据布局(X C-contiguous 或 F-contiguous,稀疏 csr 或 csc)。正是这种看似简单的线性模型的复杂性导致了为不同惩罚设置不同的估计器类。

用户视角: 首先,当前的设计受到科学文献的启发,其中具有不同正则化/惩罚的线性回归模型被赋予了不同的名称,例如 ridge regression。拥有具有相应名称的不同模型类使用户更容易找到这些回归模型。其次,如果所有上述 5 个线性模型都统一为一个类,那么参数就会有很多选项,就像 solver 参数一样。最重要的是,不同的参数之间会有很多排他性的交互。例如,参数 solverprecomputeselection 的可能选项将取决于所选的惩罚参数 alphal1_ratio 的值。

贡献#

我如何为 scikit-learn 做出贡献?#

请参阅 Contributing to pandas。在想要添加新算法之前(这通常是一项重大且耗时的任务),建议从 已知问题 开始。请不要直接联系 scikit-learn 的贡献者来讨论为 scikit-learn 做出贡献事宜。

为什么我的拉取请求没有得到关注?#

scikit-learn 的审查过程需要大量时间,贡献者不应因拉取请求缺乏活动或审查而灰心。我们非常关心第一次就把事情做好,因为维护和后续更改的成本很高。我们很少发布任何“实验性”代码,因此我们所有的贡献都将立即受到高度使用,并且最初应具有尽可能高的质量。

除此之外,scikit-learn 的审查带宽有限;许多审查者和核心开发人员都在业余时间为 scikit-learn 工作。如果您的拉取请求审查缓慢,很可能是因为审查者很忙。我们请求您的理解,并要求您不要仅仅因为这个原因就关闭您的拉取请求或停止您的工作。

有关如何使您的拉取请求更易于审查并更有可能快速审查的提示,请参阅 如何改进我的 issue 或拉取请求?

如何改进我的 issue 或拉取请求?#

为了帮助您的 issue 受到关注或提高您的拉取请求被审查的可能性,您可以尝试

对于您的拉取请求,以下几点将使其更易于审查

  • 确保您的 PR 满足 拉取请求清单 中的所有项目。

  • 确保您的 PR 解决了对解决方案有明确共识的 issue。

  • 确保更改最小且与所描述的 issue 直接相关。

issue 或拉取请求的“spam”标签是什么意思?#

“spam”标签向审阅者表明,该 issue 或拉取请求可能没有得到作者足够的努力或准备以进行富有成效的审阅。维护人员正在使用此标签来处理低价值 PR 和 issue 的增加。

如果 issue 或 PR 被标记为 spam 并同时关闭,则该决定是最终的。发生这种情况的一个常见原因是人们为仍在讨论中的 issue 打开 PR。请等待讨论达成一致后再打开 PR。

如果您的 issue 或 PR 被标记为 spam 但未关闭,请参阅 如何改进我的 issue 或拉取请求?,了解有关改进 issue 或拉取请求并增加移除标签可能性的提示。

新算法的收录标准是什么?#

我们只考虑收录成熟的算法。经验法则是发表至少 3 年、引用次数 200+,并且广泛使用且有用。对广泛使用的方法提供明确改进(例如增强的数据结构或更高效的近似技术)的技术也将被考虑收录。

在符合上述标准的算法或技术中,只接受那些非常适合 scikit-learn 当前 API 的算法,即 fitpredict/transform 接口,并且输入/输出通常是 numpy 数组或稀疏矩阵。

贡献者应通过研究论文和/或在其他类似包中的实现来支持所提议添加的重要性,通过常见用例/应用程序证明其有用性,并通过基准测试和/或图表证实性能改进(如果有)。预期所提议的算法至少在某些领域应优于 scikit-learn 中已实现的算法。

请不要提议您(您的好朋友、同事或老板)创建的算法。scikit-learn 不是宣传您自己作品的好地方。

如果新算法能加快现有模型,则更容易被收录,条件是

  • 它不引入新的超参数(因为它使库更具未来适应性),

  • 很容易清楚地记录贡献何时提高了速度以及何时没有,例如,“当 n_features >> n_samples 时”,

  • 基准测试清楚地显示了速度提升。

另外,请注意,您的实现不必在 scikit-learn 中即可与 scikit-learn 工具一起使用。您可以以与 scikit-learn 兼容的方式实现您喜欢的算法,将其上传到 GitHub 并告知我们。我们很乐意将其列在 相关项目 下。如果您在 GitHub 上已经有一个遵循 scikit-learn API 的包,您可能也有兴趣查看 scikit-learn-contrib

为什么你们对 scikit-learn 中包含的算法如此挑剔?#

代码带有维护成本,我们需要平衡我们拥有的代码量与团队规模(再加上复杂性随功能数量非线性扩展的事实)。该包依赖于核心开发人员利用他们的空闲时间修复错误、维护代码和审查贡献。任何添加的算法都需要开发人员未来的关注,届时最初的作者可能早已失去兴趣。另请参阅 新算法的收录标准是什么?。有关开源软件中长期维护问题的精彩阅读,请查看 Roads and Bridges 执行摘要

使用 scikit-learn#

如何开始使用 scikit-learn?#

如果您是 scikit-learn 的新手,或者希望加强您的理解,我们强烈推荐 scikit-learn MOOC(大规模开放在线课程)

请参阅我们的 外部资源、视频和讲座页面 了解更多详细信息。

获得 scikit-learn 使用帮助的最佳方式是什么?#

  • 一般机器学习问题:使用带有 [machine-learning] 标签的 Cross Validated

  • scikit-learn 使用问题:使用带有 [scikit-learn][python] 标签的 Stack Overflow。您也可以使用 邮件列表

请确保包含一个最小的重现代码片段(理想情况下少于 10 行),该片段突出显示您在玩具数据集上的问题(例如来自 sklearn.datasets 或使用具有固定随机种子的 numpy.random 函数随机生成)。请删除与重现问题无关的任何代码行。

通过简单地将您的代码片段复制粘贴到已安装 scikit-learn 的 Python shell 中,应该可以重现问题。不要忘记包含导入语句。有关编写良好重现代码片段的更多指导,请参阅:https://stackoverflow.com/help/mcve

如果您的代码片段引发了您不理解的异常(即使在 Google 搜索之后),请确保包含运行重现脚本时获得的完整回溯。

对于错误报告或功能请求,请使用 GitHub 上的 issue 跟踪器

警告

请不要直接通过电子邮件联系任何作者寻求帮助、报告错误或解决任何其他与 scikit-learn 相关的问题。

如何保存、导出或部署用于生产的估计器?#

请参阅 模型持久性

如何创建一个 bunch 对象?#

bunch 对象有时用作函数和方法的输出。它们通过允许通过键 bunch["value_key"] 或通过属性 bunch.value_key 访问值来扩展字典。

它们不应用作输入。因此,您几乎不需要创建 Bunch 对象,除非您正在扩展 scikit-learn 的 API。

如何将我自己的数据集加载为 scikit-learn 可用的格式?#

通常,scikit-learn 适用于存储为 numpy 数组或 scipy 稀疏矩阵的任何数字数据。可转换为数字数组的其他类型,例如 pandas.DataFrame,也是可接受的。

有关将数据文件加载到这些可用数据结构中的更多信息,请参阅 加载外部数据集

如何处理字符串数据(或树、图...)?#

scikit-learn 估计器假定您将向它们提供实值特征向量。这个假设几乎硬编码在整个库中。但是,您可以通过几种方式将非数值输入提供给估计器。

如果您有文本文档,可以使用词频特征;请参阅 文本特征提取 以获取内置的 文本向量化器。有关从任何类型数据中提取更一般特征的信息,请参阅 从字典加载特征特征哈希

另一种常见情况是您拥有非数值数据和这些数据上的自定义距离(或相似性)度量。示例包括具有编辑距离(又称 Levenshtein 距离)的字符串,例如 DNA 或 RNA 序列。这些可以编码为数字,但这样做很痛苦且容易出错。处理任意数据上的距离度量有两种方法。

首先,许多估计器接受预先计算的距离/相似性矩阵,因此如果数据集不是太大,您可以计算所有输入对的距离。如果数据集很大,您可以使用只有一个“特征”的特征向量,该特征是单独数据结构中的索引,并提供一个自定义度量函数,该函数在该数据结构中查找实际数据。例如,要使用具有 Levenshtein 距离的 dbscan

>>> import numpy as np
>>> from leven import levenshtein
>>> from sklearn.cluster import dbscan
>>> data = ["ACCTCCTAGAAG", "ACCTACTAGAAGTT", "GAATATTAGGCCGA"]
>>> def lev_metric(x, y):
...     i, j = int(x[0]), int(y[0])  # extract indices
...     return levenshtein(data[i], data[j])
...
>>> X = np.arange(len(data)).reshape(-1, 1)
>>> X
array([[0],
       [1],
       [2]])
>>> # We need to specify algorithm='brute' as the default assumes
>>> # a continuous feature space.
>>> dbscan(X, metric=lev_metric, eps=5, min_samples=2, algorithm='brute')
(array([0, 1]), array([ 0,  0, -1]))

请注意,上面的示例使用了第三方编辑距离包 leven。对于树核、图核等,可以使用类似的技巧,但需要注意。

为什么在 OSX 或 Linux 下使用 n_jobs > 1 时有时会崩溃/冻结?#

scikit-learn 中的一些工具(例如 GridSearchCVcross_val_score)在内部依赖 Python 的 multiprocessing 模块,通过传递 n_jobs > 1 作为参数将执行并行化到多个 Python 进程。

问题是 Python multiprocessing 执行 fork 系统调用,但出于性能原因没有紧随其后执行 exec 系统调用。许多库,例如 OSX 下的 Accelerate 或 vecLib(某些版本)、MKL(某些版本)、GCC 的 OpenMP 运行时、nvidia 的 Cuda(以及可能许多其他库),都管理自己的内部线程池。在调用 fork 时,子进程中的线程池状态会损坏:线程池认为它有许多线程,而只有主线程状态被 fork。可以更改库以使其检测何时发生 fork 并在这种情况下重新初始化线程池:我们为 OpenBLAS 做了这件事(自 0.2.10 起合并到上游 main 中),我们为 GCC 的 OpenMP 运行时贡献了一个 补丁(尚未审查)。

但归根结底,真正的罪魁祸首是 Python 的 multiprocessing,它执行 fork 而不执行 exec 以减少启动和使用新 Python 进程进行并行计算的开销。不幸的是,这违反了 POSIX 标准,因此像 Apple 这样的某些软件编辑者拒绝将 Accelerate 和 vecLib 中缺乏 fork-safety 视为错误。

在 Python 3.4+ 中,现在可以配置 multiprocessing 以使用 "forkserver""spawn" 启动方法(而不是默认的 "fork")来管理进程池。为了在使用 scikit-learn 时解决这个问题,您可以将 JOBLIB_START_METHOD 环境变量设置为 "forkserver"。但是,用户应该知道使用 "forkserver" 方法会阻止 joblib.Parallel 调用在 shell 会话中交互式定义的函数。

如果您有自定义代码直接使用 multiprocessing 而不是通过 joblib 使用它,您可以为程序全局启用 "forkserver" 模式。在主脚本中插入以下指令

import multiprocessing

# other imports, custom code, load data, define model...

if __name__ == "__main__":
    multiprocessing.set_start_method("forkserver")

    # call scikit-learn utils with n_jobs > 1 here

您可以在 multiprocessing documentation 中找到有关新启动方法的更多详细信息。

为什么我的作业使用的核心数比 n_jobs 指定的要多?#

这是因为 n_jobs 只控制使用 joblib 并行化的例程的作业数,但并行代码可能来自其他来源

  • 一些例程可能使用 OpenMP 并行化(对于用 C 或 Cython 编写的代码),

  • scikit-learn 大量依赖 numpy,而 numpy 又可能依赖于 MKL、OpenBLAS 或 BLIS 等数值库,这些库可以提供并行实现。

有关详细信息,请参阅我们的 并行性说明

如何为整个执行设置 random_state#

请参阅 控制随机性