google/gin-config

GitHub: google/gin-config

Gin 是一个基于依赖注入的 Python 轻量级配置框架,通过配置文件和装饰器管理函数及类参数,特别适合参数众多的机器学习实验。

Stars: 2154 | Forks: 120

# Gin 配置 **作者**: Dan Holtmann-Rice, Sergio Guadarrama, Nathan Silberman **贡献者**: Oscar Ramirez, Marek Fiser Gin 提供了一个轻量级的 Python 配置框架,基于 依赖注入。函数或类可以使用 `@gin.configurable` 装饰器进行修饰,允许使用 简单但强大的语法从配置文件(或通过命令行传递)提供 默认参数值。 这消除了定义和维护配置对象(例如 protos)的需要,或编写样板参数管道和工厂代码,同时通常 极大地扩展了项目的灵活性和可配置性。 Gin 特别适合机器学习实验(例如使用 TensorFlow),这些实验往往有许多参数,并且经常以复杂的方式嵌套。 这不是一个官方的 Google 产品。 ## 目录 [TOC] ## 基本用法 本节提供了 Gin 主要功能的高级概述,顺序 大致从“基础”到“高级”。有关这些和其他功能的更多细节可以 在[用户指南]中找到。 ### 1. 设置 使用 pip 安装 Gin: ``` pip install gin-config ``` 从源码安装 Gin: ``` git clone https://github.com/google/gin-config cd gin-config python -m setup.py install ``` 导入 Gin(不包含 TensorFlow 功能): ``` import gin ``` 通过 `gin.tf` 模块导入特定于 TensorFlow 的额外功能: ``` import gin.tf ``` 通过 `gin.torch` 模块导入特定于 PyTorch 的额外功能: ``` import gin.torch ``` ### 2. 使用 Gin 配置默认值(`@gin.configurable` 和“绑定”) 在最基本的情况下,Gin 可以被视为一种提供或更改 函数或构造函数参数默认 值的方法。为了使函数的参数 “可配置”,Gin 提供了 `gin.configurable` 装饰器: ``` @gin.configurable def dnn(inputs, num_outputs, layer_sizes=(512, 512), activation_fn=tf.nn.relu): ... ``` 这个装饰器向 Gin 注册了 `dnn` 函数,并自动使其 所有参数都可配置。要在 ".gin" 配置文件中为 上面的 `layer_sizes` 参数设置(“绑定”)一个值: ``` # 在 "config.gin" 内部 dnn.layer_sizes = (1024, 512, 128) ``` 绑定的语法是 `function_name.parameter_name = value`。所有 Python 字面 值都支持作为 `value`(数字、字符串、列表、元组、字典)。一旦 配置文件被 Gin 解析,未来对 `dnn` 的任何调用都将使用 Gin 为 `layer_sizes` 指定的值(除非调用者显式提供一个值)。 类也可以被标记为可配置的,在这种情况下,配置 将应用于构造函数参数: ``` @gin.configurable class DNN(object): # Constructor parameters become configurable. def __init__(self, num_outputs, layer_sizes=(512, 512), activation_fn=tf.nn.relu): ... def __call__(inputs): ... ``` 在配置文件中,将值绑定到构造函数 参数时会使用类名: ``` # 在 "config.gin" 内部 DNN.layer_sizes = (1024, 512, 128) ``` 最后,在定义或导入所有可配置的类或函数之后, 解析您的配置文件以绑定您的配置(要同时允许多个 配置文件和命令行覆盖,请参阅 [`gin.parse_config_files_and_bindings`][multiple files]): ``` gin.parse_config_file('config.gin') ``` 请注意,除了添加 `gin.configurable` 装饰器和对 Gin 的解析函数之一的调用外,Python 代码不需要进行其他更改。 ### 3. 传递函数、类和实例(“可配置引用”) 除了接受 Python 字面值外,Gin 还支持传递其他 Gin 可配置的函数或类。在上面的例子中,我们可能想要 更改 `activation_fn` 参数。如果我们向 Gin 注册了,比如说 `tf.nn.tanh` (参见[注册外部函数][external configurables]),我们可以 通过将其引用为 `@tanh`(或 `@tf.nn.tanh`)来将其传递给 `activation_fn`: ``` # 在 "config.gin" 内部 dnn.activation_fn = @tf.nn.tanh ``` Gin 将 `@name` 结构称为*可配置引用*。可配置 引用同样适用于类: ``` def train_fn(..., optimizer_cls, learning_rate): optimizer = optimizer_cls(learning_rate) ... ``` 然后,在配置文件中: ``` # 在 "config.gin" 内部 train_fn.optimizer_cls = @tf.train.GradientDescentOptimizer ... ``` 有时需要传递调用特定函数或 类构造函数的结果。Gin 支持通过 `@name()` 语法“求值”可配置引用。例如,假设我们想从 上面的代码中使用 `DNN` 的类形式(它实现了 `__call__` 以便像函数一样“表现”),在以下 Python 代码中: ``` def build_model(inputs, network_fn, ...): logits = network_fn(inputs) ... ``` 我们可以将 `DNN` 类的实例传递给 `network_fn` 参数: ``` # 在 "config.gin" 内部 build_model.network_fn = @DNN() ``` 要使用已求值的引用,被引用函数或类的 所有参数都必须通过 Gin 提供。对函数或构造函数的调用 发生在将结果传递给的目标函数被调用*之前*。在上面的例子中,这将是在 `build_model` 被调用之前。 结果不会被缓存,因此每次 调用 `build_model` 时都会构造一个新的 `DNN` 实例。 ### 4. 以不同方式配置同一个函数(“作用域”) 如果我们想以不同的方式配置同一个函数会发生什么?例如, 假设我们正在构建一个 GAN,其中我们可能有一个“生成器” 网络和一个“判别器”网络。我们想使用上面的 `dnn` 函数 来构建两者,但使用不同的参数: ``` def build_model(inputs, generator_network_fn, discriminator_network_fn, ...): ... ``` 为了处理这种情况,Gin 提供了“作用域”,它为给定函数或类的一组特定绑定提供了一个名称。在绑定和引用中, “作用域名称”位于函数名称之前,由 "`/`" 分隔(即, `scope_name/function_name`): ``` # 在 "config.gin" 内部 build_model.generator_network_fn = @generator/dnn build_model.discriminator_network_fn = @discriminator/dnn generator/dnn.layer_sizes = (128, 256) generator/dnn.num_outputs = 784 discriminator/dnn.layer_sizes = (512, 256) discriminator/dnn.num_outputs = 1 dnn.activation_fn = @tf.nn.tanh ``` 在这个例子中,生成器网络具有递增的层宽度和 784 个输出,而判别器网络具有递减的层宽度和 1 个输出。 在“根”(无作用域)函数名称上设置的任何参数都会被 有作用域的变体继承(除非被显式覆盖),因此在上面的例子中,生成器 和判别器都使用 `tf.nn.tanh` 激活函数。这 通常适用于作用域层次结构,例如,如果我们在名为 `a/b` 的作用域中,配置将继承作用域 `a` 的所有值。 ### 5. 完全的分层配置 {#full-hierarchical} 项目中最大程度的灵活性和可配置性是通过 编写小型模块化函数并通过(可能有作用域的)引用将它们“分层连接”起来实现的。例如,这段代码勾勒了一个通用的训练 设置,可以与 `tf.estimator.Estimator` API 一起使用: ``` @gin.configurable def build_model_fn(network_fn, loss_fn, optimize_loss_fn): def model_fn(features, labels): logits = network_fn(features) loss = loss_fn(labels, logits) train_op = optimize_loss_fn(loss) ... return model_fn @gin.configurable def optimize_loss(loss, optimizer_cls, learning_rate): optimizer = optimizer_cls(learning_rate) return optimizer.minimize(loss) @gin.configurable def input_fn(file_pattern, batch_size, ...): ... @gin.configurable def run_training(train_input_fn, eval_input_fn, estimator, steps=1000): estimator.train(train_input_fn, steps=steps) estimator.evaluate(eval_input_fn) ... ``` 结合合适的[外部可配置项]来注册 TensorFlow 函数/类(例如,`Estimator` 和各种优化器),这可以 按如下方式进行配置: ``` # 在 "config.gin" 内部 run_training.train_input_fn = @train/input_fn run_training.eval_input_fn = @eval/input_fn input_fn.batch_size = 64 # Shared by both train and eval... train/input_fn.file_pattern = ... eval/input_fn.file_pattern = ... run_training.estimator = @tf.estimator.Estimator() tf.estimator.Estimator.model_fn = @build_model_fn() build_model_fn.network_fn = @dnn dnn.layer_sizes = (1024, 512, 256) build_model_fn.loss_fn = @tf.losses.sparse_softmax_cross_entropy build_model_fn.optimize_loss_fn = @optimize_loss optimize_loss.optimizer_cls = @tf.train.MomentumOptimizer MomentumOptimizer.momentum = 0.9 optimize_loss.learning_rate = 0.01 ``` 请注意,通过不同的配置文件,可以轻松地在不同的网络函数、 优化器、数据集、损失函数等之间切换。 ### 6. 附加功能 在[用户指南]中有更详细说明的附加功能包括: - 自动记录所有已配置的参数值(["操作 配置"][operative config]),包括 [TensorBoard 集成]。 - ["宏"][macros],用于指定在配置中的多个 位置使用的值,以及 Python 定义的常量。 - [模块导入][imports] 和 [配置文件包含][includes]。 - 通过模块对可配置名称进行[消除歧义][modules]。 ## 最佳实践 在高级层面上,我们建议使用实现项目所需的可配置程度的最小功能集。许多项目可能只 需要上述第 2 或第 3 节中概述的功能。极端的可配置性 会以牺牲可理解性为代价,应针对给定项目仔细评估这种权衡。 Gin 仍处于 alpha 开发阶段,某些边缘情况的行为可能 会以不向后兼容的方式更改。我们建议遵循以下最佳 实践: - 尽量少用已求值的可配置引用(`@name()`),特别是 在与宏结合使用时(其值未被缓存的事实可能 会让新用户感到惊讶)。 - 避免作用域的嵌套(即,`scope1/scope2/function_name`)。虽然 受支持,但围绕其顺序和行为仍存在一些持续的争论。 - 当将无作用域的引用(`@name`)作为有作用域 函数(`some_scope/fn.param`)的参数传递时,无作用域的引用会在传递给它的函数的 作用域内被调用……但不要依赖此行为。 - 在可能的情况下,优先使用函数或类的名称作为其 可配置名称,而不是覆盖它。如果发生命名冲突, 使用模块名(鼓励将其重命名以符合常见用法) 以消除歧义。 - 事实上,为了提高复杂配置文件的可读性,我们温和地建议 始终包含模块名,以帮助更容易地在 Python 代码中找到相应的 定义。 - 在进行["完全的分层配置"](#full-hierarchical)时,构建 代码以最大程度地减少在没有作为参数传递的情况下配置的“顶层”函数的 数量。换句话说, 配置树应该只有一个根。 简而言之,请负责任地使用 Gin :) ## 语法快速参考 Gin 特有语法的快速参考(否则它支持 非控制流的 Python 语法,包括字面值和行 延续)。请注意,在使用函数和类名的地方,这些 可能包含点分模块名前缀(`some.module.function_name`)。
语法 描述
@gin.configurable Python 代码中的装饰器,用于向 Gin 注册函数或类, 通过将其包装/替换为遵循 Gin 参数覆盖的“可配置”版本。使用 `@gin.configurable` 注解的函数或类,即使直接从其他 Python 代码中调用,其参数也会被任何提供的 配置覆盖。 .
@gin.register Python 代码中的装饰器,它仅向 Gin 注册函数或 类,但*不会*将其替换为其“可配置”版本。 当从其他 Python 代码中直接调用时,使用 `@gin.register` 注解的函数或类*不会*被 Gin 配置覆盖其 参数。但是,配置字符串或文件中对 这些函数的任何引用(`@some_name` 语法,见下文)都将应用任何提供的 配置。
name.param = value Gin 绑定的基本语法。一旦解析了此内容,当调用 名为 name 的函数或类时,它将 接收 value 作为 param 的值,除非 调用者显式提供了一个值。任何 Python 字面值都可以 作为 value 提供。
@some_name 对另一个名为 some_name 的函数或类的引用。这可以作为绑定的值给出,以 提供函数或类类型的参数。
@some_name() 求值引用。不是直接提供 函数或类,而是传递调用 some_name 的 结果。请注意,结果不会被缓存;每次 需要时都会重新计算。
scope/name.param = value 作用域绑定。该绑定仅在 name 于作用域 scope 内被调用时生效。
@scope/some_name 作用域引用。当它被调用时,该调用将在 作用域 scope 内进行,并应用任何相关的作用域绑定。
MACRO_NAME = value 一个宏。这为右侧的 表达式提供了一个简写名称。
%MACRO_NAME 对宏 MACRO_NAME 的引用。这具有 将 %MACRO_NAME 文本替换为其 关联表达式的效果。特别要注意, 求值引用的结果不会被缓存。
标签:Apex, Python, SOC Prime, 依赖注入, 凭据扫描, 开发工具, 无后门, 机器学习, 逆向工具