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, 依赖注入, 凭据扫描, 开发工具, 无后门, 机器学习, 逆向工具