贡献#

本项目是一个社区合作项目,欢迎所有人贡献。它托管在 scikit-learn/scikit-learn。scikit-learn 的决策过程和治理结构详见 Scikit-learn 治理和决策

Scikit-learn 在添加新算法方面有些选择性,贡献和帮助项目的最佳方式是着手解决已知问题。请参阅新贡献者问题以开始。

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

贡献方式#

有许多方式可以为 scikit-learn 做出贡献,最常见的是为项目贡献代码或文档。改进文档与改进库本身同等重要。如果您在文档中发现错别字或已做出改进,请随时创建 GitHub issue 或最好提交 GitHub pull request。完整的文档可以在 doc/ 目录下找到。

但还有许多其他方式可以提供帮助。特别是帮助改进、分类和调查问题以及审阅其他开发者的拉取请求是非常有价值的贡献,它们减轻了项目维护者的负担。

另一种贡献方式是报告您遇到的问题,并对他人报告且与您相关的问题点赞。如果您能口口相传:在您的博客和文章中引用本项目,从您的网站链接到它,或简单地点击星标以表达“我正在使用它”,也会对我们有所帮助。

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

自动化贡献策略#

请勿提交由全自动工具生成的问题或拉取请求。维护者保留自行决定关闭此类提交并阻止任何负责这些提交的账户的权利。

理想情况下,贡献应以问题形式的人际讨论为基础。

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

我们使用 GitHub issues 来跟踪所有错误和功能请求;如果您发现错误或希望实现某个功能,请随时创建 issue。

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

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

如何撰写一份好的错误报告#

当您向 GitHub 提交问题时,请尽力遵循这些指导方针!这将使我们更容易为您提供良好的反馈

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

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

  • 如果抛出异常,请提供完整的追踪信息

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

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

如果您想帮助整理问题,请阅读有关错误分类和问题整理的内容。

贡献代码#

注意

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

找到一个可着手处理的问题的简单方法是在您的搜索中应用“help wanted”标签。这将列出所有尚未认领的问题。为了认领一个问题,请在该问题下准确评论/take,以便 CI 自动将该问题分配给您。

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

  • 不要修改无关行,以使 PR 专注于其描述或问题中说明的范围。

  • 只编写有价值的行内注释,避免陈述显而易见的事实:解释“为什么”而不是“是什么”。

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

视频资源#

这些视频是关于如何为 scikit-learn 贡献的分步介绍,是以下文本指南的极佳补充。请务必仍查阅下面的指南,因为它们描述了我们最新的工作流程。

注意

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

如何贡献#

为 scikit-learn 贡献的首选方式是在 GitHub 上 Fork 主仓库,然后提交“拉取请求”(PR)。

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

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

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

  3. 将您 GitHub 账户上的 scikit-learn 仓库 Fork 到您的本地磁盘

    git clone git@github.com: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==0.11.2 mypy numpydoc
    
  1. 添加 upstream 远程仓库。这会保存对 scikit-learn 主仓库的引用,您可以使用它来使您的仓库与最新更改保持同步

    git remote add upstream git@github.com:scikit-learn/scikit-learn.git
    
  2. 通过运行以下命令检查 upstreamorigin 远程别名是否配置正确

    git remote -v
    

    这将显示

    origin    git@github.com:YourLogin/scikit-learn.git (fetch)
    origin    git@github.com:YourLogin/scikit-learn.git (push)
    upstream  git@github.com:scikit-learn/scikit-learn.git (fetch)
    upstream  git@github.com: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 创建拉取请求。这将向潜在的审阅者发送通知。如果您的拉取请求在几天后没有收到关注,您可能需要考虑向开发频道的 discord 发送消息以获得更多关注(但不保证即时回复)。

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

git fetch upstream
git merge upstream/main

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

拉取请求清单#

在 PR 合并之前,需要得到两名核心开发人员的批准。一项未完成的贡献——您期望在获得完整审阅之前进行更多工作——应标记为草稿拉取请求,并在成熟时更改为“准备审阅”。草稿 PR 可能有助于:表明您正在进行某项工作以避免重复工作,请求对功能或 API 进行广泛审阅,或寻求协作者。草稿 PR 通常受益于在 PR 描述中包含任务列表

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

  1. 为您的拉取请求提供一个有用的标题,概括您的贡献所做的工作。此标题在合并后通常会成为提交消息,因此它应为后代概括您的贡献。在某些情况下,“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. 如果您的 PR 可能影响用户,您需要添加一个描述您的 PR 更改的更新日志条目。有关更多详细信息,请参阅 README

  6. 遵循编码指南

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

  8. 拉取请求通常会解决一个或多个其他问题(或拉取请求)。如果合并您的拉取请求意味着应该关闭其他一些问题/PR,您应该使用关键字创建链接(例如,Fixes #1234;只要每个问题/PR 前面都有一个关键字,就允许多个问题/PR)。合并后,这些问题/PR 将由 GitHub 自动关闭。如果您的拉取请求仅与某些其他问题/PR 相关,或者它仅部分解决了目标问题,则无需使用关键字即可创建链接(例如,Towards #1234)。

  9. PR 应通过性能和效率基准(参见性能监控)或使用示例来证实更改。示例还向用户展示了库的功能和复杂性。请参阅 examples/ 目录中的其他示例作为参考。示例应说明新功能在实践中为何有用,如果可能,将其与 scikit-learn 中可用的其他方法进行比较。

  10. 新功能会带来一些维护开销。我们期望 PR 作者至少在最初参与他们提交的代码的维护。新功能需要在用户指南中通过叙述性文档和小型代码片段进行说明。如果相关,请在文献中添加参考文献,如果可能,附上 PDF 链接。

  11. 用户指南还应包含算法的预期时间和空间复杂度以及可扩展性,例如“该算法可以扩展到大量样本 > 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 Pipelines 用于在 Linux、Mac 和 Windows 上测试 scikit-learn,具有不同的依赖项和设置。

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

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

提交消息标记#

请注意,如果最新提交消息中出现以下任一标记,将执行以下操作。

提交消息标记

CI 执行的操作

[ci skip]

CI 被完全跳过

[cd build]

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

[lint skip]

Azure pipeline 跳过 linting

[scipy-dev]

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

[free-threaded]

使用 CPython 3.13 free-threaded 进行构建和测试

[pyodide]

使用 Pyodide 进行构建和测试

[azure parallel]

并行运行 Azure CI 作业

[float32]

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

[doc skip]

不构建文档

[doc quick]

文档已构建,但排除示例图库绘图

[doc build]

文档已构建,包括示例图库绘图(非常耗时)

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

解决锁定文件中的冲突#

这是一个帮助解决环境和锁定文件中冲突的 bash 代码片段

# pull latest upstream/main
git pull upstream main --no-rebase
# resolve conflicts - keeping the upstream/main version for specific files
git checkout --theirs  build_tools/*/*.lock build_tools/*/*environment.yml \
    build_tools/*/*lock.txt build_tools/*/*requirements.txt
git add build_tools/*/*.lock build_tools/*/*environment.yml \
    build_tools/*/*lock.txt build_tools/*/*requirements.txt
git merge --continue

这会将 upstream/main 合并到我们的分支中,自动优先处理 upstream/main 以解决冲突的环境和锁定文件(这已经足够了,因为我们之后会重新生成锁定文件)。

请注意,这只修复了环境和锁定文件中的冲突,您可能还有其他冲突需要解决。

最后,我们需要通过运行以下命令为 CI 重新生成环境和锁定文件

python build_tools/update_environments_and_lock_files.py

停滞的拉取请求#

由于贡献功能可能是一个漫长的过程,一些拉取请求可能看起来不活跃但尚未完成。在这种情况下,接管它们是对项目的一项重大服务。接管的良好礼仪是

  • 确定 PR 是否停滞

    • 如果一个拉取请求已被我们确定为其他贡献者的候选,它可能带有“stalled”或“help wanted”标签。

    • 要判断一个不活跃的 PR 是否停滞,请询问贡献者她/他是否计划在近期继续处理该 PR。如果在 2 周内未收到任何能推动 PR 前进的活动,则表明该 PR 已停滞,并将导致该 PR 被标记为“help wanted”。

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

      冲刺结束后,将向冲刺参与者传达冲刺期间打开的未合并 PR 的跟进情况,并且这些 PR 将被标记为“sprint”。带有“sprint”标签的 PR 可以由冲刺负责人重新分配或宣布为停滞。

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

停滞和未认领的问题#

一般来说,可供认领的问题将带有 “help wanted” 标签。然而,并非所有需要贡献者的问题都会带有此标签,因为“help wanted”标签并非始终与问题的状态保持最新。贡献者可以使用以下指南找到仍可认领的问题

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

    • 检查是否有链接的拉取请求

    • 检查对话,看看是否有人表示他们正在创建拉取请求

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

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

新贡献者问题#

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

  • “Good first issue” 标签

    开始为 scikit-learn 贡献的一个好方法是从问题追踪器中的“good first issue”列表中选择一个项。解决这些问题使您无需太多先验知识即可开始为项目做出贡献。如果您已经为 scikit-learn 做出贡献,则应改为查看“Easy”问题。

  • “Easy” 标签

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

  • “Help wanted” 标签

    我们通常使用“help wanted”标签来标记问题,无论其难度如何。此外,我们使用“help wanted”标签来标记已被其原始贡献者放弃且可供其他人从原始贡献者离开的地方接手的拉取请求。带有“help wanted”标签的问题列表可以在此处找到。请注意,并非所有需要贡献者的问题都会带有此标签。

文档#

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

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

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

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

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

编写文档字符串的指南#
  • 您可以使用 pytest 来测试文档字符串,例如,假设 RandomForestClassifier 的文档字符串已被修改,以下命令将测试其文档字符串的合规性

    pytest --doctest-modules sklearn/ensemble/_forest.py -k RandomForestClassifier
    
  • 正确的章节顺序是:Parameters(参数)、Returns(返回值)、See Also(另请参阅)、Notes(注释)、Examples(示例)。有关其他可能章节的信息,请参阅numpydoc 文档

  • 在编写参数和属性文档时,以下是一些格式良好的示例

    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
    

    总的来说,请记住以下几点

    • 使用 Python 基本类型。(bool 而不是 boolean

    • 使用括号定义形状:array-like of shape (n_samples,)array-like of shape (n_samples, n_features)

    • 对于具有多个选项的字符串,使用方括号:input: {'log', 'squared', 'multinomial'}

    • 1D 或 2D 数据可以是 {array-like, ndarray, sparse matrix, dataframe} 的子集。请注意,array-like 也可以是 list,而 ndarray 明确仅指 numpy.ndarray

    • 当使用“类框架”特征(例如列名)时,请指定 dataframe

    • 指定列表的数据类型时,使用 of 作为分隔符:list of int。当参数支持提供形状和/或数据类型详细信息的数组以及此类数组列表时,您可以使用 array-like of shape (n_samples,) or list of such arrays 之一。

    • 指定 ndarray 的 dtype 时,在定义形状后使用例如 dtype=np.int32ndarray of shape (n_samples,), dtype=np.int32。您可以将多个 dtype 指定为集合:array-like of shape (n_samples,), dtype={np.float64, np.float32}。如果想提及任意精度,请使用 integralfloating 而不是 Python 的 dtype intfloat。当同时支持 intfloating 时,无需指定 dtype。

    • 当默认值为 None 时,只需在末尾使用 default=None 指定 None。请务必在文档字符串中说明参数或属性为 None 意味着什么。

  • 在文档字符串中添加“See Also”以引用相关的类/函数。

  • 文档字符串中的“See Also”应每行一个引用,并带有一个冒号和解释,例如

    See Also
    --------
    SelectKBest : Select features based on the k highest scores.
    SelectFpr : Select features based on a false positive rate test.
    
  • “Notes”部分是可选的。它旨在提供有关函数/类/类方法/方法的特定行为的信息。

  • 也可以将 Note 添加到属性,但在这种情况下需要使用 .. rubric:: Note 指令。

  • 在“示例”部分添加一两个代码片段,以展示其用法。代码应可直接运行,即应包含所有必需的导入。本节应尽可能简洁。

编写用户指南和其他 reStructuredText 文档的指南#

在数学和算法细节之间保持良好平衡,并向读者提供算法功能的直观理解,这一点很重要。

  • 首先,用简明扼要、概括性的语言解释算法/代码对数据的作用。

  • 突出显示此功能的实用性及其推荐应用。如果可用,请考虑包含算法的复杂度(\(O\left(g\left(n\right)\right)\)),因为“经验法则”可能非常依赖于机器。只有当这些复杂度不可用时,才可提供经验法则。

  • 包含一个相关图(从示例生成)以提供直观理解。

  • 包含一到两个简短的代码示例来演示该功能的使用。

  • 引入任何必要的数学方程,后跟参考文献。通过推迟数学方面的内容,文档对于主要对理解功能实际影响而不是其底层机制感兴趣的用户来说更容易访问。

  • 编辑 reStructuredText (.rst) 文件时,请尽可能将行长度保持在 88 个字符以下(例外情况包括链接和表格)。

  • 在 scikit-learn 的 reStructuredText 文件中,文本周围的单反引号和双反引号都将渲染为行内字面量(通常用于代码,例如 list)。这是由于我们设置的特定配置。现在应该使用单反引号。

  • 信息过多会使用户难以访问他们感兴趣的内容。请使用下拉菜单,采用以下语法将其分解

    .. dropdown:: Dropdown title
    
      Dropdown content.
    

    上述代码片段将生成以下下拉菜单

  • 可以使用下拉菜单默认隐藏的信息包括

    • 低层级部分,例如 References(参考文献)、Properties(属性)等(例如,参见检测误差权衡 (DET) 中的子部分);

    • 深入的数学细节;

    • 特定用例的叙述;

    • 总的来说,可能只对那些希望超越特定工具实用性之外的用户感兴趣的叙述。

  • 请勿对低层级部分 Examples 使用下拉菜单,因为它应该对所有用户保持可见。请确保 Examples 部分紧随主讨论之后,中间折叠部分尽可能少。

  • 请注意,下拉菜单会破坏交叉引用。如果合理,请将引用与提及它的文本一起隐藏。否则,请勿使用下拉菜单。

编写参考文献的指南#
  • 当书目参考文献带有 arxiv数字对象标识符 (DOI) 识别号时,请使用 sphinx 指令 :arxiv::doi:。例如,请参阅谱聚类图中的参考文献。

  • 对于文档字符串中的“References”部分,请参见sklearn.metrics.silhouette_score 作为示例。

  • 要交叉引用 scikit-learn 文档中的其他页面,请使用 reStructuredText 交叉引用语法

    • 章节:要链接到文档中的任意章节,请使用引用标签(请参阅Sphinx 文档)。例如

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

      您不应修改现有的 Sphinx 引用标签,因为这会破坏现有的交叉引用以及指向 scikit-learn 文档中特定章节的外部链接。

    • 词汇表:链接到常用术语和 API 元素词汇表中的术语

      :term:`cross_validation`
      
    • 函数:要链接到函数的文档,请使用函数的完整导入路径

      :func:`~sklearn.model_selection.cross_val_score`
      

      但是,如果文档中您上方有 .. currentmodule:: 指令,则只需使用指定当前模块之后的函数路径。例如

      .. currentmodule:: sklearn.model_selection
      
      :func:`cross_val_score`
      
    • 类:要链接到类的文档,请使用类的完整导入路径,除非文档上方有 .. currentmodule:: 指令(参见上文)

      :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

这将运行所有示例,这需要一些时间。您也可以根据文件名只运行少量示例。以下是一种运行文件名中包含 plot_calibration 的所有示例的方法

EXAMPLES_PATTERN="plot_calibration" make html

对于更高级的用例,您可以使用正则表达式。

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

make latexpdf

Sphinx 版本

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

GitHub Actions 上生成的文档#

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

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

测试和提高测试覆盖率#

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

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

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

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

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

  1. 运行 pytest --cov sklearn /path/to/tests。输出会列出每个文件中未测试的行号。

  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:

发生了明显不应该发生的事情。估计器产生错误结果和意外错误的情况归于此处。

增强:

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

文档:

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

新功能:

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

还有另外四个标签可以帮助新贡献者

Good first issue:

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

Easy:

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

Moderate:

可能需要一些机器学习或包的知识,但对于项目新手来说仍然易于上手。

Help wanted:

此标签标记了目前缺少贡献者或需要其他贡献者接管工作的 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 添加到 doc/api_reference.py 文件中的 API_REFERENCE 中,以反映API 参考中的更改。

弃用一个属性或方法

如果要弃用一个属性或方法,请在该属性上使用装饰器 deprecated。请注意,如果存在 property 装饰器,deprecated 装饰器应放置在其之前,以便文档字符串能够正确渲染。例如,将属性 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”,以便贡献者知道可以不处理;

    • 在后续的拉取请求中跟进,出于礼貌,你可能需要告知原始贡献者。

  • 不要仓促行事,花时间让你的评论清晰明了,并为你的建议提供充分理由。

  • 你是项目的外在形象。每个人都会有不顺心的时候,在这种情况下你值得休息一下:尽量放慢速度并保持离线。

阅读现有代码库#

阅读和消化现有代码库始终是一项艰巨的任务,需要时间和经验才能掌握。尽管我们通常努力编写简洁的代码,但考虑到项目的庞大规模,理解代码在开始时可能让人感到不知所措。以下是一些可能有助于使这项任务变得更轻松、更快速的提示(排名不分先后)。

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

  • 在深入阅读函数/类的代码之前,先通读文档字符串,尝试了解每个参数/属性的作用。停下来思考一分钟“如果我必须自己完成,我会怎么做?”也可能会有所帮助。

  • 最棘手的事情通常是识别代码的哪些部分是相关的,哪些不是。在 scikit-learn 中,大量的输入检查都会执行,尤其是在 fit 方法的开头。有时,代码中只有很小一部分在做实际工作。例如,查看 LinearRegressionfit 方法,你可能正在寻找的仅仅是对 scipy.linalg.lstsq 的调用,但它被埋藏在多行输入检查和不同类型参数的处理中。

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

  • 有时,阅读给定功能的测试会让你了解其预期目的。你可以使用 git grep(见下文)来查找为某个功能编写的所有测试。特定功能/类的大多数测试都放在模块的 tests/ 文件夹下。

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

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

  • 掌握你的工具。

    • 对于这样一个大型项目,高效使用你喜欢的编辑器或 IDE 对于消化代码库大有帮助。能够快速跳转(或查看)到函数/类/属性定义非常有帮助。能够快速查看给定名称在文件中何处使用也同样有用。

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

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

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

    在 black 的文档中了解更多关于避免破坏 git blame 的信息