Unverified Commit 25db55ca authored by kvartet's avatar kvartet Committed by GitHub
Browse files

Update Chinese documents (#3243)

parent 53b565e4
# 自定义压缩算法
```eval_rst
.. contents::
```
为了简化实现新压缩算法的过程,NNI 设计了简单灵活,同时支持剪枝和量化的接口。 首先会介绍如何自定义新的剪枝算法,然后介绍如何自定义新的量化算法。
**重要说明**,为了更好的理解如何定制新的剪枝、量化算法,应先了解 NNI 中支持各种剪枝算法的框架。 参考[模型压缩框架](https://nni.readthedocs.io/en/latest/Compressor/Framework.html)
## 自定义剪枝算法
要实现新的剪枝算法,需要实现`权重掩码`类,它是 `WeightMasker` 的子类,以及`Pruner` 类,它是 `Pruner` 的子类。
`权重掩码`的实现如下:
```python
class MyMasker(WeightMasker):
def __init__(self, model, pruner):
super().__init__(model, pruner)
# 此处可初始化,如为算法收集计算权重所需要的统计信息。
def calc_mask(self, sparsity, wrapper, wrapper_idx=None):
# 根据 wrapper.weight, 和 sparsity,
# 及其它信息来计算掩码
# mask = ...
return {'weight_mask': mask}
```
参考 NNI 提供的[权重掩码](https://github.com/microsoft/nni/blob/master/src/sdk/pynni/nni/compression/torch/pruning/structured_pruning.py)来实现自己的。
基础的 `Pruner` 如下:
```python
class MyPruner(Pruner):
def __init__(self, model, config_list, optimizer):
super().__init__(model, config_list, optimizer)
self.set_wrappers_attribute("if_calculated", False)
# 创建权重掩码实例
self.masker = MyMasker(model, self)
def calc_mask(self, wrapper, wrapper_idx=None):
sparsity = wrapper.config['sparsity']
if wrapper.if_calculated:
# 如果是一次性剪枝算法,不需要再次剪枝
return None
else:
# 调用掩码函数来实际计算当前层的掩码
masks = self.masker.calc_mask(sparsity=sparsity, wrapper=wrapper, wrapper_idx=wrapper_idx)
wrapper.if_calculated = True
return masks
```
参考 NNI 提供的[Pruner](https://github.com/microsoft/nni/blob/master/src/sdk/pynni/nni/compression/torch/pruning/one_shot.py) 来实现自己的。
***
## 自定义量化算法
要实现新的量化算法,需要继承 `nni.compression.torch.Quantizer`。 然后,根据算法逻辑来重写成员函数。 需要重载的成员函数是 `quantize_weight``quantize_weight` 直接返回量化后的权重,而不是 mask。这是因为对于量化算法,量化后的权重不能通过应用 mask 来获得。
```python
from nni.compression.torch import Quantizer
class YourQuantizer(Quantizer):
def __init__(self, model, config_list):
"""
建议使用 NNI 定义的规范来配置
"""
super().__init__(model, config_list)
def quantize_weight(self, weight, config, **kwargs):
"""
quantize 需要重载此方法来为权重提供掩码
此方法挂载于模型的 :meth:`forward`。
Parameters
----------
weight : Tensor
要被量化的权重
config : dict
权重量化的配置
"""
# 此处逻辑生成 `new_weight`
return new_weight
def quantize_output(self, output, config, **kwargs):
"""
重载此方法输出量化
此方法挂载于模型的 `:meth:`forward`。
Parameters
----------
output : Tensor
需要被量化的输出
config : dict
输出量化的配置
"""
# 实现生成 `new_output`
return new_output
def quantize_input(self, *inputs, config, **kwargs):
"""
重载此方法量化输入
此方法挂载于模型的 :meth:`forward`。
Parameters
----------
inputs : Tensor
需要被量化的张量
config : dict
输入量化的配置
"""
# 生成 `new_input` 的代码
return new_input
def update_epoch(self, epoch_num):
pass
def step(self):
"""
根据 bind_model 函数传入的模型或权重进行一些处理
"""
pass
```
### 定制 backward 函数
有时,量化操作必须自定义 backward 函数,例如 [Straight-Through Estimator](https://stackoverflow.com/questions/38361314/the-concept-of-straight-through-estimator-ste),可如下定制 backward 函数:
```python
from nni.compression.torch.compressor import Quantizer, QuantGrad, QuantType
class ClipGrad(QuantGrad):
@staticmethod
def quant_backward(tensor, grad_output, quant_type):
"""
此方法应被子类重载来提供定制的 backward 函数,
默认实现是 Straight-Through Estimator
Parameters
----------
tensor : Tensor
量化操作的输入
grad_output : Tensor
量化操作输出的梯度
quant_type : QuantType
量化类型,可为 `QuantType.QUANT_INPUT`, `QuantType.QUANT_WEIGHT`, `QuantType.QUANT_OUTPUT`,
可为不同的类型定义不同的行为。
Returns
-------
tensor
量化输入的梯度
"""
# 对于 quant_output 函数,如果张量的绝对值大于 1,则将梯度设置为 0
if quant_type == QuantType.QUANT_OUTPUT:
grad_output[torch.abs(tensor) > 1] = 0
return grad_output
class YourQuantizer(Quantizer):
def __init__(self, model, config_list):
super().__init__(model, config_list)
# 定制 backward 函数来重载默认的 backward 函数
self.quant_grad = ClipGrad
```
如果不定制 `QuantGrad`,默认的 backward 为 Straight-Through Estimator。 _即将推出_...
# 模型压缩框架概述
```eval_rst
.. contents::
```
下图展示了模型压缩框架的组件概览。
![](../../img/compressor_framework.jpg)
NNI 模型压缩框架中主要有三个组件/类:`Compressor`, `Pruner``Quantizer`。 下面会逐个详细介绍:
## Compressor
Compressor 是 Pruner 和 Quantizer 的基类,提供了统一的接口,可用同样的方式使用它们。 例如,使用 Pruner:
```python
from nni.compression.torch import LevelPruner
# 读取预训练的模型,或在使用 Pruner 前进行训练。
configure_list = [{
'sparsity': 0.7,
'op_types': ['Conv2d', 'Linear'],
}]
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4)
pruner = LevelPruner(model, configure_list, optimizer)
model = pruner.compress()
# 剪枝已准备好,开始调优模型,
# 模型会在训练过程中自动剪枝
```
使用 Quantizer:
```python
from nni.compression.torch import DoReFaQuantizer
configure_list = [{
'quant_types': ['weight'],
'quant_bits': {
'weight': 8,
},
'op_types':['Conv2d', 'Linear']
}]
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4)
quantizer = DoReFaQuantizer(model, configure_list, optimizer)
quantizer.compress()
```
查看[示例代码](https://github.com/microsoft/nni/tree/master/examples/model_compress)了解更多信息。
`Compressor` 类提供了一些工具函数:
### 设置包装的属性
有时,`calc_mask` 需要保存一些状态数据,可以像 PyTorch 的 module 一样,使用 `set_wrappers_attribute` API 来注册属性。 这些缓存会注册到 `module 包装`中。 用户可以通过 `module 包装`来直接访问这些缓存。 在上述示例中,使用了 `set_wrappers_attribute` 类设置缓冲 `if_calculated`,它用来标识某层的掩码是否已经计算过了。
### 在 forward 时收集数据
有时,需要在 forward 方法中收集数据,例如,需要激活的平均值。 可通过向 module 中添加定制的 Collector 来做到。
```python
class MyMasker(WeightMasker):
def __init__(self, model, pruner):
super().__init__(model, pruner)
# 为所有包装类设置 `collected_activation` 属性
# 保存所有层的激活值
self.pruner.set_wrappers_attribute("collected_activation", [])
self.activation = torch.nn.functional.relu
def collector(wrapper, input_, output):
# 通过每个包装的 collected_activation 属性,来评估收到的激活值
wrapper.collected_activation.append(self.activation(output.detach().cpu()))
self.pruner.hook_id = self.pruner.add_activation_collector(collector)
```
收集函数会在每次 forward 方法运行时调用。
还可这样来移除收集方法:
```python
# 保存 Collector 的标识
collector_id = self.pruner.add_activation_collector(collector)
# 当 Collector 不再需要后,可以通过保存的 Collector 标识来删除
self.pruner.remove_activation_collector(collector_id)
```
***
## Pruner
Pruner 接收 `model`, `config_list` 以及 `optimizer` 参数。 通过往 `optimizer.step()` 上增加回调,在训练过程中根据 `config_list` 来对模型剪枝。
Pruner 类是 Compressor 的子类,因此它包含了 Compressor 的所有功能,并添加了剪枝所需要的组件,包括:
### 权重掩码
`权重掩码`是剪枝算法的实现,可将由 `module 包装`所包装起来的一层根据稀疏度进行修建。
### 剪枝模块包装
`剪枝 module 的包装` 包含:
1. 原始的 module
2. `calc_mask` 使用的一些缓存
3. 新的 forward 方法,用于在运行原始的 forward 方法前应用掩码。
使用 `module 包装`的原因:
1. 计算掩码所需要的 `calc_mask` 方法需要一些缓存,这些缓存需要注册在 `module 包装`里,这样就不需要修改原始的 module。
2. 新的 `forward` 方法用来在原始 `forward` 调用前,将掩码应用到权重上。
### 剪枝回调
当 Pruner 构造时会添加剪枝的回调,用来在 `optimizer.step()` 被调用时,调用 Pruner 的 calc_mask。
***
## Quantizer
Quantizer 也是 `Compressor` 的子类,用来通过减少权重或激活值的位宽来压缩模型,这样可以减少模型推理时的计算时间。 它包含:
### 量化 module 包装
模型中每个要量化的模块和层,都需要量化包装,它通过提供 `forward` 方法来量化原始模型的权重、输入和输出。
### 量化回调
量化回调会在调用 `optimizer.step()` 时设置。
### 量化相关函数
`Quantizer` 类为子类提供一下方法来实现量化算法:
```python
class Quantizer(Compressor):
"""
PyTorch 的量化基类
"""
def quantize_weight(self, weight, wrapper, **kwargs):
"""
重载此方法实现权重的量化。
此方法挂载于模型的 :meth:`forward`。
Parameters
----------
weight : Tensor
需要量化的权重
wrapper : QuantizerModuleWrapper
原始 module 的包装
"""
raise NotImplementedError('Quantizer must overload quantize_weight()')
def quantize_output(self, output, wrapper, **kwargs):
"""
重载此方法实现输出的量化。
此方法挂载于模型的 :meth:`forward`。
Parameters
----------
output : Tensor
需要量化的输出
wrapper : QuantizerModuleWrapper
原始 module 的包装
"""
raise NotImplementedError('Quantizer must overload quantize_output()')
def quantize_input(self, *inputs, wrapper, **kwargs):
"""
重载此方法实现输入的量化。
此方法挂载于模型的 :meth:`forward`。
Parameters
----------
inputs : Tensor
需要量化的输入
wrapper : QuantizerModuleWrapper
原始 module 的包装
"""
raise NotImplementedError('Quantizer must overload quantize_input()')
```
***
## 多 GPU 支持
在多 GPU 训练中,缓存和参数会在每次 `forward` 方法被调用时,复制到多个 GPU 上。 如果缓存和参数要在 `forward` 更新,就需要通过`原地`更新来提高效率。 因为 `calc_mask` 会在 `optimizer.step` 方法中的调用,会在 `forward` 方法后才被调用,且只会发生在单 GPU 上,因此它天然的就支持多 GPU 的情况。
# 使用 NNI 进行模型压缩
```eval_rst
.. contents::
```
随着更多层和节点大型神经网络的使用,降低其存储和计算成本变得至关重要,尤其是对于某些实时应用程序。 模型压缩可用于解决此问题。
NNI 的模型压缩工具包,提供了最先进的模型压缩算法和策略,帮助压缩并加速模型。 NNI 模型压缩支持的主要功能有:
* 支持多种流行的剪枝和量化算法。
* 通过 NNI 强大的自动调优功能,可使用最先进的策略来自动化模型的剪枝和量化过程。
* 加速压缩的模型,使其在推理时有更低的延迟,同时文件也会变小。
* 提供优化且易用的压缩工具,帮助用户深入了解压缩过程和结果。
* 提供简洁的接口,帮助用户实现自己的压缩算法。
*注意,PyTorch 和 TensorFlow 有统一的 API 接口,当前仅支持 PyTorch 版本,未来会提供 TensorFlow 的支持。*
## 支持的算法
包括剪枝和量化算法。
### 剪枝算法
剪枝算法通过删除冗余权重或层通道来压缩原始网络,从而降低模型复杂性并解决过拟合问题。
| 名称 | 算法简介 |
| ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| [Level Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#level-pruner) | 根据权重的绝对值,来按比例修剪权重。 |
| [AGP Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#agp-pruner) | 自动的逐步剪枝(是否剪枝的判断:基于对模型剪枝的效果)[参考论文](https://arxiv.org/abs/1710.01878) |
| [Lottery Ticket Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#lottery-ticket-hypothesis) | "The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks" 提出的剪枝过程。 它会反复修剪模型。 [参考论文](https://arxiv.org/abs/1803.03635) |
| [FPGM Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#fpgm-pruner) | Filter Pruning via Geometric Median for Deep Convolutional Neural Networks Acceleration [参考论文](https://arxiv.org/pdf/1811.00250.pdf) |
| [L1Filter Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#l1filter-pruner) | 在卷积层中具有最小 L1 权重规范的剪枝滤波器(用于 Efficient Convnets 的剪枝滤波器) [参考论文](https://arxiv.org/abs/1608.08710) |
| [L2Filter Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#l2filter-pruner) | 在卷积层中具有最小 L2 权重规范的剪枝滤波器 |
| [ActivationAPoZRankFilterPruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#activationapozrankfilterpruner) | 基于指标 APoZ(平均百分比零)的剪枝滤波器,该指标测量(卷积)图层激活中零的百分比。 [参考论文](https://arxiv.org/abs/1607.03250) |
| [ActivationMeanRankFilterPruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#activationmeanrankfilterpruner) | 基于计算输出激活最小平均值指标的剪枝滤波器 |
| [Slim Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#slim-pruner) | 通过修剪 BN 层中的缩放因子来修剪卷积层中的通道 (Learning Efficient Convolutional Networks through Network Slimming) [参考论文](https://arxiv.org/abs/1708.06519) |
| [TaylorFO Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#taylorfoweightfilterpruner) | 基于一阶泰勒展开的权重对滤波器剪枝 (Importance Estimation for Neural Network Pruning) [参考论文](http://jankautz.com/publications/Importance4NNPruning_CVPR19.pdf) |
| [ADMM Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#admm-pruner) | 基于 ADMM 优化技术的剪枝 [参考论文](https://arxiv.org/abs/1804.03294) |
| [NetAdapt Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#netadapt-pruner) | 在满足计算资源预算的情况下,对预训练的网络迭代剪枝 [参考论文](https://arxiv.org/abs/1804.03230) |
| [SimulatedAnnealing Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#simulatedannealing-pruner) | 通过启发式的模拟退火算法进行自动剪枝 [参考论文](https://arxiv.org/abs/1907.03141) |
| [AutoCompress Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#autocompress-pruner) | 通过迭代调用 SimulatedAnnealing Pruner 和 ADMM Pruner 进行自动剪枝 [参考论文](https://arxiv.org/abs/1907.03141) |
参考此[基准测试](https://github.com/microsoft/nni/blob/master/docs/zh_CN/CommunitySharings/ModelCompressionComparison.md)来查看这些剪枝器在一些基准问题上的表现。
### 量化算法
量化算法通过减少表示权重或激活所需的精度位数来压缩原始网络,这可以减少计算和推理时间。
| 名称 | 算法简介 |
| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Naive Quantizer](https://nni.readthedocs.io/zh/latest/Compressor/Quantizer.html#naive-quantizer) | 默认将权重量化为 8 位 |
| [QAT Quantizer](https://nni.readthedocs.io/zh/latest/Compressor/Quantizer.html#qat-quantizer) | 为 Efficient Integer-Arithmetic-Only Inference 量化并训练神经网络。 [参考论文](http://openaccess.thecvf.com/content_cvpr_2018/papers/Jacob_Quantization_and_Training_CVPR_2018_paper.pdf) |
| [DoReFa Quantizer](https://nni.readthedocs.io/zh/latest/Compressor/Quantizer.html#dorefa-quantizer) | DoReFa-Net: 通过低位宽的梯度算法来训练低位宽的卷积神经网络。 [参考论文](https://arxiv.org/abs/1606.06160) |
| [BNN Quantizer](https://nni.readthedocs.io/zh/latest/Compressor/Quantizer.html#bnn-quantizer) | 二进制神经网络:使用权重和激活限制为 +1 或 -1 的深度神经网络。 [参考论文](https://arxiv.org/abs/1602.02830) |
## 自动模型压缩
有时,给定的目标压缩率很难通过一次压缩就得到最好的结果。 自动模型压缩算法,通常需要通过对不同层采用不同的稀疏度来探索可压缩的空间。 NNI 提供了这样的算法,来帮助用户在模型中为每一层指定压缩度。 此外,还可利用 NNI 的自动调参功能来自动的压缩模型。 详细文档参考[这里](./AutoCompression.md)
## 模型加速
模型压缩的目的是减少推理延迟和模型大小。 但现有的模型压缩算法主要通过模拟的方法来检查压缩模型性能(如精度)。例如,剪枝算法中使用掩码,而量化算法中量化值仍然是以 32 位浮点数来存储。 只要给出这些算法产生的掩码和量化位,NNI 可真正的加速模型。 模型加速的详细文档参考[这里](./ModelSpeedup.md)
## 压缩工具
压缩工具包括了一些有用的工具,能帮助用户理解并分析要压缩的模型。 例如,可检查每层对剪枝的敏感度。 可很容易地计算模型的 FLOPs 和参数数量。 点击[这里](./CompressionUtils.md),查看压缩工具的完整列表。
## 自定义压缩算法
NNI 模型压缩提供了简洁的接口,用于自定义新的压缩算法。 接口的设计理念是,将框架相关的实现细节包装起来,让用户能聚焦于压缩逻辑。 点击[这里](./Framework.md),查看自定义新压缩算法(包括剪枝和量化算法)的详细教程。
## 参考和反馈
* 在 GitHub 中[提交此功能的 Bug](https://github.com/microsoft/nni/issues/new?template=bug-report.md)
* 在 GitHub 中[提交新功能或改进请求](https://github.com/microsoft/nni/issues/new?template=enhancement.md)
* 了解更多关于 [NNI 中的特征工程](../FeatureEngineering/Overview.md)
* 了解更多关于 [NNI 中的 NAS](../NAS/Overview.md)
* 了解更多关于 [NNI 中的超参调优](../Tuner/BuiltinTuner.md)
# NNI 支持的剪枝算法
NNI 提供了一些支持细粒度权重剪枝和结构化的滤波器剪枝算法。 **细粒度的剪枝**通常会导致非结构化的模型,这需要特定的硬件或软件来加速这样的稀疏网络。 **滤波器剪枝**通过删除整个滤波器来实现加速。 NNI 还提供了算法来进行**剪枝规划**
**细粒度剪枝**
* [Level Pruner](#level-pruner)
**滤波器剪枝**
* [Slim Pruner](#slim-pruner)
* [FPGM Pruner](#fpgm-pruner)
* [L1Filter Pruner](#l1filter-pruner)
* [L2Filter Pruner](#l2filter-pruner)
* [Activation APoZ Rank Filter Pruner](#activationAPoZRankFilter-pruner)
* [Activation Mean Rank Filter Pruner](#activationmeanrankfilter-pruner)
* [Taylor FO On Weight Pruner](#taylorfoweightfilter-pruner)
**剪枝计划**
* [AGP Pruner](#agp-pruner)
* [NetAdapt Pruner](#netadapt-pruner)
* [SimulatedAnnealing Pruner](#simulatedannealing-pruner)
* [AutoCompress Pruner](#autocompress-pruner)
* [AutoML for Model Compression Pruner](#automl-for-model-compression-pruner)
* [Sensitivity Pruner](#sensitivity-pruner)
**其它**
* [ADMM Pruner](#admm-pruner)
* [Lottery Ticket 假设](#Lottery-Ticket-假设)
## Level Pruner
这是个基本的一次性 Pruner:可设置目标稀疏度(以分数表示,0.6 表示会剪除 60%)。
首先按照绝对值对指定层的权重排序。 然后按照所需的稀疏度,将值最小的权重屏蔽为 0。
### 用法
TensorFlow 代码
```python
from nni.compression.tensorflow import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
```
PyTorch 代码
```python
from nni.compression.torch import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
```
#### Level Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.LevelPruner
```
##### Tensorflow
```eval_rst
.. autoclass:: nni.compression.tensorflow.LevelPruner
```
## Slim Pruner
这是一次性的 Pruner,在 ['Learning Efficient Convolutional Networks through Network Slimming'](https://arxiv.org/pdf/1708.06519.pdf) 中提出,作者 Zhuang Liu, Jianguo Li, Zhiqiang Shen, Gao Huang, Shoumeng Yan 以及 Changshui Zhang。
![](../../img/slim_pruner.png)
> Slim Pruner **会遮盖卷据层通道之后 BN 层对应的缩放因子**,训练时在缩放因子上的 L1 正规化应在批量正规化 (BN) 层之后来做。BN 层的缩放因子在修剪时,是**全局排序的**,因此稀疏模型能自动找到给定的稀疏度。
### 用法
PyTorch 代码
```python
from nni.compression.torch import SlimPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['BatchNorm2d'] }]
pruner = SlimPruner(model, config_list)
pruner.compress()
```
#### Slim Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.SlimPruner
```
### 重现实验
我们实现了 ['Learning Efficient Convolutional Networks through Network Slimming'](https://arxiv.org/pdf/1708.06519.pdf) 中的一项实验。根据论文,对 CIFAR-10 上的 **VGGNet** 剪除了 $70\%$ 的通道,即约 $88.5\%$ 的参数。 实验结果如下:
| 模型 | 错误率(论文/我们的) | 参数量 | 剪除率 |
| ------------- | ----------- | ------ | ----- |
| VGGNet | 6.34/6.40 | 20.04M | |
| Pruned-VGGNet | 6.20/6.26 | 2.03M | 88.5% |
实验代码在 [examples/model_compress](https://github.com/microsoft/nni/tree/master/examples/model_compress/)
***
## FPGM Pruner
这是一种一次性的 Pruner,FPGM Pruner 是论文 [Filter Pruning via Geometric Median for Deep Convolutional Neural Networks Acceleration](https://arxiv.org/pdf/1811.00250.pdf) 的实现
具有最小几何中位数的 FPGMPruner 修剪过滤器。
![](../../img/fpgm_fig1.png)
> 以前的方法使用 “smaller-norm-less-important” 准则来修剪卷积神经网络中规范值较小的。 本文中,分析了基于规范的准则,并指出其所依赖的两个条件不能总是满足:(1) 滤波器的规范偏差应该较大;(2) 滤波器的最小规范化值应该很小。 为了解决此问题,提出了新的滤波器修剪方法,即 Filter Pruning via Geometric Median (FPGM),可不考虑这两个要求来压缩模型。 与以前的方法不同,FPGM 通过修剪冗余的,而不是相关性更小的部分来压缩 CNN 模型。
### 用法
PyTorch 代码
```python
from nni.compression.torch import FPGMPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = FPGMPruner(model, config_list)
pruner.compress()
```
#### FPGM Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.FPGMPruner
```
## L1Filter Pruner
这是一种一次性的 Pruner,由 ['PRUNING FILTERS FOR EFFICIENT CONVNETS'](https://arxiv.org/abs/1608.08710) 提出,作者 Hao Li, Asim Kadav, Igor Durdanovic, Hanan Samet 和 Hans Peter Graf。
![](../../img/l1filter_pruner.png)
> L1Filter Pruner 修剪**卷积层**中的滤波器
>
> 从第 i 个卷积层修剪 m 个滤波器的过程如下:
>
> 1. 对于每个滤波器 ![](http://latex.codecogs.com/gif.latex?F_{i,j}),计算其绝对内核权重之和![](http://latex.codecogs.com/gif.latex?s_j=\sum_{l=1}^{n_i}\sum|K_l|)
> 2. 将滤波器按 ![](http://latex.codecogs.com/gif.latex?s_j) 排序。
> 3. 修剪 ![](http://latex.codecogs.com/gif.latex?m) 具有最小求和值及其相应特征图的滤波器。 在 下一个卷积层中,被剪除的特征图所对应的内核也被移除。
> 4. 为第 ![](http://latex.codecogs.com/gif.latex?i) 和 ![](http://latex.codecogs.com/gif.latex?i+1) 层创建新的内核举证,并保留剩余的内核 权重,并复制到新模型中。
### 用法
PyTorch 代码
```python
from nni.compression.torch import L1FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'] }]
pruner = L1FilterPruner(model, config_list)
pruner.compress()
```
#### L1Filter Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.L1FilterPruner
```
### 重现实验
我们通过 **L1FilterPruner** 实现了 ['PRUNING FILTERS FOR EFFICIENT CONVNETS'](https://arxiv.org/abs/1608.08710) 中的一项实验, 即论文中,在 CIFAR-10 数据集上修剪 **VGG-16****VGG-16-pruned-A**,其中大约剪除了 $64\%$ 的参数。 实验结果如下:
| 模型 | 错误率(论文/我们的) | 参数量 | 剪除率 |
| --------------- | ----------- | -------- | ----- |
| VGG-16 | 6.75/6.49 | 1.5x10^7 | |
| VGG-16-pruned-A | 6.60/6.47 | 5.4x10^6 | 64.0% |
实验代码在 [examples/model_compress](https://github.com/microsoft/nni/tree/master/examples/model_compress/)
***
## L2Filter Pruner
这是一种结构化剪枝算法,用于修剪权重的最小 L2 规范滤波器。 它被实现为一次性修剪器。
### 用法
PyTorch 代码
```python
from nni.compression.torch import L2FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'] }]
pruner = L2FilterPruner(model, config_list)
pruner.compress()
```
### L2Filter Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.L2FilterPruner
```
***
## ActivationAPoZRankFilter Pruner
ActivationAPoZRankFilter Pruner 是从卷积层激活的输出,用最小的重要性标准 `APoZ` 修剪滤波器,来达到预设的网络稀疏度。 剪枝标准 `APoZ` 的解释在论文 [Network Trimming: A Data-Driven Neuron Pruning Approach towards Efficient Deep Architectures](https://arxiv.org/abs/1607.03250) 中。
APoZ 定义为:
![](../../img/apoz.png)
### 用法
PyTorch 代码
```python
from nni.compression.torch import ActivationAPoZRankFilterPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = ActivationAPoZRankFilterPruner(model, config_list, statistics_batch_num=1)
pruner.compress()
```
注意:ActivationAPoZRankFilterPruner 用于修剪深度神经网络中的卷积层,因此 `op_types` 字段仅支持卷积层。
参考[示例](https://github.com/microsoft/nni/blob/master/examples/model_compress/model_prune_torch.py)了解更多信息。
### ActivationAPoZRankFilterPruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.ActivationAPoZRankFilterPruner
```
***
## ActivationMeanRankFilter Pruner
ActivationMeanRankFilter Pruner 是从卷积层激活的输出,用最小的重要性标准`平均激活`来修剪滤波器,来达到预设的网络稀疏度。 剪枝标准`平均激活`,在论文 [Pruning Convolutional Neural Networks for Resource Efficient Inference](https://arxiv.org/abs/1611.06440) 的 2.2 节中进行了介绍。 本文中提到的其他修剪标准将在以后的版本中支持。
### 用法
PyTorch 代码
```python
from nni.compression.torch import ActivationMeanRankFilterPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = ActivationMeanRankFilterPruner(model, config_list, statistics_batch_num=1)
pruner.compress()
```
注意:ActivationMeanRankFilterPruner 用于修剪深度神经网络中的卷积层,因此 `op_types` 字段仅支持卷积层。
参考[示例](https://github.com/microsoft/nni/blob/master/examples/model_compress/model_prune_torch.py)了解更多信息。
### ActivationMeanRankFilterPruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.ActivationMeanRankFilterPruner
```
***
## TaylorFOWeightFilter Pruner
TaylorFOWeightFilter Pruner 根据权重上的一阶泰勒展开式,来估计重要性并进行剪枝,从而达到预设的网络稀疏度。 过滤器的估计重要性在论文 [Importance Estimation for Neural Network Pruning](http://jankautz.com/publications/Importance4NNPruning_CVPR19.pdf) 中有定义。 本文中提到的其他修剪标准将在以后的版本中支持。
>
![](../../img/importance_estimation_sum.png)
### 用法
PyTorch 代码
```python
from nni.compression.torch import TaylorFOWeightFilterPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = TaylorFOWeightFilterPruner(model, config_list, statistics_batch_num=1)
pruner.compress()
```
#### TaylorFOWeightFilter Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.TaylorFOWeightFilterPruner
```
***
## AGP Pruner
这是一种迭代的 Pruner,在 [To prune, or not to prune: exploring the efficacy of pruning for model compression](https://arxiv.org/abs/1710.01878)中,作者 Michael Zhu 和 Suyog Gupta 提出了一种逐渐修建权重的算法。
> 引入了一种新的自动逐步剪枝算法,在 n 个剪枝步骤中,稀疏度从初始的稀疏度值 si(通常为 0)增加到最终的稀疏度值 sf,从训练步骤 t0 开始,剪枝频率 ∆t: ![](../../img/agp_pruner.png)
> 在训练网络时,每隔 ∆t 步更新二值权重掩码,以逐渐增加网络的稀疏性,同时允许网络训练步骤从任何剪枝导致的精度损失中恢复。 根据我们的经验,∆t 设为 100 到 1000 个训练步骤之间时,对于模型最终精度的影响可忽略不计。 一旦模型达到了稀疏度目标 sf,权重掩码将不再更新。 背后的稀疏函数直觉在公式(1)。
### 用法
通过下列代码,可以在 10 个 Epoch 中将权重稀疏度从 0% 剪枝到 80%。
PyTorch 代码
```python
from nni.compression.torch import AGPPruner
config_list = [{
'initial_sparsity': 0,
'final_sparsity': 0.8,
'start_epoch': 0,
'end_epoch': 10,
'frequency': 1,
'op_types': ['default']
}]
# 使用 Pruner 前,加载预训练模型、或训练模型。
# model = MyModel()
# model.load_state_dict(torch.load('mycheckpoint.pth'))
# AGP Pruner 会在 optimizer.step() 上回调,在微调模型时剪枝,
# 因此,必须要有 optimizer 才能完成模型剪枝。
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4)
pruner = AGPPruner(model, config_list, optimizer, pruning_algorithm='level')
pruner.compress()
```
AGP Pruner 默认使用 `LevelPruner` 算法来修建权重,还可以设置 `pruning_algorithm` 参数来使用其它剪枝算法:
* `level`: LevelPruner
* `slim`: SlimPruner
* `l1`: L1FilterPruner
* `l2`: L2FilterPruner
* `fpgm`: FPGMPruner
* `taylorfo`: TaylorFOWeightFilterPruner
* `apoz`: ActivationAPoZRankFilterPruner
* `mean_activation`: ActivationMeanRankFilterPruner
在训练代码中每完成一个 Epoch,需要更新一下 Epoch 的值。
PyTorch 代码
```python
pruner.update_epoch(epoch)
```
参考[示例](https://github.com/microsoft/nni/blob/master/examples/model_compress/model_prune_torch.py)了解更多信息。
#### AGP Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.AGPPruner
```
***
## NetAdapt Pruner
NetAdapt 在满足资源预算的情况下,自动简化预训练的网络。 给定整体稀疏度,NetAdapt 可通过迭代剪枝自动为不同层生成不同的稀疏分布。
参考 [NetAdapt: Platform-Aware Neural Network Adaptation for Mobile Applications](https://arxiv.org/abs/1804.03230) 了解详细信息。
![](../../img/algo_NetAdapt.png)
#### 用法
PyTorch 代码
```python
from nni.compression.torch import NetAdaptPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = NetAdaptPruner(model, config_list, short_term_fine_tuner=short_term_fine_tuner, evaluator=evaluator,base_algo='l1', experiment_data_dir='./')
pruner.compress()
```
参考[示例](https://github.com/microsoft/nni/blob/master/examples/model_compress/auto_pruners_torch.py)了解更多信息。
#### NetAdapt Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.NetAdaptPruner
```
## SimulatedAnnealing Pruner
此 Pruner 基于先验经验,实现了引导式的启发搜索方法,模拟退火(SA)算法。 增强的模拟退火算法基于以下发现:具有更多权重的深度神经网络层通常具有较高的可压缩度,对整体精度的影响更小。
- 随机初始化剪枝率的分布(稀疏度)。
- 当 current_temperature < stop_temperature 时:
1. 对当前分布生成扰动
2. 对扰动的分布进行快速评估
3. 根据性能和概率来决定是否接受扰动,如果不接受,返回步骤 1
4. 冷却,current_temperature = current_temperature * cool_down_rate
更多详细信息,参考 [AutoCompress: An Automatic DNN Structured Pruning Framework for Ultra-High Compression Rates](https://arxiv.org/abs/1907.03141)
#### 用法
PyTorch 代码
```python
from nni.compression.torch import SimulatedAnnealingPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = SimulatedAnnealingPruner(model, config_list, evaluator=evaluator, base_algo='l1', cool_down_rate=0.9, experiment_data_dir='./')
pruner.compress()
```
参考[示例](https://github.com/microsoft/nni/blob/master/examples/model_compress/auto_pruners_torch.py)了解更多信息。
#### SimulatedAnnealing Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.SimulatedAnnealingPruner
```
## AutoCompress Pruner
每一轮中,AutoCompressPruner 会用相同的稀疏度对模型进行剪枝,从而达到总体的稀疏度:
1. 使用 SimulatedAnnealingPruner 生成稀疏度分布
2. 执行基于 ADMM 的结构化剪枝,为下一轮生成剪枝结果。
这里会使用 `speedup` 来执行真正的剪枝。
更多详细信息,参考 [AutoCompress: An Automatic DNN Structured Pruning Framework for Ultra-High Compression Rates](https://arxiv.org/abs/1907.03141)
#### 用法
PyTorch 代码
```python
from nni.compression.torch import ADMMPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = AutoCompressPruner(
model, config_list, trainer=trainer, evaluator=evaluator,
dummy_input=dummy_input, num_iterations=3, optimize_mode='maximize', base_algo='l1',
cool_down_rate=0.9, admm_num_iterations=30, admm_training_epochs=5, experiment_data_dir='./')
pruner.compress()
```
参考[示例](https://github.com/microsoft/nni/blob/master/examples/model_compress/auto_pruners_torch.py)了解更多信息。
#### AutoCompress Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.AutoCompressPruner
```
## AutoML for Model Compression Pruner
自动机器学习用于模型压缩剪枝器(AMCPruner)借助强化学习来提供模型压缩策略。 这种基于学习的压缩策略比传统的基于规则的压缩策略有更高的压缩比,更好地保存了精度,节省了人力。
![](../../img/amc_pruner.jpg)
更多详细信息,参考 [AMC: AutoML for Model Compression and Acceleration on Mobile Devices](https://arxiv.org/pdf/1802.03494.pdf)
#### 用法
PyTorch 代码
```python
from nni.compression.torch import AMCPruner
config_list = [{
'op_types': ['Conv2d', 'Linear']
}]
pruner = AMCPruner(model, config_list, evaluator, val_loader, flops_ratio=0.5)
pruner.compress()
```
参考[示例](https://github.com/microsoft/nni/blob/master/examples/model_compress/amc/)了解更多信息。
#### AutoCompress Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.AMCPruner
```
## ADMM Pruner
Alternating Direction Method of Multipliers (ADMM) 是一种数学优化技术,它将原始的非凸问题分解为两个可以迭代解决的子问题。 在权重修剪问题中,这两个子问题分别通过 1) 梯度下降算法和 2) 欧几里得投影来解决。
在解决这两个子问题的过程中,原始模型的权重会被改变。 One-Shot Pruner 会根据给定的配置对模型剪枝。
此解决方案框架既适用于非结构化剪枝也适用于结构化剪枝的变体。
更多详细信息,参考 [A Systematic DNN Weight Pruning Framework using Alternating Direction Method of Multipliers](https://arxiv.org/abs/1804.03294)
#### 用法
PyTorch 代码
```python
from nni.compression.torch import ADMMPruner
config_list = [{
'sparsity': 0.8,
'op_types': ['Conv2d'],
'op_names': ['conv1']
}, {
'sparsity': 0.92,
'op_types': ['Conv2d'],
'op_names': ['conv2']
}]
pruner = ADMMPruner(model, config_list, trainer=trainer, num_iterations=30, epochs=5)
pruner.compress()
```
参考[示例](https://github.com/microsoft/nni/blob/master/examples/model_compress/auto_pruners_torch.py)了解更多信息。
#### ADMM Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.ADMMPruner
```
## Lottery Ticket 假设
[The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks](https://arxiv.org/abs/1803.03635), 作者 Jonathan Frankle 和 Michael Carbin,提供了全面的测量和分析,并阐明了 *lottery ticket 假设*: 密集的、随机初始化的、包含子网络的前馈网络 (*winning tickets*) -- 在单独训练时 -- 在相似的迭代次数后达到了与原始网络相似的准确度。
本文中,作者使用叫做*迭代*修剪的方法:
> 1. 随机初始化一个神经网络 f(x;theta_0) (其中 theta_0 为 D_{theta}).
> 2. 将网络训练 j 次,得出参数 theta_j。
> 3. 在 theta_j 修剪参数的 p%,创建掩码 m。
> 4. 将其余参数重置为 theta_0 的值,创建获胜彩票 f(x;m*theta_0)。
> 5. 重复步骤 2、3 和 4。
如果配置的最终稀疏度为 P (e.g., 0.8) 并且有 n 次修建迭代,每次迭代修剪前一轮中剩余权重的 1-(1-P)^(1/n)。
### 用法
PyTorch 代码
```python
from nni.compression.torch import LotteryTicketPruner
config_list = [{
'prune_iterations': 5,
'sparsity': 0.8,
'op_types': ['default']
}]
pruner = LotteryTicketPruner(model, config_list, optimizer)
pruner.compress()
for _ in pruner.get_prune_iterations():
pruner.prune_iteration_start()
for epoch in range(epoch_num):
...
```
上述配置意味着有 5 次迭代修剪。 由于在同一次运行中执行了 5 次修剪,LotteryTicketPruner 需要 `model``optimizer` (**注意,如果使用 `lr_scheduler`,也需要添加**) 来在每次开始新的修剪迭代时,将其状态重置为初始值。 使用 `get_prune_iterations` 来获取修建迭代,并在每次迭代开始时调用 `prune_iteration_start`。 为了模型能较好收敛,`epoch_num` 最好足够大。因为假设是在后几轮中具有较高稀疏度的性能(准确度)可与第一轮获得的相当。
*稍后支持 TensorFlow 版本。*
#### LotteryTicket Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.LotteryTicketPruner
```
### 重现实验
在重现时,在 MNIST 使用了与论文相同的配置。 [此处](https://github.com/microsoft/nni/tree/master/examples/model_compress/lottery_torch_mnist_fc.py)为实现代码。 在次实验中,修剪了10次,在每次修剪后,训练了 50 个 epoch。
![](../../img/lottery_ticket_mnist_fc.png)
上图展示了全连接网络的结果。 `round0-sparsity-0.0` 是没有剪枝的性能。 与论文一致,修剪约 80% 也能获得与不修剪时相似的性能,收敛速度也会更快。 如果修剪过多(例如,大于 94%),则精度会降低,收敛速度会稍慢。 与本文稍有不同,论文中数据的趋势比较明显。
## Sensitivity Pruner
在每一轮,SensitivityPruner 根据对每一层准确率的敏感度对模型进行剪枝,直到满足整个模型最终配置的稀疏度:
1. 分析模型当前状态下各层的敏感度。
2. 根据敏感度对每一层剪枝。
更多详细信息,参考 [Learning both Weights and Connections for Efficient Neural Networks ](https://arxiv.org/abs/1506.02626)
#### 用法
PyTorch 代码
```python
from nni.compression.torch import SensitivityPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = SensitivityPruner(model, config_list, finetuner=fine_tuner, evaluator=evaluator)
# eval_args and finetune_args are the parameters passed to the evaluator and finetuner respectively
pruner.compress(eval_args=[model], finetune_args=[model])
```
#### Sensitivity Pruner 的用户配置
##### PyTorch
```eval_rst
.. autoclass:: nni.compression.torch.SensitivityPruner
```
# 支持的量化算法
支持的量化算法列表
* [Naive Quantizer](#naive-quantizer)
* [QAT Quantizer](#qat-quantizer)
* [DoReFa Quantizer](#dorefa-quantizer)
* [BNN Quantizer](#bnn-quantizer)
## Naive Quantizer
Naive Quantizer 将 Quantizer 权重默认设置为 8 位,可用它来测试量化算法。
### 用法
PyTorch
```python
model = nni.compression.torch.NaiveQuantizer(model).compress()
```
***
## QAT Quantizer
[Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference](http://openaccess.thecvf.com/content_cvpr_2018/papers/Jacob_Quantization_and_Training_CVPR_2018_paper.pdf) 中,作者 Benoit Jacob 和 Skirmantas Kligys 提出了一种算法在训练中量化模型。
> 我们提出了一种方法,在训练的前向过程中模拟量化效果。 此方法不影响反向传播,所有权重和偏差都使用了浮点数保存,因此能很容易的进行量化。 然后,前向传播通过实现浮点算法的舍入操作,来在推理引擎中模拟量化的推理。 * 权重在与输入卷积操作前进行量化。 如果在层中使用了批量归一化(参考 [17]),批量归一化参数会被在量化前被“折叠”到权重中。 * 激活操作在推理时会被量化,例如,在激活函数被应用到卷积或全连接层输出之后,或在增加旁路连接,或连接多个层的输出之后(如:ResNet)。 Activations are quantized at points where they would be during inference, e.g. after the activation function is applied to a convolutional or fully connected layer’s output, or after a bypass connection adds or concatenates the outputs of several layers together such as in ResNets.
### 用法
可在训练代码前将模型量化为 8 位。
PyTorch 代码
```python
from nni.compression.torch import QAT_Quantizer
model = Mnist()
config_list = [{
'quant_types': ['weight'],
'quant_bits': {
'weight': 8,
}, # 这里可以仅使用 `int`,因为所有 `quan_types` 使用了一样的位长,参考下方 `ReLu6` 配置。
'op_types':['Conv2d', 'Linear']
}, {
'quant_types': ['output'],
'quant_bits': 8,
'quant_start_step': 7000,
'op_types':['ReLU6']
}]
quantizer = QAT_Quantizer(model, config_list)
quantizer.compress()
```
查看示例进一步了解
#### QAT Quantizer 的用户配置
压缩算法的公共配置可在 [`config_list` 说明](./QuickStart.md)中找到。
此算法所需的配置:
* **quant_start_step:** int
在运行到某步骤前,对模型禁用量化。这让网络在进入更稳定的 状态后再激活量化,这样不会配除掉一些分数显著的值,默认为 0
### 注意
当前不支持批处理规范化折叠。
***
## DoReFa Quantizer
[DoReFa-Net: Training Low Bitwidth Convolutional Neural Networks with Low Bitwidth Gradients](https://arxiv.org/abs/1606.06160) 中,作者 Shuchang Zhou 和 Yuxin Wu 提出了 DoReFa 算法在训练时量化权重,激活函数和梯度。
### 用法
要实现 DoReFa Quantizer,在训练代码前加入以下代码。
PyTorch 代码
```python
from nni.compression.torch import DoReFaQuantizer
config_list = [{
'quant_types': ['weight'],
'quant_bits': 8,
'op_types': 'default'
}]
quantizer = DoReFaQuantizer(model, config_list)
quantizer.compress()
```
查看示例进一步了解
#### DoReFa Quantizer 的用户配置
压缩算法的公共配置可在 [`config_list` 说明](./QuickStart.md)中找到。
此算法所需的配置:
***
## BNN Quantizer
[Binarized Neural Networks: Training Deep Neural Networks with Weights and Activations Constrained to +1 or -1](https://arxiv.org/abs/1602.02830) 中,
> 引入了一种训练二进制神经网络(BNN)的方法 - 神经网络在运行时使用二进制权重。 在训练时,二进制权重和激活用于计算参数梯度。 在 forward 过程中,BNN 会大大减少内存大小和访问,并将大多数算术运算替换为按位计算,可显著提高能源效率。
### 用法
PyTorch 代码
```python
from nni.compression.torch import BNNQuantizer
model = VGG_Cifar10(num_classes=10)
configure_list = [{
'quant_bits': 1,
'quant_types': ['weight'],
'op_types': ['Conv2d', 'Linear'],
'op_names': ['features.0', 'features.3', 'features.7', 'features.10', 'features.14', 'features.17', 'classifier.0', 'classifier.3']
}, {
'quant_bits': 1,
'quant_types': ['output'],
'op_types': ['Hardtanh'],
'op_names': ['features.6', 'features.9', 'features.13', 'features.16', 'features.20', 'classifier.2', 'classifier.5']
}]
quantizer = BNNQuantizer(model, configure_list)
model = quantizer.compress()
```
可以查看示例 [examples/model_compress/BNN_quantizer_cifar10.py](https://github.com/microsoft/nni/tree/master/examples/model_compress/BNN_quantizer_cifar10.py) 了解更多信息。
#### BNN Quantizer 的用户配置
压缩算法的公共配置可在 [`config_list` 说明](./QuickStart.md)中找到。
此算法所需的配置:
### 实验
我们实现了 [Binarized Neural Networks: Training Deep Neural Networks with Weights and Activations Constrained to +1 or -1](https://arxiv.org/abs/1602.02830) 中的一个实验,对 CIFAR-10 上的 **VGGNet** 进行了量化操作。 实验结果如下:
| 模型 | 精度 |
| ------ | ------ |
| VGGNet | 86.93% |
实验代码可在 [examples/model_compress/BNN_quantizer_cifar10.py](https://github.com/microsoft/nni/tree/master/examples/model_compress/BNN_quantizer_cifar10.py)
\ No newline at end of file
# 模型压缩教程
```eval_rst
.. contents::
```
本教程中,[第一部分](#模型压缩快速入门)会简单介绍 NNI 上模型压缩的用法。 然后在[第二部分](#使用指南)中进行详细介绍。
## 模型压缩快速入门
NNI 为模型压缩提供了非常简单的 API。 压缩包括剪枝和量化算法。 算法的用法相同,这里以 [slim Pruner](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#slim-pruner) 为例来介绍。
### 编写配置
编写配置来指定要剪枝的层。 以下配置表示剪枝所有的 `BatchNorm2d`,稀疏度设为 0.7,其它层保持不变。
```python
configure_list = [{
'sparsity': 0.7,
'op_types': ['BatchNorm2d'],
}]
```
配置说明在[这里](#config-list-说明)。 注意,不同的 Pruner 可能有自定义的配置字段,例如,AGP Pruner 有 `start_epoch`。 详情参考每个 Pruner 的[使用](./Pruner.md),来调整相应的配置。
### 选择压缩算法
选择 Pruner 来修剪模型。 首先,使用模型来初始化 Pruner,并将配置作为参数传入,然后调用 `compress()` 来压缩模型。
```python
pruner = SlimPruner(model, configure_list)
model = pruner.compress()
```
然后,使用正常的训练方法来训练模型 (如,SGD),剪枝在训练过程中是透明的。 一些 Pruner 只在最开始剪枝一次,接下来的训练可被看作是微调优化。 有些 Pruner 会迭代的对模型剪枝,在训练过程中逐步修改掩码。
### 导出压缩结果
训练完成后,可获得剪枝后模型的精度。 可将模型权重到处到文件,同时将生成的掩码也导出到文件。 也支持导出 ONNX 模型。
```python
pruner.export_model(model_path='pruned_vgg19_cifar10.pth', mask_path='mask_vgg19_cifar10.pth')
```
模型的完整示例代码在[这里](https://github.com/microsoft/nni/blob/master/examples/model_compress/model_prune_torch.py)
### 加速模型
掩码实际上并不能加速模型。 要基于导出的掩码,来对模型加速,因此,NNI 提供了 API 来加速模型。 在模型上调用 `apply_compression_results` 后,模型会变得更小,推理延迟也会减小。
```python
from nni.compression.torch import apply_compression_results
apply_compression_results(model, 'mask_vgg19_cifar10.pth')
```
参考[这里](ModelSpeedup.md),了解详情。
## 使用指南
将压缩应用到模型的示例代码如下:
PyTorch 代码
```python
from nni.compression.torch import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
```
TensorFlow 代码
```python
from nni.compression.tensorflow 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.md)
压缩算法首先通过传入 `config_list` 来实例化。 `config_list` 会稍后介绍。
函数调用 `pruner.compress()` 来修改用户定义的模型(在 Tensorflow 中,通过 `tf.get_default_graph()` 来获得模型,而 PyTorch 中 model 是定义的模型类),并修改模型来插入 mask。 然后运行模型时,这些掩码即会生效。 掩码可在运行时通过算法来调整。
*注意,`pruner.compress` 只会在模型权重上直接增加掩码,不包括调优的逻辑。 如果要想调优压缩后的模型,需要在 `pruner.compress` 后增加调优的逻辑。*
### `config_list` 说明
用户可为压缩算法指定配置 (即, `config_list`)。 例如,压缩模型时,用户可能希望指定稀疏率,为不同类型的操作指定不同的稀疏比例,排除某些类型的操作,或仅压缩某类操作。 配置规范可用于表达此类需求。 可将其视为一个 Python 的 `list` 对象,其中每个元素都是一个 `dict` 对象。
`list` 中的 `dict` 会依次被应用,也就是说,如果一个操作出现在两个配置里,后面的 `dict` 会覆盖前面的配置。
`dict` 中有不同的键值。 以下是所有压缩算法都支持的:
* __op_types__:指定要压缩的操作类型。 'default' 表示使用算法的默认设置。
* __op_names__:指定需要压缩的操作的名称。 如果没有设置此字段,操作符不会通过名称筛选。
* __exclude__:默认为 False。 如果此字段为 True,表示要通过类型和名称,将一些操作从压缩中排除。
其它算法的键值,可参考[剪枝算法](./Pruner.md)[量化算法](./Quantizer.md),查看每个算法的键值。
配置的简单示例如下:
```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`
#### 其它量化算法字段
**如果使用量化算法,则需要设置更多键值。 如果使用剪枝算法,则可以忽略这些键值**
* __quant_types__ : 字符串列表。
要应用量化的类型,当前支持 'weight', 'input', 'output'。 'weight' 是指将量化操作应用到 module 的权重参数上。 'input' 是指对 module 的 forward 方法的输入应用量化操作。 'output' 是指将量化运法应用于模块 forward 方法的输出,有些论文中将其称为 '激活(activation)'。
* __quant_bits__ : int 或 dict {str : int}
量化的位宽,键是量化类型,值是量化位宽度,例如:
```
{
quant_bits: {
'weight': 8,
'output': 4,
},
}
```
当值为 int 类型时,所有量化类型使用相同的位宽。 例如:
```
{
quant_bits: 8, # 权重和输出的位宽都为 8 bits
}
```
### 更新优化状态的 API
一些压缩算法使用 Epoch 来控制压缩过程(如,[AGP](https://nni.readthedocs.io/zh/latest/Compressor/Pruner.html#agp-pruner)),一些算法需要在每个批处理步骤后执行一些逻辑。 因此,NNI 提供了两个 API:`pruner.update_epoch(epoch)``pruner.step()`
`update_epoch` 会在每个 Epoch 时调用,而 `step` 会在每次批处理后调用。 注意,大多数算法不需要调用这两个 API。 详细情况可参考具体算法文档。 对于不需要这两个 API 的算法,可以调用它们,但不会有实际作用。
### 导出压缩模型
使用下列 API 可轻松将压缩后的模型导出,稀疏模型的 `state_dict` 会保存在 `model.pth` 文件中,可通过 `torch.load('model.pth')` 加载。 在导出的 `model.pth` 中,被掩码遮盖的权重为零。
```
pruner.export_model(model_path='model.pth')
```
`mask_dict``onnx` 格式的剪枝模型(需要指定 `input_shape`)可这样导出:
```python
pruner.export_model(model_path='model.pth', mask_path='mask.pth', onnx_path='model.onnx', input_shape=[1, 1, 28, 28])
```
如果需要实际加速压缩后的模型,参考 [NNI 模型加速](./ModelSpeedup.md)
\ No newline at end of file
## GBDTSelector
GBDTSelector 基于 [LightGBM](https://github.com/microsoft/LightGBM),这是一个基于树学习算法的梯度提升框架。
当将数据传递到 GBDT 模型时,该模型将构建提升树。 特征的重要性来自于构造时的分数,其表达了每个特征在模型构造提升决策树时有多有用。
可使用此方法作为 Feature Selector 中较强的基准,特别是在使用 GBDT 模型进行分类或回归时。
当前,支持的 `importance_type``split``gain`。 未来会支持定制 `importance_type`,也就是说用户可以定义如何计算`特征分数`
### 用法
首先,安装依赖项:
```
pip install lightgbm
```
然后
```python
from nni.feature_engineering.gbdt_selector import GBDTSelector
# 读取数据
...
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# 初始化 Selector
fgs = GBDTSelector()
# 拟合数据
fgs.fit(X_train, y_train, ...)
# 获取重要的特征
# 此处会返回重要特征的索引。
print(fgs.get_selected_features(10))
...
```
也可在 `/examples/feature_engineering/gbdt_selector/` 目录找到示例。
**`fit` 函数参数要求**
* **X** (数组,必需) - 训练的输入样本,shape = [n_samples, n_features]
* **y** (数组,必需) - 目标值 (分类中为标签,回归中为实数),shape = [n_samples].
* **lgb_params** (dict, 必需) - lightgbm 模型参数。 详情参考[这里](https://lightgbm.readthedocs.io/en/latest/Parameters.html)
* **eval_ratio** (float, 必需) - 数据大小的比例 用于从 self.X 中拆分出评估和训练数据。
* **early_stopping_rounds** (int, 必需) - lightgbm 中的提前终止设置。 详情参考[这里](https://lightgbm.readthedocs.io/en/latest/Parameters.html)
* **importance_type** (str, 必需) - 可为 'split' 或 'gain'。 'split' 表示 '结果包含特征在模型中使用的次数' 而 'gain' 表示 '结果包含此特征拆分出的总收益'。 详情参考[这里](https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.Booster.html#lightgbm.Booster.feature_importance)
* **num_boost_round** (int, 必需) - 提升的轮数。 详情参考[这里](https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.train.html#lightgbm.train)
**`get_selected_features` 函数参数的要求**
* **topk** (int, 必需) - 想要选择的 k 个最好的特征。
GBDTSelector
------------
GBDTSelector 基于 `LightGBM <https://github.com/microsoft/LightGBM>`__,这是一个基于树学习算法的梯度提升框架。
当将数据传递到 GBDT 模型时,该模型将构建提升树。 特征的重要性来自于构造时的分数,其表达了每个特征在模型构造提升决策树时有多有用。
可使用此方法作为 Feature Selector 中较强的基准,特别是在使用 GBDT 模型进行分类或回归时。
当前,支持的 ``importance_type`` 有 ``split`` 和 ``gain``。 未来会支持定制 ``importance_type``,也就是说用户可以定义如何计算 ``特征分数``。
用法
^^^^^
首先,安装依赖项:
.. code-block:: bash
pip install lightgbm
然后
.. code-block:: python
from nni.feature_engineering.gbdt_selector import GBDTSelector
# 下载数据
...
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# 初始化 selector
fgs = GBDTSelector()
# 拟合数据
fgs.fit(X_train, y_train, ...)
# 获取重要的特征
# 此处会返回重要特征的索引。
print(fgs.get_selected_features(10))
...
也可在 ``/examples/feature_engineering/gbdt_selector/`` 目录找到示例。
``fit`` 函数参数要求
*
**X** (数组,必需) - 训练的输入样本,shape = [n_samples, n_features]
*
**y** (数组,必需) - 目标值 (分类中为标签,回归中为实数),shape = [n_samples].
*
**lgb_params** (dict, 必需) - lightgbm 模型参数。 详情参考 `这里 <https://lightgbm.readthedocs.io/en/latest/Parameters.html>`__
*
**eval_ratio** (float, 必需) - 数据大小的比例 用于从 self.X 中拆分出评估和训练数据。
*
**early_stopping_rounds** (int, 必需) - lightgbm 中的提前终止设置。 详情参考 `这里 <https://lightgbm.readthedocs.io/en/latest/Parameters.html>`__。
*
**importance_type** (str, 必需) - 可为 'split' 或 'gain'。 'split' 表示 '结果包含特征在模型中使用的次数' 而 'gain' 表示 '结果包含此特征拆分出的总收益'。 详情参考 `这里 <https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.Booster.html#lightgbm.Booster.feature_importance>`__。
*
**num_boost_round** (int, 必需) - 提升的轮数。 详情参考 `这里 <https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.train.html#lightgbm.train>`__.
**get_selected_features 函数参数的要求**
**topk** (int, 必需) - 想要选择的 k 个最好的特征。
## GradientFeatureSelector
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) 该方法扩展了一个近期的结果,即在亚线性数据中通过展示计算能迭代的学习(即,在迷你批处理中),在**线性的时间空间中**的特征数量 D 及样本大小 N。
1) 该方法扩展了一个近期的结果,
即在亚线性数据中通过展示计算能迭代的学习(即,在迷你批处理中),在 **线性的时间空间中** 的特征数量 D 及样本大小 N。
2) 这与在搜索领域的离散到连续的放松一起,可以在非常**大的数据集**上进行**高效、基于梯度**的搜索算法。
2) 这与在搜索领域的离散到连续的放松一起,可以在非常 **大的数据集** 上进行 **高效、基于梯度** 的搜索算法。
3) 最重要的是,此算法能在特征和目标间为 N > D 和 N < D 都找到**高阶相关性**,这与只考虑一种情况和交互式的方法所不同。
3) 最重要的是,此算法能在特征和目标间为 N > D 和 N < D 都找到 **高阶相关性**,这与只考虑一种情况和交互式的方法所不同。
用法
^^^^^
### 用法
.. code-block:: python
```python
from nni.feature_engineering.gradient_selector import FeatureGradientSelector
from nni.feature_engineering.gradient_selector import FeatureGradientSelector
# 读取数据
...
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# 下载数据
...
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# 初始化 Selector
fgs = FeatureGradientSelector()
# 拟合数据
fgs.fit(X_train, y_train)
# 获取重要的特征
# 此处会返回重要特征的索引。
print(fgs.get_selected_features())
# 初始化 selector
fgs = FeatureGradientSelector(n_features=10)
# 拟合数据
fgs.fit(X_train, y_train)
# 获取重要的特征
# 此处会返回重要特征的索引。
print(fgs.get_selected_features())
...
```
...
也可在 `/examples/feature_engineering/gradient_feature_selector/` 目录找到示例。
也可在 ``/examples/feature_engineering/gradient_feature_selector/`` 目录找到示例。
**FeatureGradientSelector 构造函数的参数**
* **order** (int, 可选, 默认为 4) - 要包含的交互顺序。 较高的顺序可能会更准确,但会增加运行时间。 12 是允许的顺序的最大值。
* **penatly** (int, 可选, 默认为 1) - 乘以正则项的常数。
*
**order** (int, 可选, 默认为 4) - 要包含的交互顺序。 较高的顺序可能会更准确,但会增加运行时间。 12 是允许的顺序的最大值。
* **n_features** (int, 可选, 默认为 None) - 如果为 None,会自动根据搜索来选择特征的数量。 否则,表示要选择的最好特征的数量。
*
**penatly** (int, 可选, 默认为 1) - 乘以正则项的常数。
* **max_features** (int, 可选, 默认为 None) - 如果不为 None,会使用 'elbow method' 来确定以 max_features 为上限的特征数量。
*
**n_features** (int, 可选, 默认为 None) - 如果为 None,会自动根据搜索来选择特征的数量。 否则,表示要选择的最好特征的数量。
* **learning_rate** (float, 可选, 默认为 1e-1) - 学习率
*
**max_features** (int, 可选, 默认为 None) - 如果不为 None,会使用 'elbow method' 来确定以 max_features 为上限的特征数量。
* **init** (*zero, on, off, onhigh, offhigh, 或 sklearn, 可选, 默认为zero*) - 如何初始化向量分数。 默认值为 'zero'。
*
**learning_rate** (float, 可选, 默认为 1e-1) - 学习率
* **n_epochs** (int, 可选, 默认为 1) - 要运行的 Epoch 数量
*
**init** (<em x-id="3">zero, on, off, onhigh, offhigh, 或 sklearn, 可选, 默认为zero</em>) - 如何初始化向量分数。 默认值为 'zero'。
* **shuffle** (bool, 可选, 默认为 True) - 在 Epoch 之前需要随机化 "rows"。
*
**n_epochs** (int, 可选, 默认为 1) - 要运行的 Epoch 数量
* **batch_size** (int, 可选, 默认为 1000) - 一次处理的 "rows" 数量。
*
**shuffle** (bool, 可选, 默认为 True) - 在 Epoch 之前需要随机化 "rows"。
* **target_batch_size** (int, 可选, 默认为 1000) - 累计梯度的 "rows" 数量。 当行数过多无法读取到内存中,但估计精度所需。
*
**batch_size** (int, 可选, 默认为 1000) - 一次处理的 "rows" 数量。
* **classification** (bool, 可选, 默认为 True) - 如果为 True,为分类问题,否则是回归问题。
*
**target_batch_size** (int, 可选, 默认为 1000) - 累计梯度的 "rows" 数量。 当行数过多无法读取到内存中,但估计精度所需。
* **ordinal** (bool, 可选, 默认为 True) - 如果为 True,是有序的分类。 需要 classification 也为 True。
*
**classification** (bool, 可选, 默认为 True) - 如果为 True,为分类问题,否则是回归问题。
* **balanced** (bool, 可选, 默认为 True) - 如果为 True,优化中每类的权重都一样,否则需要通过支持来对每类加权。 需要 classification 也为 True。
*
**ordinal** (bool, 可选, 默认为 True) - 如果为 True,是有序的分类。 需要 classification 也为 True。
* **prerocess** (str, 可选, 默认为 'zscore') - 'zscore' 是将数据中心化并归一化的党委方差,'center' 表示仅将数据均值调整到 0。
*
**balanced** (bool, 可选, 默认为 True) - 如果为 True,优化中每类的权重都一样,否则需要通过支持来对每类加权。 需要 classification 也为 True。
* **soft_grouping** (bool, 可选, 默认为 True) - 如果为 True,将同一来源的特征分组到一起。 用于支持分组或组内特征的稀疏性。
*
**prerocess** (str, 可选, 默认为 'zscore') - 'zscore' 是将数据中心化并归一化的党委方差,'center' 表示仅将数据均值调整到 0。
* **verbose** (int, 可选, 默认为 0) - 控制拟合时的信息详细程度。 设为 0 表示不打印,1 或更大值表示打印详细数量的步骤。
*
**soft_grouping** (bool, 可选, 默认为 True) - 如果为 True,将同一来源的特征分组到一起。 用于支持分组或组内特征的稀疏性。
* **device** (str, 可选, 默认为 'cpu') - 'cpu' 表示在 CPU 上运行,'cuda' 表示在 GPU 上运行。 在 GPU 上运行得更快
*
**verbose** (int, 可选, 默认为 0) - 控制拟合时的信息详细程度。 设为 0 表示不打印,1 或更大值表示打印详细数量的步骤。
*
**device** (str, 可选, 默认为 'cpu') - 'cpu' 表示在 CPU 上运行,'cuda' 表示在 GPU 上运行。 在 GPU 上运行得更快
**`fit` 函数参数要求**
**fit 函数参数要求**
* **X** (数组,必需) - 训练的输入样本,shape = [n_samples, n_features]
* **y** (数组,必需) - 目标值 (分类中为标签,回归中为实数),shape = [n_samples].
*
**X** (数组,必需) - 训练的输入样本,shape = [n_samples, n_features]
* **groups** (数组, 可选, 默认为 None) - 必需选择为一个单元的列的分组。 例如 [0,0,1,2] 指定前两列是组的一部分。 形状是 [n_features]。
*
**y** (数组,必需) - 目标值 (分类中为标签,回归中为实数),shape = [n_samples].
**`get_selected_features` 函数参数的要求**
*
**groups** (数组, 可选, 默认为 None) - 必需选择为一个单元的列的分组。 例如, [0,0,1,2] 指定前两列是组的一部分。 形状是 [n_features]。
目前, `get_selected_features` 函数没有参数。
**get_selected_features 函数参数的要求**
目前,**get_selected_features** 函数没有参数。
# NNI 中的特征工程
我们很高兴的宣布,基于 NNI 的特征工程工具发布了试用版本。该版本仍处于试验阶段,根据使用反馈会进行改进。 诚挚邀请您使用、反馈,或更多贡献。
当前支持以下特征选择器:
- [GradientFeatureSelector](./GradientFeatureSelector.md)
- [GBDTSelector](./GBDTSelector.md)
这些 Selector 适用于结构化的数据(也就是不适用于图像,语音和文本数据)。
另外,Selector 仅用于特征选择。 如果需要: 1) 在特征选择时,通过 NNI 生成高阶的组合特征; 2) 使用分布式资源; 可以尝试[本示例](https://github.com/microsoft/nni/tree/master/examples/feature_engineering/auto-feature-engineering)
## 如何使用
```python
from nni.feature_engineering.gradient_selector import FeatureGradientSelector
# from nni.feature_engineering.gbdt_selector import GBDTSelector
# 读取数据
...
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# 初始化 Selector
fgs = FeatureGradientSelector(...)
# 拟合数据
fgs.fit(X_train, y_train)
# 获取重要的特征
# 此处会返回重要特征的索引。
print(fgs.get_selected_features(...))
...
```
使用内置 Selector 时,需要 `import` 对应的特征选择器,并 `initialize`。 可在 Selector 中调用 `fit` 函数来传入数据。 之后,可通过 `get_seleteced_features` 来获得重要的特征。 不同 Selector 的函数参数可能不同,在使用前需要先检查文档。
## 如何定制?
NNI 内置了_最先进的_特征工程算法的 Selector。 NNI 也支持定制自己的特征 Selector。
如果要实现定制的特征 Selector,需要:
1. 继承基类 FeatureSelector
1. 实现 _fit_ 和 _get_selected_features_ 函数
1. 与 sklearn 集成 (可选)
示例如下:
**1. 继承基类 FeatureSelector**
```python
from nni.feature_engineering.feature_selector import FeatureSelector
class CustomizedSelector(FeatureSelector):
def __init__(self, ...):
...
```
**2. 实现 _fit_ 和 _get_selected_features_ 函数**
```python
from nni.tuner import Tuner
from nni.feature_engineering.feature_selector import FeatureSelector
class CustomizedSelector(FeatureSelector):
def __init__(self, ...):
...
def fit(self, X, y, **kwargs):
"""
将数据拟合到 FeatureSelector
参数
------------
X : numpy 矩阵
训练输入样本,形状为 [n_samples, n_features]。
y: numpy 矩阵
目标值 (分类中的类标签,回归中为实数)。 形状是 [n_samples]。
"""
self.X = X
self.y = y
...
def get_selected_features(self):
"""
获取重要特征
Returns
-------
list :
返回重要特征的索引。
"""
...
return self.selected_features_
...
```
**3. 与 sklearn 集成**
`sklearn.pipeline.Pipeline` 可将模型连接在一起,例如特征选择,规范化,以及分类、回归,来组成一个典型的机器学习问题工作流。 下列步骤可帮助集成 sklearn,将定制的特征 Selector 作为管道的模块。
1. 继承类 _sklearn.base.BaseEstimator_
1. 实现 _BaseEstimator_ 中的 _get_params_ 和 _set_params_ 函数
1. 继承类 _sklearn.feature_selection.base.SelectorMixin_
1. 实现 _SelectorMixin_ 中的 _get_support_, _transform_ 和 _inverse_transform_ 函数
示例如下:
**1. 继承类 BaseEstimator 及其函数**
```python
from sklearn.base import BaseEstimator
from nni.feature_engineering.feature_selector import FeatureSelector
class CustomizedSelector(FeatureSelector, BaseEstimator):
def __init__(self, ...):
...
def get_params(self, ...):
"""
为此 estimator 获取参数
"""
params = self.__dict__
params = {key: val for (key, val) in params.items()
if not key.endswith('_')}
return params
def set_params(self, **params):
"""
为此 estimator 设置参数
"""
for param in params:
if hasattr(self, param):
setattr(self, param, params[param])
return self
```
**2. 继承 SelectorMixin 类及其函数**
```python
from sklearn.base import BaseEstimator
from sklearn.feature_selection.base import SelectorMixin
from nni.feature_engineering.feature_selector import FeatureSelector
class CustomizedSelector(FeatureSelector, BaseEstimator, SelectorMixin):
def __init__(self, ...):
...
def get_params(self, ...):
"""
获取参数。
"""
params = self.__dict__
params = {key: val for (key, val) in params.items()
if not key.endswith('_')}
return params
def set_params(self, **params):
"""
为此 estimator 设置参数
"""
for param in params:
if hasattr(self, param):
setattr(self, param, params[param])
return self
def get_support(self, indices=False):
"""
获取 mask,整数索引或选择的特征。
Parameters
----------
indices : bool
默认为 False. 如果为 True,返回值为整数数组,否则为布尔的 mask。
Returns
-------
list :
返回 support: 从特征向量中选择保留的特征索引。
如果 indices 为 False,布尔数据的形状为 [输入特征的数量],如果元素为 True,表示保留相对应的特征。
如果 indices 为 True,整数数组的形状为 [输出特征的数量],值表示
输入特征向量中的索引。
"""
...
return mask
def transform(self, X):
"""将 X 减少为选择的特征。
Parameters
----------
X : array
形状为 [n_samples, n_features]
Returns
-------
X_r : array
形状为 [n_samples, n_selected_features]
仅输入选择的特征。
"""
...
return X_r
def inverse_transform(self, X):
"""
反转变换操作
Parameters
----------
X : array
形状为 [n_samples, n_selected_features]
Returns
-------
X_r : array
形状为 [n_samples, n_original_features]
"""
...
return X_r
```
与 sklearn 继承后,可如下使用特征 Selector:
```python
from sklearn.linear_model import LogisticRegression
# 加载数据
...
X_train, y_train = ...
# 构造 pipeline
pipeline = make_pipeline(XXXSelector(...), LogisticRegression())
pipeline = make_pipeline(SelectFromModel(ExtraTreesClassifier(n_estimators=50)), LogisticRegression())
pipeline.fit(X_train, y_train)
# 分数
print("Pipeline Score: ", pipeline.score(X_train, y_train))
```
## 基准测试
`Baseline` 表示没有进行特征选择,直接将数据传入 LogisticRegression。 此基准测试中,仅用了 10% 的训练数据作为测试数据。 对于 GradientFeatureSelector,仅使用了前 20 个特征。 下列指标是在给定测试数据和标签上的平均精度。
| 数据集 | 所有特征 + LR (acc, time, memory) | GradientFeatureSelector + LR (acc, time, memory) | TreeBasedClassifier + LR (acc, time, memory) | 训练次数 | 特征数量 |
| ------------- | ----------------------------- | ------------------------------------------------ | -------------------------------------------- | ---------- | --------- |
| colon-cancer | 0.7547, 890ms, 348MiB | 0.7368, 363ms, 286MiB | 0.7223, 171ms, 1171 MiB | 62 | 2,000 |
| gisette | 0.9725, 215ms, 584MiB | 0.89416, 446ms, 397MiB | 0.9792, 911ms, 234MiB | 6,000 | 5,000 |
| avazu | 0.8834, N/A, N/A | N/A, N/A, N/A | N/A, N/A, N/A | 40,428,967 | 1,000,000 |
| rcv1 | 0.9644, 557ms, 241MiB | 0.7333, 401ms, 281MiB | 0.9615, 752ms, 284MiB | 20,242 | 47,236 |
| news20.binary | 0.9208, 707ms, 361MiB | 0.6870, 565ms, 371MiB | 0.9070, 904ms, 364MiB | 19,996 | 1,355,191 |
| real-sim | 0.9681, 433ms, 274MiB | 0.7969, 251ms, 274MiB | 0.9591, 643ms, 367MiB | 72,309 | 20,958 |
此基准测试可在[这里](https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/)下载
代码参考 `/examples/feature_engineering/gradient_feature_selector/benchmark_test.py`
## 参考和反馈
* 在 GitHub 中[提交此功能的 Bug](https://github.com/microsoft/nni/issues/new?template=bug-report.md)
* 在 GitHub 中[提交新功能或改进请求](https://github.com/microsoft/nni/issues/new?template=enhancement.md)
* 了解 NNI 中[神经网络结构搜索的更多信息](https://github.com/microsoft/nni/blob/master/docs/zh_CN/NAS/Overview.md)
* 了解 NNI 中[模型自动压缩的更多信息](https://github.com/microsoft/nni/blob/master/docs/zh_CN/Compressor/Overview.md)
* 了解如何[使用 NNI 进行超参数调优](https://github.com/microsoft/nni/blob/master/docs/zh_CN/Tuner/BuiltinTuner.md)
NNI 中的特征工程
============================
我们很高兴的宣布,基于 NNI 的特征工程工具发布了试用版本。该版本仍处于试验阶段,根据使用反馈会进行改进。 诚挚邀请您使用、反馈,或有更多贡献。
当前支持以下特征选择器:
* `GradientFeatureSelector <./GradientFeatureSelector.rst>`__
* `GBDTSelector <./GBDTSelector.rst>`__
这些 Selector 适用于结构化的数据(也就是不适用于图像,语音和文本数据)。
另外,Selector 仅用于特征选择。 如果需要:
1) 在特征选择时,通过 NNI 生成高阶的组合特征;
2) 使用分布式资源;
可以尝试 :githublink:`本示例 <examples/feature_engineering/auto-feature-engineering>`。
如何使用
-----------
.. code-block:: python
from nni.feature_engineering.gradient_selector import FeatureGradientSelector
# from nni.feature_engineering.gbdt_selector import GBDTSelector
# 下载数据
...
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)
# 初始化 selector
fgs = FeatureGradientSelector(...)
# 拟合数据
fgs.fit(X_train, y_train)
# 获取重要的特征
# 此处会返回重要特征的索引。
print(fgs.get_selected_features(...))
...
使用内置 Selector 时,需要 ``import`` 对应的特征选择器,并 ``initialize``。 可在 Selector 中调用 ``fit`` 函数来传入数据。 之后,可通过 ``get_seleteced_features`` 来获得重要的特征。 不同 Selector 的函数参数可能不同,在使用前需要先检查文档。
如何定制
-----------------
NNI 内置了**最先进的** 特征工程算法的 Selector。 NNI 也支持定制自己的特征 Selector。
如果要实现定制的特征 Selector,需要:
#. 继承基类 FeatureSelector
#. 实现 *fit* 和 *get_selected_features* 函数
#. 与 sklearn 集成 (可选)
示例如下:
**1. 继承基类 FeatureSelector**
.. code-block:: python
from nni.feature_engineering.feature_selector import FeatureSelector
class CustomizedSelector(FeatureSelector):
def __init__(self, ...):
...
**2. 实现 fit 和 get_selected_features 函数**
.. code-block:: python
from nni.tuner import Tuner
from nni.feature_engineering.feature_selector import FeatureSelector
class CustomizedSelector(FeatureSelector):
def __init__(self, ...):
...
def fit(self, X, y, **kwargs):
"""
将数据拟合到 FeatureSelecto
参数
------------
X : numpy 矩阵
训练输入样本,形状为 [n_samples, n_features]。
y: numpy 矩阵
目标值 (分类中的类标签,回归中为实数)。 形状是 [n_samples]。
"""
self.X = X
self.y = y
...
def get_selected_features(self):
"""
获取重要的特征
Returns
-------
list :
返回重要特征的索引。
"""
...
return self.selected_features_
...
**3. 与 sklearn 集成**
``sklearn.pipeline.Pipeline`` 可将模型连接在一起,例如特征选择,规范化,以及分类、回归,来组成一个典型的机器学习问题工作流。
下列步骤可帮助集成 sklearn,将定制的特征 Selector 作为管道的模块。
#. 继承类 *sklearn.base.BaseEstimator*
#. 实现 *BaseEstimator* 中的 *get_params* 和 *set_params* 函数
#. 继承类 *sklearn.feature_selection.base.SelectorMixin*
#. 实现 *SelectorMixin* 中的 *get_support, transform* 和 *inverse_transform* 函数
示例如下:
**1. 继承类 BaseEstimator 及其函数**
.. code-block:: python
from sklearn.base import BaseEstimator
from nni.feature_engineering.feature_selector import FeatureSelector
class CustomizedSelector(FeatureSelector, BaseEstimator):
def __init__(self, ...):
...
def get_params(self, ...):
"""
为此 estimator 获取参数
"""
params = self.__dict__
params = {key: val for (key, val) in params.items()
if not key.endswith('_')}
return params
def set_params(self, **params):
"""
为此 estimator 设置参数
"""
for param in params:
if hasattr(self, param):
setattr(self, param, params[param])
return self
**2. 继承 SelectorMixin 类及其函数**
.. code-block:: python
from sklearn.base import BaseEstimator
from sklearn.feature_selection.base import SelectorMixin
from nni.feature_engineering.feature_selector import FeatureSelector
class CustomizedSelector(FeatureSelector, BaseEstimator, SelectorMixin):
def __init__(self, ...):
...
def get_params(self, ...):
"""
为此 estimator 获取参数
"""
params = self.__dict__
params = {key: val for (key, val) in params.items()
if not key.endswith('_')}
return params
def set_params(self, **params):
"""
为此 estimator 设置参数
"""
for param in params:
if hasattr(self, param):
setattr(self, param, params[param])
return self
def get_support(self, indices=False):
"""
获取 mask,整数索引或选择的特征。
参数
----------
indices : bool
默认值:false。 如果为 True,返回值为整数数组,否则为布尔的 mask。
Returns
-------
list :
返回 support: 从特征向量中选择保留的特征索引。
如果 indices 为 False,布尔数据的形状为 [输入特征的数量],如果元素为 True,表示保留相对应的特征。
如果 indices 为 True,整数数组的形状为 [输出特征的数量],值表示
输入特征向量中的索引。
"""
...
return mask
def transform(self, X):
"""将 X 减少为选择的特征。
参数
----------
X : array
形状为 [n_samples, n_features]
Returns
-------
X_r : array
形状为 [n_samples, n_selected_features]
仅输入选择的特征
"""
...
return X_r
def inverse_transform(self, X):
"""
反转变换操作
参数
----------
X : array
形状为 [n_samples, n_selected_features]
Returns
-------
X_r : array
形状为 [n_samples, n_original_features]
"""
...
return X_r
与 sklearn 继承后,可如下使用特征 Selector:
.. code-block:: python
from sklearn.linear_model import LogisticRegression
# 下载数据
...
X_train, y_train = ...
# 构造 a pipeline
pipeline = make_pipeline(XXXSelector(...), LogisticRegression())
pipeline = make_pipeline(SelectFromModel(ExtraTreesClassifier(n_estimators=50)), LogisticRegression())
pipeline.fit(X_train, y_train)
# 分数
print("Pipeline Score: ", pipeline.score(X_train, y_train))
基准测试
---------
``Baseline`` 表示没有进行特征选择,直接将数据传入 LogisticRegression。 此基准测试中,仅用了 10% 的训练数据作为测试数据。 对于 GradientFeatureSelector,仅使用了前 20 个特征。 下列指标是在给定测试数据和标签上的平均精度。
.. list-table::
:header-rows: 1
:widths: auto
* - 数据集
- 所有特征 + LR (acc, time, memory)
- GradientFeatureSelector + LR (acc, time, memory)
- TreeBasedClassifier + LR (acc, time, memory)
- #训练次数
- #特征数量
* - colon-cancer
- 0.7547, 890ms, 348MiB
- 0.7368, 363ms, 286MiB
- 0.7223, 171ms, 1171 MiB
- 62
- 2,000
* - gisette
- 0.9725, 215ms, 584MiB
- 0.89416, 446ms, 397MiB
- 0.9792, 911ms, 234MiB
- 6,000
- 5,000
* - avazu
- 0.8834, N/A, N/A
- N/A, N/A, N/A
- N/A, N/A, N/A
- 40,428,967
- 1,000,000
* - rcv1
- 0.9644, 557ms, 241MiB
- 0.7333, 401ms, 281MiB
- 0.9615, 752ms, 284MiB
- 20,242
- 47,236
* - news20.binary
- 0.9208, 707ms, 361MiB
- 0.6870, 565ms, 371MiB
- 0.9070, 904ms, 364MiB
- 19,996
- 1,355,191
* - real-sim
- 0.9681, 433ms, 274MiB
- 0.7969, 251ms, 274MiB
- 0.9591, 643ms, 367MiB
- 72,309
- 20,958
此基准测试的 `下载地址 <https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/>`__。
代码可参考 ``/examples/feature_engineering/gradient_feature_selector/benchmark_test.py``。
参考和反馈
----------------------
* 在Github 中 `提交此功能的 Bug <https://github.com/microsoft/nni/issues/new?template=bug-report.rst>`__
* 在Github 中 `提交新功能或请求改进 <https://github.com/microsoft/nni/issues/new?template=enhancement.rst>`__
* 了解 NNI 中 :githublink:`NAS 的更多信息 <docs/zh_CN/NAS/Overview.rst>`
* 了解 NNI 中 :githublink:`模型压缩的更多信息 <docs/zh_CN/Compression/Overview.rst>`
* 了解更多关于 NNI 中 :githublink:`超参调优的更多信息 <docs/zh_CN/Tuner/BuiltinTuner.rst>`\ ;
# 自定义 NAS 算法
## 扩展 One-Shot Trainer
如果要在真实任务上使用 Trainer,还需要更多操作,如分布式训练,低精度训练,周期日志,写入 TensorBoard,保存检查点等等。 如前所述,一些 Trainer 支持了上述某些功能。 有两种方法可往已有的 Trainer 中加入功能:继承已有 Trainer 并重载,或复制已有 Trainer 并修改。
无论哪种方法,都需要实现新的 Trainer。 基本上,除了新的 Mutator 的概念,实现 One-Shot Trainer 与普通的深度学习 Trainer 相同。 因此,有两处会有所不同:
* 初始化
```python
model = Model()
mutator = MyMutator(model)
```
* 训练
```python
for _ in range(epochs):
for x, y in data_loader:
mutator.reset() # 在模型中重置所有 Choice
out = model(x) # 与普通模型相同
loss = criterion(out, y)
loss.backward()
# 以下代码没有不同
```
要展示 Mutator 的用途,需要先了解 One-Shot NAS 的工作原理。 通常 One-Shot NAS 会同时优化模型权重和架构权重。 它会反复的:对架构采样,或由超网络中的几种架构组成,然后像普通深度学习模型一样训练,将训练后的参数更新到超网络中,然后用指标或损失作为信号来指导架构的采样。 Mutator,在这里用作架构采样,通常会是另一个深度学习模型。 因此,可将其看作一个通过定义参数,并使用优化器进行优化的任何模型。 Mutator 是由一个模型来初始化的。 一旦 Mutator 绑定到了某个模型,就不能重新绑定到另一个模型上。
`mutator.reset()` 是关键步骤。 这一步确定了模型最终的所有 Choice。 重置的结果会一直有效,直到下一次重置来刷新数据。 重置后,模型可看作是普通的模型来进行前向和反向传播。
最后,Mutator 会提供叫做 `mutator.export()` 的方法来将模型的架构参数作为 dict 导出。 注意,当前 dict 是从 Mutable 键值到选择张量的映射。 为了存储到 JSON,用户需要将张量显式的转换为 Python 的 list。
同时,NNI 提供了工具,能更容易地实现 Trainer。 参考 [Trainer](./NasReference.md) 了解详情。
## 实现新的 Mutator
这是为了演示 `mutator.reset()``mutator.export()` 的伪代码。
```python
def reset(self):
self.apply_on_model(self.sample_search())
```
```python
def export(self):
return self.sample_final()
```
重置时,新架构会通过 `sample_search()` 采样,并应用到模型上。 然后,对模型进行一步或多步的搜索。 导出时,新架构通过 `sample_final()` 来采样,**不对模型做操作**。 可用于检查点或导出最终架构。
`sample_search()``sample_final()` 返回值的要求一致:从 Mutable 键值到张量的映射。 张量可以是 BoolTensor (true 表示选择,false 表示没有),或 FloatTensor 将权重应用于每个候选对象。 选定的分支会被计算出来(对于 `LayerChoice`,模型会被调用;对于 `InputChoice`,只有权重),并通过 Choice 的剪枝操作来剪枝模型。 这是 Mutator 实现的示例,大多数算法只需要关心前面部分。
```python
class RandomMutator(Mutator):
def __init__(self, model):
super().__init__(model) # 记得调用 super
# 别的操作
def sample_search(self):
result = dict()
for mutable in self.mutables: # 这是用户模型中所有 Mutable 模块
# 共享同样键值的 Mutable 会去重
if isinstance(mutable, LayerChoice):
# 决定此模型会选择 `gen_index`
gen_index = np.random.randint(mutable.length)
result[mutable.key] = torch.tensor([i == gen_index for i in range(mutable.length)],
dtype=torch.bool)
elif isinstance(mutable, InputChoice):
if mutable.n_chosen is None: # n_chosen 是 None,表示选择所有数字
result[mutable.key] = torch.randint(high=2, size=(mutable.n_candidates,)).view(-1).bool()
# 其它
return result
def sample_final(self):
return self.sample_search() # 使用同样的逻辑 其它操作
```
随机 Mutator 的完整示例在[这里](https://github.com/microsoft/nni/blob/master/src/sdk/pynni/nni/nas/pytorch/random/mutator.py)
对于高级用法,例如,需要在 `LayerChoice` 执行的时候操作模型,可继承 `BaseMutator`,并重载 `on_forward_layer_choice``on_forward_input_choice`。这些是 `LayerChoice``InputChoice` 对应的回调实现。 还可使用属性 `mutables` 来获得模型中所有的 `LayerChoice``InputChoice`。 详细信息,[参考这里](https://github.com/microsoft/nni/tree/master/src/sdk/pynni/nni/nas/pytorch)
```eval_rst
.. tip::
用于调试的随机 Mutator。 使用
.. code-block:: python
mutator = RandomMutator(model)
mutator.reset()
会立刻从搜索空间中选择一个来激活。
```
## 实现分布式 NAS Tuner
在学习编写分布式 NAS Tuner前,应先了解如何写出通用的 Tuner。 阅读[自定义 Tuner](../Tuner/CustomizeTuner.md) 的教程。
当调用 "[nnictl ss_gen](../Tutorial/Nnictl.md)" 时,会生成下面这样的搜索空间文件:
```json
{
"key_name": {
"_type": "layer_choice",
"_value": ["op1_repr", "op2_repr", "op3_repr"]
},
"key_name": {
"_type": "input_choice",
"_value": {
"candidates": ["in1_key", "in2_key", "in3_key"],
"n_chosen": 1
}
}
}
```
这是 Tuner 在 `update_search_space` 中会收到的搜索空间。 Tuner 需要解析搜索空间,并在 `generate_parameters` 中生成新的候选。 有效的 "参数" 格式如下:
```json
{
"key_name": {
"_value": "op1_repr",
"_idx": 0
},
"key_name": {
"_value": ["in2_key"],
"_idex": [1]
}
}
```
和普通超参优化 Tuner 类似,通过 `generate_parameters` 来发送。 参考 [SPOS](./SPOS.md) 示例代码。
自定义 NAS 算法
=========================
扩展 One-Shot Trainer
---------------------------------------
如果要在真实任务上使用 Trainer,还需要更多操作,如分布式训练,低精度训练,周期日志,写入 TensorBoard,保存检查点等等。 如前所述,一些 Trainer 支持了上述某些功能。 有两种方法可往已有的 Trainer 中加入功能:继承已有 Trainer 并重载,或复制已有 Trainer 并修改。
无论哪种方法,都需要实现新的 Trainer 基本上,除了新的 Mutator 的概念,实现 One-Shot Trainer 与普通的深度学习 Trainer 相同。 因此,有两处会有所不同:
* 初始化
.. code-block:: python
model = Model()
mutator = MyMutator(model)
* 训练
.. code-block:: python
for _ in range(epochs):
for x, y in data_loader:
mutator.reset() # reset all the choices in model
out = model(x) # like traditional model
loss = criterion(out, y)
loss.backward()
# no difference below
要展示 Mutator 的用途,需要先了解 One-Shot NAS 的工作原理。 通常 One-Shot NAS 会同时优化模型权重和架构权重。 它会反复的对架构采样,或由超网络中的几种架构组成,然后像普通深度学习模型一样训练,将训练后的参数更新到超网络中,然后用指标或损失作为信号来指导架构的采样。 Mutator,在这里用作架构采样,通常会是另一个深度学习模型。 因此,可将其看作一个通过定义参数,并使用优化器进行优化的任何模型。 Mutator 是由一个模型来初始化的。 一旦 Mutator 绑定到了某个模型,就不能重新绑定到另一个模型上。
``mutator.reset()`` 是关键步骤。 这一步确定了模型最终的所有 Choice 重置的结果会一直有效,直到下一次重置来刷新数据。 重置后,模型可看作是普通的模型来进行前向和反向传播。
最后,Mutator 会提供叫做 ``mutator.export()`` 的方法来将模型的架构参数作为 dict 导出。 注意,当前 dict 是从 Mutable 键值到选择张量的映射。 为了存储到 JSON,用户需要将张量显式的转换为 Python list
同时,NNI 提供了工具,能更容易地实现 Trainer 可以参考 `Trainers <./NasReference.rst>`__
实现新的 Mutator
----------------------
这是为了演示 ``mutator.reset()`` ``mutator.export()`` 的伪代码。
.. code-block:: python
def reset(self):
self.apply_on_model(self.sample_search())
.. code-block:: python
def export(self):
return self.sample_final()
重置时,新架构会通过 ``sample_search()`` 采样,并应用到模型上。 然后,对模型进行一步或多步的搜索。 导出时,新架构会通过 ``sample_final()`` 采样,并且对模型不做任何操作。 可用于检查点或导出最终架构。
``sample_search()`` ``sample_final()`` 返回值的要求一致:从 Mutable 键值到张量的映射。 张量可以是 BoolTensor true 表示选择,false 表示没有),或 FloatTensor 将权重应用于每个候选对象。 选定的分支会被计算出来(对于 ``LayerChoice`` ,模型会被调用;对于 ``InputChoice`` ,只有权重),并通过 Choice 的剪枝操作来剪枝模型。 这是 Mutator 实现的示例,大多数算法只需要关心前面部分。
.. code-block:: python
class RandomMutator(Mutator):
def __init__(self, model):
super().__init__(model) # don't forget to call super
# do something else
def sample_search(self):
result = dict()
for mutable in self.mutables: # this is all the mutable modules in user model
# mutables share the same key will be de-duplicated
if isinstance(mutable, LayerChoice):
# decided that this mutable should choose `gen_index`
gen_index = np.random.randint(mutable.length)
result[mutable.key] = torch.tensor([i == gen_index for i in range(mutable.length)],
dtype=torch.bool)
elif isinstance(mutable, InputChoice):
if mutable.n_chosen is None: # n_chosen is None, then choose any number
result[mutable.key] = torch.randint(high=2, size=(mutable.n_candidates,)).view(-1).bool()
# else do something else
return result
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的完整示例。
对于高级用法,例如,需要在 ``LayerChoice`` 执行的时候操作模型,可继承 ``BaseMutator``,并重载 ``on_forward_layer_choice`` 和 ``on_forward_input_choice`` 。这些是 ``LayerChoice`` 和 ``InputChoice`` 对应的回调实现。 还可使用属性 ``mutables`` 来获得模型中所有的 ``LayerChoice`` 和 ``InputChoice``。 详情请参考 :githublink:`reference <src/sdk/pynni/nni/nas/pytorch>` 。
.. tip::
用于调试的随机 Mutator。 使用
.. code-block:: python
mutator = RandomMutator(model)
mutator.reset()
将立即在搜索空间中将一个可能的候选者设置为活动候选者。
实现分布式 NAS Tuner
-----------------------------------
在学习编写分布式 NAS Tuner前,应先了解如何写出通用的 Tuner。 请参阅这篇 `Customize Tuner <../Tuner/CustomizeTuner.rst>`__ 。
当调用 `nnictl ss_gen <../Tutorial/Nnictl.rst>`_ 时,会生成下面这样的搜索空间文件:
.. code-block:: json
{
"key_name": {
"_type": "layer_choice",
"_value": ["op1_repr", "op2_repr", "op3_repr"]
},
"key_name": {
"_type": "input_choice",
"_value": {
"candidates": ["in1_key", "in2_key", "in3_key"],
"n_chosen": 1
}
}
}
这是 Tuner 在 ``update_search_space`` 中会收到的搜索空间。 Tuner 需要解析搜索空间,并在 ``generate_parameters`` 中生成新的候选。 有效的 "参数" 格式如下:
.. code-block:: json
{
"key_name": {
"_value": "op1_repr",
"_idx": 0
},
"key_name": {
"_value": ["in2_key"],
"_idex": [1]
}
}
和普通超参优化 Tuner 类似,通过 ``generate_parameters`` 来发送。 请参考 `SPOS <./SPOS.rst>`__ 的代码例子来书写用例。
# NAS 基准测试
NAS 基准测试
==============
```eval_rst
.. toctree::
:hidden:
用法示例 <BenchmarksExample>
```
示例用法 <BenchmarksExample>
## 介绍
介绍
------------
为了提高 NAS 算法的可复现性并降低对计算资源的需求,研究者们提出了一系列 NAS 基准测试如[NAS-Bench-101](https://arxiv.org/abs/1902.09635), [NAS-Bench-201](https://arxiv.org/abs/2001.00326), [NDS](https://arxiv.org/abs/1905.13214)等等。 NNI 为用户提供了查询接口来获取这些基准测试。 只需要几行代码,研究者就可以通过使用这些基准测试容易且公平地评估他们的 NAS 算法。
为了提高 NAS 算法的可复现性并降低对计算资源的需求,研究者们提出了一系列 NAS 基准测试如 `NAS-Bench-101 <https://arxiv.org/abs/1902.09635>`_, `NAS-Bench-201 <https://arxiv.org/abs/2001.00326>`_, `NDS <https://arxiv.org/abs/1905.13214>`_ 等等。 NNI 为用户提供了查询接口来获取这些基准测试。 只需要几行代码,研究者就可以通过使用这些基准测试容易且公平地评估他们的 NAS 算法。
## 先决条件
先决条件
-------------
* 准备目录来保存基准测试的数据库。 默认情况下,目录为 `${HOME}/.nni/nasbenchmark`。 可以将其放在任意位置, 并在导入 NNI 之前指定 `NASBENCHMARK_DIR` 通过 `export NASBENCHMARK_DIR=/path/to/your/nasbenchmark`.
* 通过 `pip3 install peewee` 命令安装 `peewee`,NNI 用其连接数据库。
## 准备数据
* 准备目录来保存基准测试的数据库。 默认情况下,目录为 ``${HOME}/.nni/nasbenchmark`` 。 可以将其放在任意位置, 并在导入 NNI 之前指定 ``NASBENCHMARK_DIR`` 通过 ``export NASBENCHMARK_DIR=/path/to/your/nasbenchmark``.
* 通过 ``pip3 install peewee`` 命令安装 ``peewee``,NNI 用其连接数据库。
为了避免存储和法规问题,NNI 不提供数据库。 步骤:
准备数据
----------------
1. 将 NNI 克隆到机器上并进入 `examples/nas/benchmarks` 目录。
```
git clone -b ${NNI_VERSION} https://github.com/microsoft/nni
cd nni/examples/nas/benchmarks
```
`${NNI_VERSION}` 替换为发布的版本或分支名称,例如:`v1.8`
为了避免存储和法规问题,NNI 不提供数据库。 尝试以下步骤:
2. 通过 `pip3 install -r xxx.requirements.txt` 安装依赖。 `xxx` 可以为 `nasbench101`, `nasbench201``nds`.
3. 通过 `./xxx.sh`生成数据库。 存储数据库的目录可以通过环境变量 `NASBENCHMARK_DIR` 设置,默认为 `~/.nni/nasbenchmark`。 注意 NAS-Bench-201 数据库将从 google drive 被下载。
#.
将 NNI 克隆到机器上并进入 ``examples/nas/benchmarks`` 目录。
.. code-block:: bash
git clone -b ${NNI_VERSION} https://github.com/microsoft/nni
cd nni/examples/nas/benchmarks
将 ``${NNI_VERSION}`` 替换为发布的版本或分支名称,例如:``v1.9``。
#.
通过 ``pip3 install -r xxx.requirements.txt`` 安装依赖。 ``xxx`` 可以是 ``nasbench101``\ ,``nasbench201`` ,``nds``。
#. 通过 ``./xxx.sh`` 生成数据库。 存储数据库的目录可以通过环境变量 ``NASBENCHMARK_DIR`` 设置,默认为 ``~/.nni/nasbenchmark``。 注意 NAS-Bench-201 数据库将从 google drive 被下载。
确保至少有 10GB 的可用磁盘空间,运行过程可能需要几个小时。
## 示例用法
示例用法
--------------
参考[基准测试 API 的用法](./BenchmarksExample)
参考 `Benchmarks API 的示例用法 <./BenchmarksExample>`_
## NAS-Bench-101
NAS-Bench-101
-------------
[论文](https://arxiv.org/abs/1902.09635) &nbsp; &nbsp; [代码](https://github.com/google-research/nasbench)
`Paper link <https://arxiv.org/abs/1902.09635>`__ &nbsp; &nbsp; `Open-source <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``
注意,NAS-Bench-101 消除了非法的 Cell(如,从输入到输出没有路径,或存在冗余的计算)。 此外,同构的 Cell 会被去掉,即,所有的 Cell 从计算上看是一致的。
### API 文档
API 文档
^^^^^^^^^^^^^^^^^
```eval_rst
.. autofunction:: nni.nas.benchmarks.nasbench101.query_nb101_trial_stats
.. autoattribute:: nni.nas.benchmarks.nasbench101.INPUT
......@@ -70,18 +81,17 @@ NAS-Bench-101 包含 423,624 个独立的神经网络,再加上 4 个 Epoch (4
.. autofunction:: nni.nas.benchmarks.nasbench101.graph_util.infer_num_vertices
.. autofunction:: nni.nas.benchmarks.nasbench101.graph_util.hash_module
```
## NAS-Bench-201
[论文](https://arxiv.org/abs/2001.00326) &nbsp; &nbsp; [API](https://github.com/D-X-Y/NAS-Bench-201) &nbsp; &nbsp;[实现](https://github.com/D-X-Y/AutoDL-Projects)
NAS-Bench-201
-------------
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 次。
`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>`__
### API 文档
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 次。
API 文档
^^^^^^^^^^^^^^^^^
```eval_rst
.. autofunction:: nni.nas.benchmarks.nasbench201.query_nb201_trial_stats
.. autoattribute:: nni.nas.benchmarks.nasbench201.NONE
......@@ -99,21 +109,21 @@ NAS-Bench-201 是单元格的搜索空间,并将张量当作节点,运算符
.. autoclass:: nni.nas.benchmarks.nasbench201.Nb201TrialStats
.. autoclass:: nni.nas.benchmarks.nasbench201.Nb201IntermediateStats
```
## NDS
NDS
---
[论文](https://arxiv.org/abs/1905.13214) &nbsp; &nbsp; [代码](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 的设计)。 大部分配置只采用固定的随机种子训练一次,但少部分会训练两到三次。
NNI 会将不同配置的结果存到单个数据库中,而不是单独的文件中,以便从各个维度进行比较。 在实现上,`model_family` 用来保存模型类型,`model_spec` 用来保存构建模型所需的参数,在使用 NAS 时,`cell_spec` 保存运算符和连接的详细信息,`generator` 表示配置生成的采样策略。 详情可参考 API 文档。
NNI 会将不同配置的结果存到单个数据库中,而不是单独的文件中,以便从各个维度进行比较。 在实现上,``model_family`` 用来保存模型类型,``model_spec`` 用来保存构建模型所需的参数,在使用 NAS 时,``cell_spec`` 保存运算符和连接的详细信息,``generator`` 表示配置生成的采样策略。 详情可参考 API 文档。
## 可用的运算符
可用的运算符
-------------------
NDS 中可用的运算符列表。
```eval_rst
.. autoattribute:: nni.nas.benchmarks.nds.constants.NONE
.. autoattribute:: nni.nas.benchmarks.nds.constants.SKIP_CONNECT
......@@ -145,11 +155,10 @@ NDS 中可用的运算符列表。
.. autoattribute:: nni.nas.benchmarks.nds.constants.SEP_CONV_7X7
.. autoattribute:: nni.nas.benchmarks.nds.constants.DIL_SEP_CONV_3X3
```
### API 文档
API 文档
^^^^^^^^^^^^^^^^^
```eval_rst
.. autofunction:: nni.nas.benchmarks.nds.query_nds_trial_stats
.. autoclass:: nni.nas.benchmarks.nds.NdsTrialConfig
......@@ -157,4 +166,3 @@ NDS 中可用的运算符列表。
.. autoclass:: nni.nas.benchmarks.nds.NdsTrialStats
.. autoclass:: nni.nas.benchmarks.nds.NdsIntermediateStats
```
# CDARTS
## 介绍
CDARTS 在搜索和评估网络之间构建了循环反馈机制。 首先,搜索网络会生成初始结构用于评估,以便优化评估网络的权重。 然后,通过分类中通过的标签,以及评估网络中特征蒸馏的正则化来进一步优化搜索网络中的架构。 重复上述循环来优化搜索和评估网路,从而使结构得到训练,成为最终的评估网络。
`CdartsTrainer` 的实现中,首先分别实例化了两个 Model 和 Mutator。 第一个 Model 被称为"搜索网络",使用 `RegularizedDartsMutator` 来进行变化。它与 `DartsMutator` 稍有差别。 第二个 Model 是“评估网络”,它里用前面搜索网络的 Mutator 来创建了一个离散的 Mutator,来每次采样一条路径。 Trainer 会交替训练 Model 和 Mutator。 如果对 Trainer 和 Mutator 的实现感兴趣,可参考[这里](#reference)
## 重现结果
这是基于 NNI 平台的 CDARTS,该平台目前支持 CIFAR10 搜索和重新训练。 同时也支持 ImageNet 的搜索和重新训练,并有相应的接口。 在 NNI 上重现的结果略低于论文,但远高于原始 DARTS。 这里展示了在 CIFAR10 上的三个独立实验的结果。
| 运行 | 论文 | NNI |
| -- |:-----:|:-----:|
| 1 | 97.52 | 97.44 |
| 2 | 97.53 | 97.48 |
| 3 | 97.58 | 97.56 |
## 示例
[示例代码](https://github.com/microsoft/nni/tree/master/examples/nas/cdarts)
```bash
#如果未克隆 NNI 代码。 如果代码已被克隆,请忽略此行并直接进入代码目录。
git clone https://github.com/Microsoft/nni.git
# 为分布式训练安装 apex
git clone https://github.com/NVIDIA/apex
cd apex
python setup.py install --cpp_ext --cuda_ext
# 搜索最好的架构
cd examples/nas/cdarts
bash run_search_cifar.sh
# 训练最好的架构
bash run_retrain_cifar.sh
```
## 参考
### PyTorch
```eval_rst
.. autoclass:: nni.nas.pytorch.cdarts.CdartsTrainer
:members:
.. autoclass:: nni.nas.pytorch.cdarts.RegularizedDartsMutator
:members:
.. autoclass:: nni.nas.pytorch.cdarts.DartsDiscreteMutator
:members:
.. autoclass:: nni.nas.pytorch.cdarts.RegularizedMutatorParallel
:members:
```
CDARTS
======
介绍
------------
`CDARTS <https://arxiv.org/pdf/2006.10724.pdf>`__ 建立了搜索和评估网络的循环反馈机制。 首先,搜索网络会生成初始结构用于评估,以便优化评估网络的权重。 然后,通过分类中通过的标签,以及评估网络中特征蒸馏的正则化来进一步优化搜索网络中的架构。 重复上述循环来优化搜索和评估网路,从而使结构得到训练,成为最终的评估网络。
在 ``CdartsTrainer`` 的实现中,首先分别实例化了两个 Model 和 Mutator。 第一个 Model 被称为"搜索网络",使用 ``RegularizedDartsMutator`` 来进行变化。它与 ``DartsMutator`` 稍有差别。 第二个 Model 是“评估网络”,它里用前面搜索网络的 Mutator 来创建了一个离散的 Mutator,来每次采样一条路径。 Trainer 会交替训练 Model 和 Mutator。 如果用户对这些 trainers 和 mutators 的详情感兴趣,可以参考 `论文 <https://arxiv.org/pdf/2006.10724.pdf>`__ 。
重现结果
--------------------
这是基于 NNI 平台的 CDARTS,该平台目前支持 CIFAR10 搜索和重新训练。 同时也支持 ImageNet 的搜索和重新训练,并有相应的接口。 在 NNI 上重现的结果略低于论文,但远高于原始 DARTS。 这里展示了在 CIFAR10 上的三个独立实验的结果。
.. list-table::
:header-rows: 1
:widths: auto
* - Runs
- Paper
- NNI
* - 1
- 97.52
- 97.44
* - 2
- 97.53
- 97.48
* - 3
- 97.58
- 97.56
样例
--------
`示例代码 <https://github.com/microsoft/nni/tree/master/examples/nas/cdarts>`__
.. code-block:: bash
# In case NNI code is not cloned. If the code is cloned already, ignore this line and enter code folder.
git clone https://github.com/Microsoft/nni.git
# install apex for distributed training.
git clone https://github.com/NVIDIA/apex
cd apex
python setup.py install --cpp_ext --cuda_ext
# search the best architecture
cd examples/nas/cdarts
bash run_search_cifar.sh
# train the best architecture.
bash run_retrain_cifar.sh
参考
---------
PyTorch
^^^^^^^
.. autoclass:: nni.algorithms.nas.pytorch.cdarts.CdartsTrainer
:members:
.. autoclass:: nni.algorithms.nas.pytorch.cdarts.RegularizedDartsMutator
:members:
.. autoclass:: nni.algorithms.nas.pytorch.cdarts.DartsDiscreteMutator
:members:
.. autoclass:: nni.algorithms.nas.pytorch.cdarts.RegularizedMutatorParallel
:members:
# 经典 NAS 算法
在经典 NAS 算法中,每个结构都作为 Trial 来训练,而 NAS 算法来充当 Tuner。 因此,训练过程能使用 NNI 中的超参调优框架,Tuner 为下一个 Trial 生成新的结构,Trial 在训练平台中运行。
## 快速入门
下例展示了如何使用经典 NAS 算法。 与 NNI 超参优化非常相似。
```python
model = Net()
# 从 Tuner 中获得选择的架构,并应用到模型上
get_and_apply_next_architecture(model)
train(model) # 训练模型的代码
acc = test(model) # 测试训练好的模型
nni.report_final_result(acc) # 报告所选架构的性能
```
首先,实例化模型。 模型中,搜索空间通过 `LayerChoice``InputChoice` 来定义。 然后,调用 `get_and_apply_next_architecture(model)` 来获得特定的结构。 此函数会从 Tuner (即,经典的 NAS 算法)中接收结构,并应用到 `model` 上。 此时,`model` 成为了某个结构,不再是搜索空间。 然后可以像普通 PyTorch 模型一样训练此模型。 获得模型精度后,调用 `nni.report_final_result(acc)` 来返回给 Tuner。
至此,Trial 代码已准备好了。 然后,准备好 NNI 的 Experiment,即搜索空间文件和 Experiment 配置文件。 与 NNI 超参优化不同的是,要通过运行命令(详情参考[这里](../Tutorial/Nnictl.md))从 Trial 代码中自动生成搜索空间文件。
`nnictl ss_gen --trial_command="运行 Trial 代码的命令"`
此命令会自动生成 `nni_auto_gen_search_space.json` 文件。 然后,将生成的搜索空间文件路径填入 Experiment 配置文件的 `searchSpacePath` 字段。 配置文件中的其它字段,可参考[此教程](../Tutorial/QuickStart.md)
目前,经典 NAS 仅支持 [PPO Tuner](../Tuner/BuiltinTuner.md)[随机 Tuner ](https://github.com/microsoft/nni/tree/master/examples/tuners/random_nas_tuner)。 未来将支持更多经典 NAS 算法。
完整的 [PyTorch 示例](https://github.com/microsoft/nni/tree/master/examples/nas/classic_nas),以及 [TensorFlow 示例](https://github.com/microsoft/nni/tree/master/examples/nas/classic_nas-tf)
## 用于调试的独立模式
为了便于调试,其支持独立运行模式,可直接运行 Trial 命令,而不启动 NNI Experiment。 可以通过此方法来检查 Trial 代码是否可正常运行。 在独立模式下,`LayerChoice``InputChoice` 会选择第一个的候选项。
\ No newline at end of file
.. role:: raw-html(raw)
:format: html
经典 NAS 算法
======================
在经典 NAS 算法中,每个结构都作为 Trial 来训练,而 NAS 算法来充当 Tuner 因此,训练过程能使用 NNI 中的超参调优框架,Tuner 为下一个 Trial 生成新的结构,Trial 在训练平台中运行。
快速入门
-----------
下例展示了如何使用经典 NAS 算法。 NNI 超参优化非常相似。
.. code-block:: python
model = Net()
# get the chosen architecture from tuner and apply it on model
get_and_apply_next_architecture(model)
train(model) # your code for training the model
acc = test(model) # test the trained model
nni.report_final_result(acc) # report the performance of the chosen architecture
首先,实例化模型。 模型中,搜索空间通过 ``LayerChoice`` ``InputChoice`` 来定义。 然后,调用 ``get_and_apply_next_architecture(model)`` 来获得特定的结构。 此函数会从 Tuner (即,经典的 NAS 算法)中接收结构,并应用到 ``model`` 上。 此时,``model`` 成为了某个结构,不再是搜索空间。 然后可以像普通 PyTorch 模型一样训练此模型。 获得模型精度后,调用 ``nni.report_final_result(acc)`` 来返回给 Tuner
至此,Trial 代码已准备好了。 然后,准备好 NNI Experiment,即搜索空间文件和 Experiment 配置文件。 NNI 超参优化不同的是,要通过运行命令(详情参考 `这里 <../Tutorial/Nnictl.rst>`_) Trial 代码中自动生成搜索空间文件。
``nnictl ss_gen --trial_command="运行 Trial 代码的命令"``
此命令会自动生成 ``nni_auto_gen_search_space.json`` 文件。 然后,将生成的搜索空间文件路径填入 Experiment 配置文件的 ``searchSpacePath`` 字段。 配置文件中的其它字段,可参考 `此教程 <../Tutorial/QuickStart.md>`_
现在我们只支持经典NAS算法中的 :githublink:`PPO Tuner <examples/tuners/random_nas_tuner>` 未来将支持更多经典 NAS 算法。
完整的示例可以参考 :githublink:`PyTorch <examples/nas/classic_nas>` :githublink:`TensorFlow <examples/nas/classic_nas-tf>`
用于调试的独立模式
----------------------------------
为了便于调试,其支持独立运行模式,可直接运行 Trial 命令,而不启动 NNI Experiment 可以通过此方法来检查 Trial 代码是否可正常运行。 在独立模式下,``LayerChoice`` ``InputChoice`` 会选择最开始的候选项。
:raw-html:`<a name="regulaized-evolution-tuner"></a>`
Regularized Evolution Tuner
---------------------------
这是一个用于 NNI 神经网络架构搜索(NAS)接口的 Tuner 它使用了 `evolution 算法 <https://arxiv.org/pdf/1802.01548.pdf>`_
Tuner 首先随机初始化 ``population`` 模型的数量并进行评估。 之后,每次生成新架构时,Tuner 都会从 ``population`` 中随机选择一定数量的 ``sample`` 架构,然后将父模型 ``sample`` 中的最佳模型 mutates 产生子模型。 突变包括隐藏突变和op突变。 隐藏状态突变包括在单元格内形成循环的约束下,用 cell 的另一个隐藏状态替换隐藏状态。 op 变异的行为就像隐藏状态变异一样,只是将一个 op 替换为 op 集中的另一个 op 请注意,不允许将子模型与其父模型保持相同。 评估子模型后,将其添加到 ``population`` 的尾部,然后弹出前一个。
请注意,**试验并发应小于模型的数量** \,否则将引发 NO_MORE_TRIAL 异常。
下面的伪代码总结了整个过程。
.. image:: ../../img/EvoNasTuner.png
:target: ../../img/EvoNasTuner.png
:alt:
.. 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/>`
在这项工作中,我们提出了一种简单有效的体系结构提炼方法。 中心思想是子网可以在整个训练过程中进行协作学习并相互教,目的是促进各个模型的融合。 我们介绍了优先路径的概念,它是指在训练过程中表现出卓越性能的体系结构候选人。 从优先路径中提取知识可以促进子网的训练。 由于优先路径会根据其性能和复杂性而动态变化,因此最终获得的路径就是百里挑一。 与最近的架构 `MobileNetV3 <https://arxiv.org/abs/1905.02244>`__ 和 `EfficientNet <https://arxiv.org/abs/1905.11946>`__ 系列在对齐设置下相比,发现的体系结构具有更高的性能。
.. image:: https://raw.githubusercontent.com/microsoft/Cream/main/demo/intro.jpg
重现结果
------------------
ImageNet 的 top-1 准确性。 Cream 搜索算法的 top-1 准确性超过 ImageNet 上的 MobileNetV3 和 EfficientNet-B0 / B1。
如下所示,使用 16 Gpus 的训练比使用 8 Gpus 的训练略胜一筹。
.. list-table::
:header-rows: 1
:widths: auto
* - Model (M Flops)
- 8Gpus
- 16Gpus
* - 14M
- 53.7
- 53.8
* - 43M
- 65.8
- 66.5
* - 114M
- 72.1
- 72.8
* - 287M
- 76.7
- 77.6
* - 481M
- 78.9
- 79.2
* - 604M
- 79.4
- 80.0
.. image:: ../../img/cream_flops100.jpg
:scale: 50%
.. image:: ../../img/cream_flops600.jpg
:scale: 50%
示例
--------
`示例代码 <https://github.com/microsoft/nni/tree/master/examples/nas/cream>`__
请在示例目录下运行下面的脚本。
准备数据
----------------
首先你需要下载 `ImageNet-2012 <http://www.image-net.org/>`__ 到目录 ``./data/imagenet`` 里,然后把验证集移动到子文件夹 ``./data/imagenet/val`` 。 你可以用下面这个命令来移动验证集:https://raw.githubusercontent.com/soumith/imagenetloader.torch/master/valprep.sh
把 imagenet 数据放在 ``./data`` 里, 如下:
.. code-block:: bash
./data/imagenet/train
./data/imagenet/val
...
快速入门
-----------
I. 搜索
^^^^^^^^^
首先构建搜索环境。
.. code-block:: bash
pip install -r ./requirements
git clone https://github.com/NVIDIA/apex.git
cd apex
python setup.py install --cpp_ext --cuda_ext
搜索架构需要配置参数 ``FLOPS_MINIMUM`` 和 ``FLOPS_MAXIMU`` 以指定所需的模型触发器,例如 [0,600] MB flops。 可以通过修改以下两个参数来指定触发器间隔 ``./configs/train.yaml`` 。
.. code-block:: bash
FLOPS_MINIMUM: 0 # Minimum Flops of Architecture
FLOPS_MAXIMUM: 600 # Maximum Flops of Architecture
例如,如果希望搜索模型 <= 200M 的架构,请将 ``FLOPS_MINIMU`` 和 ``FLOPS_MAXIMU`` 分别设置为 ``0`` 和 ``200`` 。
在指定要搜索的体系结构之后,可以通过运行以下命令立即搜索体系结构:
.. code-block:: bash
python -m torch.distributed.launch --nproc_per_node=8 ./train.py --cfg ./configs/train.yaml
搜索的体系结构需要重新训练并获得最终模型。 最终模型以 ``.pth.tar`` 格式保存。 训练代码不久就会发布。
II. 重新训练
^^^^^^^^^^^^^^^^^^
为了训练搜索的架构,需要配置 ``MODEL_SELECTION`` 参数来指定模型触发器。 在 ``./configs/retrain.yaml`` 文件里加上 ``MODEL_SELECTION`` 可以声明训练模型。 您可以从 [14,43,112,287,481,604] 中选择一个,代表不同的 Flops(MB)。
.. code-block:: bash
MODEL_SELECTION: 43 # Retrain 43m model
MODEL_SELECTION: 481 # Retrain 481m model
......
为了训练随机架构,需要设置 ``MODEL_SELECTION`` 为 ``-1`` ,并且设置参数 ``INPUT_ARCH``:
.. code-block:: bash
MODEL_SELECTION: -1 # Train random architectures
INPUT_ARCH: [[0], [3], [3, 3], [3, 1, 3], [3, 3, 3, 3], [3, 3, 3], [0]] # Random Architectures
......
在 ``./configs/retrain.yaml`` 文件里添加 ``MODEL_SELECTION`` 之后,可以使用下面的命令来训练模型。
.. code-block:: bash
python -m torch.distributed.launch --nproc_per_node=8 ./retrain.py --cfg ./configs/retrain.yaml
III. 测试
^^^^^^^^^
要测试我们训练的模型,需要使用 ``./configs/test.yaml`` 中的 ``MODEL_SELECTION`` 来指定要测试的模型。
.. code-block:: bash
MODEL_SELECTION: 43 # test 43m model
MODEL_SELECTION: 481 # test 470m model
......
在指定了模型的触发器之后,需要在 ``./ test.sh`` 中写入恢复模型的路径:
.. code-block:: bash
RESUME_PATH: './43.pth.tar'
RESUME_PATH: './481.pth.tar'
......
我们在 `google drive <https://drive.google.com/drive/folders/1CQjyBryZ4F20Rutj7coF8HWFcedApUn2>`__ 和 `[Models-Baidu Disk (password: wqw6)] <https://pan.baidu.com/s/1TqQNm2s14oEdyNPimw3T9g>`__ 提供了 14M/43M/114M/287M/481M/604M 预训练模型。
下载完预训练模型并且在 ``./configs/test.yaml`` 文件中添加了 ``MODEL_SELECTION`` 和 ``RESUME_PATH`` 之后,可以使用下面的命令来测试模型。
.. code-block:: bash
python -m torch.distributed.launch --nproc_per_node=8 ./test.py --cfg ./configs/test.yaml
# DARTS
## 介绍
论文 [DARTS: Differentiable Architecture Search](https://arxiv.org/abs/1806.09055) 通过可微分的方式来解决架构搜索中的伸缩性挑战。 此方法基于架构的连续放松的表示,从而允许在架构搜索时能使用梯度下降。
为了实现,作者在小批量中交替优化网络权重和架构权重。 还进一步探讨了使用二阶优化(unroll)来替代一阶,来提高性能的可能性。
NNI 的实现基于[官方实现](https://github.com/quark0/darts)以及一个[第三方实现](https://github.com/khanrc/pt.darts)。 NNI 上的 DARTS 设计为可用于任何搜索空间。 与原始论文一样,为 CIFAR10 实现了 CNN 的搜索空间,来作为 DARTS 的实际示例。
## 重现结果
上述示例旨在重现本文中的结果,我们进行了一阶和二阶优化实验。 由于时间限制,我们仅从第二阶段重新训练了*一次**最佳架构*。 我们的结果目前与论文的结果相当。 稍后会增加更多结果
| | 论文中 | 重现 |
| ----------- | ------------- | ---- |
| 一阶(CIFAR10) | 3.00 +/- 0.14 | 2.78 |
| 二阶(CIFAR10) | 2.76 +/- 0.09 | 2.89 |
## 示例
### CNN 搜索空间
[示例代码](https://github.com/microsoft/nni/tree/master/examples/nas/darts)
```bash
#如果未克隆 NNI 代码。 如果代码已被克隆,请忽略此行并直接进入代码目录。
git clone https://github.com/Microsoft/nni.git
# 搜索最好的架构
cd examples/nas/darts
python3 search.py
# 训练最好的架构
python3 retrain.py --arc-checkpoint ./checkpoints/epoch_49.json
```
## 参考
### PyTorch
```eval_rst
.. autoclass:: nni.nas.pytorch.darts.DartsTrainer
:members:
.. autoclass:: nni.nas.pytorch.darts.DartsMutator
:members:
```
## 局限性
* DARTS 不支持 DataParallel,若要支持 DistributedDataParallel,则需要定制。
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