贡献#

这个项目是一个社区努力,欢迎所有人贡献。它托管在 scikit-learn/scikit-learn 上。scikit-learn 的决策过程和治理结构在 Scikit-learn 治理和决策 中概述。

在添加新算法方面,scikit-learn 有点 选择性,而参与和帮助项目的最佳方式是开始处理已知问题。请参阅 新贡献者的问题 开始。

如果您在使用此软件包时遇到问题,请随时向 GitHub 问题跟踪器 提交工单。您也可以发布功能请求或拉取请求。

贡献方式#

有很多方法可以为 scikit-learn 做贡献,最常见的是为项目贡献代码或文档。改进文档与改进库本身一样重要。如果您在文档中发现错别字,或者进行了改进,请随时向邮件列表发送电子邮件,或者最好提交 GitHub 拉取请求。完整的文档可以在 doc/ 目录下找到。

但还有很多其他方法可以提供帮助。特别是帮助 改进、分类和调查问题 以及 审查其他开发人员的拉取请求 是非常宝贵的贡献,可以减轻项目维护人员的负担。

另一种贡献方式是报告您遇到的问题,并对其他人报告的与您相关的问题表示“赞”。如果您宣传它,对我们也有帮助:从您的博客和文章中引用该项目,从您的网站链接到它,或者简单地加星号以表示“我使用它”。

如果贡献/问题涉及对 API 原则的更改或对依赖项或支持版本的更改,则必须由 增强提案 (SLEPs) 支持,其中 SLEP 必须作为拉取请求提交到 增强提案,使用 SLEP 模板 并遵循 Scikit-learn 治理和决策 中概述的决策过程。

自动贡献政策#

请不要提交由全自动工具生成的工单或拉取请求。维护人员保留自行决定关闭此类提交并阻止任何负责提交的帐户的权利。

理想情况下,贡献应该来自以工单形式进行的人与人之间的讨论。

提交错误报告或功能请求#

我们使用 GitHub 工单跟踪所有错误和功能请求;如果您发现错误或希望看到实现某个功能,请随时打开一个工单。

如果您在使用此软件包时遇到问题,请随时向 错误跟踪器 提交工单。您也可以发布功能请求或拉取请求。

建议您在提交之前检查您的工单是否符合以下规则

如何提交有效的错误报告#

当您向 GitHub 提交工单时,请尽力遵循这些指南!这将使我们更容易为您提供良好的反馈。

  • 理想的错误报告包含一个 简短的可重现代码片段,这样任何人都可以轻松地尝试重现错误。如果您的代码片段超过大约 50 行,请链接到 Gist 或 GitHub 存储库。

  • 如果无法包含可重现的代码片段,请具体说明涉及哪些估计器和/或函数以及数据的形状

  • 如果引发异常,请提供完整的跟踪信息

  • 请包括您的操作系统类型和版本号,以及您的Python、scikit-learn、numpy 和 scipy 版本。可以通过运行以下命令找到此信息

    python -c "import sklearn; sklearn.show_versions()"
    
  • 请确保所有代码片段和错误消息都以适当的代码块格式化。有关更多详细信息,请参阅 创建和突出显示代码块

如果您想帮助管理工单,请阅读有关 错误分类和工单管理 的内容。

贡献代码#

注意

为了避免重复工作,强烈建议您搜索 问题跟踪器PR 列表。如果您对重复工作有疑问,或者您想处理一个非平凡的功能,建议您首先在 问题跟踪器 中打开一个工单,以获得核心开发人员的一些反馈。

找到要处理的工单的一种简单方法是在搜索中应用“需要帮助”标签。这将列出所有尚未被认领的工单。为了认领一个工单,请在上面评论 /take,以便 CI 自动将工单分配给您。

为了维护代码库的质量并简化审查过程,任何贡献都必须符合项目的 编码指南,特别是

  • 不要修改不相关的行,以使 PR 关注其描述或工单中陈述的范围。

  • 只编写有价值的内联注释,并避免说明显而易见的内容:解释“为什么”而不是“什么”。

  • 最重要的是:不要贡献你不理解的代码。

视频资源#

这些视频是有关如何为 scikit-learn 做贡献的分步介绍,并且是以下文本指南的绝佳补充。请确保仍然查看下面的指南,因为它们描述了我们最新的最新工作流程。

注意

2021 年 1 月,scikit-learn GitHub 仓库的默认分支名称从 master 更改为 main,以使用更具包容性的术语。这些视频是在分支重命名之前创建的。对于观看这些视频以设置其工作环境并提交 PR 的贡献者,应将 master 替换为 main

如何贡献#

向 scikit-learn 贡献的首选方式是在 GitHub 上 fork 主仓库,然后提交“pull request”(PR)。

在最初的几个步骤中,我们将解释如何本地安装 scikit-learn 以及如何设置您的 git 仓库。

  1. 如果您还没有 GitHub 帐户,请创建一个帐户

  2. Fork 项目仓库:点击页面顶部的“Fork”按钮。这将在您的 GitHub 用户帐户下创建代码的副本。有关如何 fork 仓库的更多详细信息,请参阅本指南

  3. 从您的 GitHub 帐户将您 fork 的 scikit-learn 仓库克隆到您的本地磁盘。

    git clone [email protected]:YourLogin/scikit-learn.git  # add --depth 1 if your connection is slow
    cd scikit-learn
    
  4. 按照 从源代码构建 中的步骤 2-6 构建开发模式下的 scikit-learn,然后返回本文档。

  5. 安装开发依赖项。

    pip install pytest pytest-cov ruff mypy numpydoc black==24.3.0
    
  1. 添加 upstream 远程。这将保存对主 scikit-learn 仓库的引用,您可以使用它来使您的仓库与最新更改保持同步。

    git remote add upstream [email protected]:scikit-learn/scikit-learn.git
    
  2. 通过运行 git remote -v 检查 upstreamorigin 远程别名是否配置正确,它应该显示

    origin  [email protected]:YourLogin/scikit-learn.git (fetch)
    origin  [email protected]:YourLogin/scikit-learn.git (push)
    upstream        [email protected]:scikit-learn/scikit-learn.git (fetch)
    upstream        [email protected]:scikit-learn/scikit-learn.git (push)
    

您现在应该拥有一个可用的 scikit-learn 安装,并且您的 git 仓库已正确配置。运行一些测试以验证您的安装可能会有所帮助。有关示例,请参阅 有用的 pytest 别名和标志

接下来的步骤将描述修改代码和提交 PR 的过程。

  1. 将您的 main 分支与 upstream/main 分支同步,有关更多详细信息,请参阅 GitHub 文档

    git checkout main
    git fetch upstream
    git merge upstream/main
    
  2. 创建一个功能分支来保存您的开发更改。

    git checkout -b my_feature
    

    并开始进行更改。始终使用功能分支。最好不要在 main 分支上工作!

  3. 可选)安装 pre-commit 以在每次提交之前运行代码样式检查。

    pip install pre-commit
    pre-commit install
    

    可以使用 git commit -n 禁用特定提交的 pre-commit 检查。

  4. 在您的计算机上使用 Git 进行版本控制,在您的功能分支上开发功能。编辑完成后,使用 git add 添加更改的文件,然后使用 git commit

    git add modified_files
    git commit
    

    在 Git 中记录您的更改,然后使用以下命令将更改推送到您的 GitHub 帐户:

    git push -u origin my_feature
    
  5. 按照 这些 说明从您的 fork 创建一个 pull request。这将向提交者发送电子邮件。您可能需要考虑向邮件列表发送电子邮件以获得更高的可见度。

将您的本地功能分支与主 scikit-learn 仓库的最新更改保持同步通常很有帮助。

git fetch upstream
git merge upstream/main

随后,您可能需要解决冲突。您可以参考 与使用命令行解决合并冲突相关的 Git 文档

Pull Request 检查清单#

在合并 PR 之前,需要两名核心开发人员批准。不完整的贡献(您希望在收到完整审查之前进行更多工作)应标记为 草稿 Pull Request,并在其成熟时更改为“准备审查”。草稿 PR 可能有助于:表明您正在处理某些内容以避免重复工作,请求对功能或 API 的广泛审查,或寻求合作者。草稿 PR 通常受益于在 PR 描述中包含 任务列表

为了简化审查过程,我们建议您在将 PR 标记为“准备审查”之前,您的贡献应符合以下规则。粗体部分尤为重要。

  1. 为您的 Pull Request 提供一个有用的标题,总结您的贡献做了什么。此标题通常会在合并后成为提交消息,因此它应该总结您的贡献以供后代参考。在某些情况下,“Fix <ISSUE TITLE>” 就足够了。“Fix #<ISSUE NUMBER>” 从来不是一个好的标题。

  2. 确保您的代码通过测试。可以使用 pytest 运行整个测试套件,但这通常不建议这样做,因为它需要很长时间。通常只运行与您的更改相关的测试就足够了:例如,如果您更改了 sklearn/linear_model/_logistic.py 中的内容,运行以下命令通常就足够了。

    • pytest sklearn/linear_model/_logistic.py 以确保 doctest 示例正确。

    • pytest sklearn/linear_model/tests/test_logistic.py 以运行特定于该文件的测试。

    • pytest sklearn/linear_model 以测试整个 linear_model 模块。

    • pytest doc/modules/linear_model.rst 以确保用户指南示例正确。

    • pytest sklearn/tests/test_common.py -k LogisticRegression 以运行我们所有的估计器检查(特别是针对 LogisticRegression,如果您更改了该估计器)。

    可能还有其他失败的测试,但 CI 会捕获它们,因此您无需在本地运行整个测试套件。有关如何有效使用 pytest 的指南,请参阅 有用的 pytest 别名和标志

  3. 确保您的代码已正确注释和记录,并确保文档正确呈现。要构建文档,请参阅我们的 文档 指南。CI 也会构建文档:请参阅 GitHub Actions 上生成的文档

  4. 增强功能需要测试才能被接受。错误修复或新功能应提供 非回归测试。这些测试验证修复或功能的正确行为。通过这种方式,可以保证对代码库的进一步修改与所需行为一致。在错误修复的情况下,在 PR 时,非回归测试应该在 main 分支的代码库中失败,而在 PR 代码中通过。

  5. 遵循 编码指南

  6. 在适用情况下,使用 sklearn.utils 模块中的验证工具和脚本。可以在 开发人员实用程序 页面中找到可供开发人员使用的实用程序例程列表。

  7. 通常,Pull Request 会解决一个或多个其他问题(或 Pull Request)。如果合并您的 Pull Request 意味着某些其他问题/PR 应该关闭,您应该 使用关键字创建指向它们的链接(例如,Fixes #1234;只要每个问题/PR 前面都有一个关键字,就可以使用多个问题/PR)。合并后,GitHub 会自动关闭这些问题/PR。如果您的 Pull Request 只是与某些其他问题/PR 相关,或者它只是部分解决了目标问题,请创建指向它们的链接,但不要使用关键字(例如,Towards #1234)。

  8. PR 应经常通过性能和效率基准测试来证明更改的合理性(参见 性能监控)或通过使用示例。示例还向用户展示了库的功能和复杂性。请查看 examples/ 目录中的其他示例以供参考。示例应演示新功能在实践中的用途,如果可能,将其与 scikit-learn 中可用的其他方法进行比较。

  9. 新功能会带来一些维护开销。我们希望 PR 作者至少在最初阶段参与他们提交的代码的维护。新功能需要在用户指南中用叙述性文档说明,并附带少量代码片段。如果相关,请在文献中添加参考资料,如果可能,请提供 PDF 链接。

  10. 用户指南还应包括算法的预期时间和空间复杂度以及可扩展性,例如“该算法可以扩展到大量样本 > 100000,但在维度上不可扩展:n_features 预计小于 100”。

您还可以查看我们的 代码审查指南,以了解审阅者会期望什么。

您可以使用以下工具检查常见的编程错误

  • 具有良好单元测试覆盖率的代码(至少 80%,最好 100%),使用以下方法检查

    pip install pytest pytest-cov
    pytest --cov sklearn path/to/tests
    

    另请参见 测试和提高测试覆盖率

  • 使用 mypy 运行静态分析

    mypy sklearn
    

    这不能在您的拉取请求中产生新的错误。使用 # type: ignore 注释可以作为一些 mypy 不支持的情况的解决方法,特别是

    • 导入 C 或 Cython 模块时,

    • 在使用装饰器的属性上。

如果贡献包含使用基准脚本和性能分析输出的性能分析(参见 性能监控),则可以获得额外积分。还可以查看 如何优化速度 指南,以获取有关性能分析和 Cython 优化的更多详细信息。

注意

当前的 scikit-learn 代码库不符合所有这些指南,但我们希望对所有新贡献强制执行这些约束将使整体代码库质量朝着正确的方向发展。

另请参见

有关开发工作流程的两个文档完善且更详细的指南,请访问 Scipy 开发工作流程 - 和 Astropy 开发人员工作流程 部分。

持续集成 (CI)#

  • Azure 管道用于在 Linux、Mac 和 Windows 上测试 scikit-learn,并使用不同的依赖项和设置。

  • CircleCI 用于构建文档以供查看。

  • GitHub Actions 用于各种任务,包括构建轮子和源代码分发。

  • Cirrus CI 用于在 ARM 上构建。

请注意,如果最新的提交消息中出现以下标记之一,则将采取以下操作。

提交消息标记

CI 采取的操作

[ci skip]

CI 完全跳过

[cd build]

运行 CD(构建轮子和源代码分发)

[cd build gh]

仅针对 GitHub Actions 运行 CD

[cd build cirrus]

仅针对 Cirrus CI 运行 CD

[lint skip]

Azure 管道跳过 linting

[scipy-dev]

使用我们的依赖项(numpy、scipy 等)开发版本构建和测试

[nogil]

使用 CPython、Cython、NumPy、SciPy 等的 nogil 实验分支构建和测试

[pypy]

使用 PyPy 构建和测试

[pyodide]

使用 Pyodide 构建和测试

[azure parallel]

并行运行 Azure CI 作业

[cirrus arm]

运行 Cirrus CI ARM 测试

[float32]

通过设置 SKLEARN_RUN_FLOAT32_TESTS=1 运行 float32 测试。有关更多详细信息,请参见 环境变量

[doc skip]

不构建文档

[doc quick]

构建文档,但不包括示例库图

[doc build]

构建文档,包括示例库图(非常长)

请注意,默认情况下,文档会构建,但仅执行拉取请求直接修改的示例。

停滞的拉取请求#

由于贡献功能可能是一个漫长的过程,因此一些拉取请求看起来处于非活动状态但尚未完成。在这种情况下,接管它们对项目来说是一项很棒的服务。接管的良好礼仪是

  • 确定 PR 是否停滞

    • 如果我们已经将拉取请求识别为其他贡献者的候选者,则它可能具有“停滞”或“需要帮助”标签。

    • 要确定非活动 PR 是否停滞,请询问贡献者她/他是否计划在不久的将来继续处理该 PR。如果在 2 周内没有回复且没有推动 PR 向前发展的活动,则表明该 PR 停滞,并将导致该 PR 被标记为“需要帮助”。

      请注意,如果 PR 在一个月内没有回复有关贡献的早期评论,则可以安全地假设该 PR 停滞,并将等待时间缩短至一天。

      在冲刺之后,将向冲刺参与者传达冲刺期间打开的未合并 PR 的后续行动,并将这些 PR 标记为“冲刺”。标记为“冲刺”的 PR 可以由冲刺负责人重新分配或宣布停滞。

  • 接管停滞的 PR:要接管 PR,重要的是在停滞的 PR 上评论您要接管的 PR,并将新 PR 与旧 PR 链接起来。新 PR 应通过从旧 PR 中拉取来创建。

停滞的和未认领的问题#

一般来说,可供抢占的问题将具有 “需要帮助” 标签。但是,并非所有需要贡献者的问题都具有此标签,因为“需要帮助”标签并不总是与问题的状态保持一致。贡献者可以使用以下指南找到仍然可供抢占的问题

  • 首先,要确定问题是否已认领

    • 检查链接的拉取请求

    • 检查对话以查看是否有人说过他们正在创建拉取请求

  • 如果贡献者在问题上发表评论说他们正在处理它,则预计在 2 周内(新贡献者)或 4 周内(贡献者或核心开发人员)创建拉取请求,除非明确给出更长的时间范围。超过该时间,其他贡献者可以接管该问题并为其创建拉取请求。我们鼓励贡献者直接在停滞的或未认领的问题上发表评论,以让社区成员知道他们将处理它。

  • 如果问题链接到 停滞的拉取请求,我们建议贡献者遵循 停滞的拉取请求 部分中描述的步骤,而不是直接处理该问题。

面向新贡献者的问题#

新贡献者在寻找问题时应寻找以下标签。我们强烈建议新贡献者首先解决“简单”问题:这有助于贡献者熟悉贡献工作流程,并使核心开发人员了解贡献者;此外,我们经常低估解决问题有多容易!

  • 良好首个问题标签

    开始为 scikit-learn 做贡献的一个好方法是从问题跟踪器中 good first issues 列表中选择一个项目。解决这些问题可以让您在没有太多先验知识的情况下开始为项目做出贡献。如果您已经为 scikit-learn 做出了贡献,您应该查看 Easy issues。

  • Easy 标签

    如果您已经为 scikit-learn 做出了贡献,另一个为 scikit-learn 做贡献的好方法是从问题跟踪器中 Easy issues 列表中选择一个项目。您在这方面的帮助将受到经验丰富的开发人员的极大赞赏,因为它有助于释放他们的时间,让他们专注于其他问题。

  • Help wanted 标签

    我们经常使用 help wanted 标签来标记问题,无论其难度如何。此外,我们使用 help wanted 标签来标记已被其原始贡献者放弃的 Pull Request,这些 Pull Request 可供其他人接手原始贡献者停止的地方。带有 help wanted 标签的问题列表可以在 这里 找到。请注意,并非所有需要贡献者的问题都会有此标签。

文档#

我们很乐意接受任何形式的文档

  • 函数/方法/类 docstrings: 也称为“API 文档”,它们描述了对象的作用,并详细说明了任何参数、属性和方法。Docstrings 与代码一起位于 sklearn/ 中,并根据 doc/api_reference.py 生成。要添加、更新、删除或弃用 API 参考 中列出的公共 API,这里就是您需要查看的地方。

  • 用户指南: 这些提供了有关 scikit-learn 中实现的算法的更详细的信息,通常位于根目录 doc/ 目录和 doc/modules/ 中。

  • 示例: 这些提供了完整的代码示例,可以演示 scikit-learn 模块的使用、比较不同的算法或讨论它们的解释等。示例位于 examples/ 中。

  • 其他 reStructuredText 文档: 这些提供了各种其他有用的信息(例如,贡献 指南),并位于 doc/ 中。

Guidelines for writing docstrings#
  • When documenting the parameters and attributes, here is a list of some well-formatted examples

    n_clusters : int, default=3
        The number of clusters detected by the algorithm.
    
    some_param : {"hello", "goodbye"}, bool or int, default=True
        The parameter description goes here, which can be either a string
        literal (either `hello` or `goodbye`), a bool, or an int. The default
        value is True.
    
    array_parameter : {array-like, sparse matrix} of shape (n_samples, n_features) \
        or (n_samples,)
        This parameter accepts data in either of the mentioned forms, with one
        of the mentioned shapes. The default value is `np.ones(shape=(n_samples,))`.
    
    list_param : list of int
    
    typed_ndarray : ndarray of shape (n_samples,), dtype=np.int32
    
    sample_weight : array-like of shape (n_samples,), default=None
    
    multioutput_array : ndarray of shape (n_samples, n_classes) or list of such arrays
    

    In general have the following in mind:

    • Use Python basic types. (bool instead of boolean)

    • Use parenthesis for defining shapes: array-like of shape (n_samples,) or array-like of shape (n_samples, n_features)

    • For strings with multiple options, use brackets: input: {'log', 'squared', 'multinomial'}

    • 1D or 2D data can be a subset of {array-like, ndarray, sparse matrix, dataframe}. Note that array-like can also be a list, while ndarray is explicitly only a numpy.ndarray.

    • Specify dataframe when “frame-like” features are being used, such as the column names.

    • When specifying the data type of a list, use of as a delimiter: list of int. When the parameter supports arrays giving details about the shape and/or data type and a list of such arrays, you can use one of array-like of shape (n_samples,) or list of such arrays.

    • When specifying the dtype of an ndarray, use e.g. dtype=np.int32 after defining the shape: ndarray of shape (n_samples,), dtype=np.int32. You can specify multiple dtype as a set: array-like of shape (n_samples,), dtype={np.float64, np.float32}. If one wants to mention arbitrary precision, use integral and floating rather than the Python dtype int and float. When both int and floating are supported, there is no need to specify the dtype.

    • When the default is None, None only needs to be specified at the end with default=None. Be sure to include in the docstring, what it means for the parameter or attribute to be None.

  • Add “See Also” in docstrings for related classes/functions.

  • “See Also” in docstrings should be one line per reference, with a colon and an explanation, for example:

    See Also
    --------
    SelectKBest : Select features based on the k highest scores.
    SelectFpr : Select features based on a false positive rate test.
    
  • Add one or two snippets of code in “Example” section to show how it can be used.

Guidelines for writing the user guide and other reStructuredText documents#

It is important to keep a good compromise between mathematical and algorithmic details, and give intuition to the reader on what the algorithm does.

  • Begin with a concise, hand-waving explanation of what the algorithm/code does on the data.

  • Highlight the usefulness of the feature and its recommended application. Consider including the algorithm’s complexity (\(O\left(g\left(n\right)\right)\)) if available, as “rules of thumb” can be very machine-dependent. Only if those complexities are not available, then rules of thumb may be provided instead.

  • Incorporate a relevant figure (generated from an example) to provide intuitions.

  • Include one or two short code examples to demonstrate the feature’s usage.

  • Introduce any necessary mathematical equations, followed by references. By deferring the mathematical aspects, the documentation becomes more accessible to users primarily interested in understanding the feature’s practical implications rather than its underlying mechanics.

  • When editing reStructuredText (.rst) files, try to keep line length under 88 characters when possible (exceptions include links and tables).

  • In scikit-learn reStructuredText files both single and double backticks surrounding text will render as inline literal (often used for code, e.g., list). This is due to specific configurations we have set. Single backticks should be used nowadays.

  • Too much information makes it difficult for users to access the content they are interested in. Use dropdowns to factorize it by using the following syntax

    .. dropdown:: Dropdown title
    
      Dropdown content.
    

    The snippet above will result in the following dropdown:

  • Information that can be hidden by default using dropdowns is:

    • low hierarchy sections such as References, Properties, etc. (see for instance the subsections in Detection error tradeoff (DET));

    • in-depth mathematical details;

    • narrative that is use-case specific;

    • in general, narrative that may only interest users that want to go beyond the pragmatics of a given tool.

  • Do not use dropdowns for the low level section Examples, as it should stay visible to all users. Make sure that the Examples section comes right after the main discussion with the least possible folded section in-between.

  • Be aware that dropdowns break cross-references. If that makes sense, hide the reference along with the text mentioning it. Else, do not use dropdown.

Guidelines for writing references#
  • When bibliographic references are available with arxiv or Digital Object Identifier identification numbers, use the sphinx directives :arxiv: or :doi:. For example, see references in Spectral Clustering Graphs.

  • For the “References” section in docstrings, see sklearn.metrics.silhouette_score as an example.

  • To cross-reference to other pages in the scikit-learn documentation use the reStructuredText cross-referencing syntax:

    • Section: to link to an arbitrary section in the documentation, use reference labels (see Sphinx docs). For example:

      .. _my-section:
      
      My section
      ----------
      
      This is the text of the section.
      
      To refer to itself use :ref:`my-section`.
      

      You should not modify existing sphinx reference labels as this would break existing cross references and external links pointing to specific sections in the scikit-learn documentation.

    • Glossary: linking to a term in the Glossary of Common Terms and API Elements:

      :term:`cross_validation`
      
    • Function: to link to the documentation of a function, use the full import path to the function:

      :func:`~sklearn.model_selection.cross_val_score`
      

      However, if there is a .. currentmodule:: directive above you in the document, you will only need to use the path to the function succeeding the current module specified. For example:

      .. currentmodule:: sklearn.model_selection
      
      :func:`cross_val_score`
      
    • Class: to link to documentation of a class, use the full import path to the class, unless there is a .. currentmodule:: directive in the document above (see above):

      :class:`~sklearn.preprocessing.StandardScaler`
      

您可以使用任何文本编辑器编辑文档,然后按照 构建文档 生成 HTML 输出。生成的 HTML 文件将放置在 _build/html/ 中,可以在 Web 浏览器中查看,例如通过打开本地 _build/html/index.html 文件或运行本地服务器

python -m http.server -d _build/html

构建文档#

在提交拉取请求之前,请检查您的修改是否引入了新的 sphinx 警告,方法是在本地构建文档并尝试修复它们。

首先,确保您已 正确安装 开发版本。最重要的是,构建文档需要安装一些额外的软件包

pip install sphinx sphinx-gallery numpydoc matplotlib Pillow pandas \
            polars scikit-image packaging seaborn sphinx-prompt \
            sphinxext-opengraph sphinx-copybutton plotly pooch \
            pydata-sphinx-theme sphinxcontrib-sass sphinx-design \
            sphinx-remove-toctrees

要构建文档,您需要位于 doc 文件夹中

cd doc

在绝大多数情况下,您只需要生成完整的网站,而不需要示例库

make

文档将生成在 _build/html/stable 目录中,可以在 Web 浏览器中查看,例如通过打开本地 _build/html/stable/index.html 文件。要生成示例库,您可以使用

make html

这将运行所有示例,这需要一段时间。如果您只想生成一些示例,这在您只修改了一些示例时特别有用,您可以使用

EXAMPLES_PATTERN=your_regex_goes_here make html

如果您打算在脱机环境中查看文档,请设置环境变量 NO_MATHJAX=1。要构建 PDF 手册,请运行

make latexpdf

Sphinx 版本

虽然我们尽最大努力使文档在尽可能多的 Sphinx 版本下构建,但不同的版本往往表现略有不同。为了获得最佳效果,您应该使用与我们在 CircleCI 上使用的版本相同的版本。查看此 GitHub 搜索 以了解确切的版本。

GitHub Actions 上生成的文档#

当您在拉取请求中更改文档时,GitHub Actions 会自动构建它。要查看 GitHub Actions 生成的文档,只需转到您的 PR 页面底部,查找“在此处查看渲染的文档!”项,然后单击它旁边的“详细信息”。

../_images/generated-doc-ci.png

测试和提高测试覆盖率#

高质量的 单元测试 是 scikit-learn 开发过程的基石。为此,我们使用 pytest 包。测试是命名恰当的函数,位于 tests 子目录中,用于检查算法的有效性和代码的不同选项。

在文件夹中运行 pytest 将运行相应子包的所有测试。有关更详细的 pytest 工作流程,请参阅 拉取请求清单

我们希望新功能的代码覆盖率至少达到 90% 左右。

提高测试覆盖率的工作流程#

要测试代码覆盖率,您需要除了 pytest 之外,还需要安装 coverage 包。

  1. 运行 make test-coverage。输出列出了每个文件未测试的行号。

  2. 找到一个容易解决的问题,查看哪些行没有测试,专门为这些行编写或修改测试。

  3. 循环。

性能监控#

本节内容主要参考 pandas 文档

在提出对现有代码库的更改时,务必确保它们不会引入性能回归。scikit-learn 使用 asv 基准测试 来监控一些常用估计器和函数的性能。您可以在 scikit-learn 基准测试页面 上查看这些基准测试。相应的基准测试套件可以在 asv_benchmarks/ 目录中找到。

要使用 asv 的所有功能,您需要 condavirtualenv。有关更多详细信息,请查看 asv 安装网页

首先,您需要安装 asv 的开发版本

pip install git+https://github.com/airspeed-velocity/asv

并将您的目录更改为 asv_benchmarks/

cd asv_benchmarks

基准测试套件配置为针对您本地克隆的 scikit-learn 运行。确保它是最新的

git fetch upstream

在基准测试套件中,基准测试的组织方式与 scikit-learn 的结构相同。例如,您可以比较 upstream/main 和您正在处理的分支之间特定估计器的性能

asv continuous -b LogisticRegression upstream/main HEAD

该命令默认使用 conda 创建基准测试环境。如果您想改用 virtualenv,请使用 -E 标志

asv continuous -E virtualenv -b LogisticRegression upstream/main HEAD

您还可以指定要基准测试的整个模块

asv continuous -b linear_model upstream/main HEAD

您可以将 HEAD 替换为任何本地分支。默认情况下,它只会报告至少改变 10% 的基准测试。您可以使用 -f 标志控制此比率。

要运行完整的基准测试套件,只需删除 -b 标志

asv continuous upstream/main HEAD

但是这可能需要长达两个小时。 -b 标志还接受正则表达式,用于运行更复杂的基准测试子集。

要运行基准测试而不与其他分支进行比较,请使用 run 命令

asv run -b linear_model HEAD^!

您还可以使用当前 Python 环境中已安装的 scikit-learn 版本运行基准测试套件

asv run --python=same

当您以可编辑模式安装 scikit-learn 时,这特别有用,可以避免每次运行基准测试时都创建新的环境。默认情况下,在使用现有安装时不会保存结果。要保存结果,您必须指定一个提交哈希值

asv run --python=same --set-commit-hash=<commit hash>

基准测试按机器、环境和提交进行保存和组织。要查看所有保存的基准测试的列表

asv show

以及查看特定运行的报告

asv show <commit hash>

在为正在处理的拉取请求运行基准测试时,请在 github 上报告结果。

基准测试套件支持其他可配置选项,这些选项可以在 benchmarks/config.json 配置文件中设置。例如,基准测试可以针对 n_jobs 参数提供的列表中的值运行。

有关如何编写基准测试以及如何使用 asv 的更多信息,请查看 asv 文档

问题跟踪器标签#

GitHub 问题跟踪器 上的所有问题和拉取请求都应该具有以下标签(至少一个)

Bug:

正在发生明显不应该发生的事情。错误的结果以及估计器中的意外错误都属于此类。

增强功能:

提高性能、可用性、一致性。

文档:

缺少、不正确或标准以下的文档和示例。

新功能:

功能请求和实现新功能的拉取请求。

还有四个其他标签可以帮助新贡献者

适合新手问题:

此问题非常适合首次为 scikit-learn 做出贡献。如果表述不清楚,请寻求帮助。如果您已经为 scikit-learn 做出了贡献,请查看简单问题。

简单:

此问题无需太多先验经验即可解决。

中等:

可能需要一些机器学习或包的知识,但对于刚接触该项目的人来说仍然可以接受。

需要帮助:

此标签标记了一个目前缺少贡献者或需要其他贡献者接手工作的 PR 的问题。这些问题的难度可能会有所不同,可能不适合新手贡献者。请注意,并非所有需要贡献者的问题都会有此标签。

维护向后兼容性#

弃用#

如果任何公开可访问的类、函数、方法、属性或参数被重命名,我们仍然支持旧的名称两个版本,并在调用、传递或访问它时发出弃用警告。

弃用类或函数

假设函数 zero_one 被重命名为 zero_one_loss,我们将 utils.deprecated 装饰器添加到 zero_one,并从该函数中调用 zero_one_loss

from ..utils import deprecated

def zero_one_loss(y_true, y_pred, normalize=True):
    # actual implementation
    pass

@deprecated(
    "Function `zero_one` was renamed to `zero_one_loss` in 0.13 and will be "
    "removed in 0.15. Default behavior is changed from `normalize=False` to "
    "`normalize=True`"
)
def zero_one(y_true, y_pred, normalize=False):
    return zero_one_loss(y_true, y_pred, normalize)

还需要将 zero_oneAPI_REFERENCE 移动到 DEPRECATED_API_REFERENCE,并将 zero_one_loss 添加到 API_REFERENCE 中的 doc/api_reference.py 文件中,以反映 API 参考 中的更改。

弃用属性或方法

如果要弃用属性或方法,请在属性上使用装饰器 deprecated。请注意,如果存在 property 装饰器,则 deprecated 装饰器应放在 property 装饰器之前,以便正确渲染文档字符串。例如,将属性 labels_ 重命名为 classes_ 可以通过以下方式完成

@deprecated(
    "Attribute `labels_` was deprecated in 0.13 and will be removed in 0.15. Use "
    "`classes_` instead"
)
@property
def labels_(self):
    return self.classes_

弃用参数

如果必须弃用参数,则必须手动引发 FutureWarning 警告。在以下示例中,k 已弃用并重命名为 n_clusters

import warnings

def example_function(n_clusters=8, k="deprecated"):
    if k != "deprecated":
        warnings.warn(
            "`k` was renamed to `n_clusters` in 0.13 and will be removed in 0.15",
            FutureWarning,
        )
        n_clusters = k

当更改在类中时,我们在 fit 中验证并引发警告

import warnings

class ExampleEstimator(BaseEstimator):
    def __init__(self, n_clusters=8, k='deprecated'):
        self.n_clusters = n_clusters
        self.k = k

    def fit(self, X, y):
        if self.k != "deprecated":
            warnings.warn(
                "`k` was renamed to `n_clusters` in 0.13 and will be removed in 0.15.",
                FutureWarning,
            )
            self._n_clusters = self.k
        else:
            self._n_clusters = self.n_clusters

如这些示例所示,警告消息应始终给出弃用发生的版本和旧行为将被删除的版本。如果弃用发生在版本 0.x-dev 中,则消息应说明弃用发生在版本 0.x 中,并且删除将在 0.(x+2) 中进行,以便用户有足够的时间将代码适应新行为。例如,如果弃用发生在版本 0.18-dev 中,则消息应说明它发生在版本 0.18 中,并且旧行为将在版本 0.20 中删除。

警告消息还应包含对更改的简要说明,并引导用户使用替代方案。

此外,应在文档字符串中添加弃用说明,回忆与上面解释的弃用警告相同的信息。使用 .. deprecated:: 指令

.. deprecated:: 0.13
   ``k`` was renamed to ``n_clusters`` in version 0.13 and will be removed
   in 0.15.

此外,弃用需要一个测试,以确保在相关情况下引发警告,但在其他情况下不引发警告。警告应在所有其他测试中捕获(例如,使用 @pytest.mark.filterwarnings),并且示例中不应有任何警告。

更改参数的默认值#

如果需要更改参数的默认值,请将默认值替换为特定值(例如,"warn"),并在用户使用默认值时引发 FutureWarning。以下示例假设当前版本为 0.20,并且我们将 n_clusters 的默认值从 5(0.20 的旧默认值)更改为 10(0.22 的新默认值)

import warnings

def example_function(n_clusters="warn"):
    if n_clusters == "warn":
        warnings.warn(
            "The default value of `n_clusters` will change from 5 to 10 in 0.22.",
            FutureWarning,
        )
        n_clusters = 5

当更改在类中时,我们在 fit 中验证并引发警告

import warnings

class ExampleEstimator:
    def __init__(self, n_clusters="warn"):
        self.n_clusters = n_clusters

    def fit(self, X, y):
        if self.n_clusters == "warn":
            warnings.warn(
                "The default value of `n_clusters` will change from 5 to 10 in 0.22.",
                FutureWarning,
            )
            self._n_clusters = 5

与弃用类似,警告消息应始终给出更改发生的版本和旧行为将被删除的版本。

文档字符串中的参数描述需要通过添加 versionchanged 指令(包含旧的和新的默认值)来相应更新,并指向更改生效的版本

.. versionchanged:: 0.22
   The default value for `n_clusters` will change from 5 to 10 in version 0.22.

最后,我们需要一个测试来确保在相关情况下引发警告,但在其他情况下不引发警告。警告应在所有其他测试中捕获(例如,使用 @pytest.mark.filterwarnings),并且示例中不应有任何警告。

代码审查指南#

审查作为 PR 贡献给项目的代码是 scikit-learn 开发的关键组成部分。我们鼓励任何人都可以开始审查其他开发人员的代码。代码审查过程通常对所有参与者来说都是非常有教育意义的。如果您想使用某个功能,这尤其适用,因此您可以批判性地回应 PR 是否满足您的需求。虽然每个拉取请求都需要由两位核心开发人员签署,但您可以通过提供反馈来加快此过程。

注意

客观改进和主观细节之间的区别并不总是很清楚。审查人员应记住,代码审查主要是为了降低项目风险。审查代码时,应旨在防止可能需要错误修复、弃用或撤回的情况。关于文档:拼写错误、语法问题和歧义最好立即解决。

代码审查中需要涵盖的重要方面#

以下是一些在任何代码审查中都需要涵盖的重要方面,从高级问题到更详细的检查清单。

  • 我们是否希望将此功能纳入库中?它很可能会被使用吗?作为 scikit-learn 用户,您是否喜欢此更改并打算使用它?它是否在 scikit-learn 的范围内?维护新功能的成本是否值得其带来的益处?

  • 代码是否与 scikit-learn 的 API 一致?公共函数/类/参数是否命名合理且设计直观?

  • 所有公共函数/类及其参数、返回值类型和存储属性是否根据 scikit-learn 约定命名,并有清晰的文档?

  • 用户指南中是否描述了任何新功能,并用示例进行说明?

  • 每个公共函数/类是否都经过测试?是否测试了一组合理的参数、其值、值类型和组合?测试是否验证代码是正确的,即执行了文档中描述的功能?如果更改是错误修复,是否包含非回归测试?请查看 此链接 以了解如何在 Python 中进行测试。

  • 测试是否在持续集成构建中通过?如果适用,请帮助贡献者了解测试失败的原因。

  • 测试是否覆盖了每一行代码(请查看构建日志中的覆盖率报告)?如果没有,未覆盖的代码行是否属于合理的例外情况?

  • 代码是否易于阅读且冗余度低?是否需要改进变量名称以提高清晰度或一致性?是否需要添加注释?是否需要删除无用或多余的注释?

  • 代码是否可以轻松地重写以在相关设置下运行得更高效?

  • 代码是否与以前版本向后兼容?(或者是否需要弃用周期?)

  • 新代码是否会添加对其他库的任何依赖?(这不太可能被接受)

  • 文档是否正确呈现(有关更多详细信息,请参阅 文档 部分),并且图表是否具有指导意义?

审查的标准回复 包含一些审阅者可能会做出的常见评论。

沟通指南#

审查开放的拉取请求 (PR) 有助于推动项目向前发展。这是熟悉代码库的好方法,并且应该激励贡献者继续参与项目。 [1]

  • 每个 PR,无论好坏,都是一种慷慨的行为。以积极的评论开头将有助于作者感到欣慰,并且您随后的评论可能会被更清楚地听到。您可能也会感到高兴。

  • 如果可能,从大的问题开始,这样作者就知道他们被理解了。抵制立即逐行检查或从小的普遍问题开始的诱惑。

  • 不要让完美成为好的敌人。如果您发现自己提出了许多不属于 代码审查指南 的小建议,请考虑以下方法:

    • 不要提交这些建议;

    • 在它们前面加上“Nit”前缀,以便贡献者知道可以不处理这些建议;

    • 在随后的 PR 中跟进,出于礼貌,您可能希望让原始贡献者知道。

  • 不要急于求成,花时间使您的评论清晰并为您的建议提供理由。

  • 您是项目的代表。每个人都会有糟糕的日子,在这种情况下,您应该休息一下:尝试花时间并保持离线状态。

阅读现有的代码库#

阅读和理解现有的代码库始终是一项艰巨的任务,需要时间和经验才能掌握。尽管我们通常会尝试编写简单的代码,但考虑到项目的庞大规模,理解代码一开始可能看起来很令人生畏。以下是一些可能有助于使此任务更轻松、更快的技巧(无特定顺序)。

  • 熟悉 scikit-learn 对象的 API:了解 fitpredicttransform 等的用途。

  • 在深入阅读函数/类的代码之前,首先阅读文档字符串,并尝试了解每个参数/属性的作用。它还有助于停下来思考一下,如果我必须自己做,我会怎么做?

  • 最棘手的事情通常是确定哪些代码部分是相关的,哪些不是。在 scikit-learn 中,大量的输入检查是在 fit 方法的开头执行的。有时,只有一小部分代码在执行实际工作。例如,查看 fit 方法的 LinearRegression,您要查找的内容可能只是对 scipy.linalg.lstsq 的调用,但它被埋藏在多行输入检查和不同类型参数的处理中。

  • 由于使用了 继承,一些方法可能在父类中实现。所有估计器至少继承自 BaseEstimator,以及一个 Mixin 类(例如 ClassifierMixin),它根据估计器的性质(分类器、回归器、转换器等)启用默认行为。

  • 有时,阅读给定函数的测试可以让你了解其预期用途。你可以使用 git grep(见下文)查找为函数编写的全部测试。针对特定函数/类的多数测试都放置在模块的 tests/ 文件夹下。

  • 你经常会看到类似这样的代码:out = Parallel(...)(delayed(some_function)(param) for param in some_iterable)。这使用 Joblib 并行运行 some_function。然后,out 是一个可迭代对象,包含每次调用 some_function 返回的值。

  • 我们使用 Cython 来编写快速代码。Cython 代码位于 .pyx.pxd 文件中。Cython 代码具有更类似 C 的风格:我们使用指针,执行手动内存分配等。在这里,拥有 C/C++ 的一些基本经验几乎是必须的。有关更多信息,请参阅 Cython 最佳实践、约定和知识

  • 熟练掌握你的工具。

    • 对于如此庞大的项目,熟练使用你喜欢的编辑器或 IDE 对消化代码库大有帮助。能够快速跳转(或“查看”)到函数/类/属性定义非常有用。能够快速查看给定名称在文件中的使用位置也很有用。

    • Git 也有一些内置的强大功能。使用例如 git blame手册)了解文件随时间的变化通常很有用。这也可以直接在 GitHub 上完成。 git grep示例)也非常有用,可以查看代码库中模式(例如函数调用或变量)的每次出现。

  • 配置 git blame 以忽略将代码风格迁移到 black 的提交。

    git config blame.ignoreRevsFile .git-blame-ignore-revs
    

    在 black 的 文档 中找到更多有关避免破坏 git blame 的信息。