Unverified Commit 346721be authored by kvartet's avatar kvartet Committed by GitHub
Browse files

Update Chinese translation (#3413)

parent b122c63d
模型压缩教程 快速入门
============================== ===========
.. contents:: .. toctree::
:hidden:
本教程中,`第一部分 <#quick-start-to-compress-a-model>`__ 会简单介绍 NNI 上模型压缩的用法。 `第二部分 <#detailed-usage-guide>`__ 会进行详细介绍。 教程 <Tutorial>
模型压缩快速入门
-------------------------------
NNI 为模型压缩提供非常简单的 API 压缩包括剪枝和量化算法。 它们的用法相同,这里通过 `slim pruner </Compression/Pruner.html#slim-pruner>`__ 来演示如何使用。 模型压缩通常包括三个阶段:1)预训练模型,2)压缩模型,3)微调模型。 NNI 主要关注于第二阶段,并为模型压缩提供非常简单的 API 遵循本指南,快速了解如何使用 NNI 压缩模型。
编写配置 模型剪枝
^^^^^^^^^^^^^^^^^^^ -------------
编写配置来指定要剪枝的层。 以下配置表示剪枝所有的 ``BatchNorm2d``,稀疏度设为 0.7,其它层保持不变 这里通过 `level pruner <../Compression/Pruner.rst#level-pruner>`__ 举例说明 NNI 中模型剪枝的用法
.. code-block:: python Step1. 编写配置
^^^^^^^^^^^^^^^^^^^^^^^^^^
configure_list = [{
'sparsity': 0.7,
'op_types': ['BatchNorm2d'],
}]
配置说明在 `这里 <#specification-of-config-list>`__ 注意,不同的 Pruner 可能有自定义的配置字段,例如,AGP Pruner ``start_epoch`` 详情参考每个 Pruner `用法 <./Pruner.rst>`__,来调整相应的配置。
选择压缩算法
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
选择 Pruner 来修剪模型。 首先,使用模型来初始化 Pruner,并将配置作为参数传入,然后调用 ``compress()`` 来压缩模型。
.. code-block:: python
pruner = SlimPruner(model, configure_list)
model = pruner.compress()
然后,使用正常的训练方法来训练模型 (如,SGD),剪枝在训练过程中是透明的。 一些 Pruner 只在最开始剪枝一次,接下来的训练可被看作是微调优化。 有些 Pruner 会迭代的对模型剪枝,在训练过程中逐步修改掩码。
导出压缩结果
^^^^^^^^^^^^^^^^^^^^^^^^^
训练完成后,可获得剪枝后模型的精度。 可将模型权重到处到文件,同时将生成的掩码也导出到文件, 也支持导出 ONNX 模型。
.. code-block:: python
pruner.export_model(model_path='pruned_vgg19_cifar10.pth', mask_path='mask_vgg19_cifar10.pth')
模型完整的示例代码在 :githublink:`这里 <examples/model_compress/model_prune_torch.py>`.
加速模型
^^^^^^^^^^^^^^^^^^
掩码实际上并不能加速模型。 要基于导出的掩码,来对模型加速,因此,NNI 提供了 API 来加速模型。 在模型上调用 ``apply_compression_results`` 后,模型会变得更小,推理延迟也会减小 编写配置来指定要剪枝的层。 以下配置表示剪枝所有的 ``default`` 操作,稀疏度设为 0.5,其它层保持不变
.. code-block:: python .. code-block:: python
from nni.compression.pytorch import apply_compression_results config_list = [{
apply_compression_results(model, 'mask_vgg19_cifar10.pth') 'sparsity': 0.5,
'op_types': ['default'],
参考 `这里 <ModelSpeedup.rst>`__,了解详情。 }]
使用指南 配置说明在 `这里 <./Tutorial.rst#specify-the-configuration>`__ 注意,不同的 Pruner 可能有自定义的配置字段,例如,AGP Pruner ``start_epoch`` 详情参考每个 Pruner `用法 <./Pruner.rst>`__,来调整相应的配置。
--------------------
将压缩应用到模型的示例代码如下: Step2. 选择 Pruner 来压缩模型
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PyTorch 代码 首先,使用模型来初始化 Pruner,并将配置作为参数传入,然后调用 ``compress()`` 来压缩模型。 请注意,有些算法可能会检查压缩的梯度,因此我们还定义了一个优化器并传递给 Pruner
.. code-block:: python .. code-block:: python
from nni.algorithms.compression.pytorch.pruning import LevelPruner from nni.algorithms.compression.pytorch.pruning import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
TensorFlow 代码
.. code-block:: python optimizer_finetune = torch.optim.SGD(model.parameters(), lr=0.01)
pruner = LevelPruner(model, config_list, optimizer_finetune)
from nni.algorithms.compression.tensorflow.pruning import LevelPruner model = pruner.compress()
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(tf.get_default_graph(), config_list)
pruner.compress()
可使用 ``nni.compression`` 中的其它压缩算法。 此算法分别在 ``nni.compression.torch`` ``nni.compression.tensorflow`` 中实现,支持 PyTorch TensorFlow(部分支持)。 参考 `Pruner <./Pruner.md>`__ `Quantizer <./Quantizer.md>`__ 进一步了解支持的算法。 此外,如果要使用知识蒸馏算法,可参考 `KD 示例 <../TrialExample/KDExample.rst>`__
压缩算法首先通过传入 ``config_list`` 来实例化。 ``config_list`` 会稍后介绍。
函数调用 ``pruner.compress()`` 来修改用户定义的模型(在 Tensorflow 中,通过 ``tf.get_default_graph()`` 来获得模型,而 PyTorch model 是定义的模型类),并修改模型来插入 mask 然后运行模型时,这些 mask 即会生效。 掩码可在运行时通过算法来调整。
*注意,``pruner.compress`` 只会在模型权重上直接增加掩码,不包括调优的逻辑。 如果要想调优压缩后的模型,需要在 ``pruner.compress`` 后增加调优的逻辑。* 然后,使用正常的训练方法来训练模型 (如,SGD),剪枝在训练过程中是透明的。 有些 Pruner(如 L1FilterPrunerFPGMPruner)在开始时修剪一次,下面的训练可以看作是微调。 有些 Pruner(例如AGPPruner)会迭代的对模型剪枝,在训练过程中逐步修改掩码。
``config_list`` 说明 注意,``pruner.compress`` 只会在模型权重上直接增加掩码,不包括调优的逻辑。 如果要想调优压缩后的模型,需要在 ``pruner.compress`` 后增加调优的逻辑。
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
用户可为压缩算法指定配置 (, ``config_list`` ) 例如,压缩模型时,用户可能希望指定稀疏率,为不同类型的操作指定不同的稀疏比例,排除某些类型的操作,或仅压缩某类操作。 配置规范可用于表达此类需求。 可将其视为一个 Python ``list`` 对象,其中每个元素都是一个 ``dict`` 对象。 例如:
``list`` 中的 ``dict`` 会依次被应用,也就是说,如果一个操作出现在两个配置里,后面的 ``dict`` 会覆盖前面的配置。 .. code-block:: python
``dict`` 中有不同的键值。 以下是所有压缩算法都支持的: for epoch in range(1, args.epochs + 1):
pruner.update_epoch(epoch)
train(args, model, device, train_loader, optimizer_finetune, epoch)
test(model, device, test_loader)
更多关于微调的 API `这里 <./Tutorial.rst#apis-to-control-the-fine-tuning>`__
* **op_types**:指定要压缩的操作类型。 'default' 表示使用算法的默认设置。
* **op_names**:指定需要压缩的操作的名称。 如果没有设置此字段,操作符不会通过名称筛选。
* **exclude**:默认为 False 如果此字段为 True,表示要通过类型和名称,将一些操作从压缩中排除。
其它算法的键值,可参考 `剪枝算法 <./Pruner.md>`__ `量化算法 <./Quantizer.rst>`__,查看每个算法的键值。 Step3. 导出压缩结果
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
配置的简单示例如下: 训练之后,可将模型权重导出到文件,同时将生成的掩码也导出到文件, 也支持导出 ONNX 模型。
.. code-block:: python .. code-block:: python
[ pruner.export_model(model_path='pruned_vgg19_cifar10.pth', mask_path='mask_vgg19_cifar10.pth')
{
'sparsity': 0.8,
'op_types': ['default']
},
{
'sparsity': 0.6,
'op_names': ['op_name1', 'op_name2']
},
{
'exclude': True,
'op_names': ['op_name3']
}
]
其表示压缩操作的默认稀疏度为 0.8,但 ``op_name1`` ``op_name2`` 会使用 0.6,且不压缩 ``op_name3``
其它量化算法字段
^^^^^^^^^^^^^^^^^^^^^^^^^^
如果使用量化算法,则需要设置下面的 ``config_list`` 如果使用剪枝算法,则可以忽略这些键值。
参考 :githublink:`mnist 示例 <examples/model_compress/pruning/naive_prune_torch.py>` 获取代码。
* **quant_types** : 字符串列表。 更多剪枝算法的示例在 :githublink:`basic_pruners_torch <examples/model_compress/pruning/basic_pruners_torch.py>` :githublink:`auto_pruners_torch <examples/model_compress/pruning/auto_pruners_torch.py>`
要应用量化的类型,当前支持 "权重""输入""输出" "权重"是指将量化操作
应用到 module 的权重参数上。 "输入" 是指对 module forward 方法的输入应用量化操作。 "输出"是指将量化运法应用于模块 forward 方法的输出,在某些论文中,这种方法称为"激活"
模型量化
------------------
* **quant_bits**int dict {str : int} 这里通过 `QAT Quantizer <../Compression/Quantizer.rst#qat-quantizer>`__ 举例说明在 NNI 中量化的用法。
量化的位宽,键是量化类型,值是量化位宽度,例如: Step1. 编写配置
^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: bash .. code-block:: python
{ config_list = [{
quant_bits: { 'quant_types': ['weight'],
'quant_bits': {
'weight': 8, 'weight': 8,
'output': 4, }, # 这里可以仅使用 `int`,因为所有 `quan_types` 使用了一样的位长,参考下方 `ReLu6` 配置。
}, 'op_types':['Conv2d', 'Linear']
} }, {
'quant_types': ['output'],
当值为 int 类型时,所有量化类型使用相同的位宽。 例如: 'quant_bits': 8,
'quant_start_step': 7000,
.. code-block:: bash 'op_types':['ReLU6']
}]
{
quant_bits: 8, # weight or output quantization are all 8 bits
}
下面的示例展示了一个更完整的 ``config_list``,它使用 ``op_names``(或者 ``op_types``)指定目标层以及这些层的量化位数。
.. code-block:: bash
configure_list = [{
'quant_types': ['weight'],
'quant_bits': 8,
'op_names': ['conv1']
}, {
'quant_types': ['weight'],
'quant_bits': 4,
'quant_start_step': 0,
'op_names': ['conv2']
}, {
'quant_types': ['weight'],
'quant_bits': 3,
'op_names': ['fc1']
},
{
'quant_types': ['weight'],
'quant_bits': 2,
'op_names': ['fc2']
}
]
在这个示例中,'op_names' 是层的名字,四个层将被量化为不同的 quant_bits
更新优化状态的 API 配置说明在 `这里 <./Tutorial.rst#quantization-specific-keys>`__
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
一些压缩算法使用 Epoch 来控制压缩进度 (如 `AGP </Compression/Pruner.html#agp-pruner>`__),一些算法需要在每个批处理步骤后执行一些逻辑。 因此,NNI 提供了两个 API``pruner.update_epoch(epoch)`` ``pruner.step()`` Step2. 选择 Quantizer 来压缩模型
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``update_epoch`` 会在每个 Epoch 时调用,而 ``step`` 会在每次批处理后调用。 注意,大多数算法不需要调用这两个 API 详细情况可参考具体算法文档。 对于不需要这两个 API 的算法,可以调用它们,但不会有实际作用。 .. code-block:: python
导出压缩模型 from nni.algorithms.compression.pytorch.quantization import QAT_Quantizer
^^^^^^^^^^^^^^^^^^^^^^^
使用下列 API 可轻松将压缩后的模型导出,稀疏模型的 ``state_dict`` 会保存在 ``model.pth`` 文件中,可通过 ``torch.load('model.pth')`` 加载。 在导出的 ``model.pth`` 中,被掩码遮盖的权重为零。 quantizer = QAT_Quantizer(model, config_list)
quantizer.compress()
.. code-block:: bash
pruner.export_model(model_path='model.pth') Step3. 导出压缩结果
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``mask_dict`` ``onnx`` 格式的剪枝模型(需要指定 ``input_shape``)可这样导出: 您可以使用 ``torch.save`` api 直接导出量化模型。量化后的模型可以通过 ``torch.load`` 加载,不需要做任何额外的修改。
.. code-block:: python .. code-block:: python
pruner.export_model(model_path='model.pth', mask_path='mask.pth', onnx_path='model.onnx', input_shape=[1, 1, 28, 28]) # 保存使用 NNI QAT 算法生成的量化模型
torch.save(model.state_dict(), "quantized_model.pth")
参考 :githublink:`mnist example <examples/model_compress/quantization/QAT_torch_quantizer.py>` 获取示例代码。
如果需要实际加速压缩后的模型,参考 `NNI 模型加速 <./ModelSpeedup.rst>`__ 恭喜! 您已经通过 NNI 压缩了您的第一个模型。 更深入地了解 NNI 中的模型压缩,请查看 `Tutorial <./Tutorial.rst>`__
\ No newline at end of file
教程
========
.. contents::
在本教程中,我们将更详细地解释 NNI 中模型压缩的用法。
设定压缩目标
----------------------
指定配置
^^^^^^^^^^^^^^^^^^^^^^^^^
用户可为压缩算法指定配置 (即, ``config_list`` )。 例如,压缩模型时,用户可能希望指定稀疏率,为不同类型的操作指定不同的稀疏比例,排除某些类型的操作,或仅压缩某类操作。 配置规范可用于表达此类需求。 可将其视为一个 Python 的 ``list`` 对象,其中每个元素都是一个 ``dict`` 对象。
``list`` 中的 ``dict`` 会依次被应用,也就是说,如果一个操作出现在两个配置里,后面的 ``dict`` 会覆盖前面的配置。
``dict`` 中有不同的键值。 以下是所有压缩算法都支持的:
* **op_types**:指定要压缩的操作类型。 'default' 表示使用算法的默认设置。
* **op_names**:指定需要压缩的操作的名称。 如果没有设置此字段,操作符不会通过名称筛选。
* **exclude**:默认为 False。 如果此字段为 True,表示要通过类型和名称,将一些操作从压缩中排除。
其他一些键值通常是针对某个特定算法的,可参考 `剪枝算法 <./Pruner.rst>`__ 和 `量化算法 <./Quantizer.rst>`__,查看每个算法的键值。
配置的简单示例如下:
.. code-block:: python
[
{
'sparsity': 0.8,
'op_types': ['default']
},
{
'sparsity': 0.6,
'op_names': ['op_name1', 'op_name2']
},
{
'exclude': True,
'op_names': ['op_name3']
}
]
其表示压缩操作的默认稀疏度为 0.8,但 ``op_name1`` 和 ``op_name2`` 会使用 0.6,且不压缩 ``op_name3``。
量化算法特定键
^^^^^^^^^^^^^^^^^^^^^^^^^^
如果使用量化算法,则需要设置下面的 ``config_list``。 如果使用剪枝算法,则可以忽略这些键值。
* **quant_types** : 字符串列表。
要应用量化的类型,当前支持 "权重","输入","输出"。 "权重"是指将量化操作
应用到 module 的权重参数上。 "输入" 是指对 module 的 forward 方法的输入应用量化操作。 "输出"是指将量化运法应用于模块 forward 方法的输出,在某些论文中,这种方法称为"激活"。
* **quant_bits**:int 或 dict {str : int}
量化的位宽,键是量化类型,值是量化位宽度,例如:
.. code-block:: bash
{
quant_bits: {
'weight': 8,
'output': 4,
},
}
当值为 int 类型时,所有量化类型使用相同的位宽。 例如:
.. code-block:: bash
{
quant_bits: 8, # 权重和输出的位宽都为 8 bits
}
下面的示例展示了一个更完整的 ``config_list``,它使用 ``op_names``(或者 ``op_types``)指定目标层以及这些层的量化位数。
.. code-block:: bash
config_list = [{
'quant_types': ['weight'],
'quant_bits': 8,
'op_names': ['conv1']
}, {
'quant_types': ['weight'],
'quant_bits': 4,
'quant_start_step': 0,
'op_names': ['conv2']
}, {
'quant_types': ['weight'],
'quant_bits': 3,
'op_names': ['fc1']
},
{
'quant_types': ['weight'],
'quant_bits': 2,
'op_names': ['fc2']
}
]
在这个示例中,'op_names' 是层的名字,四个层将被量化为不同的 quant_bits。
导出压缩结果
-------------------------
导出裁剪后的模型
^^^^^^^^^^^^^^^^^^^^^^^
使用下列 API 可轻松将裁剪后的模型导出,稀疏模型权重的 ``state_dict`` 会保存在 ``model.pth`` 文件中,可通过 ``torch.load('model.pth')`` 加载。 注意,导出的 ``model.pth`` 具有与原始模型相同的参数,只是掩码的权重为零。 ``mask_dict`` 存储剪枝算法产生的二进制值,可以进一步用来加速模型。
.. code-block:: python
# 导出模型的权重和掩码。
pruner.export_model(model_path='model.pth', mask_path='mask.pth')
# 将掩码应用到模型
from nni.compression.pytorch import apply_compression_results
apply_compression_results(model, mask_file, device)
用 ``onnx`` 格式导出模型,(需要指定\ ``input_shape`` ):
.. code-block:: python
pruner.export_model(model_path='model.pth', mask_path='mask.pth', onnx_path='model.onnx', input_shape=[1, 1, 28, 28])
导出量化后的模型
^^^^^^^^^^^^^^^^^^^^^^^^^^
您可以使用 ``torch.save`` api 直接导出量化模型。量化后的模型可以通过 ``torch.load`` 加载,不需要做任何额外的修改。 下面的例子展示了使用 QAT quantizer 保存、加载量化模型并获取相关参数的过程。
.. code-block:: python
# 保存使用 NNI QAT 算法生成的量化模型
torch.save(model.state_dict(), "quantized_model.pth")
# 模拟模型加载过程
# 初始化新模型并在加载之前压缩它
qmodel_load = Mnist()
optimizer = torch.optim.SGD(qmodel_load.parameters(), lr=0.01, momentum=0.5)
quantizer = QAT_Quantizer(qmodel_load, config_list, optimizer)
quantizer.compress()
# 加载量化的模型
qmodel_load.load_state_dict(torch.load("quantized_model.pth"))
# 获取加载后模型的 scale, zero_point 和 conv1 的权重
conv1 = qmodel_load.conv1
scale = conv1.module.scale
zero_point = conv1.module.zero_point
weight = conv1.module.weight
模型加速
------------------
掩码实际上并不能加速模型。 应该基于导出的掩码来对模型加速,因此,NNI 提供了 API 来加速模型。 在模型上调用 ``apply_compression_results`` 后,模型会变得更小,推理延迟也会减小。
.. code-block:: python
from nni.compression.pytorch import apply_compression_results, ModelSpeedup
dummy_input = torch.randn(config['input_shape']).to(device)
m_speedup = ModelSpeedup(model, dummy_input, masks_file, device)
m_speedup.speedup_model()
参考 `这里 <ModelSpeedup.rst>`__,了解详情。 模型加速的示例代码在 :githublink:`这里 <examples/model_compress/pruning/model_speedup.py>`。
控制微调过程
-------------------------------
控制微调的 API
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
某些压缩算法会控制微调过程中的压缩进度(例如, `AGP <../Compression/Pruner.rst#agp-pruner>`__),一些算法需要在每个批处理步骤后执行一些逻辑。 因此,NNI 提供了两个 API:``pruner.update_epoch(epoch)`` 和 ``pruner.step()``。
``update_epoch`` 会在每个 Epoch 时调用,而 ``step`` 会在每次批处理后调用。 注意,大多数算法不需要调用这两个 API。 详细情况可参考具体算法文档。 对于不需要这两个 API 的算法,可以调用它们,但不会有实际作用。
强化微调过程
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
知识蒸馏有效地从大型教师模型中学习小型学生模型。 用户可以通过知识蒸馏来增强模型的微调过程,提高压缩模型的性能。 示例代码在 :githublink:`这里 <examples/model_compress/pruning/finetune_kd_torch.py>`。
高级用法
==============
.. toctree::
:maxdepth: 2
框架 <./Framework>
自定义压缩算法 <./CustomizeCompressor>
自动模型压缩 <./AutoPruningUsingTuners>
...@@ -40,7 +40,7 @@ GBDTSelector 基于 `LightGBM <https://github.com/microsoft/LightGBM>`__,这 ...@@ -40,7 +40,7 @@ GBDTSelector 基于 `LightGBM <https://github.com/microsoft/LightGBM>`__,这
也可在 ``/examples/feature_engineering/gbdt_selector/`` 目录找到示例。 也可在 ``/examples/feature_engineering/gbdt_selector/`` 目录找到示例。
``fit`` 函数参数要求 **fit 函数参数要求**
* *
......
...@@ -3,7 +3,8 @@ GradientFeatureSelector ...@@ -3,7 +3,8 @@ GradientFeatureSelector
GradientFeatureSelector 的算法来源于 `Feature Gradients: Scalable Feature Selection via Discrete Relaxation <https://arxiv.org/pdf/1908.10382.pdf>`__。 GradientFeatureSelector 的算法来源于 `Feature Gradients: Scalable Feature Selection via Discrete Relaxation <https://arxiv.org/pdf/1908.10382.pdf>`__。
GradientFeatureSelector 算法基于梯度搜索算法的特征选择。 GradientFeatureSelector,基于梯度搜索算法
的特征选择。
1) 该方法扩展了一个近期的结果, 1) 该方法扩展了一个近期的结果,
即在亚线性数据中通过展示计算能迭代的学习(即,在迷你批处理中),在 **线性的时间空间中** 的特征数量 D 及样本大小 N。 即在亚线性数据中通过展示计算能迭代的学习(即,在迷你批处理中),在 **线性的时间空间中** 的特征数量 D 及样本大小 N。
......
...@@ -81,9 +81,9 @@ ...@@ -81,9 +81,9 @@
def sample_final(self): def sample_final(self):
return self.sample_search() # use the same logic here. you can do something different return self.sample_search() # use the same logic here. you can do something different
可以在 :githublink:`这里<src/sdk/pynni/nni/nas/pytorch/random/mutator.py>` 找到随机mutator的完整示例。 可以在 :githublink:`这里 <nni/nas/pytorch/mutator.py>` 找到随机 mutator 的完整示例。
对于高级用法,例如,需要在 ``LayerChoice`` 执行的时候操作模型,可继承 ``BaseMutator``,并重载 ``on_forward_layer_choice`` 和 ``on_forward_input_choice`` 。这些是 ``LayerChoice`` 和 ``InputChoice`` 对应的回调实现。 还可使用属性 ``mutables`` 来获得模型中所有的 ``LayerChoice`` 和 ``InputChoice``。 详情请参考 :githublink:`reference <src/sdk/pynni/nni/nas/pytorch>` 。 对于高级用法,例如,需要在 ``LayerChoice`` 执行的时候操作模型,可继承 ``BaseMutator``,并重载 ``on_forward_layer_choice`` 和 ``on_forward_input_choice`` 。这些是 ``LayerChoice`` 和 ``InputChoice`` 对应的回调实现。 还可使用属性 ``mutables`` 来获得模型中所有的 ``LayerChoice`` 和 ``InputChoice``。 详情请参考 :githublink:`这里 <nni/nas/pytorch/>` 。
.. tip:: .. tip::
用于调试的随机 Mutator。 使用 用于调试的随机 Mutator。 使用
......
...@@ -32,7 +32,7 @@ NAS 基准测试 ...@@ -32,7 +32,7 @@ NAS 基准测试
git clone -b ${NNI_VERSION} https://github.com/microsoft/nni git clone -b ${NNI_VERSION} https://github.com/microsoft/nni
cd nni/examples/nas/benchmarks cd nni/examples/nas/benchmarks
将 ``${NNI_VERSION}`` 替换为发布的版本或分支名称,例如:``v1.9``。 将 ``${NNI_VERSION}`` 替换为发布的版本或分支名称,例如:``v2.0``。
#. #.
通过 ``pip3 install -r xxx.requirements.txt`` 安装依赖。 ``xxx`` 可以是 ``nasbench101``\ ,``nasbench201`` ,``nds``。 通过 ``pip3 install -r xxx.requirements.txt`` 安装依赖。 ``xxx`` 可以是 ``nasbench101``\ ,``nasbench201`` ,``nds``。
...@@ -44,12 +44,13 @@ NAS 基准测试 ...@@ -44,12 +44,13 @@ NAS 基准测试
示例用法 示例用法
-------------- --------------
请参考 `Benchmarks API 的示例用法 <./BenchmarksExample>`_。 请参考 `Benchmarks API 的示例用法 <./BenchmarksExample.rst>`_。
NAS-Bench-101 NAS-Bench-101
------------- -------------
`Paper link <https://arxiv.org/abs/1902.09635>`__ &nbsp; &nbsp; `Open-source <https://github.com/google-research/nasbench>`__ * `论文链接 <https://arxiv.org/abs/1902.09635>`__
* `开源地址 <https://github.com/google-research/nasbench>`__
NAS-Bench-101 包含 423,624 个独立的神经网络,再加上 4 个 Epoch (4, 12, 36, 108) 时的变化,以及每个都要训练 3 次。 这是基于 Cell 的搜索空间,通过枚举最多 7 个有向图的运算符来构造并堆叠 Cell,连接数量不超过 9 个。 除了第一个 (必须为 ``INPUT`` ) 和最后一个运算符 (必须为 ``OUTPUT`` ),可选的运算符有 ``CONV3X3_BN_RELU`` , ``CONV1X1_BN_RELU`` 和 ``MAXPOOL3X3`` 。 NAS-Bench-101 包含 423,624 个独立的神经网络,再加上 4 个 Epoch (4, 12, 36, 108) 时的变化,以及每个都要训练 3 次。 这是基于 Cell 的搜索空间,通过枚举最多 7 个有向图的运算符来构造并堆叠 Cell,连接数量不超过 9 个。 除了第一个 (必须为 ``INPUT`` ) 和最后一个运算符 (必须为 ``OUTPUT`` ),可选的运算符有 ``CONV3X3_BN_RELU`` , ``CONV1X1_BN_RELU`` 和 ``MAXPOOL3X3`` 。
...@@ -85,7 +86,9 @@ API 文档 ...@@ -85,7 +86,9 @@ API 文档
NAS-Bench-201 NAS-Bench-201
------------- -------------
`Paper link <https://arxiv.org/abs/2001.00326>`__ &nbsp; &nbsp; `Open-source API <https://github.com/D-X-Y/NAS-Bench-201>`__ &nbsp; &nbsp;\ `Implementations <https://github.com/D-X-Y/AutoDL-Projects>`__ * `论文链接 <https://arxiv.org/abs/2001.00326>`__
* `开源 API <https://github.com/D-X-Y/NAS-Bench-201>`__
* `实现 <https://github.com/D-X-Y/AutoDL-Projects>`__
NAS-Bench-201 是单元格的搜索空间,并将张量当作节点,运算符当作边。 搜索空间包含了 4 个节点所有密集连接的有向图,共有 15,625 个候选项。 每个操作符都是从预定义的运算符集(\ ``NONE``\ ,``SKIP_CONNECT``\ ,``CONV_1X1``\ ,``CONV_3X3`` 和``AVG_POOL_3X3``\ )中选出的。 训练方法根据数据集 (CIFAR-10, CIFAR-100, ImageNet) 和 Epoch 数量 (12 和 200),而有所不同。 每个架构和训练方法的组合会随机重复 1 到 3 次。 NAS-Bench-201 是单元格的搜索空间,并将张量当作节点,运算符当作边。 搜索空间包含了 4 个节点所有密集连接的有向图,共有 15,625 个候选项。 每个操作符都是从预定义的运算符集(\ ``NONE``\ ,``SKIP_CONNECT``\ ,``CONV_1X1``\ ,``CONV_3X3`` 和``AVG_POOL_3X3``\ )中选出的。 训练方法根据数据集 (CIFAR-10, CIFAR-100, ImageNet) 和 Epoch 数量 (12 和 200),而有所不同。 每个架构和训练方法的组合会随机重复 1 到 3 次。
...@@ -113,7 +116,8 @@ API 文档 ...@@ -113,7 +116,8 @@ API 文档
NDS NDS
--- ---
`论文链接 <https://arxiv.org/abs/1905.13214>`__ , `开源代码 <https://github.com/facebookresearch/nds>`__ * `论文链接 <https://arxiv.org/abs/1905.13214>`__
* `开源地址 <https://github.com/facebookresearch/nds>`__
*On Network Design Spaces for Visual Recognition* 发布了来自多个模型系列,超过 100,000 个配置(模型加超参组合)的统计,包括 vanilla (受 VGG 启发的松散前馈网络), ResNet 和 ResNeXt (残差基本模块和残差瓶颈模块) 以及 NAS 单元格 (遵循 NASNet, Ameoba, PNAS, ENAS 和 DARTS 的设计)。 大部分配置只采用固定的随机种子训练一次,但少部分会训练两到三次。 *On Network Design Spaces for Visual Recognition* 发布了来自多个模型系列,超过 100,000 个配置(模型加超参组合)的统计,包括 vanilla (受 VGG 启发的松散前馈网络), ResNet 和 ResNeXt (残差基本模块和残差瓶颈模块) 以及 NAS 单元格 (遵循 NASNet, Ameoba, PNAS, ENAS 和 DARTS 的设计)。 大部分配置只采用固定的随机种子训练一次,但少部分会训练两到三次。
......
This diff is collapsed.
.. role:: raw-html(raw)
:format: html
百里挑一:一站式神经体系结构搜索的优先路径提取 百里挑一:一站式神经体系结构搜索的优先路径提取
======================================================================================= =======================================================================================
`[Paper] <https://papers.nips.cc/paper/2020/file/d072677d210ac4c03ba046120f0802ec-Paper.pdf>`__ `[Models-Google Drive] <https://drive.google.com/drive/folders/1NLGAbBF9bA1IUAxKlk2VjgRXhr6RHvRW?usp=sharing>`__ `[Models-Baidu Disk (PWD: wqw6)] <https://pan.baidu.com/s/1TqQNm2s14oEdyNPimw3T9g>`__ `[BibTex] <https://scholar.googleusercontent.com/scholar.bib?q=info:ICWVXc_SsKAJ:scholar.google.com/&output=citation&scisdr=CgUmooXfEMfTi0cV5aU:AAGBfm0AAAAAX7sQ_aXoamdKRaBI12tAVN8REq1VKNwM&scisig=AAGBfm0AAAAAX7sQ_RdYtp6BSro3zgbXVJU2MCgsG730&scisf=4&ct=citation&cd=-1&hl=ja>`__ :raw-html:`<br/>` * `论文 <https://papers.nips.cc/paper/2020/file/d072677d210ac4c03ba046120f0802ec-Paper.pdf>`__
* `模型 - Google Drive <https://drive.google.com/drive/folders/1NLGAbBF9bA1IUAxKlk2VjgRXhr6RHvRW?usp=sharing>`__
* `模型 - 百度网盘(提取码:wqw6) <https://pan.baidu.com/s/1TqQNm2s14oEdyNPimw3T9g>`__
* `BibTex <https://scholar.googleusercontent.com/scholar.bib?q=info:ICWVXc_SsKAJ:scholar.google.com/&output=citation&scisdr=CgUmooXfEMfTi0cV5aU:AAGBfm0AAAAAX7sQ_aXoamdKRaBI12tAVN8REq1VKNwM&scisig=AAGBfm0AAAAAX7sQ_RdYtp6BSro3zgbXVJU2MCgsG730&scisf=4&ct=citation&cd=-1&hl=ja>`__
在这项工作中,我们提出了一简单有效的体系结构提炼方法。 心思想是子网可以在整个训练过程中进行协作学习相互教,目的是促进各个模型的融合。 我们介绍了优先路径的概念,它是指在训练过程中表现出卓越性能的体系结构候选人。 从优先路径中提取知识可以促进子网的训练。 由于优先路径会根据其性能和复杂性而动态变化,因此最终获得的路径就是百里挑一。 与最近的架构 `MobileNetV3 <https://arxiv.org/abs/1905.02244>`__ 和 `EfficientNet <https://arxiv.org/abs/1905.11946>`__ 系列在对齐设置下相比,发现的体系结构具有更高的性能。 在这项工作中,我们提出了一简单有效的架构蒸馏方法。 其核心思想是,为了促进各个模型的收敛,子网在整个训练过程中进行协作学习相互传授。 并引入了优先路径的概念,它是指在训练过程中表现出优异性能的候选模型结构。 从优先路径中提取知识可以促进子网的训练。 由于优先路径会根据其性能和复杂性而动态变化,因此最终获得的路径就是百里挑一。 与最近的架构 `MobileNetV3 <https://arxiv.org/abs/1905.02244>`__ 和 `EfficientNet <https://arxiv.org/abs/1905.11946>`__ 系列在相同的配置下比较,Cream 发现的体系结构具有更高的性能。
.. image:: https://raw.githubusercontent.com/microsoft/Cream/main/demo/intro.jpg .. image:: https://raw.githubusercontent.com/microsoft/Cream/main/demo/intro.jpg
...@@ -51,7 +50,6 @@ ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 Imag ...@@ -51,7 +50,6 @@ ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 Imag
.. image:: ../../img/cream_flops600.jpg .. image:: ../../img/cream_flops600.jpg
:scale: 50% :scale: 50%
示例 示例
-------- --------
...@@ -62,7 +60,7 @@ ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 Imag ...@@ -62,7 +60,7 @@ ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 Imag
准备数据 准备数据
---------------- ----------------
首先你需要下载 `ImageNet-2012 <http://www.image-net.org/>`__ 到目录 ``./data/imagenet`` 里,然后把验证集移动到子文件夹 ``./data/imagenet/val`` 。 你可以用下面这个命令来移动验证集:https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh 首先你需要下载 `ImageNet-2012 <http://www.image-net.org/>`__ 到目录 ``./data/imagenet`` 里,然后把验证集移动到子文件夹 ``./data/imagenet/val`` 。 你可以用 `此脚本 <https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh>`__ 来移动验证集。
把 imagenet 数据放在 ``./data`` 里, 如下: 把 imagenet 数据放在 ``./data`` 里, 如下:
...@@ -75,7 +73,7 @@ ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 Imag ...@@ -75,7 +73,7 @@ ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 Imag
快速入门 快速入门
----------- -----------
I. 搜索 1. 搜索
^^^^^^^^^ ^^^^^^^^^
首先构建搜索环境。 首先构建搜索环境。
...@@ -105,8 +103,8 @@ I. 搜索 ...@@ -105,8 +103,8 @@ I. 搜索
搜索的体系结构需要重新训练并获得最终模型。 最终模型以 ``.pth.tar`` 格式保存。 训练代码不久就会发布。 搜索的体系结构需要重新训练并获得最终模型。 最终模型以 ``.pth.tar`` 格式保存。 训练代码不久就会发布。
II. 重新训练 2. 重新训练
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^
为了训练搜索的架构,需要配置 ``MODEL_SELECTION`` 参数来指定模型触发器。 在 ``./configs/retrain.yaml`` 文件里加上 ``MODEL_SELECTION`` 可以声明训练模型。 您可以从 [14,43,112,287,481,604] 中选择一个,代表不同的 Flops(MB)。 为了训练搜索的架构,需要配置 ``MODEL_SELECTION`` 参数来指定模型触发器。 在 ``./configs/retrain.yaml`` 文件里加上 ``MODEL_SELECTION`` 可以声明训练模型。 您可以从 [14,43,112,287,481,604] 中选择一个,代表不同的 Flops(MB)。
...@@ -130,7 +128,7 @@ II. 重新训练 ...@@ -130,7 +128,7 @@ II. 重新训练
python -m torch.distributed.launch --nproc_per_node=8 ./retrain.py --cfg ./configs/retrain.yaml python -m torch.distributed.launch --nproc_per_node=8 ./retrain.py --cfg ./configs/retrain.yaml
III. 测试 3. 测试
^^^^^^^^^ ^^^^^^^^^
要测试我们训练的模型,需要使用 ``./configs/test.yaml`` 中的 ``MODEL_SELECTION`` 来指定要测试的模型。 要测试我们训练的模型,需要使用 ``./configs/test.yaml`` 中的 ``MODEL_SELECTION`` 来指定要测试的模型。
......
...@@ -39,8 +39,8 @@ CIFAR10 Macro/Micro 搜索空间 ...@@ -39,8 +39,8 @@ CIFAR10 Macro/Micro 搜索空间
PyTorch PyTorch
^^^^^^^ ^^^^^^^
.. autoclass:: nni.algorithms.nas.pytorch.enas.EnasTrainer .. autoclass:: nni.algorithms.nas.pytorch.enas.EnasTrainer
:members: :members:
.. autoclass:: nni.algorithms.nas.pytorch.enas.EnasMutator .. autoclass:: nni.algorithms.nas.pytorch.enas.EnasMutator
:members: :members:
...@@ -48,7 +48,7 @@ One-Shot NAS 算法 ...@@ -48,7 +48,7 @@ One-Shot NAS 算法
**注意** ,在使用 One-Shot NAS 算法时,不需要启动 NNI Experiment 不需要 ``nnictl`` ,可直接运行 Python 脚本(即:``train.py`` ),如:``python3 train.py`` 训练完成后,可通过 ``trainer.export()`` 导出找到的最好的模型。 **注意** ,在使用 One-Shot NAS 算法时,不需要启动 NNI Experiment 不需要 ``nnictl`` ,可直接运行 Python 脚本(即:``train.py`` ),如:``python3 train.py`` 训练完成后,可通过 ``trainer.export()`` 导出找到的最好的模型。
NNI 中每个 Trainer 都用其对应的场景和用法。 一些 Trainer 假定任务是分类任务;一些 Trainer "epoch" 有不同的定义(如:ENAS 的每个 Epoch 一些子步骤加上 Controller 的步骤)。 大部分 Trainer 不支持分布式训练:没有使用 ``DataParallel`` ``DistributedDataParallel`` 来包装模型。 如果通过试用,想要在定制的应用中使用 Trainer,可能需要 `自定义 Trainer <./Advanced.rst>`__ NNI 中每个 Trainer 都用其对应的场景和用法。 一些 Trainer 假定任务是分类任务;一些 Trainer "epoch" 有不同的定义(如:ENAS 的每个 Epoch 一些子步骤加上 Controller 的步骤)。 大部分 Trainer 不支持分布式训练:没有使用 ``DataParallel`` ``DistributedDataParallel`` 来包装模型。 如果通过试用,想要在定制的应用中使用 Trainer,可能需要 `自定义 Trainer <./Advanced.rst#extend-the-ability-of-one-shot-trainers>`__
此外,可以使用 NAS 可视化来显示 One-Shot NAS `请看细节。 <./Visualization.rst>`__ 此外,可以使用 NAS 可视化来显示 One-Shot NAS `请看细节。 <./Visualization.rst>`__
......
...@@ -29,7 +29,7 @@ NNI 还提供了专门的 `可视化工具 <#nas-visualization>`__,用于查 ...@@ -29,7 +29,7 @@ NNI 还提供了专门的 `可视化工具 <#nas-visualization>`__,用于查
- 算法简介 - 算法简介
* - :githublink:`Random Search <examples/tuners/random_nas_tuner>` * - :githublink:`Random Search <examples/tuners/random_nas_tuner>`
- 从搜索空间中随机选择模型 - 从搜索空间中随机选择模型
* - `PPO Tuner </Tuner/BuiltinTuner.html#PPOTuner>`__ * - `PPO Tuner <../Tuner/BuiltinTuner.rst#PPO-Tuner>`__
- PPO Tuner 是基于 PPO 算法的强化学习 Tuner。 `参考论文 <https://arxiv.org/abs/1707.06347>`__ - PPO Tuner 是基于 PPO 算法的强化学习 Tuner。 `参考论文 <https://arxiv.org/abs/1707.06347>`__
...@@ -46,20 +46,22 @@ NNI 目前支持下面列出的 One-Shot NAS 算法,并且正在添加更多 ...@@ -46,20 +46,22 @@ NNI 目前支持下面列出的 One-Shot NAS 算法,并且正在添加更多
* - Name * - Name
- 算法简介 - 算法简介
* - `ENAS </NAS/ENAS.html>`__ * - `ENAS <ENAS.rst>`__
- `Efficient Neural Architecture Search via Parameter Sharing <https://arxiv.org/abs/1802.03268>`__. 在 ENAS 中,Contoller 学习在大的计算图中搜索最有子图的方式来发现神经网络。 它通过在子模型间共享参数来实现加速和出色的性能指标。 - `Efficient Neural Architecture Search via Parameter Sharing <https://arxiv.org/abs/1802.03268>`__. 在 ENAS 中,Contoller 学习在大的计算图中搜索最有子图的方式来发现神经网络。 它通过在子模型间共享参数来实现加速和出色的性能指标。
* - `DARTS </NAS/DARTS.html>`__ * - `DARTS <DARTS.rst>`__
- `DARTS: Differentiable Architecture Search <https://arxiv.org/abs/1806.09055>`__ 介绍了一种用于双级优化的可区分网络体系结构搜索的新算法。 - `DARTS: Differentiable Architecture Search <https://arxiv.org/abs/1806.09055>`__ 介绍了一种用于双级优化的可区分网络体系结构搜索的新算法。
* - `P-DARTS </NAS/PDARTS.html>`__ * - `P-DARTS <PDARTS.rst>`__
- `Progressive Differentiable Architecture Search: Bridging the Depth Gap between Search and Evaluation <https://arxiv.org/abs/1904.12760>`__ 这篇论文是基于 DARTS 的. 它引入了一种有效的算法,可在搜索过程中逐渐增加搜索的深度。 - `Progressive Differentiable Architecture Search: Bridging the Depth Gap between Search and Evaluation <https://arxiv.org/abs/1904.12760>`__ 这篇论文是基于 DARTS 的. 它引入了一种有效的算法,可在搜索过程中逐渐增加搜索的深度。
* - `SPOS </NAS/SPOS.html>`__ * - `SPOS <SPOS.rst>`__
- 论文 `Single Path One-Shot Neural Architecture Search with Uniform Sampling <https://arxiv.org/abs/1904.00420>`__ 构造了一个采用统一的路径采样方法来训练简化的超网络,并使用进化算法来提高搜索神经网络结构的效率。 - `Single Path One-Shot Neural Architecture Search with Uniform Sampling <https://arxiv.org/abs/1904.00420>`__ 论文构造了一个采用统一的路径采样方法来训练简化的超网络,并使用进化算法来提高搜索神经网络结构的效率。
* - `CDARTS </NAS/CDARTS.html>`__ * - `CDARTS <CDARTS.rst>`__
- `Cyclic Differentiable Architecture Search <https://arxiv.org/abs/****>`__ 在搜索和评估网络之间建立循环反馈机制。 通过引入的循环的可微分架构搜索框架将两个网络集成为一个架构。 - `Cyclic Differentiable Architecture Search <https://arxiv.org/pdf/2006.10724.pdf>`__ 在搜索和评估网络之间建立循环反馈机制。 通过引入的循环的可微分架构搜索框架将两个网络集成为一个架构。
* - `ProxylessNAS </NAS/Proxylessnas.html>`__ * - `ProxylessNAS <Proxylessnas.rst>`__
- `ProxylessNAS: Direct Neural Architecture Search on Target Task and Hardware <https://arxiv.org/abs/1812.00332>`__. 它删除了代理,直接从大规模目标任务和目标硬件平台进行学习。 - `ProxylessNAS: Direct Neural Architecture Search on Target Task and Hardware <https://arxiv.org/abs/1812.00332>`__. 它删除了代理,直接从大规模目标任务和目标硬件平台进行学习。
* - `TextNAS </NAS/TextNAS.html>`__ * - `TextNAS <TextNAS.rst>`__
- `TextNAS: A Neural Architecture Search Space tailored for Text Representation <https://arxiv.org/pdf/1912.10729.pdf>`__. 这是专门用于文本表示的神经网络架构搜索算法。 - `TextNAS: A Neural Architecture Search Space tailored for Text Representation <https://arxiv.org/pdf/1912.10729.pdf>`__. 这是专门用于文本表示的神经网络架构搜索算法。
* - `Cream <Cream.rst>`__
- `Cream of the Crop: Distilling Prioritized Paths For One-Shot Neural Architecture Search <https://papers.nips.cc/paper/2020/file/d072677d210ac4c03ba046120f0802ec-Paper.pdf>`__. 一种新的 NAS 算法,无需使用进化算法即可提取搜索空间中的优先路径。 在 ImageNet 上的性能具有竞争力,特别是对于小模型(例如: FLOPs < 200 M 时)。
One-shot 算法 **独立运行,不需要 nnictl**。 NNI 支持 PyTorch 和 TensorFlow 2.x。 One-shot 算法 **独立运行,不需要 nnictl**。 NNI 支持 PyTorch 和 TensorFlow 2.x。
......
...@@ -56,8 +56,7 @@ NNI 上的 ProxylessNAS ...@@ -56,8 +56,7 @@ NNI 上的 ProxylessNAS
在NNI上的实现基于 `官方实现 <https://github.com/mit-han-lab/ProxylessNAS>`__ 。 官方实现支持两种搜索方法:梯度下降和强化学习,还支持不同的硬件,包括 'mobile', 'cpu', 'gpu8', 'flops'。 在当前的 NNI 实现中,支持梯度下降训练方法,不支持不同的硬件。 完整支持正在进行中。 在NNI上的实现基于 `官方实现 <https://github.com/mit-han-lab/ProxylessNAS>`__ 。 官方实现支持两种搜索方法:梯度下降和强化学习,还支持不同的硬件,包括 'mobile', 'cpu', 'gpu8', 'flops'。 在当前的 NNI 实现中,支持梯度下降训练方法,不支持不同的硬件。 完整支持正在进行中。
下面将介绍实现的细节。 像 NNI 上其它 one-shot NAS 算法一样,ProxylessNAS 由两部分组成:*搜索空间* 和 *训练方法*。 为了让用户灵活地定义自己的搜索空间并使用内置的 ProxylessNAS 训练方法,我们将指定的搜索空间放在 :githublink:`示例代码 <examples/nas/proxylessnas>` 使用 :githublink:`NNI NAS 接口<src/sdk/pynni/nni/nas/pytorch/proxylessnas>`。 下面将介绍实现的细节。 像 NNI 上其它 one-shot NAS 算法一样,ProxylessNAS 由两部分组成:*搜索空间* 和 *训练方法*。 为了让用户灵活地定义自己的搜索空间并使用内置的 ProxylessNAS 训练方法,我们将指定的搜索空间放在 :githublink:`示例代码 <examples/nas/proxylessnas>` 使用 :githublink:`NNI NAS 接口 <nni/algorithms/nas/pytorch/proxylessnas>`。
.. image:: ../../img/proxylessnas.png .. image:: ../../img/proxylessnas.png
:target: ../../img/proxylessnas.png :target: ../../img/proxylessnas.png
......
...@@ -82,7 +82,7 @@ ENAS Micro 的搜索空间如下图所示。 请注意,在 NNI 的实现中将 ...@@ -82,7 +82,7 @@ ENAS Micro 的搜索空间如下图所示。 请注意,在 NNI 的实现中将
ENASMicroLayer ENASMicroLayer
-------------- --------------
层是从设计的模型中提取的 :githublink:`这里 <examples/nas/enas>`. 一个模型包含共享结构的多个块。 一个块由一些常规层和约简层组成,``ENASMicroLayer`` 是这两型层的统一实现。 这两类层之间的唯一区别是约简层的所有操作 ``stride=2``。 层是 :githublink:`这里 <examples/nas/enas>` 的模型提取出来的。 一个模型包含共享结构的多个块。 一个块由一些常规层和约简层组成,``ENASMicroLayer`` 是这两型层的统一实现。 这两类层之间的唯一区别是约简层的所有操作 ``stride=2``。
ENAS Micro 的一个 cell 是含有 N 个节点的有向无环图。其中节点表示张量,边表示 N 个节点间的信息流。 一个 cell 包含两个输入节点和一个输出节点。 接下来节点选择前两个之前的节点作为输入,并从 `预定义的的操作集 <#predefined-operations-enas>`__ 中选择两个操作,分别应用到输入上,然后将它们相加为该节点的输出。 例如,节点 4 选择节点 1 和节点 3 作为输入,然后分别对输入应用 ENAS Micro 的一个 cell 是含有 N 个节点的有向无环图。其中节点表示张量,边表示 N 个节点间的信息流。 一个 cell 包含两个输入节点和一个输出节点。 接下来节点选择前两个之前的节点作为输入,并从 `预定义的的操作集 <#predefined-operations-enas>`__ 中选择两个操作,分别应用到输入上,然后将它们相加为该节点的输出。 例如,节点 4 选择节点 1 和节点 3 作为输入,然后分别对输入应用
``MaxPool`` 和 ``AvgPool``,然后将它们相加作为节点 4 的输出。 未用作任何其他节点输入的节点将被视为该层的输出。 如果有多个输出节点,则模型将计算这些节点的平均值作为当前层的输出。 ``MaxPool`` 和 ``AvgPool``,然后将它们相加作为节点 4 的输出。 未用作任何其他节点输入的节点将被视为该层的输出。 如果有多个输出节点,则模型将计算这些节点的平均值作为当前层的输出。
...@@ -193,10 +193,11 @@ ENAS Macro 的搜索空间如下图所示。 ...@@ -193,10 +193,11 @@ ENAS Macro 的搜索空间如下图所示。
首先将所有输入传递到 StdConv,该操作由 1x1Conv,BatchNorm2d 和 ReLU 组成。 然后进行下列的操作之一。 最终结果通过后处理,包括BatchNorm2d和ReLU。 首先将所有输入传递到 StdConv,该操作由 1x1Conv,BatchNorm2d 和 ReLU 组成。 然后进行下列的操作之一。 最终结果通过后处理,包括BatchNorm2d和ReLU。
Separable Conv3x3:如果 ``separable=True``,则 cell 将使用 `SepConv <#DilConv>`__ 而不是常规的卷积操作。 SepConv 固定为 ``kernel_size=3``\ , ``stride=1`` 和 ``padding=1``。 * Separable Conv3x3:如果 ``separable=True``,则 cell 将使用 `SepConv <#DilConv>`__ 而不是常规的卷积操作。 SepConv 固定为 ``kernel_size=3``\ , ``stride=1`` 和 ``padding=1``。
* Separable Conv5x5: SepConv 固定为 ``kernel_size=5``\ , ``stride=1`` 和 ``padding=2``。 * Separable Conv5x5: SepConv 固定为 ``kernel_size=5``\ , ``stride=1`` 和 ``padding=2``。
* 普通的 Conv3x3: 如果 ``separable=False``\ , cell 将使用常规的转化操作 ``kernel_size=3``\ , ``stride=1`` 和 ``padding=1``。 * Normal Conv3x3: 如果 ``separable=False``\ , cell 将使用常规的转化操作 ``kernel_size=3``\ , ``stride=1`` 和 ``padding=1``。
* 普通的 Conv5x5:Conv 固定为 ``kernel_size=5``\ , ``stride=1`` 和 ``padding=2``。 *
Normal Conv5x5:Conv 固定为 ``kernel_size=5``\ , ``stride=1`` 和 ``padding=2``。
.. autoclass:: nni.nas.pytorch.search_space_zoo.enas_ops.ConvBranch .. autoclass:: nni.nas.pytorch.search_space_zoo.enas_ops.ConvBranch
......
...@@ -21,7 +21,7 @@ NAS 可视化(测试版) ...@@ -21,7 +21,7 @@ NAS 可视化(测试版)
可视化定制的 Trainer 可视化定制的 Trainer
------------------------------ ------------------------------
如果要定制 Trainer,参考 `文档 <./Advanced.rst>`__。 如果要定制 Trainer,参考 `文档 <./Advanced.rst#extend-the-ability-of-one-shot-trainers>`__。
需要对已有 Trainer 代码做两处改动来支持可视化: 需要对已有 Trainer 代码做两处改动来支持可视化:
......
定义搜索空间 编写搜索空间
==================== ====================
通常,搜索空间是要在其中找到最好结构的候选项。 无论是经典 NAS 还是 One-Shot NAS,不同的搜索算法都需要搜索空间。 NNI 提供了统一的 API 来表达神经网络架构的搜索空间。 通常,搜索空间是要在其中找到最好结构的候选项。 无论是经典 NAS 还是 One-Shot NAS,不同的搜索算法都需要搜索空间。 NNI 提供了统一的 API 来表达神经网络架构的搜索空间。
...@@ -58,10 +58,10 @@ ...@@ -58,10 +58,10 @@
# ... same ... # ... same ...
return output return output
Input Choice 可被视为可调用的模块,它接收张量数组,输出其中部分的连接、求和、平均(默认为求和),或没有选择时输出 ``None``。 就像 layer choices, input choices 应该 **用** ``__init__`` **来初始化用** ``forward`` **来回调**。 这会让搜索算法找到这些 Choice,并进行所需的准备。 Input Choice 可被视为可调用的模块,它接收张量数组,输出其中部分的连接、求和、平均(默认为求和),或没有选择时输出 ``None``。 就像 layer choices, input choices应该 ``__init__`` 来初始化用 ``forward`` 来回调。 这会让搜索算法找到这些 Choice,并进行所需的准备。
``LayerChoice`` and ``InputChoice`` 都是 **mutables**。 Mutable 表示 "可变化的"。 与传统深度学习层、模型都是固定的不同,使用 Mutable 的模块,是一组可能选择的模型。 ``LayerChoice`` and ``InputChoice`` 都是 **mutables**。 Mutable 表示 "可变化的"。 与传统深度学习层、模型都是固定的不同,使用 Mutable 的模块,是一组可能选择的模型。
用户可以为每一个 mutable 声明一个 key。 默认情况下,NNI 会分配全局唯一的,但如果需要共享 Choice(例如,两个 ``LayerChoice`` 有同样的候选操作,希望共享同样的 Choice。即,如果一个选择了第 i 个操作,第二个也要选择第 i 个操作),那么就应该给它们相同的 key。 key 标记了此 Choice,并会在存储的检查点中使用。 如果要增加导出架构的可读性,可为每个 Mutable 的 key 指派名称。 mutables 的高级用法请参照文档 `Mutables <./NasReference.rst>`__。 用户可以为每一个 mutable 声明一个 key。 默认情况下,NNI 会分配全局唯一的,但如果需要共享 Choice(例如,两个 ``LayerChoice`` 有同样的候选操作,希望共享同样的 Choice。即,如果一个选择了第 i 个操作,第二个也要选择第 i 个操作),那么就应该给它们相同的 key。 key 标记了此 Choice,并会在存储的检查点中使用。 如果要增加导出架构的可读性,可为每个 Mutable 的 key 指派名称。 mutables 的高级用法请参照文档 `Mutables <./NasReference.rst>`__。
定义了搜索空间后,下一步是从中找到最好的模型。 至于如何从定义的搜索空间进行搜索请参阅 `classic NAS algorithms <./ClassicNas.rst>`__ 和 `one-shot NAS algorithms <./NasGuide.rst>`__ 。 定义了搜索空间后,下一步是从中找到最好的模型。 至于如何从定义的搜索空间进行搜索请参阅 `经典 NAS 算法 <./ClassicNas.rst>`__ 和 `one-shot NAS 算法 <./NasGuide.rst>`__ 。
Retiarii API 参考
======================
.. contents::
内联 Mutation API
----------------------------------------
.. autoclass:: nni.retiarii.nn.pytorch.LayerChoice
:members:
.. autoclass:: nni.retiarii.nn.pytorch.InputChoice
:members:
.. autoclass:: nni.retiarii.nn.pytorch.ValueChoice
:members:
.. autoclass:: nni.retiarii.nn.pytorch.ChosenInputs
:members:
图 Mutation API
--------------------------------------
.. autoclass:: nni.retiarii.Mutator
:members:
.. autoclass:: nni.retiarii.Model
:members:
.. autoclass:: nni.retiarii.Graph
:members:
.. autoclass:: nni.retiarii.Node
:members:
.. autoclass:: nni.retiarii.Edge
:members:
.. autoclass:: nni.retiarii.Operation
:members:
Trainers
--------
.. autoclass:: nni.retiarii.trainer.FunctionalTrainer
:members:
.. autoclass:: nni.retiarii.trainer.pytorch.lightning.LightningModule
:members:
.. autoclass:: nni.retiarii.trainer.pytorch.lightning.Classification
:members:
.. autoclass:: nni.retiarii.trainer.pytorch.lightning.Regression
:members:
Oneshot Trainers
----------------
.. autoclass:: nni.retiarii.trainer.pytorch.DartsTrainer
:members:
.. autoclass:: nni.retiarii.trainer.pytorch.EnasTrainer
:members:
.. autoclass:: nni.retiarii.trainer.pytorch.ProxylessTrainer
:members:
.. autoclass:: nni.retiarii.trainer.pytorch.SinglePathTrainer
:members:
Strategies
----------
.. autoclass:: nni.retiarii.strategy.Random
:members:
.. autoclass:: nni.retiarii.strategy.GridSearch
:members:
.. autoclass:: nni.retiarii.strategy.RegularizedEvolution
:members:
.. autoclass:: nni.retiarii.strategy.TPEStrategy
:members:
Retiarii Experiments
--------------------
.. autoclass:: nni.retiarii.experiment.pytorch.RetiariiExperiment
:members:
.. autoclass:: nni.retiarii.experiment.pytorch.RetiariiExeConfig
:members:
使用 Retiarii 进行神经网络架构搜索(实验性)
==============================================================================================================
`Retiarii <https://www.usenix.org/system/files/osdi20-zhang_quanlu.pdf>`__ 是一个支持神经体系架构搜索和超参数调优的新框架。 它允许用户以高度的灵活性表达各种搜索空间,重用许多前沿搜索算法,并利用系统级优化来加速搜索过程。 该框架提供了以下全新的用户体验。
* 搜索空间可以直接在用户模型代码中表示。 调优空间可以通过定义模型来表示。
* 在 Experiment 中,神经架构候选项和超参数候选项得到了更友好的支持。
* Experiment 可以直接从 Python 代码启动。
NNI 正在把 `之前 NAS 框架 <../Overview.rst>`__ *迁移至 Retiarii 框架。 因此,此功能仍然是实验性的。 NNI 建议用户尝试新的框架,并提供有价值的反馈来改进它。 旧框架目前仍受支持。*
.. contents::
有两个步骤来开始神经架构搜索任务的 Experiment。 首先,定义要探索的模型空间。 其次,选择一种搜索方法来探索您定义的模型空间。
定义模型空间
-----------------------
模型空间是由用户定义的,用来表达用户想要探索、认为包含性能良好模型的一组模型。 在这个框架中,模型空间由两部分组成:基本模型和基本模型上可能的突变。
定义基本模型
^^^^^^^^^^^^^^^^^
定义基本模型与定义 PyTorch(或 TensorFlow)模型几乎相同, 只有两个小区别。
* 对于 PyTorch 模块(例如 ``nn.Conv2d``, ``nn.ReLU``),将代码 ``import torch.nn as nn`` 替换为 ``import nni.retiarii.nn.pytorch as nn`` 。
* 一些\ **用户定义**\ 的模块应该用 ``@blackbox_module`` 修饰。 例如,``LayerChoice`` 中使用的用户定义模块应该被修饰。 用户可参考 `这里 <#blackbox-module>`__ 获取 ``@blackbox_module`` 的详细使用说明。
下面是定义基本模型的一个简单的示例,它与定义 PyTorch 模型几乎相同。
.. code-block:: python
import torch.nn.functional as F
import nni.retiarii.nn.pytorch as nn
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(32, 1, 5)
self.pool = nn.MaxPool2d(kernel_size=2)
def forward(self, x):
return self.pool(self.conv(x))
class Model(nn.Module):
def __init__(self):
super().__init__()
self.mymodule = MyModule()
def forward(self, x):
return F.relu(self.mymodule(x))
可参考 :githublink:`Darts 基本模型 <test/retiarii_test/darts/darts_model.py>` 和 :githublink:`Mnasnet 基本模型 <test/retiarii_test/mnasnet/base_mnasnet.py>` 获取更复杂的示例。
定义模型突变
^^^^^^^^^^^^^^^^^^^^^^
基本模型只是一个具体模型,而不是模型空间。 我们为用户提供 API 和原语,用于把基本模型变形成包含多个模型的模型空间。
**以内联方式表示突变**
为了易于使用和向后兼容,我们提供了一些 API,供用户在定义基本模型后轻松表达可能的突变。 API 可以像 PyTorch 模块一样使用。
* ``nn.LayerChoice``, 它允许用户放置多个候选操作(例如,PyTorch 模块),在每个探索的模型中选择其中一个。 *注意,如果候选模块是用户定义的模块,则应将其修饰为* `blackbox module <#blackbox-module>`__。 在下面的例子中,``ops.PoolBN`` 和 ``ops.SepConv`` 应该被修饰。
.. code-block:: python
# import nni.retiarii.nn.pytorch as nn
# 在 `__init__` 中声明
self.layer = nn.LayerChoice([
ops.PoolBN('max', channels, 3, stride, 1),
ops.SepConv(channels, channels, 3, stride, 1),
nn.Identity()
]))
# 在 `forward` 函数中调用
out = self.layer(x)
* ``nn.InputChoice``, 它主要用于选择(或尝试)不同的连接。 它会从设置的几个张量中,选择 ``n_chosen`` 个张量。
.. code-block:: python
# import nni.retiarii.nn.pytorch as nn
# 在 `__init__` 中声明
self.input_switch = nn.InputChoice(n_chosen=1)
# 在 `forward` 函数中调用,三者选一
out = self.input_switch([tensor1, tensor2, tensor3])
* ``nn.ValueChoice``, 它用于从一些候选值中选择一个值。 它能用作 ``nn.modules`` 中的模块和 ``@blackbox_module`` 修饰的用户自定义模块中的输入参数。
.. code-block:: python
# import nni.retiarii.nn.pytorch as nn
# 在 `__init__` 中使用
self.conv = nn.Conv2d(XX, XX, kernel_size=nn.ValueChoice([1, 3, 5])
self.op = MyOp(nn.ValueChoice([0, 1], nn.ValueChoice([-1, 1]))
详细的 API 描述和使用说明在 `这里 <./ApiReference.rst>`__。 使用这些 API 的示例在 :githublink:`Darts base model <test/retiarii_test/darts/darts_model.py>`。
**用 Mutator 表示突变**
尽管内联突变易于使用,但其表达能力有限,无法表达某些模型空间。 为了提高表达能力和灵活性,我们提供了编写 *Mutator* 的原语,方便用户更灵活地修改基本模型。 Mutator 位于基础模型之上,因此具有编辑模型的全部能力。
用户可以按以下方式实例化多个 Mutator,这些 Mutator 将依次依次应用于基本模型来对新模型进行采样。
.. code-block:: python
applied_mutators = []
applied_mutators.append(BlockMutator('mutable_0'))
applied_mutators.append(BlockMutator('mutable_1'))
``BlockMutator`` 由用户定义,表示如何对基本模型进行突变。 用户定义的 Mutator 应该继承 ``Mutator`` 类,并在成员函数 ``mutate`` 中实现突变逻辑。
.. code-block:: python
from nni.retiarii import Mutator
class BlockMutator(Mutator):
def __init__(self, target: str, candidates: List):
super(BlockMutator, self).__init__()
self.target = target
self.candidate_op_list = candidates
def mutate(self, model):
nodes = model.get_nodes_by_label(self.target)
for node in nodes:
chosen_op = self.choice(self.candidate_op_list)
node.update_operation(chosen_op.type, chosen_op.params)
``mutate`` 的输入是基本模型的 graph IR(请参考 `这里 <./ApiReference.rst>`__ 获取 IR 的格式和 API),用户可以使用其成员函数(例如, ``get_nodes_by_label``,``update_operation``)对图进行变异。 变异操作可以与 API ``self.choice`` 相结合,以表示一组可能的突变。 在上面的示例中,节点的操作可以更改为 ``candidate_op_list`` 中的任何操作。
使用占位符使突变更容易:``nn.Placeholder``。 如果要更改模型的子图或节点,可以在此模型中定义一个占位符来表示子图或节点。 然后,使用 Mutator 对这个占位符进行变异,使其成为真正的模块。
.. code-block:: python
ph = nn.Placeholder(label='mutable_0',
related_info={
'kernel_size_options': [1, 3, 5],
'n_layer_options': [1, 2, 3, 4],
'exp_ratio': exp_ratio,
'stride': stride
}
)
Mutator 使用 ``label`` 来标识此占位符,``related_info`` 是 Mutator 所需的信息。 由于 ``related_info`` 是一个 dict,所以它可以包含用户想要输入的任何信息,并将其传递给用户定义的 Mutator。 完整的示例代码在 :githublink:`Mnasnet base model <test/retiarii_test/mnasnet/base_mnasnet.py>`。
探索定义的模型空间
------------------------------------------
在模型空间被定义之后,是时候探索这个模型空间了。 用户可以选择合适的搜索和训练方法来探索模型空间。
创建 Trainer 和探索 Strategy
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**经典搜索方法:**
在这种方法中,Trainer 负责对每个探索的模型进行训练,而 Strategy 则负责对模型进行抽样。 探索模型空间既需要 Trainer,也需要 Strategy。 我们推荐使用 PyTorch-Lightning 编写完整的训练过程。
**Oneshot(权重共享)探索方法:**
在这种方法中,用户只需要一个 Oneshot Trainer,来负责探索和训练。
在下表中,我们列出了可用的 Trainer 和 Strategy。
.. list-table::
:header-rows: 1
:widths: auto
* - Trainer
- Strategy
- Oneshot Trainer
* - 分类
- TPEStrategy
- DartsTrainer
* - 回归
- Random
- EnasTrainer
* -
- GridSearch
- ProxylessTrainer
* -
- RegularizedEvolution
- SinglePathTrainer (RandomTrainer)
使用说明和 API 文档在 `这里 <./ApiReference>`__。
下面是一个使用 Trainer 和 Strategy 的简单示例。
.. code-block:: python
import nni.retiarii.trainer.pytorch.lightning as pl
from nni.retiarii import blackbox
from torchvision import transforms
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))])
train_dataset = blackbox(MNIST, root='data/mnist', train=True, download=True, transform=transform)
test_dataset = blackbox(MNIST, root='data/mnist', train=False, download=True, transform=transform)
lightning = pl.Classification(train_dataloader=pl.DataLoader(train_dataset, batch_size=100),
val_dataloaders=pl.DataLoader(test_dataset, batch_size=100),
max_epochs=10)
.. Note:: 为了使 NNI 能够捕获数据集和 dataloader 并让其分别运行,请使用 ``blackbox`` 包装数据集,并使用 ``pl.DataLoader`` 而不是 ``torch.utils.data.DataLoader``。 参考 ``blackbox_module`` 部分获取更多细节信息。
用户可查看 `API 说明 <./ApiReference.rst>`__ 获取 Trainer 的详细用法。 参考 "`此文档 <./WriteTrainer.rst>`__" 编写一个新的 Trainer,参考 `此文档 <./WriteStrategy.rst>`__ 编写一个新的 Strategy。
发起 Experiment
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
上述内容准备就绪之后,就可以发起 Experiment 以进行模型搜索了。 NNI 设计了统一的接口来发起 Experiment, 示例如下:
.. code-block:: python
exp = RetiariiExperiment(base_model, trainer, applied_mutators, simple_strategy)
exp_config = RetiariiExeConfig('local')
exp_config.experiment_name = 'mnasnet_search'
exp_config.trial_concurrency = 2
exp_config.max_trial_number = 10
exp_config.training_service.use_active_gpu = False
exp.run(exp_config, 8081)
此代码发起了一个 NNI Experiment, 注意,如果使用内联突变,``applied_mutators`` 应为 ``None``。
一个简单 MNIST 示例的完整代码在 :githublink:`这里 <test/retiarii_test/mnist/test.py>`。
可视化 Experiment
^^^^^^^^^^^^^^^^^^^^^^^^^
用户可以像可视化普通的超参数调优 Experiment 一样可视化他们的 Experiment。 例如,在浏览器里打开 ``localhost::8081``,8081 是在 ``exp.run`` 里设置的端口。 参考 `这里 <../../Tutorial/WebUI.rst>`__ 了解更多细节。 如果用户使用的是 Oneshot Trainer,可以参考 `这里 <../Visualization.rst>`__ 去可视化他们的 Experiment。
导出 Experiment 中发现的最佳模型
---------------------------------------------------------------------
如果您使用的是\ *经典搜索方法*,那么您可以从 WebUI 中找到最好的模型。
如果您使用的是 *Oneshot(权重共享)搜索方法*,则可以使用 ``exp.export_top_models`` 导出 Experiment 中发现的几个最佳模型。
高级功能和常见问题
--------------------------------
.. _blackbox-module:
**Blackbox Module**
为了理解修饰器 ``blackbox_module``,首先需要解释一下我们的框架是如何工作的:它将用户定义的模型转换为图表示形式(称为 graph IR),每个实例化的模块都将转换为一个子图, 然后将用户定义的突变应用于图上以生成新的图, 并将每个新图转换回 PyTorch 代码执行。 ``@blackbox_module`` 这里的意思是模块不会被转换成子图,而是被转换成单个图节点。 也就是说,该模块将不再展开。 在以下情况下,用户应该/可以修饰自定义的模块类:
* 当模块类由于某些实现问题无法成功转换为子图时。 例如,目前 Retiarii 的框架不支持 adhoc 循环,如果一个模块的 forward 中有 adhoc 循环,那么这个类应该被修饰成 blackbox 模块。 下面的 ``MyModule`` 应该被修饰:
.. code-block:: python
@blackbox_module
class MyModule(nn.Module):
def __init__(self):
...
def forward(self, x):
for i in range(10): # <- adhoc loop
...
* ``LayerChoice`` 中的候选操作应修饰为 blackbox 模块。 例如,在 ``self.op = nn.LayerChoice([Op1(...), Op2(...), Op3(...)])``中,如果 ``Op1``, ``Op2``, ``Op3`` 是用户自定义的模块,则应该被修饰。
* 当用户希望在模块的输入参数中使用 ``ValueChoice`` 时,应该将该模块修饰为 blackbox 模块。 例如,在 ``self.conv = MyConv(kernel_size=nn.ValueChoice([1, 3, 5]))`` 中,``MyConv`` 应该被修饰。
* 如果没有针对某个模块的突变,那么这个模块\ *可以*\ 修饰成一个 blackbox 模块。
\ No newline at end of file
自定义 Strategy
========================
要编写新策略,应该继承基本策略类 ``BaseStrategy``,然后实现成员函数 ``run`` 此成员函数将 ``base_model`` ``applied_mutators`` 作为输入参数, 并将用户在 ``applied_mutators`` 中指定的 Mutator 应用到 ``base_model`` 中生成新模型。 当应用一个 Mutator 时,应该与一个 sampler 绑定(例如,``RandomSampler``)。 每个 sampler 都实现了从候选值中选择值的 ``choice`` 函数。 Mutator 中调用 ``choice`` 函数是用 sampler 执行的。
下面是一个非常简单的随机策略,它使选择完全随机。
.. code-block:: python
from nni.retiarii import Sampler
class RandomSampler(Sampler):
def choice(self, candidates, mutator, model, index):
return random.choice(candidates)
class RandomStrategy(BaseStrategy):
def __init__(self):
self.random_sampler = RandomSampler()
def run(self, base_model, applied_mutators):
_logger.info('stargety start...')
while True:
avail_resource = query_available_resources()
if avail_resource > 0:
model = base_model
_logger.info('apply mutators...')
_logger.info('mutators: %s', str(applied_mutators))
for mutator in applied_mutators:
mutator.bind_sampler(self.random_sampler)
model = mutator.apply(model)
# 运行模型
submit_models(model)
else:
time.sleep(2)
您会发现此策略事先并不知道搜索空间,每次从 Mutator 调用 ``choice`` 时都会被动地做出决定。 如果一个策略想在做出任何决策(如 TPESMAC)之前知道整个搜索空间,它可以使用 ``Mutator`` 提供的 ``dry_run`` 函数来获取搜索空间。 可以在 :githublink:`这里 <nni/retiarii/strategy/tpe_strategy.py>` 找到一个示例策略。
生成新模型后,该策略可以使用 NNI 提供的API(例如 ``submit_models``, ``is_stopped_exec``)提交模型并获取其报告的结果。 更多的 API `API 参考 <./ApiReference.rst>`__ 中。
\ No newline at end of file
自定义 Trainer
=======================
Trainer 对评估新模型的性能是必要的。 在 NAS 场景中,Trainer 进一步分为两类:
1. **Single-arch trainers**:用于训练和评估单个模型的 Trainer。
2. **One-shot trainers**:端到端同时处理训练和搜索的 Trainer。
Single-arch trainers
--------------------
使用 PyTorch-Lightning
^^^^^^^^^^^^^^^^^^^^^^
NNI 建议以 PyTorch-Lightning 风格编写训练代码,即编写一个 LightningModule,定义训练所需的所有元素(例如 loss function、optimizer),并定义一个 Trainer,使用 dataloader 来执行训练(可选)。 在此之前,请阅读 `PyTorch-lightning 文档 <https://pytorch-lightning.readthedocs.io/>` 了解 PyTorch-lightning 的基本概念和组件。
在实践中,在 NNI 中编写一个新的训练模块应继承 ``nni.retiarii.trainer.pytorch.lightning.LightningModule``,它将在 ``__init__`` 之后调用 ``set_model`` 函数,以将候选模型(由策略生成的)保存为 ``self.model``。 编写其余过程(如 ``training_step``)应与其他 lightning 模块相同。 Trainer 还应该通过两个 API 调用与策略进行通讯(对于中间指标而言为 ``nni.report_intermediate_result``,对于最终指标而言为 ``nni.report_final_result``),分别被添加在 ``on_validation_epoch_end`` 和 ``teardown`` 中。
示例如下。
.. code-block::python
from nni.retiarii.trainer.pytorch.lightning import LightningModule # please import this one
@blackbox_module
class AutoEncoder(LightningModule):
def __init__(self):
super().__init__()
self.decoder = nn.Sequential(
nn.Linear(3, 64),
nn.ReLU(),
nn.Linear(64, 28*28)
)
def forward(self, x):
embedding = self.model(x) # let's search for encoder
return embedding
def training_step(self, batch, batch_idx):
# training_step 定义了训练循环
# 它独立于 forward 函数
x, y = batch
x = x.view(x.size(0), -1)
z = self.model(x) # model is the one that is searched for
x_hat = self.decoder(z)
loss = F.mse_loss(x_hat, x)
# 默认日志记录到 TensorBoard
self.log('train_loss', loss)
return loss
def validation_step(self, batch, batch_idx):
x, y = batch
x = x.view(x.size(0), -1)
z = self.model(x)
x_hat = self.decoder(z)
loss = F.mse_loss(x_hat, x)
self.log('val_loss', loss)
def configure_optimizers(self):
optimizer = torch.optim.Adam(self.parameters(), lr=1e-3)
return optimizer
def on_validation_epoch_end(self):
nni.report_intermediate_result(self.trainer.callback_metrics['val_loss'].item())
def teardown(self, stage):
if stage == 'fit':
nni.report_final_result(self.trainer.callback_metrics['val_loss'].item())
然后,用户需要将所有东西(包括 LightningModule、trainer 和 dataloaders)包装成一个 ``Lightning`` 对象,并将这个对象传递给 Retiarii Experiment。
.. code-block::python
import nni.retiarii.trainer.pytorch.lightning as pl
from nni.retiarii.experiment.pytorch import RetiariiExperiment
lightning = pl.Lightning(AutoEncoder(),
pl.Trainer(max_epochs=10),
train_dataloader=pl.DataLoader(train_dataset, batch_size=100),
val_dataloaders=pl.DataLoader(test_dataset, batch_size=100))
experiment = RetiariiExperiment(base_model, lightning, mutators, strategy)
使用 FunctionalTrainer
^^^^^^^^^^^^^^^^^^^^^^
还有另一种使用功能性 API 自定义新 Trainer 的方法,该方法提供了更大的灵活性。 用户只需要编写一个 fit 函数来包装所有内容。 此函数接收一个位置参数(model)和可能的关键字参数。 通过这种方式,用户可以控制一切,但向框架公开的信息较少,因此可能进行优化的机会也较少。 示例如下。
.. code-block::python
from nni.retiarii.trainer import FunctionalTrainer
from nni.retiarii.experiment.pytorch import RetiariiExperiment
def fit(model, dataloader):
train(model, dataloader)
acc = test(model, dataloader)
nni.report_final_result(acc)
trainer = FunctionalTrainer(fit, dataloader=DataLoader(foo, bar))
experiment = RetiariiExperiment(base_model, trainer, mutators, strategy)
One-shot trainers
-----------------
One-shot Trainer 应继承 ``nni.retiarii.trainer.BaseOneShotTrainer``,并需要实现``fit()`` 函数(用于进行拟合和搜索过程)和 ``export()`` 方法(用于返回搜索到的最佳架构)。
编写一个 One-Shot Trainer 与经典 Trainer 有很大不同。 首先,init 方法参数没有限制,可以接收任何 Python 参数。 其次,输入到 One-Shot Trainer 中的模型可能带有 Retiarii 特定的模块(例如 LayerChoice 和 InputChoice)的模型。 这种模型不能直接向前传播,Trainer 需要决定如何处理这些模块。
一个典型的示例是 DartsTrainer,其中可学习参数用于在 LayerChoice 中组合多个 Choice。 Retiarii为模块替换提供了易于使用的函数,即 ``replace_layer_choice``, ``replace_input_choice``。 示例如下。
.. code-block::python
from nni.retiarii.trainer.pytorch import BaseOneShotTrainer
from nni.retiarii.trainer.pytorch.utils import replace_layer_choice, replace_input_choice
class DartsLayerChoice(nn.Module):
def __init__(self, layer_choice):
super(DartsLayerChoice, self).__init__()
self.name = layer_choice.key
self.op_choices = nn.ModuleDict(layer_choice.named_children())
self.alpha = nn.Parameter(torch.randn(len(self.op_choices)) * 1e-3)
def forward(self, *args, **kwargs):
op_results = torch.stack([op(*args, **kwargs) for op in self.op_choices.values()])
alpha_shape = [-1] + [1] * (len(op_results.size()) - 1)
return torch.sum(op_results * F.softmax(self.alpha, -1).view(*alpha_shape), 0)
class DartsTrainer(BaseOneShotTrainer):
def __init__(self, model, loss, metrics, optimizer):
self.model = model
self.loss = loss
self.metrics = metrics
self.num_epochs = 10
self.nas_modules = []
replace_layer_choice(self.model, DartsLayerChoice, self.nas_modules)
... # 初始化 dataloaders 和 optimizers
def fit(self):
for i in range(self.num_epochs):
for (trn_X, trn_y), (val_X, val_y) in zip(self.train_loader, self.valid_loader):
self.train_architecture(val_X, val_y)
self.train_model_weight(trn_X, trn_y)
@torch.no_grad()
def export(self):
result = dict()
for name, module in self.nas_modules:
if name not in result:
result[name] = select_best_of_module(module)
return result
Retsarii 源代码提供了 DartsTrainer 的完整代码。 请查看 :githublink:`nni/retiarii/trainer/pytorch/darts.py`.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment