本文作者是 Omry Yadan,他是 Facebook 人工智能软件工程师,创建了 Hydra。
Hydra 是最近发布的一个开源 Python 框架,由 Facebook AI 开发,能够简化科研和其他复杂应用程序的开发。这个新框架功能强大,可以从命令行和配置文件中组合和重写配置。作为 PyTorch 生态系统的一部分,Hydra 帮助 PyTorch 的研究人员和开发人员更容易地管理复杂的机器学习项目。Hydra 是通用的,可以应用于机器学习以外的领域。
这篇文章分为两部分,第一部分描述开发机器学习软件时出现的常见问题,第二部分主要是 Hydra 如何解决这些问题。
part1.你的代码比你想象的更加复杂
命令行是每个软件开发人员最先了解的知识之一。命令行的核心是一个字符串列表,这些字符串通常被分解为标志(例如,-verbose)和参数(例如,-port=80)。这对于许多简单的应用程序来说已经足够了,你可能只需要在命令行界面(CLI)解析库中定义 2 到 3 个命令行参数就足够了。
当人们开始使用你的应用程序时,他们将不可避免地发现缺少的功能。很快,你将添加更多功能,导致更多的命令行标志。这在机器学习中尤其常见。
上面的图片来自 PyTorch ImageNet 训练示例。尽管是一个很小的例子,但是命令行标志的数量已经很高了。其中一些标志在逻辑上描述了相同的组件,它们在理想情况下应该被分成一组(例如,与分布式训练相关的标志),但是没有一种简单的方法可以将这些标志分组并使用。
在此示例的基础上,你可能希望添加新功能,支持需要附加命令行标志的新模型、数据集或优化器。你可以想象在这个例子,随着你的扩展支持新的想法是如何做到的。
这种样式的另一个微妙问题是,所有东西都需要解析的 args 对象。这会鼓励耦合,并使单个组件更难在不同的项目中重用。
配置文件
一个常见的解决方案是切换到配置文件。配置文件可以是分层的,并且可以帮助减少定义命令行参数的代码的复杂性。不幸的是,配置文件中也会面临挑战,你将在下一节中看到。
配置文件很难更改
在尝试时,你需要使用不同的配置选项运行应用程序。起初,你可能只是在每次运行之前更改配置文件,但你很快就会意识到跟踪与每次运行相关联的更改是非常困难的。
试图解决该问题的方法可能是复制配置文件,在实验后命名,并对新文件进行更改。这种方法也不是很完美,因为它创建了一个很长的配置文件进行跟踪,这个文件将很快就不能与代码同步,变得毫无用处。此外,通过查看实验配置文件很难判断你要做什么,因为它与其他配置文件 99% 相同。
最后,对于经常更改的内容,你可能会返回到命令行标志,以允许从命令行更改它们。这是乏味的,并且会让命令行代码再次变得复杂。理想情况下,你可以从命令行重写配置中的所有内容,而不必为每种情况单独编写代码。
配置文件变得单一
当开发人员编写代码时,他们喜欢将事情分解成很小的部分(模块、函数)。这可以帮助他们保存代码模型,并使代码更易于维护。它还支持函数重用——调用一个函数比复制它容易。
配置文件不提供类似的功能。如果希望应用程序使用不同的配置选项,例如一个用于 ImageNet 数据集,一个用于 CIFAR-10 数据集,则有两个选择:
维护两个配置文件
将这两个选项放在一个配置文件中,并在运行时以某种方式使用所需的内容
第一种方法似乎很好,但后面你会意识到,随着你增加更多的选项,事情很快就会崩溃。例如,除了两个数据集选择之外,你可能还想尝试三种不同的模型体系结构(AlexNet、ResNet50 和一些新的、令人兴奋的、你称之为 BestNet 的东西)。你还可以在两个损失函数之间进行选择。这使组合总数达到 12 个!你确实希望避免维护 12 个类似的配置文件。
第二种方法最初的效果更好。你只需得到一个大的配置文件,该文件知道所选的两个数据集、三个体系结构和两个损失函数。但是,等等,当你在 AlexNet 和 ResNet50 上进行训练时,你的学习速率需要有所不同,而且你需要在单个配置文件中表达出来。
这种复杂性也会泄漏到代码中,现在需要找出在运行时使用的学习速率!在设计、运行和调试实验时,大部分未使用的大型配置会产生显著的认知负载。由于 90% 的配置未使用,很难判断每次运行最重要 10% 在哪里。
通过添加从命令行重写配置中所有内容的功能来组合配置,可以为这些问题提供一个强大的解决方案。由于这个原因,许多日益复杂的项目最终到达了开发 Hydra 所提供的功能子集的必要位置。这种功能往往与单个项目的需求紧密结合,因此很难重用,迫使开发人员不断地在每个新项目中重新发明轮子。
不幸的是,在许多开发人员意识到这一点的时候,他们已经有了一个复杂且不灵活的代码库,具有高耦合、硬编码的配置。理想情况下,你希望像编写代码一样编写配置。这使你可以扩大项目的复杂性。
part2.像使用 Hydra 编写代码一样编写配置
如果你走到了这一步,你一定会想,对于我在第 1 部分中描述的那些软件工程的问题,有什么神奇的解决方案?你可能会猜到它就是 Hydra。
Hydra 是 Facebook AI Research 开发的一个开源 Python 框架,它通过允许你组合传递给应用程序的配置来解决很多问题,包括第 1 部分中概述的问题。合成可以通过配置文件或命令行进行,合成配置中的所有内容也可以通过命令行重写。
基本示例
下面示例的源代码在这里可以找到:https://github.com/omry/hydra-article-code。
假设你的数据集的配置如下:
config.yaml
以下是加载此配置的简单 Hydra 应用程序:
my_app.py
这里最有趣的一行是 @hydra.main()修饰器。它采用一个 config_ 路径,提到了上面的 config.yaml 文件。
程序很好地打印了它得到的配置对象。毫无疑问,config 对象包含 ImageNet 数据集配置:
my_app 的常规输出
我们现在可以从命令行重写此配置文件中的任何内容:
重写 dataset.path 时的输出
构成示例
有时,你可能希望在两个不同的数据集之间进行替换,每个数据集都有自己的配置。要支持此功能,请为数据集引入一个配置组,并在其中放置单个配置文件,每个选项一个:
你还可以在 config.yaml 中添加「defaults」部分,告诉 Hydra 如何编写配置。在这种情况下,我们只想默认加载 cifar10 的配置,因为在它上面训练更快:
config.yaml
这个应用看起来几乎相同,唯一的区别是配置路径现在指向 conf/config.yaml。运行应用程序时,会加载预期的 cifar10 配置:
但我们也可以很容易地选择使用 imagenet:
你可以拥有多个配置组,让我们在优化器中添加一个:
默认情况下,还可以更新 config.yaml 以加载 adam:
config.yaml
运行应用程序时,我们会得到一个包含 cifar10 和 adam 的联合配置:
这里还有很多可以谈的,但现在,让我们转到下一个激动人心的特性。
Multirun
Multirun 是 Hydra 的一种功能,它可以多次运行你的函数,每次都组成一个不同的配置对象。这是一个自然的扩展,可以轻松地组合复杂的配置,并且非常方便地进行参数扫描,而无需编写冗长的脚本。
例如,我们可以扫描所有 4 个组合(2 个数据集 X 2 个优化器):
基本的内置启动程序是串行运行,但是其他启动程序插件可以并行运行代码,甚至远程运行代码。这些插件还没有公开,但在社区的帮助下,我希望很快能看到它们。
自动工作目录
如果仔细观察上面的输出,你会注意到 sweep 输出目录是根据我运行命令的时间生成的。人们在做研究时经常遇到的一个问题是如何保存输出。典型的解决方案是传入一个指定输出目录的命令行标志,但这很快会变得乏味。当你希望同时运行多项任务,并且必须为每个任务传递不同的输出目录时,这尤其令人恼火。
Hydra 通过为每次运行生成输出目录并在运行代码之前更改当前工作目录来解决此问题。使用 --multirun 执行扫描时,会为每个任务生成一个附加子目录。
这样可以很好地将来自同一 sweep 的任务分组在一起,同时保持每个任务与其他任务的输出分离。
你仍然可以通过 Hydra 中的 API 访问原始工作目录。
original_cwd
从 /home/omry/dev/hydra 运行时的输出:
生成的工作目录可以完全自定义,这包括让它作为路径的一部分,包含命令行参数或配置中的任何其他内容。
写在最后
本文中包含的只是 Hydra 提供的特性之一。其他功能包括动态选项卡完成、Python 日志记录子系统的自动配置、库和应用程序打包配置支持等等。
在 Facebook AI 中,我们使用 Hydra 从命令行直接向内部集群发送代码。在社区的帮助下,我希望 Hydra 能够成长为支持 AWS 和 GCP,并为 Facebook AI 之外的研究人员提供类似的功能。另一个感兴趣的领域是命令行驱动的超参数优化。第一个这样的插件,Ax 正在开发中。
Hydra 是新的,我们刚刚开始了解它是如何改变事物的。
我期待着看到社区在未来几年如何使用 Hydra。
要了解有关 Hydra 的更多信息,请参阅 Hydra 网站上的教程和文档:https://hydra.cc/ 。
雷锋网雷锋网雷锋网