任何深度学习管道的一个重要部分是超参数和其他元数据的定义。这些数据与深度学习算法一起控制管道的各个方面,例如模型架构、训练和解码。在 SpeechBrain,我们认为超参数和学习算法之间的区别应该在我们工具包的结构中很明显,所以我们分成两个主要文件:train.py
和train.yaml
.
该train.yaml
文件采用 SpeechBrain 开发的格式,我们称之为“HyperPyYAML”。我们选择扩展 YAML,因为它是一种高度可读的数据序列化格式。通过扩展一个已经有用的格式,我们能够创建一个扩展的超参数定义,使我们的实际实验代码保持小巧且高度可读。
下面是一个带有 PyTorch 代码的简短示例,用于激发 HyperPyYAML 的使用,尽管 PyTorch 不是使用 HyperPyYAML 的必要条件:
!pip install torch
!pip install hyperpyyaml
import torch
from hyperpyyaml import load_hyperpyyaml
example_hyperparams = """
base_channels: 32
kernel_size: 11
padding: !ref <kernel_size> // 2
layer1: !new:torch.nn.Conv1d
in_channels: 1
out_channels: !ref <base_channels>
kernel_size: !ref <kernel_size>
padding: !ref <padding>
layer2: !new:torch.nn.Conv1d
in_channels: !ref <base_channels>
out_channels: !ref <base_channels> * 2
kernel_size: !ref <kernel_size>
padding: !ref <padding>
layer3: !new:torch.nn.Conv1d
in_channels: !ref <base_channels> * 2
out_channels: 1
kernel_size: !ref <kernel_size>
padding: !ref <padding>
model: !new:torch.nn.Sequential
- !ref <layer1>
- !new:torch.nn.LeakyReLU
- !ref <layer2>
- !new:torch.nn.LeakyReLU
- !ref <layer3>
"""
# Create model directly by loading the YAML
loaded_hparams = load_hyperpyyaml(example_hyperparams)
model = loaded_hparams["model"]
# Transform a 2-second audio clip
input_audio = torch.rand(1, 1, 32000)
transformed_audio = model(input_audio)
print(transformed_audio.shape)
# Try a different hyperparameter value by overriding the padding value
loaded_hparams = load_hyperpyyaml(example_hyperparams, {"padding": 0})
model = loaded_hparams["model"]
transformed_audio = model(input_audio)
print(transformed_audio.shape)
如本示例所示,HyperPyYAML 允许使用组合进行复杂的超参数定义。此外,可以覆盖任何值以进行超参数调整。为了了解所有这些是如何工作的,让我们首先简要地了解一下 YAML 的基础知识。
基本的 YAML 语法
下面是一个 yaml 片段的简短示例,以及加载到 python 后的样子:
import yaml
yaml_string = """
foo: 1
bar:
- item1
- item2
baz:
item1: 3.4
item2: True
"""
yaml.safe_load(yaml_string)
{'bar': ['item1', 'item2'], 'baz': {'item1': 3.4, 'item2': True}, 'foo': 1}
如您所见,YAML 内置了对多种数据类型的支持,包括字符串、整数、浮点数、布尔值、列表和字典。我们的 HyperPyYAML 格式保留了所有这些功能。
from hyperpyyaml import load_hyperpyyaml
load_hyperpyyaml(yaml_string)
{'bar': ['item1', 'item2'], 'baz': {'item1': 3.4, 'item2': True}, 'foo': 1}
我们对 yaml 格式是使用 YAML 标签添加的。标签添加在项目定义之前,并以!
. 为了说明标签的使用方式,这里是一个示例,我们做了一个小添加,!tuple
标签:
yaml_string = """
foo: !tuple (3, 4)
"""
load_hyperpyyaml(yaml_string)
{'foo': (3, 4)}
现在您了解了 YAML 基础知识,是时候继续添加内容了!
标签!new:
和!name:
YAML 标签可以包含一个后缀来更具体地定义它是什么类型的标签。我们使用它来定义一个能够创建任何 python 对象的标签,而不仅仅是一个基本类型。此标记以!new:
对象的类型开头并包含该类型。例如:
yaml_string = """
foo: !new:collections.Counter
"""
loaded_yaml = load_hyperpyyaml(yaml_string)
loaded_yaml["foo"]
Counter()
loaded_yaml["foo"].update({"a": 3, "b": 5})
loaded_yaml["foo"]["a"] += 1
loaded_yaml["foo"]
Counter({'a': 4, 'b': 5})
当然,许多 Python 对象在创建过程中都带有参数。这些参数可以与位置参数的列表或关键字参数的字典一起传递。
yaml_string = """
foo: !new:collections.Counter
- [a, b, r, a, c, a, d, a, b, r, a]
bar: !new:collections.Counter
a: 2
b: 1
c: 5
"""
load_hyperpyyaml(yaml_string)
{'bar': Counter({'a': 2, 'b': 1, 'c': 5}), 'foo': Counter({'a': 5, 'b': 2, 'c': 1, 'd': 1, 'r': 2})}
另一个可用于创建的 Python 对象是函数对象。在 HyperPyYAML 中,这可以通过!name:
标签来完成。在幕后,此标记用于使用functools.partial
提供的默认参数创建新的函数定义。例如:
yaml_string = """
foo: !name:collections.Counter
a: 2
"""
loaded_yaml = load_hyperpyyaml(yaml_string)
loaded_yaml["foo"](b=4)
Counter({'a': 2, 'b': 4})
默认参数可以被覆盖,就像一个普通的python函数
loaded_yaml["foo"](a=3, b=5)
Counter({'a': 3, 'b': 5})
标签!ref
和!copy
当然,有些超参数会在多个地方使用,因此我们添加了一种机制来引用另一个名为 的项!ref
。应用此标记的节点必须是一个字符串,其中包含要复制的节点的位置。子节点可以用方括号访问,和 Python 一样。例如:
yaml_string = """
foo:
a: 3
b: 4
bar:
c: !ref <foo>
d: !ref <foo[b]>
"""
load_hyperpyyaml(yaml_string)
{'bar': {'c': {'a': 3, 'b': 4}, 'd': 4}, 'foo': {'a': 3, 'b': 4}}
该!ref
标签可以支持基本超参数组合的简单算术和字符串连接。
yaml_string = """
folder1: abc/def
folder2: ghi/jkl
folder3: !ref <folder1>/<folder2>
foo: 1024
bar: 512
baz: !ref <foo> // <bar> + 1
"""
load_hyperpyyaml(yaml_string)
{'bar': 512, 'baz': 3, 'folder1': 'abc/def', 'folder2': 'ghi/jkl', 'folder3': 'abc/def/ghi/jkl', 'foo': 1024}
所述!ref
标签还可以指物体,在这种情况下,它使对同一个对象的引用,而不是拷贝。如果您更喜欢制作副本,请使用!copy
标签。
yaml_string = """
foo: !new:collections.Counter
a: 4
bar: !ref <foo>
baz: !copy <foo>
"""
loaded_yaml = load_hyperpyyaml(yaml_string)
loaded_yaml["foo"].update({"b": 10})
print(loaded_yaml["bar"])
print(loaded_yaml["baz"])
Counter({'b': 10, 'a': 4})
Counter({'a': 4})
其他标签
我们还引入了各种其他标签:
!tuple
创建 python 元组。请注意,这是隐式解析的,因此您无需显式写出元组标记,只需像在 Python 中一样使用括号即可。!include
直接插入其他yaml文件!apply
加载并执行python函数,存储结果
我们使用!apply
在加载 yaml 开始时设置随机种子,以便模型每次运行具有相同的参数。结果不存储,因为它以__
.
yaml_string = """
sum: !apply:sum
- [1, 2]
__set_seed: !apply:torch.manual_seed [1234]
"""
load_hyperpyyaml(yaml_string)
{'sum': 3}
覆盖
为了使用超参数的各种值运行实验,我们有一个系统来覆盖 yaml 文件中列出的值。
overrides = {"foo": 7}
fake_file = """
foo: 2
bar: 5
"""
load_hyperpyyaml(fake_file, overrides)
{'bar': 5, 'foo': 7}
如本例所示,覆盖可以采用普通的 Python 字典。但是,这种形式不支持 python 对象。要覆盖 python 对象,覆盖还可以采用 HyperPyYAML 语法采用 yaml 格式的字符串。
load_hyperpyyaml(fake_file, "foo: !new:collections.Counter")
{'bar': 5, 'foo': Counter()}
结论
我们很自豪地展示我们的 HyperPyYAML 语法,我们认为它提供了一种可读且简洁的方式来构建超参数定义。此外,它还消除了实验文件中不必要的复杂性,使算法变得清晰。从第一个例子中可以明显看出,覆盖很容易,使得超参数调整变得轻而易举。总的来说,我们发现这个包是深度学习的宝贵工具!