常见问题#
在此,我们尝试回答邮件列表中经常出现的一些问题。
关于项目#
项目名称是什么(很多人都会弄错)?#
是 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 中图片的许可?#
您可以通过 BSD 3-Clause 许可证,在您的工作中使用 scikit-learn 仓库中包含的图像以及 scikit-learn 文档中生成的图像。强烈鼓励并感谢引用 scikit-learn。请参阅引用 scikit-learn。
实现决策#
为什么不支持深度学习或强化学习?未来会有这样的支持吗?#
深度学习和强化学习都需要丰富的词汇来定义架构,深度学习还需要 GPU 进行高效计算。然而,这两种学习方式都不符合 scikit-learn 的设计约束。因此,深度学习和强化学习目前不在 scikit-learn 的目标范围之内。
您可以在会增加 GPU 支持吗?找到有关添加 GPU 支持的更多信息。
请注意,scikit-learn 目前在 sklearn.neural_network
中实现了一个简单的多层感知器。我们只接受此模块的错误修复。如果您想实现更复杂的深度学习模型,请转向流行的深度学习框架,例如 tensorflow、keras 和 pytorch。
scikit-learn 会增加图模型或序列预测功能吗?#
在可预见的未来不会。scikit-learn 致力于为机器学习中的基本任务提供统一的 API,并使用管道(pipelines)和网格搜索(grid search)等元算法将所有内容联系起来。结构化学习所需的概念、API、算法和专业知识与 scikit-learn 所提供的不同。如果我们开始进行任意的结构化学习,我们将需要重新设计整个包,并且项目可能会因自身重量而崩溃。
有两个 API 类似于 scikit-learn 的项目可以进行结构化预测
为什么从 scikit-learn 中移除了 HMMs?#
会增加 GPU 支持吗?#
默认添加 GPU 支持将引入繁重的硬件特定软件依赖,并且现有算法需要重新实现。这将使普通用户更难安装 scikit-learn,也使开发人员更难维护代码。
然而,自 2023 年以来,如果输入数据以 PyTorch 或 CuPy 数组形式提供,并且 scikit-learn 已配置为接受此类输入(如数组 API 支持(实验性)中所述),则有限但不断增长的scikit-learn 估计器列表已经可以在 GPU 上运行。这种数组 API 支持使得 scikit-learn 能够在 GPU 上运行,而无需在主包中引入繁重且硬件特定的软件依赖。
大多数依赖 NumPy 进行计算密集型操作的估计器都可以考虑支持数组 API,从而支持 GPU。
然而,并非所有 scikit-learn 估计器都适合通过数组 API 在 GPU 上高效运行,这出于基本的算法原因。例如,scikit-learn 中目前用 Cython 实现的基于树的模型根本不是基于数组的算法。其他算法,如 k-means 或 k-近邻,虽然依赖于基于数组的算法,但也是用 Cython 实现的。Cython 用于手动交错连续的数组操作,以避免引入导致性能下降的大型中间数组内存访问:这种低级算法重写称为“内核融合”,在可预见的未来无法通过数组 API 表达。
对于无法使用数组 API 高效实现的估计器,添加高效的 GPU 支持将需要为 scikit-learn 设计并采用更灵活的扩展系统。此可能性正在以下 GitHub issue 中考虑(讨论中)
与其他工具相比,为什么 scikit-learn 中的分类变量需要预处理?#
scikit-learn 的大部分内容假定数据以 NumPy 数组或 SciPy 稀疏矩阵的形式存在,且为单一数值数据类型。这些目前不明确表示分类变量。因此,与 R 的 data.frames
或 pandas.DataFrame
不同,我们需要将分类特征显式转换为数值,如分类特征编码中所述。另请参阅混合类型列转换器,了解如何处理异构(例如分类和数值)数据的示例。
请注意,最近 HistGradientBoostingClassifier
和 HistGradientBoostingRegressor
通过选项 categorical_features="from_dtype"
获得了对分类特征的原生支持。此选项依赖于根据 pandas.CategoricalDtype
和 polars.datatypes.Categorical
数据类型来推断哪些数据列是分类的。
scikit-learn 是否原生支持各种类型的数据框?#
Scikit-learn 对 pandas.DataFrame
和 polars.DataFrame
的支持有限。Scikit-learn 估计器可以接受这两种数据框类型作为输入,并且 scikit-learn 转换器可以使用 set_output
API 输出数据框。更多详情请参阅引入 set_output API。
然而,scikit-learn 估计器内部的计算依赖于数值运算,这些运算在 NumPy 数组或 SciPy 稀疏矩阵等同构数据结构上执行效率更高。因此,大多数 scikit-learn 估计器会在内部将数据框输入转换为这些同构数据结构。同样,数据框输出也是从这些同构数据结构生成的。
另请注意,ColumnTransformer
通过将按名称或数据类型选择的数据框列的同构子集映射到专用的 scikit-learn 转换器,方便地处理异构 pandas 数据框。因此,在处理异构数据框时,ColumnTransformer
经常用于 scikit-learn 管道的第一步(有关更多详细信息,请参阅管道:链式估计器)。
另请参阅混合类型列转换器,了解如何处理异构(例如分类和数值)数据的示例。
您是否计划在 pipeline 中为目标 y
实现 `transform`?#
目前,`transform` 在管道中仅适用于特征 X
。关于无法在管道中转换 y
的问题,存在长期讨论。请关注 GitHub issue #4143。同时,您可以查看 TransformedTargetRegressor
、pipegraph 和 imbalanced-learn。请注意,scikit-learn 已经解决了 y
在训练前应用可逆变换并在预测后反转的情况。scikit-learn 旨在解决 y
应在训练时而不是测试时进行变换的用例,例如重采样和类似用途,就像在 imbalanced-learn 中一样。一般来说,这些用例可以通过自定义的元估计器(meta estimator)而不是 Pipeline
来解决。
为什么线性模型有这么多不同的估计器?#
通常,每种模型类型都有一个分类器和一个回归器,例如 GradientBoostingClassifier
和 GradientBoostingRegressor
。它们都有类似的选项,并且都有参数 loss
,这在回归情况下特别有用,因为它能够估计条件均值和条件分位数。
对于线性模型,有许多估计器类彼此非常接近。让我们来看看
LinearRegression
,无惩罚Ridge
,L2 惩罚Lasso
,L1 惩罚(稀疏模型)ElasticNet
,L1 + L2 惩罚(较不稀疏的模型)SGDRegressor
,loss="squared_loss"
维护者视角: 它们原则上都做同样的事情,仅在施加的惩罚方式上有所不同。然而,这对于底层优化问题的解决方式有很大影响。最终,这归结为使用了不同的线性代数方法和技巧。一个特殊情况是 SGDRegressor
,它包含了前面所有 4 种模型,并且优化过程不同。另一个副作用是不同的估计器偏好不同的数据布局(X
是 C-连续或 F-连续,稀疏 csr 或 csc)。这些看似简单的线性模型的复杂性是为不同惩罚设置不同估计器类别的理由。
用户视角: 首先,当前设计灵感来源于科学文献,其中具有不同正则化/惩罚的线性回归模型被赋予了不同的名称,例如 *岭回归*。拥有不同名称的模型类使用户更容易找到这些回归模型。其次,如果上面提到的所有 5 种线性模型统一为一个类,那么将会有很多带有许多选项的参数,例如 solver
参数。最重要的是,不同参数之间会有很多排他性交互。例如,参数 solver
、precompute
和 selection
的可能选项将取决于惩罚参数 alpha
和 l1_ratio
的选择值。
贡献#
我如何为 scikit-learn 贡献?#
请参阅贡献。在想要添加新算法之前(这通常是一项重大而漫长的任务),建议从已知问题入手。请不要直接联系 scikit-learn 的贡献者咨询有关贡献事宜。
为什么我的拉取请求没有得到关注?#
scikit-learn 的审查过程需要大量时间,贡献者不应因其拉取请求缺乏活动或审查而气馁。我们非常重视第一次就将事情做好,因为维护和后续更改的成本很高。我们很少发布任何“实验性”代码,因此我们所有的贡献都将立即被大量使用,并且最初应具有尽可能高的质量。
除此之外,scikit-learn 的审查带宽有限;许多审查者和核心开发人员都在利用自己的时间为 scikit-learn 工作。如果您的拉取请求审查缓慢,很可能是因为审查人员很忙。我们请求您的理解,并请求您不要仅仅因为这个原因而关闭您的拉取请求或中断您的工作。
新算法的收录标准是什么?#
我们只考虑纳入成熟的算法。经验法则是至少发表 3 年、引用量 200+,并且广泛使用和有用。对于广泛使用的方法,如果一项技术提供了明确的改进(例如增强的数据结构或更有效的近似技术),也将被考虑纳入。
在符合上述标准的算法或技术中,只有那些与 scikit-learn 当前 API 良好契合的才会被接受,即具有 fit
、predict/transform
接口,并且通常输入/输出为 numpy 数组或稀疏矩阵。
贡献者应通过研究论文和/或在其他类似软件包中的实现来支持所提议添加的重要性,通过常见用例/应用程序展示其有用性,并通过基准测试和/或图表证实性能改进(如果有)。预计所提议的算法应至少在某些方面优于 scikit-learn 中已实现的现有方法。
如果新的算法能加速现有模型,则更容易被收录,条件是
它不引入新的超参数(因为这使得库更具前瞻性),
很容易清楚地记录该贡献何时提高了速度,何时没有,例如,“当
n_features >> n_samples
时”,基准测试清楚地显示了加速效果。
此外,请注意,您的实现不必包含在 scikit-learn 中即可与 scikit-learn 工具一起使用。您可以以与 scikit-learn 兼容的方式实现您喜欢的算法,将其上传到 GitHub 并告知我们。我们很乐意将其列在相关项目下。如果您已经在 GitHub 上拥有一个遵循 scikit-learn API 的包,您可能也会对查看 scikit-learn-contrib 感兴趣。
为什么您对 scikit-learn 中包含的算法如此挑剔?#
代码伴随着维护成本,我们需要平衡代码量与团队规模(更何况复杂性与功能数量呈非线性增长)。该软件包依赖核心开发人员利用业余时间修复错误、维护代码和审查贡献。任何新添加的算法都需要开发人员未来的关注,届时原作者可能早已失去兴趣。另请参阅新算法的收录标准是什么?。关于开源软件长期维护问题的精彩阅读,请查看《道路与桥梁》执行摘要。
使用 scikit-learn#
如何开始使用 scikit-learn?#
如果您是 scikit-learn 的新手,或者希望加深理解,我们强烈推荐 scikit-learn 慕课(大规模开放在线课程)。
有关更多详细信息,请参阅我们的外部资源、视频和讲座页面。
获取 scikit-learn 使用帮助的最佳方式是什么?#
通用机器学习问题:请在 Cross Validated 上使用
[machine-learning]
标签提问。scikit-learn 使用问题:请在 Stack Overflow 上使用
[scikit-learn]
和[python]
标签提问。您也可以使用邮件列表。
请务必包含一个最小可重现代码片段(理想情况下少于 10 行),该片段应在一个玩具数据集(例如来自 sklearn.datasets
或使用具有固定随机种子的 numpy.random
函数随机生成的数据集)上突出显示您的问题。请删除任何不必要的代码行以重现您的问题。
只需将您的代码片段复制粘贴到已安装 scikit-learn 的 Python shell 中,问题即可重现。不要忘记包含 import 语句。有关编写良好可重现代码片段的更多指导,请访问:https://stackoverflow.com/help/mcve。
如果您的问题引发了一个您不理解的异常(即使在 Google 搜索之后),请务必包含运行重现脚本时获得的完整回溯信息。
对于错误报告或功能请求,请使用 GitHub 上的问题跟踪器。
警告
请勿直接向任何作者发送电子邮件寻求帮助、报告错误或咨询任何其他与 scikit-learn 相关的问题。
如何保存、导出或部署估计器以用于生产?#
请参阅模型持久化。
如何创建 Bunch 对象?#
Bunch 对象有时用作函数和方法的输出。它们通过允许通过键 bunch["value_key"]
或通过属性 bunch.value_key
访问值来扩展字典。
它们不应作为输入使用。因此,除非您正在扩展 scikit-learn 的 API,否则您几乎不需要创建 Bunch
对象。
如何将我的数据集加载为 scikit-learn 可用的格式?#
通常,scikit-learn 处理存储为 numpy 数组或 scipy 稀疏矩阵的任何数值数据。其他可转换为数值数组的类型,例如 pandas.DataFrame
,也同样可以接受。
有关将数据文件加载到这些可用数据结构中的更多信息,请参阅加载外部数据集。
如何处理字符串数据(或树、图等)?#
scikit-learn 估计器假设您将向它们提供实值特征向量。这个假设几乎硬编码在库的所有部分中。但是,您可以通过多种方式向估计器提供非数值输入。
如果您有文本文档,可以使用词频特征;有关内置的文本向量化器,请参阅文本特征提取。有关从任何类型数据中进行更通用特征提取的信息,请参阅从字典加载特征和特征哈希。
另一种常见情况是您拥有非数值数据以及这些数据上的自定义距离(或相似性)度量。例如,具有编辑距离(又称 Levenshtein 距离)的字符串,如 DNA 或 RNA 序列。这些可以编码为数字,但这样做既痛苦又容易出错。处理任意数据上的距离度量可以通过两种方式完成。
首先,许多估计器接受预先计算的距离/相似度矩阵,因此如果数据集不是太大,您可以计算所有输入对的距离。如果数据集很大,您可以使用只有一个“特征”的特征向量,该特征是独立数据结构中的一个索引,并提供一个自定义度量函数,该函数在该数据结构中查找实际数据。例如,要将 dbscan
与 Levenshtein 距离一起使用
>>> 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 的一些工具,例如 GridSearchCV
和 cross_val_score
,在内部依赖 Python 的 multiprocessing
模块,通过传递参数 n_jobs > 1
来将执行并行化到多个 Python 进程。
问题在于 Python 的 multiprocessing
为了性能原因,在不跟随 exec
系统调用的情况下执行 fork
系统调用。许多库,如 OSX 下的(某些版本的)Accelerate 或 vecLib、(某些版本的)MKL、GCC 的 OpenMP 运行时、NVIDIA 的 CUDA(可能还有许多其他库),管理着自己的内部线程池。当调用 fork
时,子进程中的线程池状态会损坏:线程池认为它有许多线程,而实际上只有主线程的状态被分叉。可以更改库以使其检测何时发生 fork 并在这种情况下重新初始化线程池:我们为 OpenBLAS 做了这件事(自 0.2.10 版本起已合并到主线),并且我们向 GCC 的 OpenMP 运行时贡献了一个补丁(尚未审查)。
但归根结底,真正的罪魁祸首是 Python 的 multiprocessing
,它执行 fork
而不执行 exec
以减少启动和使用新 Python 进程进行并行计算的开销。不幸的是,这违反了 POSIX 标准,因此像 Apple 这样的一些软件厂商拒绝将 Accelerate 和 vecLib 中缺乏 fork-safety 视为一个 bug。
在 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 文档中找到关于新启动方法的更多详细信息。
为什么我的任务使用的核心数超过了 n_jobs
指定的数量?#
这是因为 n_jobs
只控制通过 joblib
并行化的例程的工作数量,但并行代码可能来自其他来源:
一些例程可能通过 OpenMP 进行并行化(对于用 C 或 Cython 编写的代码),
scikit-learn 大量依赖 numpy,而 numpy 又可能依赖于 MKL、OpenBLAS 或 BLIS 等数值库,这些库可以提供并行实现。
有关更多详细信息,请参阅我们的并行性说明。
如何为整个执行设置 random_state
?#
请参阅控制随机性。