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
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>`.
加速模型
^^^^^^^^^^^^^^^^^^
Step1. 编写配置
^^^^^^^^^^^^^^^^^^^^^^^^^^
掩码实际上并不能加速模型。 要基于导出的掩码,来对模型加速,因此,NNI 提供了 API 来加速模型。 在模型上调用 ``apply_compression_results`` 后,模型会变得更小,推理延迟也会减小
编写配置来指定要剪枝的层。 以下配置表示剪枝所有的 ``default`` 操作,稀疏度设为 0.5,其它层保持不变
.. code-block:: python
from nni.compression.pytorch import apply_compression_results
apply_compression_results(model, 'mask_vgg19_cifar10.pth')
参考 `这里 <ModelSpeedup.rst>`__,了解详情。
config_list = [{
'sparsity': 0.5,
'op_types': ['default'],
}]
使用指南
--------------------
配置说明在 `这里 <./Tutorial.rst#specify-the-configuration>`__ 注意,不同的 Pruner 可能有自定义的配置字段,例如,AGP Pruner ``start_epoch`` 详情参考每个 Pruner `用法 <./Pruner.rst>`__,来调整相应的配置。
将压缩应用到模型的示例代码如下:
Step2. 选择 Pruner 来压缩模型
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PyTorch 代码
首先,使用模型来初始化 Pruner,并将配置作为参数传入,然后调用 ``compress()`` 来压缩模型。 请注意,有些算法可能会检查压缩的梯度,因此我们还定义了一个优化器并传递给 Pruner
.. code-block:: python
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
from nni.algorithms.compression.tensorflow.pruning import LevelPruner
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 即会生效。 掩码可在运行时通过算法来调整。
optimizer_finetune = torch.optim.SGD(model.parameters(), lr=0.01)
pruner = LevelPruner(model, config_list, optimizer_finetune)
model = pruner.compress()
*注意,``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
[
{
'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`` 如果使用剪枝算法,则可以忽略这些键值。
pruner.export_model(model_path='pruned_vgg19_cifar10.pth', mask_path='mask_vgg19_cifar10.pth')
参考 :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
{
quant_bits: {
config_list = [{
'quant_types': ['weight'],
'quant_bits': {
'weight': 8,
'output': 4,
},
}
当值为 int 类型时,所有量化类型使用相同的位宽。 例如:
.. code-block:: bash
{
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
}, # 这里可以仅使用 `int`,因为所有 `quan_types` 使用了一样的位长,参考下方 `ReLu6` 配置。
'op_types':['Conv2d', 'Linear']
}, {
'quant_types': ['output'],
'quant_bits': 8,
'quant_start_step': 7000,
'op_types':['ReLU6']
}]
更新优化状态的 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
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>`__,这
也可在 ``/examples/feature_engineering/gbdt_selector/`` 目录找到示例。
``fit`` 函数参数要求
**fit 函数参数要求**
*
......
......@@ -3,7 +3,8 @@ GradientFeatureSelector
GradientFeatureSelector 的算法来源于 `Feature Gradients: Scalable Feature Selection via Discrete Relaxation <https://arxiv.org/pdf/1908.10382.pdf>`__。
GradientFeatureSelector 算法基于梯度搜索算法的特征选择。
GradientFeatureSelector,基于梯度搜索算法
的特征选择。
1) 该方法扩展了一个近期的结果,
即在亚线性数据中通过展示计算能迭代的学习(即,在迷你批处理中),在 **线性的时间空间中** 的特征数量 D 及样本大小 N。
......
......@@ -81,9 +81,9 @@
def sample_final(self):
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::
用于调试的随机 Mutator。 使用
......
......@@ -32,7 +32,7 @@ NAS 基准测试
git clone -b ${NNI_VERSION} https://github.com/microsoft/nni
cd nni/examples/nas/benchmarks
将 ``${NNI_VERSION}`` 替换为发布的版本或分支名称,例如:``v1.9``。
将 ``${NNI_VERSION}`` 替换为发布的版本或分支名称,例如:``v2.0``。
#.
通过 ``pip3 install -r xxx.requirements.txt`` 安装依赖。 ``xxx`` 可以是 ``nasbench101``\ ,``nasbench201`` ,``nds``。
......@@ -44,12 +44,13 @@ NAS 基准测试
示例用法
--------------
请参考 `Benchmarks API 的示例用法 <./BenchmarksExample>`_。
请参考 `Benchmarks API 的示例用法 <./BenchmarksExample.rst>`_。
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`` 。
......@@ -85,7 +86,9 @@ API 文档
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 次。
......@@ -113,7 +116,8 @@ API 文档
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 的设计)。 大部分配置只采用固定的随机种子训练一次,但少部分会训练两到三次。
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# NAS 基准测试示例"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import pprint\n",
"import time\n",
"\n",
"from nni.nas.benchmarks.nasbench101 import query_nb101_trial_stats\n",
"from nni.nas.benchmarks.nasbench201 import query_nb201_trial_stats\n",
"from nni.nas.benchmarks.nds import query_nds_trial_stats\n",
"\n",
"ti = time.time()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## NAS-Bench-101"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"使用以下网络结构作为示例:\n",
"\n",
"![nas-101](../../img/nas-bench-101-example.png)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"tags": []
},
"outputs": [
"cells": [
{
"output_type": "stream",
"name": "stdout",
"text": "{'config': {'arch': {'input1': [0],\n 'input2': [1],\n 'input3': [2],\n 'input4': [0],\n 'input5': [0, 3, 4],\n 'input6': [2, 5],\n 'op1': 'conv3x3-bn-relu',\n 'op2': 'maxpool3x3',\n 'op3': 'conv3x3-bn-relu',\n 'op4': 'conv3x3-bn-relu',\n 'op5': 'conv1x1-bn-relu'},\n 'hash': '00005c142e6f48ac74fdcf73e3439874',\n 'id': 4,\n 'num_epochs': 108,\n 'num_vertices': 7},\n 'id': 10,\n 'intermediates': [{'current_epoch': 54,\n 'id': 19,\n 'test_acc': 77.40384340286255,\n 'train_acc': 82.82251358032227,\n 'training_time': 883.4580078125,\n 'valid_acc': 77.76442170143127},\n {'current_epoch': 108,\n 'id': 20,\n 'test_acc': 92.11738705635071,\n 'train_acc': 100.0,\n 'training_time': 1769.1279296875,\n 'valid_acc': 92.41786599159241}],\n 'parameters': 8.55553,\n 'test_acc': 92.11738705635071,\n 'train_acc': 100.0,\n 'training_time': 106147.67578125,\n 'valid_acc': 92.41786599159241}\n{'config': {'arch': {'input1': [0],\n 'input2': [1],\n 'input3': [2],\n 'input4': [0],\n 'input5': [0, 3, 4],\n 'input6': [2, 5],\n 'op1': 'conv3x3-bn-relu',\n 'op2': 'maxpool3x3',\n 'op3': 'conv3x3-bn-relu',\n 'op4': 'conv3x3-bn-relu',\n 'op5': 'conv1x1-bn-relu'},\n 'hash': '00005c142e6f48ac74fdcf73e3439874',\n 'id': 4,\n 'num_epochs': 108,\n 'num_vertices': 7},\n 'id': 11,\n 'intermediates': [{'current_epoch': 54,\n 'id': 21,\n 'test_acc': 82.04126358032227,\n 'train_acc': 87.96073794364929,\n 'training_time': 883.6810302734375,\n 'valid_acc': 82.91265964508057},\n {'current_epoch': 108,\n 'id': 22,\n 'test_acc': 91.90705418586731,\n 'train_acc': 100.0,\n 'training_time': 1768.2509765625,\n 'valid_acc': 92.45793223381042}],\n 'parameters': 8.55553,\n 'test_acc': 91.90705418586731,\n 'train_acc': 100.0,\n 'training_time': 106095.05859375,\n 'valid_acc': 92.45793223381042}\n{'config': {'arch': {'input1': [0],\n 'input2': [1],\n 'input3': [2],\n 'input4': [0],\n 'input5': [0, 3, 4],\n 'input6': [2, 5],\n 'op1': 'conv3x3-bn-relu',\n 'op2': 'maxpool3x3',\n 'op3': 'conv3x3-bn-relu',\n 'op4': 'conv3x3-bn-relu',\n 'op5': 'conv1x1-bn-relu'},\n 'hash': '00005c142e6f48ac74fdcf73e3439874',\n 'id': 4,\n 'num_epochs': 108,\n 'num_vertices': 7},\n 'id': 12,\n 'intermediates': [{'current_epoch': 54,\n 'id': 23,\n 'test_acc': 80.58894276618958,\n 'train_acc': 86.34815812110901,\n 'training_time': 883.4569702148438,\n 'valid_acc': 81.1598539352417},\n {'current_epoch': 108,\n 'id': 24,\n 'test_acc': 92.15745329856873,\n 'train_acc': 100.0,\n 'training_time': 1768.9759521484375,\n 'valid_acc': 93.04887652397156}],\n 'parameters': 8.55553,\n 'test_acc': 92.15745329856873,\n 'train_acc': 100.0,\n 'training_time': 106138.55712890625,\n 'valid_acc': 93.04887652397156}\n"
}
],
"source": [
"arch = {\n",
" 'op1': 'conv3x3-bn-relu',\n",
" 'op2': 'maxpool3x3',\n",
" 'op3': 'conv3x3-bn-relu',\n",
" 'op4': 'conv3x3-bn-relu',\n",
" 'op5': 'conv1x1-bn-relu',\n",
" 'input1': [0],\n",
" 'input2': [1],\n",
" 'input3': [2],\n",
" 'input4': [0],\n",
" 'input5': [0, 3, 4],\n",
" 'input6': [2, 5]\n",
"}\n",
"for t in query_nb101_trial_stats(arch, 108, include_intermediates=True):\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"一个 NAS-Bench-101 的网络结构可以被训练多次。 生成器返回的每一个元素是一个字典,包含了该 Trial 设置(网络结构+超参数)中其中一个训练结果,如训练集/验证集/测试集准确率,训练时间,Epoch数等等。 NAS-Bench-201 和 NDS 的结果遵循了相似的格式。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## NAS-Bench-201"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"使用以下网络结构作为示例:\n",
"\n",
"![nas-201](../../img/nas-bench-201-example.png)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "markdown",
"metadata": {},
"source": [
"# NAS 基准测试示例"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "{'config': {'arch': {'0_1': 'avg_pool_3x3',\n '0_2': 'conv_1x1',\n '0_3': 'conv_1x1',\n '1_2': 'skip_connect',\n '1_3': 'skip_connect',\n '2_3': 'skip_connect'},\n 'dataset': 'cifar100',\n 'id': 7,\n 'num_cells': 5,\n 'num_channels': 16,\n 'num_epochs': 200},\n 'flops': 15.65322,\n 'id': 3,\n 'latency': 0.013182918230692545,\n 'ori_test_acc': 53.11,\n 'ori_test_evaluation_time': 1.0195916947864352,\n 'ori_test_loss': 1.7307863704681397,\n 'parameters': 0.135156,\n 'seed': 999,\n 'test_acc': 53.07999995727539,\n 'test_evaluation_time': 0.5097958473932176,\n 'test_loss': 1.731276072692871,\n 'train_acc': 57.82,\n 'train_loss': 1.5116578379058838,\n 'training_time': 2888.4371995925903,\n 'valid_acc': 53.14000000610351,\n 'valid_evaluation_time': 0.5097958473932176,\n 'valid_loss': 1.7302966793060304}\n{'config': {'arch': {'0_1': 'avg_pool_3x3',\n '0_2': 'conv_1x1',\n '0_3': 'conv_1x1',\n '1_2': 'skip_connect',\n '1_3': 'skip_connect',\n '2_3': 'skip_connect'},\n 'dataset': 'cifar100',\n 'id': 7,\n 'num_cells': 5,\n 'num_channels': 16,\n 'num_epochs': 200},\n 'flops': 15.65322,\n 'id': 7,\n 'latency': 0.013182918230692545,\n 'ori_test_acc': 51.93,\n 'ori_test_evaluation_time': 1.0195916947864352,\n 'ori_test_loss': 1.7572312774658203,\n 'parameters': 0.135156,\n 'seed': 777,\n 'test_acc': 51.979999938964845,\n 'test_evaluation_time': 0.5097958473932176,\n 'test_loss': 1.7429540189743042,\n 'train_acc': 57.578,\n 'train_loss': 1.5114233912658692,\n 'training_time': 2888.4371995925903,\n 'valid_acc': 51.88,\n 'valid_evaluation_time': 0.5097958473932176,\n 'valid_loss': 1.7715086591720581}\n{'config': {'arch': {'0_1': 'avg_pool_3x3',\n '0_2': 'conv_1x1',\n '0_3': 'conv_1x1',\n '1_2': 'skip_connect',\n '1_3': 'skip_connect',\n '2_3': 'skip_connect'},\n 'dataset': 'cifar100',\n 'id': 7,\n 'num_cells': 5,\n 'num_channels': 16,\n 'num_epochs': 200},\n 'flops': 15.65322,\n 'id': 11,\n 'latency': 0.013182918230692545,\n 'ori_test_acc': 53.38,\n 'ori_test_evaluation_time': 1.0195916947864352,\n 'ori_test_loss': 1.7281623031616211,\n 'parameters': 0.135156,\n 'seed': 888,\n 'test_acc': 53.67999998779297,\n 'test_evaluation_time': 0.5097958473932176,\n 'test_loss': 1.7327697801589965,\n 'train_acc': 57.792,\n 'train_loss': 1.5091403088760376,\n 'training_time': 2888.4371995925903,\n 'valid_acc': 53.08000000610352,\n 'valid_evaluation_time': 0.5097958473932176,\n 'valid_loss': 1.7235548280715942}\n"
}
],
"source": [
"arch = {\n",
" '0_1': 'avg_pool_3x3',\n",
" '0_2': 'conv_1x1',\n",
" '1_2': 'skip_connect',\n",
" '0_3': 'conv_1x1',\n",
" '1_3': 'skip_connect',\n",
" '2_3': 'skip_connect'\n",
"}\n",
"for t in query_nb201_trial_stats(arch, 200, 'cifar100'):\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"中间结果也可得到。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import pprint\n",
"import time\n",
"\n",
"from nni.nas.benchmarks.nasbench101 import query_nb101_trial_stats\n",
"from nni.nas.benchmarks.nasbench201 import query_nb201_trial_stats\n",
"from nni.nas.benchmarks.nds import query_nds_trial_stats\n",
"\n",
"ti = time.time()"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "{'id': 4, 'arch': {'0_1': 'avg_pool_3x3', '0_2': 'conv_1x1', '0_3': 'conv_1x1', '1_2': 'skip_connect', '1_3': 'skip_connect', '2_3': 'skip_connect'}, 'num_epochs': 12, 'num_channels': 16, 'num_cells': 5, 'dataset': 'imagenet16-120'}\nIntermediates: 12\n{'id': 8, 'arch': {'0_1': 'avg_pool_3x3', '0_2': 'conv_1x1', '0_3': 'conv_1x1', '1_2': 'skip_connect', '1_3': 'skip_connect', '2_3': 'skip_connect'}, 'num_epochs': 200, 'num_channels': 16, 'num_cells': 5, 'dataset': 'imagenet16-120'}\nIntermediates: 200\n{'id': 8, 'arch': {'0_1': 'avg_pool_3x3', '0_2': 'conv_1x1', '0_3': 'conv_1x1', '1_2': 'skip_connect', '1_3': 'skip_connect', '2_3': 'skip_connect'}, 'num_epochs': 200, 'num_channels': 16, 'num_cells': 5, 'dataset': 'imagenet16-120'}\nIntermediates: 200\n{'id': 8, 'arch': {'0_1': 'avg_pool_3x3', '0_2': 'conv_1x1', '0_3': 'conv_1x1', '1_2': 'skip_connect', '1_3': 'skip_connect', '2_3': 'skip_connect'}, 'num_epochs': 200, 'num_channels': 16, 'num_cells': 5, 'dataset': 'imagenet16-120'}\nIntermediates: 200\n"
}
],
"source": [
"for t in query_nb201_trial_stats(arch, None, 'imagenet16-120', include_intermediates=True):\n",
" print(t['config'])\n",
" print('Intermediates:', len(t['intermediates']))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## NDS"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"使用以下网络结构作为示例:<br>\n",
"![nds](../../img/nas-bench-nds-example.png)\n",
"\n",
"这里, `bot_muls`, `ds`, `num_gs`, `ss` 和 `ws` 分别代表 \"bottleneck multipliers\", \"depths\", \"number of groups\", \"strides\" and \"widths\"。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "markdown",
"metadata": {},
"source": [
"## NAS-Bench-101"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "{'best_test_acc': 90.48,\n 'best_train_acc': 96.356,\n 'best_train_loss': 0.116,\n 'config': {'base_lr': 0.1,\n 'cell_spec': {},\n 'dataset': 'cifar10',\n 'generator': 'random',\n 'id': 45505,\n 'model_family': 'residual_bottleneck',\n 'model_spec': {'bot_muls': [0.0, 0.25, 0.25, 0.25],\n 'ds': [1, 16, 1, 4],\n 'num_gs': [1, 2, 1, 2],\n 'ss': [1, 1, 2, 2],\n 'ws': [16, 64, 128, 16]},\n 'num_epochs': 100,\n 'proposer': 'resnext-a',\n 'weight_decay': 0.0005},\n 'final_test_acc': 90.39,\n 'final_train_acc': 96.298,\n 'final_train_loss': 0.116,\n 'flops': 69.890986,\n 'id': 45505,\n 'iter_time': 0.065,\n 'parameters': 0.083002,\n 'seed': 1}\n"
}
],
"source": [
"model_spec = {\n",
" 'bot_muls': [0.0, 0.25, 0.25, 0.25],\n",
" 'ds': [1, 16, 1, 4],\n",
" 'num_gs': [1, 2, 1, 2],\n",
" 'ss': [1, 1, 2, 2],\n",
" 'ws': [16, 64, 128, 16]\n",
"}\n",
"# Use none as a wildcard\n",
"for t in query_nds_trial_stats('residual_bottleneck', None, None, model_spec, None, 'cifar10'):\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "markdown",
"metadata": {},
"source": [
"使用以下架构为例:\n",
"\n",
"![nas-101](../../img/nas-bench-101-example.png)"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "[{'current_epoch': 1,\n 'id': 4494501,\n 'test_acc': 41.76,\n 'train_acc': 30.421000000000006,\n 'train_loss': 1.793},\n {'current_epoch': 2,\n 'id': 4494502,\n 'test_acc': 54.66,\n 'train_acc': 47.24,\n 'train_loss': 1.415},\n {'current_epoch': 3,\n 'id': 4494503,\n 'test_acc': 59.97,\n 'train_acc': 56.983,\n 'train_loss': 1.179},\n {'current_epoch': 4,\n 'id': 4494504,\n 'test_acc': 62.91,\n 'train_acc': 61.955,\n 'train_loss': 1.048},\n {'current_epoch': 5,\n 'id': 4494505,\n 'test_acc': 66.16,\n 'train_acc': 64.493,\n 'train_loss': 0.983},\n {'current_epoch': 6,\n 'id': 4494506,\n 'test_acc': 66.5,\n 'train_acc': 66.274,\n 'train_loss': 0.937},\n {'current_epoch': 7,\n 'id': 4494507,\n 'test_acc': 67.55,\n 'train_acc': 67.426,\n 'train_loss': 0.907},\n {'current_epoch': 8,\n 'id': 4494508,\n 'test_acc': 69.45,\n 'train_acc': 68.45400000000001,\n 'train_loss': 0.878},\n {'current_epoch': 9,\n 'id': 4494509,\n 'test_acc': 70.14,\n 'train_acc': 69.295,\n 'train_loss': 0.857},\n {'current_epoch': 10,\n 'id': 4494510,\n 'test_acc': 69.47,\n 'train_acc': 70.304,\n 'train_loss': 0.832}]\n"
}
],
"source": [
"model_spec = {\n",
" 'bot_muls': [0.0, 0.25, 0.25, 0.25],\n",
" 'ds': [1, 16, 1, 4],\n",
" 'num_gs': [1, 2, 1, 2],\n",
" 'ss': [1, 1, 2, 2],\n",
" 'ws': [16, 64, 128, 16]\n",
"}\n",
"for t in query_nds_trial_stats('residual_bottleneck', None, None, model_spec, None, 'cifar10', include_intermediates=True):\n",
" pprint.pprint(t['intermediates'][:10])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "code",
"execution_count": 2,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"arch = {\n",
" 'op1': 'conv3x3-bn-relu',\n",
" 'op2': 'maxpool3x3',\n",
" 'op3': 'conv3x3-bn-relu',\n",
" 'op4': 'conv3x3-bn-relu',\n",
" 'op5': 'conv1x1-bn-relu',\n",
" 'input1': [0],\n",
" 'input2': [1],\n",
" 'input3': [2],\n",
" 'input4': [0],\n",
" 'input5': [0, 3, 4],\n",
" 'input6': [2, 5]\n",
"}\n",
"for t in query_nb101_trial_stats(arch, 108, include_intermediates=True):\n",
" pprint.pprint(t)"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "{'best_test_acc': 93.58,\n 'best_train_acc': 99.772,\n 'best_train_loss': 0.011,\n 'config': {'base_lr': 0.1,\n 'cell_spec': {},\n 'dataset': 'cifar10',\n 'generator': 'random',\n 'id': 108998,\n 'model_family': 'residual_basic',\n 'model_spec': {'ds': [1, 12, 12, 12],\n 'ss': [1, 1, 2, 2],\n 'ws': [16, 24, 24, 40]},\n 'num_epochs': 100,\n 'proposer': 'resnet',\n 'weight_decay': 0.0005},\n 'final_test_acc': 93.49,\n 'final_train_acc': 99.772,\n 'final_train_loss': 0.011,\n 'flops': 184.519578,\n 'id': 108998,\n 'iter_time': 0.059,\n 'parameters': 0.594138,\n 'seed': 1}\n"
}
],
"source": [
"model_spec = {'ds': [1, 12, 12, 12], 'ss': [1, 1, 2, 2], 'ws': [16, 24, 24, 40]}\n",
"for t in query_nds_trial_stats('residual_basic', 'resnet', 'random', model_spec, {}, 'cifar10'):\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "markdown",
"metadata": {},
"source": [
"一个 NAS-Bench-101 的网络结构可以被训练多次。 生成器返回的每一个元素是一个字典,包含了该 Trial 设置(网络结构+超参数)中其中一个训练结果,如训练集/验证集/测试集准确率,训练时间,Epoch数等等。 NAS-Bench-201 和 NDS 的结果遵循了相似的格式。"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "{'best_test_acc': 84.5,\n 'best_train_acc': 89.66499999999999,\n 'best_train_loss': 0.302,\n 'config': {'base_lr': 0.1,\n 'cell_spec': {},\n 'dataset': 'cifar10',\n 'generator': 'random',\n 'id': 139492,\n 'model_family': 'vanilla',\n 'model_spec': {'ds': [1, 12, 12, 12],\n 'ss': [1, 1, 2, 2],\n 'ws': [16, 24, 32, 40]},\n 'num_epochs': 100,\n 'proposer': 'vanilla',\n 'weight_decay': 0.0005},\n 'final_test_acc': 84.35,\n 'final_train_acc': 89.633,\n 'final_train_loss': 0.303,\n 'flops': 208.36393,\n 'id': 154692,\n 'iter_time': 0.058,\n 'parameters': 0.68977,\n 'seed': 1}\n"
}
],
"source": [
"# get the first one\n",
"pprint.pprint(next(query_nds_trial_stats('vanilla', None, None, None, None, None)))"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "markdown",
"metadata": {},
"source": [
"## NAS-Bench-201"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "{'best_test_acc': 93.37,\n 'best_train_acc': 99.91,\n 'best_train_loss': 0.006,\n 'config': {'base_lr': 0.1,\n 'cell_spec': {'normal_0_input_x': 0,\n 'normal_0_input_y': 1,\n 'normal_0_op_x': 'avg_pool_3x3',\n 'normal_0_op_y': 'conv_7x1_1x7',\n 'normal_1_input_x': 2,\n 'normal_1_input_y': 0,\n 'normal_1_op_x': 'sep_conv_3x3',\n 'normal_1_op_y': 'sep_conv_5x5',\n 'normal_2_input_x': 2,\n 'normal_2_input_y': 2,\n 'normal_2_op_x': 'dil_sep_conv_3x3',\n 'normal_2_op_y': 'dil_sep_conv_3x3',\n 'normal_3_input_x': 4,\n 'normal_3_input_y': 4,\n 'normal_3_op_x': 'skip_connect',\n 'normal_3_op_y': 'dil_sep_conv_3x3',\n 'normal_4_input_x': 2,\n 'normal_4_input_y': 4,\n 'normal_4_op_x': 'conv_7x1_1x7',\n 'normal_4_op_y': 'sep_conv_3x3',\n 'normal_concat': [3, 5, 6],\n 'reduce_0_input_x': 0,\n 'reduce_0_input_y': 1,\n 'reduce_0_op_x': 'avg_pool_3x3',\n 'reduce_0_op_y': 'dil_sep_conv_3x3',\n 'reduce_1_input_x': 0,\n 'reduce_1_input_y': 0,\n 'reduce_1_op_x': 'sep_conv_3x3',\n 'reduce_1_op_y': 'sep_conv_3x3',\n 'reduce_2_input_x': 2,\n 'reduce_2_input_y': 0,\n 'reduce_2_op_x': 'skip_connect',\n 'reduce_2_op_y': 'sep_conv_7x7',\n 'reduce_3_input_x': 4,\n 'reduce_3_input_y': 4,\n 'reduce_3_op_x': 'conv_7x1_1x7',\n 'reduce_3_op_y': 'skip_connect',\n 'reduce_4_input_x': 0,\n 'reduce_4_input_y': 5,\n 'reduce_4_op_x': 'conv_7x1_1x7',\n 'reduce_4_op_y': 'conv_7x1_1x7',\n 'reduce_concat': [3, 6]},\n 'dataset': 'cifar10',\n 'generator': 'random',\n 'id': 1,\n 'model_family': 'nas_cell',\n 'model_spec': {'aux': False,\n 'depth': 12,\n 'drop_prob': 0.0,\n 'num_nodes_normal': 5,\n 'num_nodes_reduce': 5,\n 'width': 32},\n 'num_epochs': 100,\n 'proposer': 'amoeba',\n 'weight_decay': 0.0005},\n 'final_test_acc': 93.27,\n 'final_train_acc': 99.91,\n 'final_train_loss': 0.006,\n 'flops': 664.400586,\n 'id': 1,\n 'iter_time': 0.281,\n 'parameters': 4.190314,\n 'seed': 1}\n"
}
],
"source": [
"# count number\n",
"model_spec = {'num_nodes_normal': 5, 'num_nodes_reduce': 5, 'depth': 12, 'width': 32, 'aux': False, 'drop_prob': 0.0}\n",
"cell_spec = {\n",
" 'normal_0_op_x': 'avg_pool_3x3',\n",
" 'normal_0_input_x': 0,\n",
" 'normal_0_op_y': 'conv_7x1_1x7',\n",
" 'normal_0_input_y': 1,\n",
" 'normal_1_op_x': 'sep_conv_3x3',\n",
" 'normal_1_input_x': 2,\n",
" 'normal_1_op_y': 'sep_conv_5x5',\n",
" 'normal_1_input_y': 0,\n",
" 'normal_2_op_x': 'dil_sep_conv_3x3',\n",
" 'normal_2_input_x': 2,\n",
" 'normal_2_op_y': 'dil_sep_conv_3x3',\n",
" 'normal_2_input_y': 2,\n",
" 'normal_3_op_x': 'skip_connect',\n",
" 'normal_3_input_x': 4,\n",
" 'normal_3_op_y': 'dil_sep_conv_3x3',\n",
" 'normal_3_input_y': 4,\n",
" 'normal_4_op_x': 'conv_7x1_1x7',\n",
" 'normal_4_input_x': 2,\n",
" 'normal_4_op_y': 'sep_conv_3x3',\n",
" 'normal_4_input_y': 4,\n",
" 'normal_concat': [3, 5, 6],\n",
" 'reduce_0_op_x': 'avg_pool_3x3',\n",
" 'reduce_0_input_x': 0,\n",
" 'reduce_0_op_y': 'dil_sep_conv_3x3',\n",
" 'reduce_0_input_y': 1,\n",
" 'reduce_1_op_x': 'sep_conv_3x3',\n",
" 'reduce_1_input_x': 0,\n",
" 'reduce_1_op_y': 'sep_conv_3x3',\n",
" 'reduce_1_input_y': 0,\n",
" 'reduce_2_op_x': 'skip_connect',\n",
" 'reduce_2_input_x': 2,\n",
" 'reduce_2_op_y': 'sep_conv_7x7',\n",
" 'reduce_2_input_y': 0,\n",
" 'reduce_3_op_x': 'conv_7x1_1x7',\n",
" 'reduce_3_input_x': 4,\n",
" 'reduce_3_op_y': 'skip_connect',\n",
" 'reduce_3_input_y': 4,\n",
" 'reduce_4_op_x': 'conv_7x1_1x7',\n",
" 'reduce_4_input_x': 0,\n",
" 'reduce_4_op_y': 'conv_7x1_1x7',\n",
" 'reduce_4_input_y': 5,\n",
" 'reduce_concat': [3, 6]\n",
"}\n",
"\n",
"for t in query_nds_trial_stats('nas_cell', None, None, model_spec, cell_spec, 'cifar10'):\n",
" assert t['config']['model_spec'] == model_spec\n",
" assert t['config']['cell_spec'] == cell_spec\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "markdown",
"metadata": {},
"source": [
"使用以下架构为例:\n",
"\n",
"![nas-201](../../img/nas-bench-201-example.png)"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "NDS (amoeba) count: 5107\n"
}
],
"source": [
"# count number\n",
"print('NDS (amoeba) count:', len(list(query_nds_trial_stats(None, 'amoeba', None, None, None, None, None))))"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"tags": []
},
"outputs": [
"cell_type": "code",
"execution_count": 3,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"arch = {\n",
" '0_1': 'avg_pool_3x3',\n",
" '0_2': 'conv_1x1',\n",
" '1_2': 'skip_connect',\n",
" '0_3': 'conv_1x1',\n",
" '1_3': 'skip_connect',\n",
" '2_3': 'skip_connect'\n",
"}\n",
"for t in query_nb201_trial_stats(arch, 200, 'cifar100'):\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"中间结果也可得到。"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"for t in query_nb201_trial_stats(arch, None, 'imagenet16-120', include_intermediates=True):\n",
" print(t['config'])\n",
" print('Intermediates:', len(t['intermediates']))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## NDS"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"使用以下架构为例:<br>\n",
"![nds](../../img/nas-bench-nds-example.png)\n",
"\n",
"这里,`bot_muls`, `ds`, `num_gs`, `ss` 和 `ws` 分别表示 \"bottleneck multipliers\", \"depths\", \"number of groups\", \"strides\" 和 \"widths\" 。"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"model_spec = {\n",
" 'bot_muls': [0.0, 0.25, 0.25, 0.25],\n",
" 'ds': [1, 16, 1, 4],\n",
" 'num_gs': [1, 2, 1, 2],\n",
" 'ss': [1, 1, 2, 2],\n",
" 'ws': [16, 64, 128, 16]\n",
"}\n",
"# Use none as a wildcard\n",
"for t in query_nds_trial_stats('residual_bottleneck', None, None, model_spec, None, 'cifar10'):\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"model_spec = {\n",
" 'bot_muls': [0.0, 0.25, 0.25, 0.25],\n",
" 'ds': [1, 16, 1, 4],\n",
" 'num_gs': [1, 2, 1, 2],\n",
" 'ss': [1, 1, 2, 2],\n",
" 'ws': [16, 64, 128, 16]\n",
"}\n",
"for t in query_nds_trial_stats('residual_bottleneck', None, None, model_spec, None, 'cifar10', include_intermediates=True):\n",
" pprint.pprint(t['intermediates'][:10])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"model_spec = {'ds': [1, 12, 12, 12], 'ss': [1, 1, 2, 2], 'ws': [16, 24, 24, 40]}\n",
"for t in query_nds_trial_stats('residual_basic', 'resnet', 'random', model_spec, {}, 'cifar10'):\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# get the first one\n",
"pprint.pprint(next(query_nds_trial_stats('vanilla', None, None, None, None, None)))"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# count number\n",
"model_spec = {'num_nodes_normal': 5, 'num_nodes_reduce': 5, 'depth': 12, 'width': 32, 'aux': False, 'drop_prob': 0.0}\n",
"cell_spec = {\n",
" 'normal_0_op_x': 'avg_pool_3x3',\n",
" 'normal_0_input_x': 0,\n",
" 'normal_0_op_y': 'conv_7x1_1x7',\n",
" 'normal_0_input_y': 1,\n",
" 'normal_1_op_x': 'sep_conv_3x3',\n",
" 'normal_1_input_x': 2,\n",
" 'normal_1_op_y': 'sep_conv_5x5',\n",
" 'normal_1_input_y': 0,\n",
" 'normal_2_op_x': 'dil_sep_conv_3x3',\n",
" 'normal_2_input_x': 2,\n",
" 'normal_2_op_y': 'dil_sep_conv_3x3',\n",
" 'normal_2_input_y': 2,\n",
" 'normal_3_op_x': 'skip_connect',\n",
" 'normal_3_input_x': 4,\n",
" 'normal_3_op_y': 'dil_sep_conv_3x3',\n",
" 'normal_3_input_y': 4,\n",
" 'normal_4_op_x': 'conv_7x1_1x7',\n",
" 'normal_4_input_x': 2,\n",
" 'normal_4_op_y': 'sep_conv_3x3',\n",
" 'normal_4_input_y': 4,\n",
" 'normal_concat': [3, 5, 6],\n",
" 'reduce_0_op_x': 'avg_pool_3x3',\n",
" 'reduce_0_input_x': 0,\n",
" 'reduce_0_op_y': 'dil_sep_conv_3x3',\n",
" 'reduce_0_input_y': 1,\n",
" 'reduce_1_op_x': 'sep_conv_3x3',\n",
" 'reduce_1_input_x': 0,\n",
" 'reduce_1_op_y': 'sep_conv_3x3',\n",
" 'reduce_1_input_y': 0,\n",
" 'reduce_2_op_x': 'skip_connect',\n",
" 'reduce_2_input_x': 2,\n",
" 'reduce_2_op_y': 'sep_conv_7x7',\n",
" 'reduce_2_input_y': 0,\n",
" 'reduce_3_op_x': 'conv_7x1_1x7',\n",
" 'reduce_3_input_x': 4,\n",
" 'reduce_3_op_y': 'skip_connect',\n",
" 'reduce_3_input_y': 4,\n",
" 'reduce_4_op_x': 'conv_7x1_1x7',\n",
" 'reduce_4_input_x': 0,\n",
" 'reduce_4_op_y': 'conv_7x1_1x7',\n",
" 'reduce_4_input_y': 5,\n",
" 'reduce_concat': [3, 6]\n",
"}\n",
"\n",
"for t in query_nds_trial_stats('nas_cell', None, None, model_spec, cell_spec, 'cifar10'):\n",
" assert t['config']['model_spec'] == model_spec\n",
" assert t['config']['cell_spec'] == cell_spec\n",
" pprint.pprint(t)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"tags": []
},
"outputs": [],
"source": [
"# count number\n",
"print('NDS (amoeba) count:', len(list(query_nds_trial_stats(None, 'amoeba', None, None, None, None, None))))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## NLP"
]
},
{
"cell_type": "markdown",
"metadata": {
"pycharm": {
"metadata": false
}
},
"source": [
"使用以下两种结构作为示例。 \n",
"论文中的 arch 被称为嵌套变量的 “receipe”,目前还没有在 NNI 的基准测试中使用。\n",
"一个架构有多个节点,节点输入和节点操作,您可以参考文档了解更多详细信息。\n",
"\n",
"arch1 : <img src=\"../../img/nas-bench-nlp-example1.jpeg\" width=400 height=300 /> \n",
"\n",
"\n",
"arch2 : <img src=\"../../img/nas-bench-nlp-example2.jpeg\" width=400 height=300 /> \n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"{'config': {'arch': {'h_new_0_input_0': 'node_3',\n 'h_new_0_input_1': 'node_2',\n 'h_new_0_input_2': 'node_1',\n 'h_new_0_op': 'blend',\n 'node_0_input_0': 'x',\n 'node_0_input_1': 'h_prev_0',\n 'node_0_op': 'linear',\n 'node_1_input_0': 'node_0',\n 'node_1_op': 'activation_tanh',\n 'node_2_input_0': 'h_prev_0',\n 'node_2_input_1': 'node_1',\n 'node_2_input_2': 'x',\n 'node_2_op': 'linear',\n 'node_3_input_0': 'node_2',\n 'node_3_op': 'activation_leaky_relu'},\n 'dataset': 'ptb',\n 'id': 20003},\n 'id': 16291,\n 'test_loss': 4.680262297102549,\n 'train_loss': 4.132040537087838,\n 'training_time': 177.05208373069763,\n 'val_loss': 4.707944253177966}\n"
]
}
],
"source": [
"import pprint\n",
"from nni.nas.benchmarks.nlp import query_nlp_trial_stats\n",
"\n",
"arch1 = {'h_new_0_input_0': 'node_3', 'h_new_0_input_1': 'node_2', 'h_new_0_input_2': 'node_1', 'h_new_0_op': 'blend', 'node_0_input_0': 'x', 'node_0_input_1': 'h_prev_0', 'node_0_op': 'linear','node_1_input_0': 'node_0', 'node_1_op': 'activation_tanh', 'node_2_input_0': 'h_prev_0', 'node_2_input_1': 'node_1', 'node_2_input_2': 'x', 'node_2_op': 'linear', 'node_3_input_0': 'node_2', 'node_3_op': 'activation_leaky_relu'}\n",
"for i in query_nlp_trial_stats(arch=arch1, dataset=\"ptb\"):\n",
" pprint.pprint(i)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[{'current_epoch': 46,\n 'id': 1796,\n 'test_loss': 6.233430054978619,\n 'train_loss': 6.4866799231542664,\n 'training_time': 146.5680329799652,\n 'val_loss': 6.326836978687959},\n {'current_epoch': 47,\n 'id': 1797,\n 'test_loss': 6.2402057403023825,\n 'train_loss': 6.485401405247535,\n 'training_time': 146.05511450767517,\n 'val_loss': 6.3239741605870865},\n {'current_epoch': 48,\n 'id': 1798,\n 'test_loss': 6.351145308363877,\n 'train_loss': 6.611281181173992,\n 'training_time': 145.8849437236786,\n 'val_loss': 6.436160816865809},\n {'current_epoch': 49,\n 'id': 1799,\n 'test_loss': 6.227155079159031,\n 'train_loss': 6.473414458249545,\n 'training_time': 145.51414465904236,\n 'val_loss': 6.313294354607077}]\n"
]
}
],
"source": [
"arch2 = {\"h_new_0_input_0\":\"node_0\",\"h_new_0_input_1\":\"node_1\",\"h_new_0_op\":\"elementwise_sum\",\"node_0_input_0\":\"x\",\"node_0_input_1\":\"h_prev_0\",\"node_0_op\":\"linear\",\"node_1_input_0\":\"node_0\",\"node_1_op\":\"activation_tanh\"}\n",
"for i in query_nlp_trial_stats(arch=arch2, dataset='wikitext-2', include_intermediates=True):\n",
" pprint.pprint(i['intermediates'][45:49])"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": "Elapsed time: 2.2023813724517822 seconds\n"
"cell_type": "code",
"execution_count": 4,
"metadata": {
"pycharm": {},
"tags": []
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Elapsed time: 5.60982608795166 seconds\n"
]
}
],
"source": [
"print('Elapsed time: ', time.time() - ti, 'seconds')"
]
}
],
"source": [
"print('Elapsed time: ', time.time() - ti, 'seconds')"
]
}
],
"metadata": {
"language_info": {
"name": "python",
"codemirror_mode": {
"name": "ipython",
],
"metadata": {
"file_extension": ".py",
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"name": "python",
"version": "3.8.5-final"
},
"mimetype": "text/x-python",
"name": "python",
"npconvert_exporter": "python",
"orig_nbformat": 2,
"pygments_lexer": "ipython3",
"version": 3
},
"version": "3.6.10-final"
},
"orig_nbformat": 2,
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"npconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": 3,
"kernelspec": {
"name": "python361064bitnnilatestcondabff8d66a619a4d26af34fe0fe687c7b0",
"display_name": "Python 3.6.10 64-bit ('nnilatest': conda)"
}
},
"nbformat": 4,
"nbformat_minor": 2
"nbformat": 4,
"nbformat_minor": 2
}
\ No newline at end of file
.. 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
......@@ -51,7 +50,6 @@ ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 Imag
.. image:: ../../img/cream_flops600.jpg
:scale: 50%
示例
--------
......@@ -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`` 里, 如下:
......@@ -75,7 +73,7 @@ ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 Imag
快速入门
-----------
I. 搜索
1. 搜索
^^^^^^^^^
首先构建搜索环境。
......@@ -105,8 +103,8 @@ I. 搜索
搜索的体系结构需要重新训练并获得最终模型。 最终模型以 ``.pth.tar`` 格式保存。 训练代码不久就会发布。
II. 重新训练
^^^^^^^^^^^^^^^^^^
2. 重新训练
^^^^^^^^^^^
为了训练搜索的架构,需要配置 ``MODEL_SELECTION`` 参数来指定模型触发器。 在 ``./configs/retrain.yaml`` 文件里加上 ``MODEL_SELECTION`` 可以声明训练模型。 您可以从 [14,43,112,287,481,604] 中选择一个,代表不同的 Flops(MB)。
......@@ -130,7 +128,7 @@ II. 重新训练
python -m torch.distributed.launch --nproc_per_node=8 ./retrain.py --cfg ./configs/retrain.yaml
III. 测试
3. 测试
^^^^^^^^^
要测试我们训练的模型,需要使用 ``./configs/test.yaml`` 中的 ``MODEL_SELECTION`` 来指定要测试的模型。
......
......@@ -39,8 +39,8 @@ CIFAR10 Macro/Micro 搜索空间
PyTorch
^^^^^^^
.. autoclass:: nni.algorithms.nas.pytorch.enas.EnasTrainer
.. autoclass:: nni.algorithms.nas.pytorch.enas.EnasTrainer
:members:
.. autoclass:: nni.algorithms.nas.pytorch.enas.EnasMutator
.. autoclass:: nni.algorithms.nas.pytorch.enas.EnasMutator
:members:
......@@ -48,7 +48,7 @@ One-Shot NAS 算法
**注意** ,在使用 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>`__
......
......@@ -29,7 +29,7 @@ NNI 还提供了专门的 `可视化工具 <#nas-visualization>`__,用于查
- 算法简介
* - :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>`__
......@@ -46,20 +46,22 @@ NNI 目前支持下面列出的 One-Shot NAS 算法,并且正在添加更多
* - Name
- 算法简介
* - `ENAS </NAS/ENAS.html>`__
* - `ENAS <ENAS.rst>`__
- `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>`__ 介绍了一种用于双级优化的可区分网络体系结构搜索的新算法。
* - `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 的. 它引入了一种有效的算法,可在搜索过程中逐渐增加搜索的深度。
* - `SPOS </NAS/SPOS.html>`__
- 论文 `Single Path One-Shot Neural Architecture Search with Uniform Sampling <https://arxiv.org/abs/1904.00420>`__ 构造了一个采用统一的路径采样方法来训练简化的超网络,并使用进化算法来提高搜索神经网络结构的效率。
* - `CDARTS </NAS/CDARTS.html>`__
- `Cyclic Differentiable Architecture Search <https://arxiv.org/abs/****>`__ 在搜索和评估网络之间建立循环反馈机制。 通过引入的循环的可微分架构搜索框架将两个网络集成为一个架构。
* - `ProxylessNAS </NAS/Proxylessnas.html>`__
* - `SPOS <SPOS.rst>`__
- `Single Path One-Shot Neural Architecture Search with Uniform Sampling <https://arxiv.org/abs/1904.00420>`__ 论文构造了一个采用统一的路径采样方法来训练简化的超网络,并使用进化算法来提高搜索神经网络结构的效率。
* - `CDARTS <CDARTS.rst>`__
- `Cyclic Differentiable Architecture Search <https://arxiv.org/pdf/2006.10724.pdf>`__ 在搜索和评估网络之间建立循环反馈机制。 通过引入的循环的可微分架构搜索框架将两个网络集成为一个架构。
* - `ProxylessNAS <Proxylessnas.rst>`__
- `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>`__. 这是专门用于文本表示的神经网络架构搜索算法。
* - `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。
......
......@@ -56,8 +56,7 @@ NNI 上的 ProxylessNAS
在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
:target: ../../img/proxylessnas.png
......
......@@ -82,7 +82,7 @@ ENAS Micro 的搜索空间如下图所示。 请注意,在 NNI 的实现中将
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 作为输入,然后分别对输入应用
``MaxPool`` 和 ``AvgPool``,然后将它们相加作为节点 4 的输出。 未用作任何其他节点输入的节点将被视为该层的输出。 如果有多个输出节点,则模型将计算这些节点的平均值作为当前层的输出。
......@@ -193,10 +193,11 @@ ENAS Macro 的搜索空间如下图所示。
首先将所有输入传递到 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``。
* 普通的 Conv3x3: 如果 ``separable=False``\ , cell 将使用常规的转化操作 ``kernel_size=3``\ , ``stride=1`` 和 ``padding=1``。
* 普通的 Conv5x5:Conv 固定为 ``kernel_size=5``\ , ``stride=1`` 和 ``padding=2``。
* Normal Conv3x3: 如果 ``separable=False``\ , cell 将使用常规的转化操作 ``kernel_size=3``\ , ``stride=1`` 和 ``padding=1``。
*
Normal Conv5x5:Conv 固定为 ``kernel_size=5``\ , ``stride=1`` 和 ``padding=2``。
.. autoclass:: nni.nas.pytorch.search_space_zoo.enas_ops.ConvBranch
......
......@@ -21,7 +21,7 @@ NAS 可视化(测试版)
可视化定制的 Trainer
------------------------------
如果要定制 Trainer,参考 `文档 <./Advanced.rst>`__。
如果要定制 Trainer,参考 `文档 <./Advanced.rst#extend-the-ability-of-one-shot-trainers>`__。
需要对已有 Trainer 代码做两处改动来支持可视化:
......
定义搜索空间
编写搜索空间
====================
通常,搜索空间是要在其中找到最好结构的候选项。 无论是经典 NAS 还是 One-Shot NAS,不同的搜索算法都需要搜索空间。 NNI 提供了统一的 API 来表达神经网络架构的搜索空间。
......@@ -58,10 +58,10 @@
# ... same ...
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 的模块,是一组可能选择的模型。
用户可以为每一个 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