Skip to content

Latest commit

 

History

History
1203 lines (799 loc) · 91.8 KB

README_zh-CN.md

File metadata and controls

1203 lines (799 loc) · 91.8 KB

深度学习调优手册

这不是 Google 官方支持的文档。

Varun Godbole, George E. Dahl, Justin Gilmer, Christopher J. Shallue, Zachary Nado

† Google Research, Brain Team

‡ Harvard University

目录

这份手册是为谁准备的?

这份手册适用于对提升深度学习模型的性能感兴趣的工程师和研究人员(包括个人和团队)。阅读这份手册需要具备机器学习和深度学习的基本知识。

这份手册的重点是超参数调整,也涉及到了深度学习训练的其他方面,例如流水线实现和优化,但手册并不会在这些方面做详细阐述。

我们假设机器学习问题是一个监督学习问题或者与之相似的问题(例如自监督学习)。所以本文档中的一些建议可能也适用于其他类型的问题。

为什么需要这份调优手册?

目前,深度神经网络如同一个黑箱,想要在实践中获得良好性能,需要付出大量的努力和猜测。更糟糕的是,很少有人来记录各种实践中能让深度学习获得良好性能的具体方法。论文为了呈现一个比较清晰的故事,通常不会详细阐述调优的具体过程。工业界的机器学习工程师很少有时间来总结他们的调优过程。虽然教科书的作者在实践工作中有丰富的经验,也能提供有用的经验,但是在撰书时他们倾向于阐述基本概念而不是实践指导。在准备撰写这份手册时,我们没有找到任何比较全面的工作来试图阐述如何让深度学习获得良好性能。然而,我们找到的只是发在博客文章和社交媒体上的小建议、从论文附录中偷学的小技巧、关于某一个特定项目或流水线的个例研究等等。关于深度学习调优的资料非常混乱。这一现象使得深度学习专家和普通从业者在使用非常类似的方法时取得的结果却相差甚远。与此同时,这些专家也乐意承认他们所做的一些事情可能没有充分的理由。随着深度学习的成熟并对世界产生重要影响,深度学习社区需要更多涵盖有用方法的资源,包括对于获得良好结果至关重要的所有实用细节。

我们是一个由五名研究人员和工程师组成的团队,已在深度学习领域工作多年,最早的甚至在 2006 年就开始涉足相关工作。我们已经将深度学习应用于从语音识别到天文学等各个领域的研究中,并在此过程中学到了很多东西。本文档来自于我们自己训练神经网络、教授新机器学习工程师以及为同事提供深度学习实践建议的经验。虽然很高兴看到深度学习能从只有少数学术实验室采用的机器学习方法发展为数十亿人使用的技术驱动产品,但它作为一门工程学科仍处于起步阶段。我们希望本文档能鼓励更多的人为该领域实验方案的系统化做出贡献。

这份文档来源于我们构建自己深度学习方法的实践过程,因此它仅代表了我们在撰写本文时的观点,而不是任何客观规律。我们自己在调参时也非常挣扎,所以调参成了本手册的特别重点。同时手册也涵盖了我们在工作中遇到的其他重要问题。我们想让本手册成为一份能够随着我们观点的更新而持续完善的文档。举个例子,关于调试和避免训练失败的材料,在两年前是无法成稿的,因为它是基于最近的结果和正在进行的调查撰写的。所以手册中的一些建议需要根据新的成果和工作流不断更新。只有在社区开始记录和讨论各种的方案之后,我们才有可能探索出最佳的深度学习秘诀。为此,我们鼓励在手册中发现问题的读者提出替代建议和令人信服的证据,以便我们完善本手册。我们也希望看到能有不同建议的手册出现,让深度学习社区作为一个整体不断发展和完善。最后,我们会对标有🤖表情符号的章节做更多深入的研究。在尝试编写这份手册之后,我们才发现在深度学习从业者的工作流程中有这么多有趣且容易被忽视的问题。

开始新项目的指南

在调优过程中的许多抉择,我们可以在项目开始时一次性做出决定。只有偶尔在情况发生变化时,才需要重新考虑。

下面的指南中基于以下假设:

  • 问题制定、数据清理等基本工作已经完成,花时间在模型架构和训练配置上是有意义的。
  • 已经有一个流水线设置来进行训练和评估,并且可以很容易地为各种感兴趣的模型执行训练和预测工作。
  • 已选择并实现适当的评估指标。这些指标应该尽可能地代表在部署环境中测量的内容。

选择模型架构

概括: 在开始一个新项目时,尝试使用已经有效的模型。

  • 首先选择一个完善且常用的模型架构来开始工作。之后再构建自定义模型也来得及。
  • 模型架构通常具有各种超参数,用于确定模型的大小和其他细节(例如层数、层宽度、激活函数类型)。
  • 如果可以的话,请尝试找到一篇尽可能接近手头问题的相关论文,并将复现该论文中的模型作为开始。

选择优化器

概括: 从最针对手头问题的常用优化器开始。

  • 在所有类型的机器学习问题和模型架构中,没有“最好的”优化器这一说法。甚至仅比较优化器的性能就是一项艰巨的任务。🤖
  • 我们建议始终从成熟、常用的优化器开始着手,尤其是在开始新项目时。
    • 理想情况下,选择用于同一类型问题的最常用的优化器。
  • 所选优化器的 *全部* 超参数都需要关注。
    • 具有更多超参数的优化器可能需要更多的调整工作才能找到最佳配置。
    • 当我们试图找到各种其他超参数(例如架构超参数)的最佳值时需要将优化器超参数视为冗余参数,这在项目的初始阶段尤其重要。
    • 在项目的初始阶段,从更简单的优化器开始会更可取(例如,具有固定动量的 SGD 或具有固定 $\epsilon$、$\beta_{1}$ 和 $\beta_{2}$ 的 Adam ) ,之后可以切换到更通用的优化器。
  • 我们喜欢的较为完善的优化器包括(但不限于):

选择批量大小

概括: 批量大小决定了训练速度,并且不应该被直接用于调整验证集性能。通常来说,可用硬件支持的最大批量大小是较为理想的数值。

  • 批量大小是决定训练时间计算资源消耗的关键因素。
  • 增加批量大小通常会减少训练时间。这非常有益,因为它:
    • 能使固定时间间隔内超参数调整更彻底,最终训练出更好的模型。
    • 减少开发周期的延迟,能更多地测试新想法。
  • 批量大小和资源消耗并没有明确的关系,增加批量大小让资源消耗增加、减少或是保持不变都有可能。
  • 批量大小不应该被当作验证集性能的可调超参数。

确定可行的批量大小并估计训练吞吐量

[点击展开]
  • 对于给定的模型和优化器,可用硬件通常能能够支持一系列批量大小。限制因素通常是加速器(GPU/TPU 等)的内存。
  • 不幸的是,如果不运行或者编译完整的训练程序,就很难计算出适合内存的批量大小。
  • 最简单的解决方案通常是以不同的批次大小(例如,用 2 的幂来尝试)运行少量的训练实验,直到其中一个实验超过可用内存。
  • 对于每个批量大小,我们应该训练足够长的时间以准确估计训练吞吐量

训练吞吐量 =(# 每秒处理的样本数量)

或者,等效地,每步时间

每步时间 =(批量大小)/(训练吞吐量)

  • 当加速器尚未饱和时,如果批量大小加倍,训练吞吐量也应该加倍(或至少接近加倍)。等效地,随着批量大小的增加,每步的时间应该是恒定的(或至少接近恒定的)。
  • 如果与上述情况不符,那么训练流水线可能存在瓶颈,例如 I/O 或计算节点间的同步。有必要在开始下一步前对此进行诊断和矫正。
  • 如果训练吞吐量到某个批量大小之后就不再增加,那么我们只考虑使用该批量大小(即使硬件支持更大的批量大小)
    • 使用更大批量大小的所有好处都假定训练吞吐量增加。如果没有,请修复瓶颈或使用较小的批量大小。
    • 梯度积累模拟比硬件可以支持的更大的批量大小,因此不提供任何吞吐量优势。在应用工作中通常应避免使用它。
  • 每次更改模型或优化器时,可能都需要重复这些步骤(例如,不同的模型架构可能允许更大的批次大小适合内存)。

选择批量大小以最小化训练时间

[点击展开]

训练时间 =(每步时间)x(总步长)

  • 对于所有可行的批量大小,我们通常可以认为每步的时间近似恒定。当没有来自并行计算的开销并且所有训练瓶颈都已被诊断和纠正时,这是正确的(有关如何识别训练瓶颈的信息,请参阅上一节)。实际上,增加批量大小通常至少会产生一些开销。
  • 随着批量大小的增加,达到固定性能目标所需的总步长通常会减少(前提是在更改批量大小时重新调整所有相关超参数;Shallue et al. 2018)。
    • 例如,将批量大小加倍可能会使所需的总步骤数减半。这称为完美缩放
    • 完美缩放适用于所有批次大小直至临界批次大小,超过该批次大小将实现收益递减。
    • 最终,增加批量大小不再减少训练步骤的数量(但永远不会增加)。
  • 因此,最小化训练时间的批量大小通常是最大的批量大小,仍然可以减少所需的训练步骤数。
    • 这个批量大小取决于数据集、模型和优化器,除了通过实验为每个新问题找到它之外,如何计算它是一个悬而未决的问题。🤖
    • 比较批量大小时,请注意示例预算/epoch预算(运行所有实验,同时固定训练示例演示文稿的数量)和步长预算(运行具有训练步骤数的所有实验)之间的区别固定的)。
      • 将批量大小与 epoch 预算进行比较只会探索完美的扩展机制,即使更大的批量大小仍可能通过减少所需的训练步骤数来提供有意义的加速。
    • 通常,可用硬件支持的最大批大小将小于关键批大小。因此,一个好的经验法则(不运行任何实验)是使用尽可能大的批量大小。
  • 如果最终增加了训练时间,那么使用更大的批量大小就没有意义了。

选择批量大小以最小化资源消耗

[点击展开]
  • 有两种类型的资源成本与增加批量大小相关:
    1. 预付费用,例如购买新硬件或重写训练流水线以实现多 GPU/多 TPU 训练。
    2. 使用成本,例如根据团队的资源预算计费,从云提供商处计费,电力/维护成本。
  • 如果增加批量大小的前期成本很高,那么最好推迟增加批量大小,直到项目成熟并且更容易评估成本效益权衡。实现多主机并行训练程序可以引入 Bug微妙的问题 因此无论如何从更简单的流水线开始可能更好。(另一方面,在需要大量调整实验的过程早期,训练时间的大幅加速可能非常有益)。
  • 我们将总的使用成本(可能包括多种不同的成本)称为“资源消耗”。我们可以将资源消耗分解为以下组件:

资源消耗 =(每步资源消耗)x(总步长)

  • 增加批量大小通常允许我们减少总步长。资源消耗是增加还是减少将取决于每步消耗的变化情况。
    • 增加批量大小可能减少资源消耗。例如,如果具有较大批量大小的每个步骤都可以在与较小批量大小相同的硬件上运行(每个步骤的时间只增加一点点),那么每个步骤资源消耗的增加可能会被减少所抵消在步长中。
    • 增加批量大小可能不变资源消耗。例如,如果将批量大小加倍,所需的步长减半,并且使用的 GPU 数量加倍,则总消耗(以 GPU 小时数表示)不会改变。
    • 增加批量大小可能增加资源消耗。例如,如果增加批大小需要升级硬件,则每步消耗的增加可能会超过步长的减少。

更改批量大小需要重新调整大多数超参数

[点击展开]
  • 大多数超参数的最佳值对批量大小敏感。因此,更改批量大小通常需要重新开始调整过程。
  • 与批量大小交互最强烈的超参数,因此对于每个批量大小单独调整最重要的是优化器超参数(例如学习率、动量)和正则化超参数。
  • 在项目开始时选择批量大小时请记住这一点。如果您以后需要切换到不同的批量大小,则为新的批量大小重新调整所有内容可能会很困难、耗时且成本高昂。

批量规范如何与批量大小交互

[点击展开]
  • Batch norm 很复杂,一般来说,应该使用与梯度计算不同的 batch size 来计算统计数据。有关详细讨论,请参阅批量规范部分

选择初始配置

  • 在开始超参数调整之前,我们必须确定起点。这包括指定 (1) 模型配置(例如层数),(2) 优化器超参数(例如学习率),以及 (3) 训练步骤数。
  • 确定此初始配置将需要一些手动配置的训练运行和反复实验。
  • 我们的指导原则是找到一个简单、相对快速、资源消耗相对较低的配置,以获得“合理”的结果。
    • “简单”意味着尽可能避免花里胡哨的东西;这些总是可以在以后添加。即使花里胡哨的东西在未来被证明是有用的,但在初始配置中添加它们可能会浪费时间调整无用的功能和/或烘烤不必要的并发症。
      • 例如,在添加花哨的衰减时间表之前以恒定的学习率开始。
    • 选择快速且消耗最少资源的初始配置将使超参数调整更加高效。
      • 例如,从较小的模型开始。
    • “合理”性能取决于问题,但至少意味着经过训练的模型在验证集上的性能比随机机会好得多(尽管它可能很糟糕,不值得部署)。
  • 选择训练步骤的数量涉及平衡以下张力:
    • 一方面,训练更多的步骤可以提高性能并使超参数调整更容易(参见 Shallue et al. 2018)。
    • 另一方面,更少步骤的训练意味着每次训练运行得更快并且使用更少的资源,通过减少周期之间的时间并允许并行运行更多实验来提高调整效率。此外,如果一开始选择了一个不必要的大步预算,那么以后可能很难改变它,例如,一旦学习率计划针对该步长进行了调整。

提高模型性能的科学方法

就本文档而言,机器学习开发的最终目标是最大化模型的效用。尽管不同应用场景的开发流程有所不同(例如时间长度、可用计算资源、模型类型等),基本步骤和原则都是相似的。

接下来的指南中我们做出了这些假设:

  • 已有能运行且得到不错结果的训练流水线。
  • 有足够的计算资源来进行调参实验,至少能够并行发起数个训练流程。

增量调整策略

概括: 从简单的配置开始,循序渐进,同时进一步了解问题。确保任何改进都有据可循,以避免增加不必要的复杂度。

  • 我们的最终目标是找到一种训练配置来最大化我们模型的性能。
    • 在某些情况下,我们的目标是在固定截止日期(例如竞赛提交期限)之前最大限度地改进模型。
    • 在其他情况下,我们希望无限期地改进模型(例如,不断改进生产中使用的模型)。
  • 原则上,我们可以使用算法自动搜索整个配置空间来最大化性能,但实践中这往往不实际。
    • 配置空间可能非常大,目前还没有任何算法可以在没有人工指导的情况下有效地搜索这个空间。
  • 大多数自动搜索算法依赖于人工设计的搜索空间,这些搜索空间往往非常重要。
  • 更有效的方法是从简单的配置开始,逐步添加功能并进行改进,同时深化对问题的理解。
    • 我们在每一轮调整中都使用自动搜索算法,并随着我们理解的深入不断更新我们的搜索空间。
  • 随着我们的探索,我们自然会找到越来越好的配置,因此我们的“最佳”模型将不断改进。
    • 当我们更新我们的最佳配置时,我们称之为上线(这不一定对应线上模型的实际上线)。
    • 对于每次上线,我们必须确保更改是有据可循的——而不仅仅是碰运气找到的配置——以避免给训练流水线增加不必要的复杂性。

综上所述,我们的增量调优策略需要重复以下四个步骤:

  1. 为下一轮实验确定适当的目标。
  2. 设计并展开实验,朝着这个目标取得进展。
  3. 从实验结果中获取经验。
  4. 考虑是否上线新的最佳配置。

本节的其余部分将更详细地讲解增量调优策略。

探索与利用

概括: 大多数时候,我们的目标是更深入地理解问题。

  • 尽管有些人认为我们会花大部分时间来提升验证集的指标,实际上我们把重心放在进一步理解问题上,而不是降低验证集错误率。
    • 也就是说,我们大部分时间都花在了“探索”上,只有一小部分时间花在了“利用”上。
  • 从长远来看,如果我们想最大化我们的最终效果,深入理解问题是至关重要的。将深化理解置于短期收益之上可以帮助我们:
    • 避免仅因历史原因而表现良好的不必要更改。
    • 确定验证集效果对哪些超参数最敏感,哪些超参数交互最多,因此需要一起重新调整,以及哪些超参数对其他变化相对不敏感,因此可以在未来的实验中固定住。
    • 发现潜在的新方向,例如在出现过拟合问题时使用新的正则化器。
    • 确定无效的方向并将其删除,从而降低后续实验的复杂度。
    • 判断超参数的优化空间是否已经饱和。
    • 围绕最佳值缩小我们的搜索空间,以提高调整效率。
  • 最终,我们可以集中提升验证集效果,即便我们无法从新的实验中进一步了解问题的结构了。

选择下一轮实验的目标

概括: 每轮实验都应该有一个明确的目标,并且范围要足够小,这样实验才能真正朝着目标取得进展。

  • 每轮实验都应该有一个明确的目标,并且范围要足够小,这样实验才能真正朝着目标取得进展:如果我们试图一次添加多个特征或回答多个问题,我们可能无法理清各自的影响。
  • 举个例子,目标可以包括:
    • 尝试对训练流程进行改进(例如,新的正则化器、预处理方法等)。
    • 了解特定模型超参数(例如激活函数)的影响
    • 最大化验证集指标。

设计下一轮实验

概括: 根据实验目标,将超参数分为三类:目标超参数、冗余超参数和固定超参数。创建一系列研究以比较目标超参数的不同值,同时优化冗余超参数。注意选择冗余超参数的搜索空间,以平衡资源成本与科学价值。

识别目标超参数、冗余超参数和固定超参数

[点击展开]
  • 对于给定的目标,所有超参数都将是目标超参数冗余超参数固定超参数
    • 目标超参数是指,我们希望测量出其对于模型由何种影响的参数。
    • 冗余超参数是指,必须优化才能公平比较不同目标超参数值的参数。类似于统计中的冗余参数
    • 固定超参数是指,在当前轮次实验中取固定值的参数。在比较目标超参数的不同值时,固定超参数的值不需要(或者我们不希望它们)改变。
      • 因为实验固定了某些超参数,从实验得出的结论可能对固定超参数的其他值无效。换句话说,固定的超参数对我们的实验结论做了限定。
  • 举个例子,如果我们的目标是“确定更深的模型是否会减少验证集错误”,那么模型层数就是目标超参数。
    • 学习率是一个冗余超参数,如果我们要公平对比不同深度的模型,我们必须分别调整学习率(通常情况下最优学习率和模型结构有关)。
    • 激活函数是一个固定超参数。我们可能通过过去的实验发现最优激活函数和模型深度无关。或者我们接受实验得到的最优深度的仅在某个激活函数上有效。或者我们也可以将激活函数作为一个冗余超参数和深度一起调优。
  • 一个超参数是目标超参数、冗余超参数还是固定超参数是根据实验目标来决定的。
    • 比如,激活函数的选择可以是一个目标超参数(对于当前问题,ReLU 或 tanh 是更好的选择吗?),一个冗余超参数(允许使用不同的激活函数,最好的 5 层模型是否优于最好的 6 层模型?),或一个固定超参数(对于一个由 ReLU 构成的网络,在特定位置添加批标准化是否有帮助?)。
  • 在设计新一轮实验时,我们根据实验目的确定目标超参数。
    • 在此阶段,我们将所有其他超参数视为冗余超参数。
  • 接下来,我们将一些冗余超参数转作为固定超参数。
    • 如果有无限的计算资源,我们会将所有非目标超参数保留为冗余超参数,这样我们从实验中得出的结论就不会受到固定超参数的限定。
    • 然而,冗余超参数越多,我们没能充分针对每个目标超参数调优冗余超参数的风险就越高,从而我们从实验中得出错误结论的风险也越高。
      • 下文所述,我们可以通过增加计算资源来应对这种风险,但通常我们的最大资源预算低于调整所有非目标超参数所需的计算资源。
    • 当我们判断将一个冗余超参数转换为固定超参数所带来的限制少于调优它所需的计算资源时,我们可以进行这种转换。
      • 一个冗余超参数和目标超参数的相互影响越多,固定这个参数所带来的限制就越多。例如,权重衰减强度的最佳值通常取决于模型大小,因此固定权重衰减的强度来比较不同的模型大小,往往得不出有效的结论。
  • 尽管超参数的类型取决于实验目标,但对于某些类别的超参数,我们有以下经验法则:
    • 在各种优化器超参数(例如学习率、动量、学习率调度参数、Adam 优化器的 beta 等)中,至少有一些是冗余超参数,因为它们往往与其他变化相互影响。
      • 它们很少是目标超参数,因为像“训练流程的最佳学习率是多少?”这样的目标没有什么意义——最优学习率很容易随着下一次训练流程的改变而改变。
      • 尽管当资源有限或有强力的证据表明它们不影响目标超参数时,我们可能固定其中一些参数,但通常应该假设优化器超参数必须单独调整,以在不同设置之间进行公平比较目标超参数。
        • 此外,我们没有优化器超参数值的先验倾向(例如,它们通常不会以任何方式影响前向传递或梯度的计算成本)。
    • 相比之下,优化器的选择通常是一个目标超参数或固定超参数。
      • 如果我们的实验目标涉及在两个或多个不同的优化器之间进行公平比较(例如“确定哪个优化器在给定的步骤数中产生最低的验证错误”),那么它就是一个目标超参数。
      • 或者,我们可能出于各种原因将其设为固定超参数,包括(1)先前的实验表明最好的优化器和当前的目标超参数无关;(2)当前优化器的训练曲线更容易理解 (3) 当前优化器比其他优化器使用更少的内存。
    • 正则化技术引入的超参数通常是冗余超参数,但是否使用正则化技术往往是目标或固定超参数。
      • 例如,dropout 增加了代码的复杂性,因此在决定是否包含它时,我们会将“no dropout”与“dropout”作为一个目标超参数,而将 dropout 率作为一个冗余超参数。
        • 如果我们决定根据这个实验将 dropout 添加到我们的训练流程中,那么在未来的实验中,dropout 率将是一个冗余超参数。
    • 模型结构超参数通常是目标或固定超参数,因为模型结构变化会影响服务和训练成本、延迟和内存需求。
      • 例如,网络层数通常是一个目标或固定的超参数,因为它往往会对训练速度和内存使用产生巨大影响。
  • 在某些情况下,一个超参数是冗余还是固定超参数将取决于科学超参数的值。
    • 例如,假设我们想知道 Nesterov momentum 和 Adam 中哪个优化器的验证错误率更低。目标超参数是 optimizer,它的值是 {"Nesterov_momentum", "Adam"}。值 optimizer="Nesterov_momentum" 引入了冗余/固定超参数 {learning_rate, momentum},但值 optimizer="Adam" 引入了冗余/固定超参数 {learning_rate, beta1, beta2, epsilon}
    • 仅针对目标超参数的某些值存在的超参数称为条件超参数
    • 我们不应该仅仅因为两个条件超参数具有相同的名称就认为它们是相同的!在上面的示例中, learning_rate 对于 optimizer="Nesterov_momentum"optimizer="Adam"不同的条件超参数. 它在两种算法中的作用相似(尽管不完全相同),但在每个优化器中运行良好的值范围通常相差几个数量级。

创建一组研究

[点击展开]
  • 确定了目标和冗余超参数之后,我们会设计一个“研究”或一系列研究,以朝着实验目标取得进展。
    • 一项研究指定了一组要运行的超参数配置以供后续分析。每个配置称为“实验”。
    • 创建研究通常涉及几个方面:选择实验所需的超参数变量,选择这些超参数的取值范围(“搜索空间”),选择实验次数,以及选择自动搜索算法。或者,我们可以通过手动指定一组超参数配置来创建研究。
  • 研究的目的是使用目标超参数的不同值运行训练流程,同时**“优化掉”**(或“优化”)冗余超参数,以便公平的比较不同的目标超参数值。
  • 在最简单的情况下,我们将对目标超参数的每个配置进行单独研究,其中每个研究都会调整冗余超参数。
    • 例如,如果我们的目标是从 Nesterov momentum 和 Adam 中选择最佳优化器,我们可以创建一个研究,并设置 optimizer="Nesterov_momentum" 和冗余超参数为 {learning_rate, momentum}。然后创建另一项研究,并设置 optimizer="Adam" 和冗余超参数为 {learning_rate, beta1, beta2, epsilon}。最后通过比较两个研究中的最优实验来比较这两个优化器。
    • 我们可以使用任何无梯度优化算法,包括贝叶斯优化或进化算法等方法,来优化冗余超参数。但是,在探索阶段我们更偏向使用准随机算法,因为这些方法在探索阶段有多种优势。在探索结束后,我们会优先选择最先进的贝叶斯优化方法。
  • 在更复杂的情况下,我们想要比较大量目标超参数的值,并且我们无法进行大量相对独立的研究,我们可以将目标超参数与冗余超参数放在相同的搜索空间中,并使用搜索算法在单个研究中对两种超参数的值进行采样。
    • 采用这种方法时,条件超参数可能会是一个问题,因为很难指定对应的搜索空间,除非所有目标超参数值都有相同的冗余超参数集。
    • 在这种情况下,我们更加偏好使用准随机搜索算法,因为它确保我们能相对均匀的采样目标超参数值。无论搜索算法如何,我们都需要确保它均匀搜索目标超参数。

平衡实验的信息量和成本

[点击展开]
  • 在设计一项研究或一系列研究时,我们需要分配有限的预算,以充分满足以下三个要求:
    1. 比较足够多目标超参数的不同值。
    2. 在足够大的搜索空间上调整冗余超参数。
    3. 对冗余超参数的搜索空间进行足够密集的采样。
  • 这三个必要条件,确保我们能从实验中获得足够多的经验。
    • 尽可能多地比较目标超参数的值可以拓宽我们从实验中获得的经验的范围。
    • 包括尽可能多的有冗余超参数,并允许每个冗余超参数有尽可能大的值域。这样我们更有信心相信,对于一个目标超参数配置,在当前搜索空间中,存在“好的”冗余参数。
      • 否则,我们可能因为没有搜索某些冗余超参数空间,而对不同目标超参数值进行了不公平的比较。
    • 尽可能密集地对冗余超参数采样,也能让我们更加有信心相信搜索流程能够找到好的冗余超参数配置。
      • 否则,我们可能对不同目标超参数值进行了不公平的比较。因为某些值恰好有更好的冗余超参数配置。
  • 不幸的是,对这三个要求的任何一个进行改进都需要增加实验次数,从而增加资源成本。或者,想办法降低其他要求,以节约资源。
    • 每个问题都有自己的特性和计算资源限制,因此如何在这三个需求之间分配资源需要一定程度的领域专业知识。
    • 在进行一项研究后,我们都会总结该研究是否将冗余超参数调整得足够好(即充分搜索了足够大的空间)以公平地比较目标超参数(更详细的描述如下)。

从实验结果中获取经验

概括: 在实现每组实验的原始目标之外,还要检查一系列其他问题,如果发现问题,可以重新实验。

  • 归根到底,每组实验都有一个特定的目标,我们需要评估实验是否提供了足够多的证据。
    • 然而,如果我们提出的问题是正确的,我们通常会发现,需要解决一些前置问题才能朝着原目标前进。
      • 如果我们回避这些问题,我们可能会得出错误的结论。
    • 由于运行实验的成本很高,我们还希望从每组实验中提取其他有用的见解,即使这些见解与当前目标并不直接相关。
  • 在分析实验以朝着最初的目标取得进展之前,我们应该问自己以下额外的问题:
    • 搜索空间够大吗?
      • 如果实验配置的最佳点在一维或多维搜索空间的边界附近,则搜索范围可能不够广。在这种情况下,我们应该扩展搜索空间并重新实验。
    • 我们是否从搜索空间中采样了足够多的点?
      • 如果不是,增加采样点或者适当降低搜索预期。
    • 每项研究中有多少实验是不可行的(即实验出现训练不收敛、损失值极差,或者因为违反某些隐含约束而根本无法运行)?
      • 当研究中很大一部分点是不可行的时,我们应该尝试调整搜索空间以避免对这些点进行采样,必要时重新参数化搜索空间。
      • 在某些情况下,大量不可行点可能表示训练代码中存在错误。
    • 模型是否存在优化问题?
    • 我们可以从最佳实验的训练曲线中学到什么?
      • 例如,最佳实验的训练曲线是否表现出过拟合?
  • 根据上述问题的回答,改进最近的研究(或研究组)以提升搜索空间、开启更多实验,或采取其他一些纠正措施。
  • 一旦我们回答了上述问题,我们就可以继续评估实验为我们最初的目标提供了多少证据(例如,评估变更是否有效)。

识别不好的搜索空间边界

[点击展开]
  • 如果最佳配置点的值接近搜索空间的边界,那么搜索空间可能是有问题的。通过朝着最佳配置的方向延展搜索空间,我们或许可以找到一个更好的配置。
  • 为了检查搜索空间边界,我们会在基本超参数轴图中绘制已完成的实验。图表示了超参数值(如学习率)和验证集指标的关系。图中的每一个点代表一次实验。
    • 通常情况下,验证集指标取一次实验训练过程中达到的最优值。

Example of bad search space boundaries Example of good search space boundaries

图 1:不良搜索空间边界和可接受的搜索空间边界示例。

  • 图 1展示了错误率(越低越好)与初始学习率的关系。
  • 如果最佳点聚集在搜索空间的边缘(在某个维度上),则可能需要扩展搜索空间边界,直到最佳点不再靠近边界。
  • 通常,一项研究将包括“不可行”的实验,这些实验训练无法收敛或得到非常糟糕的结果(在上图中用红色 X 标记)。

没有在搜索空间中采样足够的点

[点击展开]
  • 一般来说,较难知道搜索空间的采样足够密集。🤖
  • 运行更多的实验当然更好,但代价是显而易见的。
  • 由于很难知道我们什么时候采样足够密集,我们通常会使用我们负担的起的采样率,并尝试通过反复查看各种超参数轴图来校准我们的直觉,并了解有多少点处于搜索空间中效果“较好”的区域。

检查训练曲线

[点击展开]

概括: 检查训练曲线是发现常见问题的有效方法,可以帮助我们确定下一步要采取的行动和优先级。

  • 虽然在许多情况下,我们实验的主要目标只需要考虑每次实验的验证错误率,但是将每次实验简化成一个数字是不够的,我们可能因此而忽略表面之下的重要细节。
  • 对于每项研究,我们至少查看所有最佳实验的训练曲线(训练或者验证错误率与训练轮数的关系)。
  • 即使这不是达到实验的主要目标所必需的,检查训练曲线是发现常见问题的有效方法,可以帮助我们确定下一步要采取的行动和优先级。
  • 在检查训练曲线时,我们可以关心如下问题。
  • 是否有任何实验过拟合?
    • 当验证错误在训练期间的某个时刻开始增加时,就是过拟合。
    • 当我们为每个目标超参数选择“最优”冗余超参数值的时候,我们至少应该检查每个最优目标超参数值是否有过拟合的问题。
      • 如果任何最佳实验出现过拟合问题,我们通常希望在比较目标超参数的值之前使用额外的正则化技术重新运行实验,或更好地调整现有的正则化参数。
        • 如果目标超参数包括正则化参数,这可能不适用,因为如果这些正则化参数的低强度设置导致过拟合,也就不足为奇了。
      • 使用常见的正则化方法减少过度拟合通常很简单,这些方法不怎么增加代码复杂性或计算量(例如,dropout、标签平滑、权重衰减),因此在下一轮实验中添加这些正则化方法通常是可以接受的。
      • 例如,如果目标超参数是“层数”,并且使用最大层数的最优实验出现了过拟合,我们通常会增加正则化方法重新实验,而不是直接选择层数更少的配置。
      • 即使“最佳”实验都没有表现出有过拟合,但是其余任何实验出现了过拟合问题,那么可能还是存在问题。
        • 选择最佳实验这种手段,会抑制出现过拟合问题的配置,偏向无问题的配置。换句话说,这个手段会选择正则化更强的配置。
        • 然而,任何让训练变得更糟的配置都会有正则化的效果,即使这不是配置的本意。例如,选择较小的学习率可以通过放慢优化过程来正则化训练,但我们通常不以这种标准来选择学习率。
        • 因此,我们必须意识到,目标超参数的最佳配置可能是通过选择一些“坏”的参数值来达到的。
  • 训练后期的训练或验证错误率方差高吗?
    • 如果是这样,我们可能难以比较不同目标超参数值的好坏(因为每次实验都可能结束在”运气较好“或者”运气不好“的轮次中)。因此我们也难以在生产环境中复现最佳实验(因为线上模型可能不会在”运气较好“的轮次中训练完成)。
    • 不同轮次之间方差较大的最可能原因是训练批次方差(源自训练集采样)、小验证集以及在训练后期使用过高的学习率。
    • 可能的补救措施包括增加批次大小、获取更多验证数据、使用学习率衰减或使用 Polyak 平均。
  • 训练结束时实验是否仍在改进?
  • 训练集和验证集的性能在最后的训练步骤之前很久就饱和了吗?
  • 虽然我们不能一一列举,但还有许多其他问题可以通过检查训练曲线来发现(例如,训练期间的训练损失增加通常表示训练流水线有问题)。

使用隔离图检测变更是否有效

[点击展开]

Isolation plot that investigates the best value of weight decay for ResNet-50 trained on ImageNet.

图 2:研究在 ImageNet 上训练的 ResNet-50 的最佳权重衰减值的隔离图。

  • 通常,一组实验的目标是比较目标超参数的不同值。
    • 例如,我们可能想要确定取得最佳验证错误率的权重衰减值。
  • 隔离图是基本超参数轴图的特例。隔离图上的每个点都对应于最好实验在一些(或所有)冗余超参数上的表现。
    • 换句话说,我们绘制了“优化掉”冗余超参数后的模型性能。
  • 隔离图可以更轻松地在目标超参数的不同值之间进行比较。
  • 例如,图 2揭示了权重衰减的值,该值可为在 ImageNet 上训练的 ResNet-50 的特定配置产生最佳验证性能。
    • 如果我们的目标是确定是否完全包括权重衰减,那么我们会将此图中的最佳点与没有权重衰减的基线进行比较。为了公平比较,基线的学习率也应该进行调优。
  • 当我们有(准)随机搜索生成的数据并,并且想要绘制连续超参数的隔离图时,我们可以通过对基本超参数轴图的 x 轴值进行分桶并在每个垂直切片中进行最佳实验来近似隔离图。

将制图自动化

[点击展开]
  • 绘图越困难,我们看得就越少。所以我们应该尽可能将绘图自动化。
  • 我们至少应该为实验中用到的超参数自动生成基本超参数轴图。
  • 此外,我们会自动为所有实验生成训练曲线,并确保我们能够很轻松的找到每次研究的最佳实验和对应实验的训练曲线。
  • 上面描述的图表是很好的起点。但除此之外,还有许多其他有用的图表和可视化方法。就像杰弗里 · 辛顿 (Geoffrey Hinton) 说的,“每当你绘制新图表时,都能学到一些新知识。”

确定是否采用训练流水线更改或超参数配置

概括: 在决定是否更改我们的模型、训练流程或采用新的超参数配置时,我们需要了解导致不同结果的根因。

  • 当我们迭代模型时,我们可能会发现与现有配置相比,某个候选最初有更好的验证错误率,但在重复实验后并没有持续的优势。大致上,我们可以将导致这种不一致的最重要原因分为以下几大类:
    • 培训流程差异重新训练差异实验差异:源自同样超参数配置下,使用不同随机种子带来的差异。
      • 例如,不同的随机初始化、训练数据打乱、随机失活、数据扩充操作的模式以及并行计算操作的顺序,都是实验差异的潜在来源。
    • 超参数搜索差异研究差异:由我们选择超参数的流程引起的差异。
      • 例如,我们可能会在特定搜索空间运行相同的实验,但使用两个不同的种子进行准随机搜索,并最终选择不同的超参数值。
    • 数据收集和抽样差异:随机拆分训练、验证和测试数据带来的差异。或更普遍的,训练数据生成过程带来的差异。
  • 我们可以使用严格的统计测试在有限验证集上估算验证错误率,但通常实验差异本身就可以在使用相同超参数设置的两个不同训练之间产生统计显著差异。
  • 当我们试图得出超出超参数空间中单个点水平的结论时,我们最关心的是研究差异。
    • 研究差异取决于实验次数和搜索空间,我们遇到过它大于实验方差的情况以及它小得多的情况。
  • 因此,在采用候选更改之前,请考虑将最佳实验运行 N 次以了解每次运行的实验差异。
    • 通常,我们可以仅在对训练流水线进行了重大变更后再测量实验差异,但在某些应用程序中,我们可能需要更频繁的估计实验差异。
    • 在其他应用程序中,测量实验差异的成本太高所以不值得做。
  • 归根结底,虽然我们只想采用真正产生改进了的变更(包括新的超参数配置),但要完全肯定一个变更是有效的也并不可取。
  • 因此,如果一个新的超参数点(或其他变化)得到比基线更好的结果(尽可能考虑新点和基线的再训练差异),那么我们可能应该采用它作为新的基线。
    • 但是,我们应该只采用效果增益大于复杂度提升的变更。

探索结束后

概括: 一旦我们完成了对搜索空间的探索并决定了应该调整哪些超参数,贝叶斯优化工具是一个很好的选择。

  • 我们的优先级总有一天会从更多了解参数调优问题转变到找到一个最佳的配置上。。
  • 这时,我们应该已经有了一个包含着已知最佳配置的搜索空间。
  • 我们的探索工作应该已经揭示了最重要的超参数(以及它们的合理范围),我们可以使用这些超参数来构建搜索空间,并使用尽可能大的计算预算进行最终的自动搜参研究。
  • 由于我们已经不需要更进一步了解调优问题本身了,许多准随机搜索的优点不再适用,应该使用贝叶斯优化工具来自动找到最佳超参数配置。
    • 如果搜索空间包含大量发散点(获得 NaN 训练损失或什至训练损失比均值差很多标准差的点),使用黑盒优化工具来正确处理发散实验很重要(请参阅具有未知约束的贝叶斯优化是处理此问题的绝佳方法)。
  • 此时,我们还应该考虑检查测试集上的性能。
    • 原则上,我们甚至可以将验证集放到训练集中,并重新训练通过贝叶斯优化找到的最佳配置。但是,这仅适用于未来不会再次上线的情况(例如,Kaggle 竞赛)。

确定每次训练运行的步长

  • 有两种类型的工作负载:计算密集型和非计算密集型工作负载。
  • 当训练为受计算限制时,训练受限于我们愿意等待的时间,而不是受我们拥有多少训练数据或其他因素的限制。
    • 在这种情况下,如果我们能够以某种方式训练更长时间或更有效,我们应该会看到更低的训练损失,并且通过适当的调整,可以改善验证损失。
    • 换句话说,加速中训练等同于改善训练,“最佳”训练时间始终是“只要我们负担得起”。
    • 也就是说,仅仅因为工作负载受计算限制并不意味着更长/更快的训练是改善结果的唯一途径。
  • 当训练为不受计算限制时,我们可以负担得起训练的时间,只要我们愿意,并且在某些时候,训练更长的时间并没有多大帮助(甚至会导致有问题的过度拟合)。
    • 在这种情况下,我们应该期望能够训练到非常低的训练损失,训练时间更长可能会略微减少训练损失,但不会显着减少验证损失。
    • 特别是当训练不受计算限制时,更慷慨的训练时间预算可以使调整更容易,特别是在调整学习率衰减计划时,因为它们与训练预算有特别强的相互作用。
      • 换句话说,非常吝啬的训练时间预算可能需要将学习率衰减计划调整到完美,以实现良好的错误率。
  • 无论给定的工作负载是否受计算限制,增加梯度方差(跨批次)的方法通常会导致训练进度变慢,因此可能会增加达到特定验证损失所需的训练步骤数。高梯度方差可能由以下原因引起:
    • 使用较小的批量大小
    • 添加数据扩充
    • 添加一些类型的正则化(例如 dropout)

当训练受不是计算限制时决定训练多长时间

  • 我们的主要目标是确保我们训练足够长的时间以使模型达到最佳结果,同时避免在训练步骤的数量上过度浪费。
  • 如有疑问,宁可延长训练时间。假设追溯(最佳)检查点选择被正确使用并且检查点足够频繁,那么在训练时间更长时性能永远不会降低。
  • 切勿在研究中调整 max_train_steps 数字。选择一个值并将其用于所有试验。从这些试验中,绘制回顾性检查点选择找到的训练步骤,以优化 max_train_steps 的选择。
    • 例如,如果最佳步骤总是在训练的前 10% 期间,那么最大步骤数就太高了。
    • 或者,如果最佳步骤始终出现在最后 25% 的训练中,我们可能会受益于更长时间的训练和重新调整衰减时间表。
  • 当架构或数据发生变化(例如添加数据扩充)时,训练步骤的理想数量可能会发生变化。
  • 下面我们将描述如何根据使用恒定学习率“完美拟合”训练集所需的步长,为 max_train_steps 选择初始候选值。
    • 请注意,我们并没有以精确或数学上明确定义的方式使用短语“完全适合训练集”。它只是一个非正式的描述符,表示非常低的训练损失。
      • 例如,当使用对数损失进行训练时,缺少正则化项,我们可能会看到训练损失一直在缓慢改善,直到我们达到浮点数限制,因为网络权重无限制地增长并且模型对训练集的预测变得越来越有信心。在这种情况下,我们可以说模型在错误分类误差在训练集上达到零时“完全适合”训练集。
    • 如果训练过程中的梯度噪声量增加,我们发现 max_train_steps 的起始值可能需要增加。
      • 例如,如果将数据增强或正则化器(如 dropout)引入模型。
    • 如果训练过程有所改进,则可能会减少 max_train_steps
      • 例如,使用更好调整的优化器或更好调整的学习率计划。

使用学习率扫描为 max_train_steps 选择初始候选者的算法

[点击展开]
  • 此过程假设不仅可以“完美”地拟合训练集,而且可以使用恒定的学习率计划来实现。
  • 如果可以完美地拟合整个训练集,则必须存在一个完美地拟合训练集的配置(具有 max_train_steps 的某个值);找到任何此类配置并使用其值 max_train_steps 作为起点 N
  • 在没有数据增强和正则化的情况下运行恒定的学习率扫描(即网格搜索学习率),其中每个试验训练 N 步骤。
  • 扫描中最快的试验达到完美训练性能所需的步长是我们对 max_train_steps 的初步猜测。
  • **笔记:**错误的搜索空间可能会导致自欺欺人。
    • 例如,如果一项研究中的所有学习率都太小,我们可能会错误地得出结论认为需要非常大的 max_train_steps 值。
    • 至少,我们应该检查研究中的最佳学习率是否不在搜索空间的边界。

在训练计算资源受限的情况下决定训练步长

  • 在某些情况下,训练损失会无限期地改善,而我们的耐心和计算资源成为限制因素。
  • 如果训练损失(或什至验证损失)无限期地改善,我们是否应该在我们负担得起的情况下一直训练?不必要。
    • 我们可以通过运行大量较短的实验并为我们希望推出的模型保留最长的“生产长度”运行来更有效地进行调整。
    • 随着试验的训练时间接近我们的耐心极限,调优实验与我们潜在的发射候选者变得更加相关,但我们可以完成的实验更少。
    • 仅训练约 10% 的生产长度时,我们可能可以回答很多问题,但始终存在这样的风险,即我们在这个时间限制下的结论不适用于生产长度的 20% 的实验,更不用说 100% 了.
  • 随着每次试验训练步长限制的增加,在多轮中进行调整是一种明智的方法。
    • 我们可以想做多少轮就做多少轮,但通常 1-3 轮是最实用的。
    • 从本质上讲,尝试使用具有非常快的周转时间的试验来尽可能多地了解问题,权衡调整彻底性与最终最长运行的相关性。
    • 一旦给定的每次试验时间限制产生了有用的见解,我们就可以增加训练时间并继续调整,根据需要从较短的运行中仔细检查我们的结论。
  • 作为起点,我们建议进行两轮调整:
    • 第 1 轮:较短的运行时间以找到好的模型和优化器超参数。
    • 第 2 轮:很少在良好的超参数点上长时间运行以获得最终模型。
  • Round iRound i+1 的最大问题是如何调整学习率衰减时间表。
    • 在轮次之间调整学习率计划时,一个常见的陷阱是使用所有额外的训练步骤而学习率太小。

第 1 轮

[点击展开]
  • 不幸的是,当训练长度显着增加时,不能保证在短的、不完整的训练中找到的好的超参数仍然是好的选择。然而,对于某些类型的超参数,它们通常具有足够的相关性,因此第 1 轮非常有用。
  • 我们希望将在较短运行中发现的哪些超参数值转移到较长的训练运行中?对于所有这些,我们需要更多的研究。但根据我们目前所知道的,以下是作者的猜测,按照转移概率的递减顺序:
    • 极有可能转移
      • 早期训练的不稳定性可以在第一轮调整中使用较少的训练步骤来解决。也许这些超参数是我们拥有的最接近确定转移赌注的东西。
        • 热身时长
        • 初始化
    • 可能转移
      • 模型架构——模型架构的戏剧性胜利通常会转移,但可能有很多反例。
    • 可能会转移
      • 优化算法/优化器超参数——我们认为这会“松散地”迁移。绝对比上面的东西要弱。
      • 数据扩充
      • 正则化
        • 如果不可能完美地拟合训练集,则模型可能处于正则化不太可能有太大帮助的状态。
    • 不太可能转移
      • 学习率时间表:不太可能完美迁移。
        • This paper表明即使是延迟调度传输,但我们不认为这在一般情况下是正确的。示例:在小 # 个训练步骤上调整 sqrt 衰减然后扩展到大 # 将导致大部分训练以过小的步骤进行。
          • 在极端培训预算的限制下,大多数计划可能会做得“足够好”,但如果对其进行调整,则可能会看到明显的性能改进。
        • 了解随机元优化中的短期偏差描述了尝试短视地选择学习率的危险。

第二轮

[点击展开]
  • 运行第 1 轮的最佳超参数配置。
  • **(推测)**🤖使用额外的步骤来延长高学习率的训练时间。
    • 例如,如果线性计划则保持衰减的长度从第 1 轮开始固定,并在开始时延长恒定 lr 的周期。
    • 对于余弦衰减,只需保留第 1 轮的基础 lr 并像 Chinchilla 论文 一样扩展 max_train_steps
  • 对于拥有非常成熟的建模和调整流水线以及非常长且昂贵的生产培训运行的团队来说,更多轮次可能是有意义的,但他们往往会矫枉过正。
    • 我们已经描述了如何从步骤 1 →步骤 2 进行转换。如果我们不关心分析时间并且如果高效使用计算是最重要的问题,那么理想的做法是成倍增加训练运行的长度(并且因此完成一项研究的端到端时间)在许多不同轮次的调整中。
      • 在每一轮中,我们都会系统地确保我们的选择继续有效。
      • 新想法通过从步骤 i 到步骤 i+1 的运行时间越来越长的实验逐渐降低风险的流水线。

训练流水线的额外指南

优化输入流水线

概括: 输入绑定流水线的原因和干预是高度任务依赖的;使用探查器并寻找常见问题。

  • 使用适当的探查器来诊断输入绑定流水线。例如,Perfetto 用于 JAX 或 TensorFlow profiler用于 TensorFlow。
  • 最终,具体原因和干预措施将高度依赖于任务。更广泛的工程考虑(例如,最小化磁盘占用空间)可能会导致更差的输入流水线性能。
  • 常见原因:
    • 数据未与训练过程共置,导致 I/O 延迟(这可能发生在通过网络读取训练数据时)。
    • 昂贵的在线数据预处理(考虑离线并保存一次)。
    • 干扰数据流水线预取的意外同步障碍。例如,在 CommonLoopUtils (链接) 中同步设备和主机之间的指标时。
  • 常用技巧:
    • 用于预取示例的仪器输入流水线(例如 tf.data.Dataset.prefetch
    • 尽早从流水线中删除未使用的功能/元数据。
    • 增加为输入流水线生成示例的作业数量的复制。例如,通过使用 tf.data 服务

评估模型性能

概括: 以比训练更大的批量运行评估。以固定的步长间隔而不是固定的时间间隔运行评估。

评估设置

[点击展开]
  • 我们可以在多种设置中评估模型的性能。
    • 在线测评 - 当模型在生产环境中提供预测服务时收集指标。
    • 离线评估 - 当模型在代表生产环境的离线训练/验证/测试集上运行时收集指标。
    • 定期评估 - 在模型训练期间收集的指标可能是离线评估的代理,和/或离线评估中使用的数据子集。
  • 在线评估是黄金标准,但在模型开发阶段通常不切实际。
  • 根据问题的不同,离线评估可能相当复杂且计算量大。
  • 定期评估是最实用、最经济的选择,但可能无法完全代表生产环境。
    • 我们在定期评估期间的目标是使用离线评估的权宜之计,而不牺牲我们在训练期间获得的信号的可靠性。

设置定期评估

[点击展开]
  • 我们在训练期间运行定期评估以实时监控其进度,促进回顾性模型检查点选择,这样我们就可以检查训练结束时的训练曲线
  • 最简单的配置是在同一个计算实例中同时执行训练和定期评估,定期在训练和评估之间交替。
    • 在这种情况下,用于执行评估的批量大小应至少与用于训练的批量大小一样大,因为在评估期间不需要维护模型激活,从而降低了每个示例的计算要求.
  • 定期评估应该以固定的步长间隔而不是时间间隔进行。
    • 基于时间间隔进行评估会使解释训练曲线变得更加困难,尤其是当训练可能会受到训练作业抢占、网络延迟问题等的影响时。
  • 有效/测试指标的周期性(当使用混洗训练/验证/测试拆分时)可以指示实现错误,例如测试数据与训练数据重叠,或训练数据未正确混洗。以固定的步骤间隔进行评估可以使这些问题更容易发现。
  • 当评估集不能被批次大小整除时,可能会出现部分批次。确保正确权衡填充的示例,以防止损失函数受到它们的影响。通常,这些填充示例的权重可以为零。
  • 每次评估保存足够的信息以支持离线分析。理想情况下,我们会保存对选定的单个示例的预测,因为它们对于调试来说非常宝贵。
    • 生成像保存模型这样的工件可以在评估工作完成后轻松进行临时模型检查。

选择样本进行定期评估

[点击展开]
  • 定期评估作业的运行速度可能不够快,无法在合理的时间内计算完整离线评估集的指标。这通常需要抽样数据进行定期评估。
  • 在构建样本数据集时,我们考虑以下因素:
    • 样本量
      • 检查在周期性作业使用的采样数据集上计算的性能是否与整个离线评估集的性能相匹配,即采样集和完整数据集之间没有偏差。
      • 用于定期评估的数据集应该足够小,以便可以轻松生成整体模型预测,但也应该足够大,以便可以准确测量模型的改进(即不会被标签噪声淹没)。
      • 它应该足够大,可以按顺序在试验中容纳多个此类评估,并且仍然可以产生准确的估计。也就是说,为了避免随着时间的推移自适应地“适应”验证集,这种方式不会推广到保留的测试集。然而,这种考虑很少是一个实际问题。
    • 不平衡的数据集
      • 对于不平衡的数据集,稀有类别示例的性能通常会很嘈杂。
      • 对于类标签中包含少量示例的数据集,记录正确预测的示例数量,以更深入地了解准确性改进(.05 灵敏度改进听起来令人兴奋,但它只是一个正确的示例吗?)。

保存检查点并回溯选择最佳检查点

概括: 运行固定步长的训练,并回顾性地从运行中选择最佳检查点。

  • 大多数深度学习框架都支持模型检查点。也就是说,模型的当前状态会定期保存在磁盘上。这允许训练作业对计算实例中断具有弹性。
  • 最佳检查点通常不是最后一个检查点,尤其是当验证集性能不会随时间持续增加而是围绕特定值波动时。
  • 设置流水线以跟踪到目前为止在训练期间看到的 N 个最佳检查点。在训练结束时,模型选择就是选择训练期间看到的最佳检查点。我们称之为回顾性最佳检查点选择
  • 支持预期提前停止通常是不必要的,因为我们预先指定了试验预算并保留了迄今为止看到的 N 个最佳检查点。

设置实验跟踪

概括: 在跟踪不同的实验时,请务必注意一些要点,例如研究中检查点的最佳性能以及研究的简短描述。

  • 我们发现,在电子表格中跟踪实验结果有助于我们解决各种建模问题。它通常有以下列:
    • 研究名称
    • 指向研究配置存储位置的链接。
    • 研究的注释或简短描述。
    • 试运行次数
    • 研究中最佳检查点在验证集上的表现。
    • 关于启动培训所必需的未提交更改的特定复制命令或注释。
  • 找到一个跟踪系统,该系统至少可以捕获上面列出的信息,并且对执行此操作的人来说很方便。未跟踪的实验也可能不存在。

批量归一化实现细节

概括: 现在 batch norm 通常可以用 LayerNorm 代替,但在不能代替的情况下,更改批处理大小或主机数量时会出现一些棘手的细节。

  • Batch norm 使用当前批次的均值和方差对激活进行归一化,但在多设备设置中,除非明确同步,否则这些统计数据在每个设备上都是不同的。
  • 轶事报告(主要在 ImageNet 上)说仅使用约 64 个示例计算这些规范化统计数据实际上在实践中效果更好(请参阅this paper的 Ghost Batch Norm)。
  • 将总批大小与用于计算批归一化统计的示例数量分离对于批大小比较特别有用。
  • Ghost batch norm 实现并不总能正确处理每台设备的批处理大小 > 虚拟批处理大小的情况。在这种情况下,我们实际上需要在每个设备上对批次进行二次抽样,以获得适当数量的批次规范统计示例。
  • 测试模式 batch norm 中使用的指数移动平均只是训练统计数据的线性组合,因此这些 EMA 只需要在将它们保存在检查点之前进行同步。但是,批归一化的一些常见实现不同步这些 EMA,只保存来自第一个设备的 EMA。

多主机流水线的注意事项

概括: 对于日志记录、评估、RNG、检查点和数据分片,多主机训练可以很容易地引入错误!

  • 确保流水线仅在一台主机上记录和检查点。
  • 确保在运行评估或检查点之前,批处理规范统计信息在主机之间同步。
  • 具有跨主机相同的 RNG 种子(用于模型初始化)和跨主机不同的种子(用于数据改组/预处理)至关重要,因此请确保适当地标记它们。
  • 通常建议跨主机分片数据文件以提高性能。

常见问题

最好的学习率衰减时间表系列是什么?

[点击展开]
  • 这是一个悬而未决的问题。目前尚不清楚如何构建一组严格的实验来自信地回答“最佳”LR 衰减时间表是什么。
  • 虽然我们不知道最好的时间表系列,但我们相信拥有一些(非恒定的)时间表很重要并且调整它很重要。
  • 在优化过程中,不同的学习率在不同的时间效果最好。有某种时间表可以使模型更有可能达到良好的学习率。

我应该使用哪种学习率衰减作为默认值?

[点击展开]
  • 我们的偏好是线性衰减或余弦衰减,其他一些时间表系列可能也不错。

为什么有些论文有复杂的学习率表?

[点击展开]
  • 具有复杂的分段学习率 (LR) 衰减时间表的论文并不少见。
  • 读者常常想知道作者是如何得出如此复杂的研究结果的。
  • 许多复杂的 LR 衰减时间表是根据验证集性能以临时方式调整时间表的结果:
    1. 使用一些简单的 LR 衰减(或恒定学习率)开始单次训练运行。
    2. 继续训练,直到表现似乎停滞不前。如果发生这种情况,请暂停训练。从这一点开始,使用可能更陡峭的 LR 衰减时间表(或更小的恒定学习率)恢复它。重复此过程,直到会议/发布截止日期。
  • 愉快地复制生成的日程通常不是一个好主意,因为最佳的特定时间表将对许多其他超参数选择敏感。
    • 最好复制生成时间表的算法,尽管在​​人为判断生成时间表时这几乎不可能。
  • 如果这种类型的验证错误敏感计划可以完全自动化,则可以很好地使用,但作为验证错误函数的人在循环计划是脆弱的并且不容易复现,因此我们建议避免使用它们。
    • 在发布使用此类时间表的结果之前,请尝试使其完全可复现。

Adam 的超参数应该如何调整?

[点击展开]
  • 如上所述,对搜索空间以及应该从搜索空间中采样多少点做出一般性陈述是非常困难的。请注意,并非 Adam 中的所有超参数都同等重要。以下经验法则对应于研究中试验次数的不同“预算”。
    • 如果一项研究中有 < 10 次试验,则只调整(基础)学习率。
    • 如果 10-25 次试验,调整学习率和$\beta_1$。
    • 如果超过 25 次试验,调整学习率,$\beta_1$和$\epsilon$。
    • 如果可以运行超过 25 次试验,则另外调整$\beta_2$。

为什么在优化的探索阶段使用准随机搜索而不是更复杂的黑盒优化算法?

[点击展开]
  • 准随机搜索(基于低差异序列)是我们在用作迭代调优过程的一部分时优于更高级的黑盒优化工具,旨在最大限度地洞察调优问题(我们称之为“探索阶段”)。贝叶斯优化和类似工具更适合开发阶段。
  • 基于随机移动的低差异序列的准随机搜索可以被认为是“抖动的、打乱的网格搜索”,因为它统一但随机地探索给定的搜索空间,并比随机搜索更分散搜索点。
  • 与更复杂的黑盒优化工具(例如贝叶斯优化、进化算法)相比,准随机搜索的优势包括:
    1. 非自适应地对搜索空间进行采样可以在不重新运行实验的情况下更改事后分析中的调整目标。
      • 例如,我们通常希望根据训练中任何一点的验证误差找到最佳试验。但准随机搜索的非自适应特性使得无需重新运行任何实验即可根据最终验证误差、训练误差或某些替代评估指标找到最佳试验成为可能。
    2. 准随机搜索以一致且统计上可复现的方式运行。
      • 即使搜索算法的实现发生变化,也应该可以复现六个月前的研究,只要它保持相同的均匀性。如果使用复杂的贝叶斯优化软件,实现可能会在版本之间发生重大变化,从而使旧搜索更难复现。回滚到旧的实现并不总是可能的(例如,如果优化工具作为服务运行)。
    3. 它对搜索空间的统一探索使得对结果以及它们可能对搜索空间提出的建议的推理变得更容易。
      • 例如,如果准随机搜索遍历中的最佳点位于搜索空间的边界,这是一个很好(但不是万无一失)的信号,表明应该更改搜索空间边界。这个部分更深入。然而,自适应黑盒优化算法可能会因为一些不幸的早期试验而忽略了搜索空间的中间部分,即使它恰好包含同样好的点,因为正是这种不均匀性正是一个好的优化算法所需要的雇用加快搜索。
    4. 与自适应算法不同,在使用准随机搜索(或其他非自适应搜索算法)时,并行运行与顺序运行不同数量的试验不会产生统计上不同的结果。
    5. 更复杂的搜索算法可能并不总能正确处理不可行的点,特别是如果它们在设计时未考虑神经网络超参数调整。
    6. 准随机搜索很简单,并且在许多调整试验将并行运行时特别有效。
      • 有趣的是 [^3],自适应算法很难击败预算是其两倍的准随机搜索,尤其是当许多试验需要并行运行时(因此很少有机会利用先前的启动新试验时的试验结果)。
      • 如果没有贝叶斯优化和其他高级黑盒优化方法方面的专业知识,我们可能无法获得它们原则上能够提供的好处。在现实的深度学习调整条件下,很难对高级黑盒优化算法进行基准测试。它们是当前研究中非常活跃的领域,对于没有经验的用户来说,更复杂的算法也有其自身的缺陷。这些方法的专家能够获得良好的结果,但在高并行条件下,搜索空间和预算往往更为重要。
  • 也就是说,如果我们的计算资源只允许少量试验并行运行,而我们有能力按顺序运行许多试验,那么贝叶斯优化就会变得更有吸引力,尽管这会使我们的调整结果更难解释。

[^3]:Ben Recht 和 Kevin Jamieson

在哪里可以找到准随机搜索的实现?

[点击展开]

需要多少次试验才能通过准随机搜索获得好的结果?

[点击展开]

A box plot showing the importance of sampling enough

图 3: ResNet-50 在 ImageNet 上进行了 100 次试验调整。通过自举,模拟了不同数量的调整预算。上面绘制了每个试验预算的最佳性能的箱线图。

  • 这个问题没有办法笼统地回答,但是我们可以看具体的例子。
  • 如图 3 所示,研究中的试验次数会对结果产生重大影响。
    • 请注意,当对 6 个试验进行抽样时,与对 20 个试验进行抽样时的四分位间距有多大。
    • 即使进行了 20 次试验,特别幸运和不幸的研究之间的差异也可能大于使用固定超参数在不同随机种子上重新训练该模型之间的典型差异,对于此工作负载可能约为 +/- 0.1% 的验证错误率为 ~23%。

如何调试和缓解优化失败?

[点击展开]

概括: 如果模型遇到优化困难,那么在尝试其他事情之前解决这些问题很重要。诊断和纠正培训失败是一个活跃的研究领域。

Changing the strides in a single residual block in a WideResnet results in training instability.

图 4:在 WideResnet 中更改单个残差块 (2x2 -> 1x1) 中的步幅会导致训练不稳定。这不会降低低学习率下的性能,但由于不稳定,高学习率不再能很好地训练。应用 1000 步的学习率预热解决了这种特殊的不稳定情况,允许以 0.1 的最大学习率进行稳定训练。

识别不稳定的工作负载

  • 如果学习率太大,任何工作负载都会变得不稳定。只有当它迫使我们使用太小的学习率时,不稳定才是一个问题。
  • 至少有两种类型的训练不稳定性值得区分:
    1. 初始化/训练早期不稳定。
    2. 训练中途突然不稳定。
  • 我们可以采用系统的方法来识别工作负载中的稳定性问题。
    1. 做一个学习率扫描,找到最好的学习率 lr*。
    2. 为刚好高于 lr* 的学习率绘制训练损失曲线。
    3. 如果学习率 > lr* 显示损失不稳定性(损失在训练期间上升而不下降),那么修复不稳定性可能会导致更好的训练。
  • 在训练过程中记录全损失梯度的 L2 范数,异常值会导致训练过程中的虚假不稳定性。这可以告知如何选择渐变/更新剪辑。

**笔记:**一些模型在早期表现出不稳定,随后出现恢复,导致缓慢但稳定的训练。可能会因为常用的评估频率不够高而发现不了这些问题!

为了检查这一点,我们可以使用 lr = 2 * current best 训练仅 ~500 步的简短运行,但评估每一步。

Illustration of the value of more frequent evaluations at the start of training.

图 5:训练开始时更频繁评估的价值说明。如果怀疑模型受到早期训练不稳定的影响,则很有用。

常见不稳定模式的潜在修复

  • 应用学习率预热
    • 最适合早期训练不稳定。
  • 应用渐变剪裁
    • 对早期和中期训练不稳定都有好处,可能会修复一些热身无法解决的问题。
  • 尝试新的优化器
    • 有时 Adam 可以处理 Momentum 无法处理的不稳定性。这是一个活跃的研究领域。
  • 我们可以确保我们正在为我们的模型架构(下面的示例)使用最佳实践/初始化。
    • 如果模型尚未包含剩余连接和规范化,则添加它。
  • 归一化应该是残差之前的最后一个操作。例如 x + Norm(f(x))。
  • Norm(x + f(x)) 已知会导致问题。
  • 尝试将剩余分支初始化为 0(例如重新调零初始化)。
  • 降低学习率
    • 这是最后的手段。

学习率预热

An example of instability during a warmup period (note the horizontal axis log scale).

图 6:预热期间不稳定的示例(注意水平轴对数刻度)。在这种情况下,成功训练需要 40k 步的热身。

何时应用学习率预热

Axis plot for model with instability

图 7a:展示训练不稳定性的模型的超参数轴图示例。最佳学习率处于可行的边缘。“不可行”试验被定义为产生 NaN 或异常高的损失值的试验。

Loss curve for model with instability

图 7b:以我们看到的不稳定学习率训练的模型的训练损失。

  • 图 7a 显示了一个超参数轴图,该图表明模型正在经历优化不稳定性,因为最佳学习率恰好位于不稳定的边缘。
  • 图 7b 显示了如何通过检查以比该峰值大 5 倍或 10 倍的学习率训练的模型的训练损失来双重检查。如果该图显示损失在稳步下降后突然上升(例如上图中的步长 ~10k),则该模型可能存在优化不稳定性。
如何应用学习率预热

Beneficial effect of warmup on training instabilities

图 8:学习率预热对解决训练不稳定性的有益影响。

  • 使用上面的部分,我们假设从业者已经确定了模型变得不稳定的学习率。这是 unstable_base_learning_rate
  • 热身涉及预先安排一个学习率计划,将学习率从 0 提高到某个稳定的 base_learning_rate,这至少比 unstable_base_learning_rate 大一个数量级。默认设置是尝试 base_learning_rate,即 10x unstable_base_learning_rate。尽管请注意,对于类似 100x unstable_base_learning_rate 的情况,可以再次运行整个过程。具体日程为:
    • 从 0 上升到 base_learning_rate 超过。
    • 以恒定速率训练 post_warmup_steps
  • 我们的目标是找到最短的 warmup_steps 数量,使我们能够获得比 unstable_base_learning_rate 高得多的峰值学习率。
  • 因此,对于每个 base_learning_rate,我们需要调整 warmup_stepspost_warmup_steps。通常将 post_warmup_steps 设置为 2*warmup_steps 就可以了。
  • 预热可以独立于现有的衰减时间表进行调整。 warmup_steps 应该以几个不同的数量级扫描。例如,一个示例研究可以尝试 [10, 103, 104, 105]。最大可行点不应超过的 10%。
  • 一旦建立了不会破坏 base_learning_rate 训练的 warmup_steps,就应该将其应用于基线模型。本质上,我们将这个时间表添加到现有时间表上,并使用上面讨论的最佳检查点选择来将这个实验与基线进行比较。例如,如果我们最初有 10,000 个 max_train_steps 并且执行了 warmup_steps 1000 步,那么新的训练程序应该总共运行 11,000 步。
  • 如果稳定训练需要长 warmup_stepsmax_train_steps 的 5%),则可能需要增加 max_train_steps 来解决这个问题.
  • 在整个工作负载范围内并没有真正的“典型”值。有些模型只需要 100 步,而其他模型(尤其是变压器)可能需要 40k+。

渐变剪裁

Gradient clipping on early training instabilities

图 9:梯度裁剪纠正早期训练不稳定性的图示。

  • 当出现大的或离群的梯度问题时,梯度裁剪最有用。
  • 裁剪可以修复早期训练的不稳定性(早期的大梯度范数),或中期训练的不稳定性(训练中期突然的梯度尖峰)。
  • 有时,较长的预热期可以纠正削波不能纠正的不稳定性:请参阅上面这一段
    • 🤖在预热期间剪裁怎么办?
  • 理想的剪辑阈值刚好高于“典型”梯度范数。
  • 下面是如何进行渐变裁剪的示例:
    • 如果梯度范数$\left | g \right |$大于梯度裁剪阈值$\lambda$,则执行${g}'= \lambda \times \frac{g}{\left | g \right |}$ where ${g}'$是新的渐变。
  • 在训练期间记录未剪切的梯度范数。默认生成:
    • 梯度范数与步长的关系图
    • 在所有步骤上聚合的梯度范数的直方图
  • 根据梯度范数的第 90 个百分位数选择梯度裁剪阈值。
    • 阈值将取决于工作负载,但 90% 是一个很好的起点。如果它不起作用,则可以调整此阈值。
    • 🤖某种适应性策略怎么样?
  • 如果我们尝试梯度裁剪并且不稳定问题仍然存在,我们可以更努力地尝试(即使阈值更小)。
  • 极端激进的梯度裁剪本质上是一种降低学习率的奇怪方式。如果我们发现自己使用了非常激进的裁剪,我们可能应该只降低学习率。
  • 我们通常会认为以某种方式将超过 50% 的更新剪裁为“极其激进”。
  • 如果我们需要进行极其激进的梯度裁剪来处理我们的不稳定问题,那么我们不妨降低学习率。

为什么将学习率和其他优化参数称为超参数?它们不是任何先验分布的参数。

[点击展开]
  • 的确,术语“超参数”在贝叶斯机器学习中有一个精确的meaning并且将学习率和我们在深度学习中调整的大多数其他参数称为“超参数”是对术语。
  • 我们更愿意使用术语“元参数”来表示学习率、架构参数以及我们在深度学习中调整的所有其他内容,因为它避免了因滥用“超参数”一词而引起的潜在混淆(混淆尤其可能在讨论贝叶斯优化时,概率响应曲面模型有自己的真实超参数)。
  • 不幸的是,尽管可能会造成混淆,但超参数这个术语在深度学习社区中已经变得极为普遍。
  • 因此,对于像本文档这样的面向广泛受众的文档,其中包括许多不太可能意识到这一技术细节的人,我们选择为该领域的一个混淆来源做出贡献,以期避免另一个混淆来源。
  • 也就是说,我们在发表研究论文时可能会做出不同的选择,我们会鼓励其他人在大多数情况下改用“元参数”。

为什么不应该调整批量大小来直接提高验证集性能?

[点击展开]
  • 更改批量大小不改变训练流水线的任何其他细节通常会影响验证集性能。
  • 但是,如果针对每个批量大小独立优化训练流水线,则两个批量大小之间的验证集性能差异通常会消失。
  • 与批量大小交互最强烈的超参数,因此对于每个批量大小单独调整最重要的是优化器超参数(例如学习率、动量)和正则化超参数。
    • 由于样本方差,较小的批次大小会在训练算法中引入更多噪声,并且这种噪声可能具有正则化效果。因此,较大的批量可能更容易过度拟合,并且可能需要更强的正则化和/或额外的正则化技术。
  • 此外,训练步长可能需要调整更改批量大小时。
  • 一旦考虑了所有这些影响,目前还没有令人信服的证据表明批量大小会影响最大可实现的验证性能(参见 Shallue et al. 2018)。

所有常用的优化算法的更新规则是什么?

[点击展开]

SGD(随机梯度下降)

$$\theta_{t+1} = \theta_{t} - \eta_t \nabla \mathcal{l}(\theta_t)$$

Momentum

$$v_0 = 0$$

$$v_{t+1} = \gamma v_{t} + \nabla \mathcal{l}(\theta_t)$$

$$\theta_{t+1} = \theta_{t} - \eta_t v_{t+1}$$

Nesterov

$$v_0 = 0$$

$$v_{t+1} = \gamma v_{t} + \nabla \mathcal{l}(\theta_t)$$

$$\theta_{t+1} = \theta_{t} - \eta_t( \gamma v_{t+1} + \nabla \mathcal{l}(\theta_{t})$$

RMSProp

$$v_0 = 1 \text {,} m_0 = 0$$

$$v_{t+1} = \rho v_{t} + (1 - \rho) \nabla \mathcal{l}(\theta_t)^2$$

$$m_{t+1} = \gamma m_{t} + \frac{\eta_t}{\sqrt{v_{t+1} + \epsilon}}\nabla \mathcal{l}(\theta_t)$$

$$\theta_{t+1} = \theta_{t} - m_{t+1}$$

ADAM

$$m_0 = 0 \text {,} v_0 = 0$$

$$m_{t+1} = \beta_1 m_{t} + (1 - \beta_1) \nabla \mathcal{l} (\theta_t)$$

$$v_{t+1} = \beta_2 v_{t} + (1 - \beta_2) \nabla \mathcal{l}(\theta_t)^2$$

$$b_{t+1} = \frac{\sqrt{1 - \beta_2^{t+1}}}{1 - \beta_1^{t+1}}$$

$$\theta_{t+1} = \theta_{t} - \alpha_t \frac{m_{t+1}}{\sqrt{v_{t+1}} + \epsilon} b_{t+1}$$

NADAM

$$m_0 = 0 \text{,} v_0 = 0$$

$$m_{t+1} = \beta_1 m_{t} + (1 - \beta_1) \nabla \mathcal{l} (\theta_t)$$

$$v_{t+1} = \beta_2 v_{t} + (1 - \beta_2) \nabla \mathcal{l} (\theta_t)^2$$

$$b_{t+1} = \frac{\sqrt{1 - \beta_2^{t+1}}}{1 - \beta_1^{t+1}}$$

$$\theta_{t+1} = \theta_{t} - \alpha_t \frac{\beta_1 m_{t+1} + (1 - \beta_1) \nabla \mathcal{l} (\theta_t)}{\sqrt{v_{t+1}} + \epsilon} b_{t+1}$$

致谢

  • 我们要感谢 Max Bileschi、Roy Frostig、Zelda Mariet、Stan Bileschi、Mohammad Norouzi、Chris DuBois 和 Charles Sutton 阅读手稿并提供宝贵的反馈建议。
  • 我们重复使用了最初由 Naman Agarwal 为其他联合研究制作的几个地块的一些实验数据。
  • 我们要感谢 Will Chen 对文件的介绍提出的宝贵建议。
  • 我们还要感谢 Rohan Anil 进行了有益的讨论。

引用

@misc{tuningplaybookgithub,
  author = {Varun Godbole and George E. Dahl and Justin Gilmer and Christopher J. Shallue and Zachary Nado},
  title = {Deep Learning Tuning Playbook},
  url = {http://github.com/google/tuning_playbook},
  year = {2023},
  note = {Version 1.0}
}

贡献

  • 这不是官方支持的 Google 产品。

  • 我们很想听听您的反馈建议!

    • 如果您喜欢本手册,请留下一颗星星!或发送电子邮件至 deep-learning-tuning-playbook [at] googlegroups.com。推荐书帮助我们证明创建更多这样的资源是合理的。
    • 如果有任何不正确的地方,请提出问题以开始讨论。对于不适合问题的问题或其他消息,请在 GitHub 上打开一个新的讨论主题。
  • 正如序言中所讨论的,这是一份动态文件。我们预计会定期进行大大小小的改进。如果您想收到通知,请留意我们的仓库(请参阅指示)。

  • 请不要在未通过问题跟踪系统与作者协调的情况下提交拉取请求。

贡献者许可协议

对该项目的贡献必须附有贡献者许可协议 (CLA)。您(或您的雇主)保留您贡献的版权;这只是允许我们使用和重新分配您的贡献作为项目的一部分。前往 https://cla.developers.google.com/ 查看您当前存档的协议或签署新协议。

您通常只需要提交一次 CLA,因此如果您已经提交过一次(即使是针对不同的项目),您可能不需要再次提交。

代码审查

所有提交的内容,包括项目成员提交的内容,都需要审查。为此,我们使用 GitHub 拉取请求。有关使用拉取请求的更多信息,请参阅 GitHub 帮助

社区准则

此项目遵循 Google 的开源社区准则