常见问题解答#
在这里,我们尝试解答邮件列表中经常出现的一些问题。
关于项目#
项目名称是什么?(很多人都会弄错)#
scikit-learn,而不是 scikit 或 SciKit 或者 sci-kit learn。也不是 scikits.learn 或 scikits-learn,这些以前曾使用过。
项目名称如何发音?#
sy-kit learn。sci 代表 science(科学)!
为什么叫 scikit?#
有多个 scikits,它们是围绕 SciPy 构建的科学工具箱。除了 scikit-learn 之外,另一个流行的工具箱是scikit-image。
你们支持 PyPy 吗?#
由于维护人员资源有限且用户数量较少,因此不正式支持使用 scikit-learn 与PyPy(具有内置即时编译器的替代 Python 实现)。
我该如何获得许可,在我的工作中使用 scikit-learn 中的图像?#
scikit-learn 代码库中包含的图像和scikit-learn 文档中生成的图像可以通过BSD 3-Clause 许可证用于您的工作。强烈建议并感谢您引用 scikit-learn。参见引用 scikit-learn。
实现决策#
为什么不支持深度学习或强化学习?将来会支持吗?#
深度学习和强化学习都需要丰富的词汇表来定义体系结构,深度学习还需要 GPU 才能进行高效的计算。但是,这两者都不符合 scikit-learn 的设计约束。因此,深度学习和强化学习目前不在 scikit-learn 力求实现的目标范围内。
您可以在你们会添加 GPU 支持吗?中找到有关添加 GPU 支持的更多信息。
请注意,scikit-learn 目前在sklearn.neural_network
中实现了一个简单的多层感知器。我们只接受此模块的错误修复。如果您想实现更复杂的深度学习模型,请转向流行的深度学习框架,例如tensorflow、keras和pytorch。
你们会添加图形模型或序列预测到 scikit-learn 吗?#
在可预见的将来不会。scikit-learn 试图为机器学习中的基本任务提供统一的 API,并使用管道和元算法(如网格搜索)将所有内容结合在一起。结构化学习所需的理念、API、算法和专业知识与 scikit-learn 提供的内容不同。如果我们开始进行任意的结构化学习,我们将需要重新设计整个软件包,并且项目可能会因自身重量而崩溃。
有两个项目具有类似于 scikit-learn 的 API,可以进行结构化预测
为什么你们从 scikit-learn 中删除了 HMMs?#
您会添加GPU支持吗?#
默认添加GPU支持会引入大量的硬件特定软件依赖项,并且现有算法需要重新实现。这将使普通用户更难安装scikit-learn,也使开发人员更难维护代码。
但是,自2023年以来,一部分不断增长的scikit-learn估计器的列表已经可以在GPU上运行,前提是输入数据以PyTorch或CuPy数组的形式提供,并且scikit-learn已配置为接受此类输入,如数组API支持(实验性)中所述。这种数组API支持允许scikit-learn在GPU上运行,而无需向主包引入繁重且特定于硬件的软件依赖项。
大多数依赖于NumPy进行计算密集型操作的估计器都可以考虑使用数组API支持,因此也支持GPU。
然而,并非所有scikit-learn估计器都适合通过数组API高效地在GPU上运行,这是由于其根本的算法原因。例如,目前在scikit-learn中使用Cython实现的基于树的模型从根本上不是基于数组的算法。其他算法,例如k均值或k近邻,依赖于基于数组的算法,但也用Cython实现。Cython用于手动交错连续的数组操作,以避免引入导致性能下降的大型中间数组的内存访问:这种低级算法重写称为“内核融合”,在可预见的未来无法通过数组API表达。
要为无法通过数组API高效实现的估计器添加高效的GPU支持,需要为scikit-learn设计和采用更灵活的扩展系统。这种可能性正在以下GitHub问题中考虑(正在讨论中)
与其他工具相比,为什么scikit-learn中的分类变量需要预处理?#
大多数scikit-learn假设数据位于单个数值dtype的NumPy数组或SciPy稀疏矩阵中。这些目前没有明确表示分类变量。因此,与R的data.frames
或pandas.DataFrame
不同,我们需要将分类特征显式转换为数值,如编码分类特征中所述。另请参阅具有混合类型的列转换器,了解处理异构(例如分类和数值)数据的示例。
请注意,最近,HistGradientBoostingClassifier
和HistGradientBoostingRegressor
通过选项categorical_features="from_dtype"
获得了对分类特征的原生支持。此选项依赖于根据pandas.CategoricalDtype
和polars.datatypes.Categorical
dtype推断数据的哪些列是分类的。
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
通过将按名称或dtype选择的dataframe列的同构子集映射到专用的scikit-learn转换器,方便处理异构pandas数据框。因此,在处理异构数据框时,通常在scikit-learn管道的第一步使用ColumnTransformer
(有关更多详细信息,请参阅管道:连接估计器)。
另请参阅具有混合类型的列转换器,了解处理异构(例如分类和数值)数据的示例。
您计划在管道中实现目标y
的转换吗?#
目前,转换功能仅适用于管道中的特征 X
。关于无法在管道中转换 y
的讨论由来已久。请关注 GitHub issue #4143。同时,您可以查看 TransformedTargetRegressor
、pipegraph 和 imbalanced-learn。需要注意的是,scikit-learn 解决了在训练前应用可逆转换并在预测后反转 y
的情况。scikit-learn 计划解决在训练时转换 y
而不在测试时转换的情况,用于重采样和类似用途,例如在 imbalanced-learn 中。一般来说,这些用例可以使用自定义元估计器来解决,而不是使用 Pipeline
。
为什么会有这么多不同的线性模型估计器?#
通常,每种模型类型都有一个分类器和一个回归器,例如 GradientBoostingClassifier
和 GradientBoostingRegressor
。两者具有相似的选项,并且都具有参数 loss
,这在回归情况下尤其有用,因为它可以估计条件均值以及条件分位数。
对于线性模型,存在许多彼此非常接近的估计器类。让我们来看一下
LinearRegression
,无惩罚项Ridge
,L2 惩罚项Lasso
,L1 惩罚项(稀疏模型)ElasticNet
,L1 + L2 惩罚项(不太稀疏的模型)SGDRegressor
withloss="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 的审查带宽有限;许多审查者和核心开发者都在业余时间从事 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用法的帮助的最佳方法是什么?#
一般机器学习问题:使用带有
[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。
如果您的问题引发您不理解的异常(即使在谷歌搜索之后),请务必包含在运行复现脚本时获得的完整回溯。
对于错误报告或功能请求,请使用GitHub上的问题跟踪器。
警告
请不要直接向任何作者发送电子邮件以寻求帮助、报告错误或处理与scikit-learn相关的任何其他问题。
如何保存、导出或部署用于生产的估计器?#
请参见模型持久性。
如何创建Bunch对象?#
Bunch对象有时用作函数和方法的输出。它们通过允许通过键bunch["value_key"]
或属性bunch.value_key
访问值来扩展字典。
它们不应作为输入使用。因此,您几乎不需要创建Bunch
对象,除非您正在扩展scikit-learn的API。
如何将我自己的数据集加载到scikit-learn可用的格式中?#
通常,scikit-learn适用于存储为NumPy数组或SciPy稀疏矩阵的任何数值数据。其他可转换为数值数组的类型,例如pandas.DataFrame
也是可以接受的。
有关将数据文件加载到这些可用数据结构中的更多信息,请参阅加载外部数据集。
如何处理字符串数据(或树、图……)?#
scikit-learn 的估计器假设你将向它们提供实值特征向量。这个假设几乎在整个库中都是硬编码的。但是,你可以通过几种方式向估计器提供非数值输入。
如果你有文本文档,你可以使用词频特征;有关内置的*文本向量化器*,请参见文本特征提取。有关从任何类型的数据中进行更通用的特征提取,请参见从字典加载特征和特征哈希。
另一种常见情况是你有非数值数据和这些数据的自定义距离(或相似度)度量。例如,带有编辑距离(又称莱文斯坦距离)的字符串,例如 DNA 或 RNA 序列。这些可以编码为数字,但这很痛苦且容易出错。可以通过两种方式处理任意数据的距离度量。
首先,许多估计器接受预计算的距离/相似度矩阵,因此如果数据集不太大,你可以计算所有输入对的距离。如果数据集很大,你可以使用只有一个“特征”的特征向量,该特征是指向单独数据结构的索引,并提供一个自定义度量函数,该函数在此数据结构中查找实际数据。例如,要使用带有莱文斯坦距离的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 工具,例如GridSearchCV
和cross_val_score
在内部依赖 Python 的multiprocessing
模块,通过将n_jobs > 1
作为参数来将执行并行化到多个 Python 进程上。
问题是 Python 的multiprocessing
执行fork
系统调用,但出于性能原因没有跟进exec
系统调用。许多库(例如 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 安全性视为错误。
在 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
?#
请参阅控制随机性。