Unverified Commit cd3a912a authored by SparkSnail's avatar SparkSnail Committed by GitHub
Browse files

Merge pull request #218 from microsoft/master

merge master
parents a0846f2a e9cba778
...@@ -100,7 +100,7 @@ If you want to use NNI to automatically train your model and find the optimal hy ...@@ -100,7 +100,7 @@ If you want to use NNI to automatically train your model and find the optimal hy
with tf.Session() as sess: with tf.Session() as sess:
mnist_network.train(sess, mnist) mnist_network.train(sess, mnist)
test_acc = mnist_network.evaluate(mnist) test_acc = mnist_network.evaluate(mnist)
+ nni.report_final_result(acc) + nni.report_final_result(test_acc)
if __name__ == '__main__': if __name__ == '__main__':
- params = {'data_dir': '/tmp/tensorflow/mnist/input_data', 'dropout_rate': 0.5, 'channel_1_num': 32, 'channel_2_num': 64, - params = {'data_dir': '/tmp/tensorflow/mnist/input_data', 'dropout_rate': 0.5, 'channel_1_num': 32, 'channel_2_num': 64,
......
...@@ -73,12 +73,6 @@ All types of sampling strategies and their parameter are listed here: ...@@ -73,12 +73,6 @@ All types of sampling strategies and their parameter are listed here:
* Which means the variable value is a value like `round(exp(normal(mu, sigma)) / q) * q` * Which means the variable value is a value like `round(exp(normal(mu, sigma)) / q) * q`
* Suitable for a discrete variable with respect to which the objective is smooth and gets smoother with the size of the variable, which is bounded from one side. * Suitable for a discrete variable with respect to which the objective is smooth and gets smoother with the size of the variable, which is bounded from one side.
* `{"_type": "mutable_layer", "_value": {mutable_layer_infomation}}`
* Type for [Neural Architecture Search Space][1]. Value is also a dictionary, which contains key-value pairs representing respectively name and search space of each mutable_layer.
* For now, users can only use this type of search space with annotation, which means that there is no need to define a json file for search space since it will be automatically generated according to the annotation in trial code.
* The following HPO tuners can be adapted to tune this search space: TPE, Random, Anneal, Evolution, Grid Search,
Hyperband and BOHB.
* For detailed usage, please refer to [General NAS Interfaces][1].
## Search Space Types Supported by Each Tuner ## Search Space Types Supported by Each Tuner
...@@ -105,5 +99,3 @@ Known Limitations: ...@@ -105,5 +99,3 @@ Known Limitations:
* Only Random Search/TPE/Anneal/Evolution tuner supports nested search space * Only Random Search/TPE/Anneal/Evolution tuner supports nested search space
* We do not support nested search space "Hyper Parameter" in visualization now, the enhancement is being considered in [#1110](https://github.com/microsoft/nni/issues/1110), any suggestions or discussions or contributions are warmly welcomed * We do not support nested search space "Hyper Parameter" in visualization now, the enhancement is being considered in [#1110](https://github.com/microsoft/nni/issues/1110), any suggestions or discussions or contributions are warmly welcomed
[1]: ../AdvancedFeature/GeneralNasInterfaces.md
...@@ -3,5 +3,3 @@ Advanced Features ...@@ -3,5 +3,3 @@ Advanced Features
.. toctree:: .. toctree::
MultiPhase<./AdvancedFeature/MultiPhase> MultiPhase<./AdvancedFeature/MultiPhase>
AdvancedNas<./AdvancedFeature/AdvancedNas>
NAS Programming Interface<./AdvancedFeature/GeneralNasInterfaces>
\ No newline at end of file
#################
Feature Engineering
#################
We are glad to announce the alpha release for Feature Engineering toolkit on top of NNI,
it's still in the experiment phase which might evolve based on usage feedback.
We'd like to invite you to use, feedback and even contribute.
For details, please refer to the following tutorials:
.. toctree::
:maxdepth: 2
Overview <FeatureEngineering/Overview>
GradientFeatureSelector <FeatureEngineering/GradientFeatureSelector>
GBDTSelector <FeatureEngineering/GBDTSelector>
#################
Model Compression
#################
NNI provides an easy-to-use toolkit to help user design and use compression algorithms.
It supports Tensorflow and PyTorch with unified interface.
For users to compress their models, they only need to add several lines in their code.
There are some popular model compression algorithms built-in in NNI.
Users could further use NNI's auto tuning power to find the best compressed model,
which is detailed in Auto Model Compression.
On the other hand, users could easily customize their new compression algorithms using NNI's interface.
For details, please refer to the following tutorials:
.. toctree::
:maxdepth: 2
Overview <Compressor/Overview>
Level Pruner <Compressor/Pruner>
AGP Pruner <Compressor/Pruner>
L1Filter Pruner <Compressor/L1FilterPruner>
Slim Pruner <Compressor/SlimPruner>
Lottery Ticket Pruner <Compressor/LotteryTicketHypothesis>
FPGM Pruner <Compressor/Pruner>
Naive Quantizer <Compressor/Quantizer>
QAT Quantizer <Compressor/Quantizer>
DoReFa Quantizer <Compressor/Quantizer>
Automatic Model Compression <Compressor/AutoCompression>
#################
NAS Algorithms
#################
Automatic neural architecture search is taking an increasingly important role on finding better models.
Recent research works have proved the feasibility of automatic NAS, and also found some models that could beat manually designed and tuned models.
Some of representative works are NASNet, ENAS, DARTS, Network Morphism, and Evolution. There are new innovations keeping emerging.
However, it takes great efforts to implement NAS algorithms, and it is hard to reuse code base of existing algorithms in new one.
To facilitate NAS innovations (e.g., design and implement new NAS models, compare different NAS models side-by-side),
an easy-to-use and flexible programming interface is crucial.
With this motivation, our ambition is to provide a unified architecture in NNI,
to accelerate innovations on NAS, and apply state-of-art algorithms on real world problems faster.
For details, please refer to the following tutorials:
.. toctree::
:maxdepth: 2
Overview <NAS/Overview>
NAS Interface <NAS/NasInterface>
ENAS <NAS/Overview>
DARTS <NAS/Overview>
P-DARTS <NAS/Overview>
...@@ -9,6 +9,9 @@ Tutorials ...@@ -9,6 +9,9 @@ Tutorials
Write Trial <TrialExample/Trials> Write Trial <TrialExample/Trials>
Tuners <tuners> Tuners <tuners>
Assessors <assessors> Assessors <assessors>
NAS (Beta) <nas>
Model Compression (Beta) <model_compression>
Feature Engineering (Beta) <feature_engineering>
WebUI <Tutorial/WebUI> WebUI <Tutorial/WebUI>
Training Platform <training_services> Training Platform <training_services>
How to use docker <Tutorial/HowToUseDocker> How to use docker <Tutorial/HowToUseDocker>
......
...@@ -79,7 +79,7 @@ trial_end ...@@ -79,7 +79,7 @@ trial_end
### 支持多阶段 Experiment 的 Tuner: ### 支持多阶段 Experiment 的 Tuner:
[TPE](../Tuner/HyperoptTuner.md), [Random](../Tuner/HyperoptTuner.md), [Anneal](../Tuner/HyperoptTuner.md), [Evolution](../Tuner/EvolutionTuner.md), [SMAC](../Tuner/SmacTuner.md), [NetworkMorphism](../Tuner/NetworkmorphismTuner.md), [MetisTuner](../Tuner/MetisTuner.md), [BOHB](../Tuner/BohbAdvisor.md), [Hyperband](../Tuner/HyperbandAdvisor.md), [ENAS Tuner ](https://github.com/countif/enas_nni/blob/master/nni/examples/tuners/enas/nni_controller_ptb.py). [TPE](../Tuner/HyperoptTuner.md), [Random](../Tuner/HyperoptTuner.md), [Anneal](../Tuner/HyperoptTuner.md), [Evolution](../Tuner/EvolutionTuner.md), [SMAC](../Tuner/SmacTuner.md), [NetworkMorphism](../Tuner/NetworkmorphismTuner.md), [MetisTuner](../Tuner/MetisTuner.md), [BOHB](../Tuner/BohbAdvisor.md), [Hyperband](../Tuner/HyperbandAdvisor.md).
### 支持多阶段 Experiment 的训练平台: ### 支持多阶段 Experiment 的训练平台:
......
# 使用 NNI 自动调优系统
随着计算机系统和网络变得越来越复杂,通过显式的规则和启发式的方法来手工优化已经越来越难,甚至不可能了。 下面是使用 NNI 来优化系统的两个示例。 可根据这些示例来调优自己的系统。
* [在 NNI 上调优 RocksDB](../TrialExample/RocksdbExamples.md)
* [使用 NNI 调优 SPTAG (Space Partition Tree And Graph) 参数](SptagAutoTune.md)
参考[论文](https://dl.acm.org/citation.cfm?id=3352031)了解详情:
Mike Liang, Chieh-Jan, et al. "The Case for Learning-and-System Co-design." ACM SIGOPS Operating Systems Review 53.1 (2019): 68-74.
...@@ -9,13 +9,13 @@ ...@@ -9,13 +9,13 @@
```python ```python
from nni.compression.torch import LevelPruner from nni.compression.torch import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }] config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(config_list) pruner = LevelPruner(model, config_list)
pruner(model) pruner.compress()
``` ```
op_type 为 'default' 表示模块类型定义在了 [default_layers.py](https://github.com/microsoft/nni/blob/master/src/sdk/pynni/nni/compression/torch/default_layers.py) op_type 为 'default' 表示模块类型定义在了 [default_layers.py](https://github.com/microsoft/nni/blob/master/src/sdk/pynni/nni/compression/torch/default_layers.py)
因此 `{ 'sparsity': 0.8, 'op_types': ['default'] }` 表示 **所有指定 op_types 的层都会被压缩到 0.8 的稀疏度**。 当调用 `pruner(model)` 时,模型会通过掩码进行压缩。随后还可以微调模型,此时**被剪除的权重不会被更新** 因此 `{ 'sparsity': 0.8, 'op_types': ['default'] }` 表示 **所有指定 op_types 的层都会被压缩到 0.8 的稀疏度**。 当调用 `pruner.compress()` 时,模型会通过掩码进行压缩。随后还可以微调模型,此时**被剪除的权重不会被更新**
## 然后,进行自动化 ## 然后,进行自动化
...@@ -84,9 +84,9 @@ config_list_agp = [{'initial_sparsity': 0, 'final_sparsity': conv0_sparsity, ...@@ -84,9 +84,9 @@ config_list_agp = [{'initial_sparsity': 0, 'final_sparsity': conv0_sparsity,
{'initial_sparsity': 0, 'final_sparsity': conv1_sparsity, {'initial_sparsity': 0, 'final_sparsity': conv1_sparsity,
'start_epoch': 0, 'end_epoch': 3, 'start_epoch': 0, 'end_epoch': 3,
'frequency': 1,'op_name': 'conv1' },] 'frequency': 1,'op_name': 'conv1' },]
PRUNERS = {'level':LevelPruner(config_list_level)'agp':AGP_Pruner(config_list_agp)} PRUNERS = {'level':LevelPruner(model, config_list_level)'agp':AGP_Pruner(model, config_list_agp)}
pruner = PRUNERS(params['prune_method']['_name']) pruner = PRUNERS(params['prune_method']['_name'])
pruner(model) pruner.compress()
... # fine tuning ... # fine tuning
acc = evaluate(model) # evaluation acc = evaluate(model) # evaluation
nni.report_final_results(acc) nni.report_final_results(acc)
......
NNI Compressor 中的 L1FilterPruner
===
## 1. 介绍
L1FilterPruner 是在卷积层中用来修剪过滤器的通用剪枝算法。
['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) 层创建新的内核举证,并保留剩余的内核 权重,并复制到新模型中。
## 2. 用法
PyTorch 代码
```
from nni.compression.torch import L1FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'], 'op_names': ['conv1', 'conv2'] }]
pruner = L1FilterPruner(model, config_list)
pruner.compress()
```
#### L1Filter Pruner 的用户配置
- **sparsity:**,指定压缩的稀疏度。
- **op_types:** 在 L1Filter Pruner 中仅支持 Conv2d。
## 3. 实验
我们实现了 ['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/)
Lottery Ticket 假设
===
## 介绍
[The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks](https://arxiv.org/abs/1803.03635) 是主要衡量和分析的论文,它提供了非常有意思的见解。 为了在 NNI 上支持此算法,主要实现了找到*获奖彩票*的训练方法。
本文中,作者使用叫做*迭代*修剪的方法:
> 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)。
## 重现结果
在重现时,在 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%),则精度会降低,收敛速度会稍慢。 与本文稍有不同,论文中数据的趋势比较明显。
...@@ -5,12 +5,17 @@ ...@@ -5,12 +5,17 @@
NNI 提供了易于使用的工具包来帮助用户设计并使用压缩算法。 其使用了统一的接口来支持 TensorFlow 和 PyTorch。 只需要添加几行代码即可压缩模型。 NNI 中也内置了一些流程的模型压缩算法。 用户还可以通过 NNI 强大的自动调参功能来找到最好的压缩后的模型,详见[自动模型压缩](./AutoCompression.md)。 另外,用户还能使用 NNI 的接口,轻松定制新的压缩算法,详见[教程](#customize-new-compression-algorithms) NNI 提供了易于使用的工具包来帮助用户设计并使用压缩算法。 其使用了统一的接口来支持 TensorFlow 和 PyTorch。 只需要添加几行代码即可压缩模型。 NNI 中也内置了一些流程的模型压缩算法。 用户还可以通过 NNI 强大的自动调参功能来找到最好的压缩后的模型,详见[自动模型压缩](./AutoCompression.md)。 另外,用户还能使用 NNI 的接口,轻松定制新的压缩算法,详见[教程](#customize-new-compression-algorithms)
## 支持的算法 ## 支持的算法
NNI 提供了两种朴素压缩算法以及三种流行的压缩算法,包括两种剪枝算法以及三种量化算法: NNI 提供了两种朴素压缩算法以及三种流行的压缩算法,包括两种剪枝算法以及三种量化算法:
| 名称 | 算法简介 | | 名称 | 算法简介 |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Level Pruner](./Pruner.md#level-pruner) | 根据权重的绝对值,来按比例修剪权重。 | | [Level Pruner](./Pruner.md#level-pruner) | 根据权重的绝对值,来按比例修剪权重。 |
| [AGP Pruner](./Pruner.md#agp-pruner) | 自动的逐步剪枝(是否剪枝的判断:基于对模型剪枝的效果)[参考论文](https://arxiv.org/abs/1710.01878) | | [AGP Pruner](./Pruner.md#agp-pruner) | 自动的逐步剪枝(是否剪枝的判断:基于对模型剪枝的效果)[参考论文](https://arxiv.org/abs/1710.01878) |
| [L1Filter Pruner](./Pruner.md#l1filter-pruner) | 剪除卷积层中最不重要的过滤器 (PRUNING FILTERS FOR EFFICIENT CONVNETS)[参考论文](https://arxiv.org/abs/1608.08710) |
| [Slim Pruner](./Pruner.md#slim-pruner) | 通过修剪 BN 层中的缩放因子来修剪卷积层中的通道 (Learning Efficient Convolutional Networks through Network Slimming)[参考论文](https://arxiv.org/abs/1708.06519) |
| [Lottery Ticket Pruner](./Pruner.md#agp-pruner) | "The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks" 提出的剪枝过程。 它会反复修剪模型。 [参考论文](https://arxiv.org/abs/1803.03635) |
| [FPGM Pruner](./Pruner.md#fpgm-pruner) | Filter Pruning via Geometric Median for Deep Convolutional Neural Networks Acceleration [参考论文](https://arxiv.org/pdf/1811.00250.pdf) |
| [Naive Quantizer](./Quantizer.md#naive-quantizer) | 默认将权重量化为 8 位 | | [Naive Quantizer](./Quantizer.md#naive-quantizer) | 默认将权重量化为 8 位 |
| [QAT Quantizer](./Quantizer.md#qat-quantizer) | 为 Efficient Integer-Arithmetic-Only Inference 量化并训练神经网络。 [参考论文](http://openaccess.thecvf.com/content_cvpr_2018/papers/Jacob_Quantization_and_Training_CVPR_2018_paper.pdf) | | [QAT Quantizer](./Quantizer.md#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](./Quantizer.md#dorefa-quantizer) | DoReFa-Net: 通过低位宽的梯度算法来训练低位宽的卷积神经网络。 [参考论文](https://arxiv.org/abs/1606.06160) | | [DoReFa Quantizer](./Quantizer.md#dorefa-quantizer) | DoReFa-Net: 通过低位宽的梯度算法来训练低位宽的卷积神经网络。 [参考论文](https://arxiv.org/abs/1606.06160) |
...@@ -20,24 +25,26 @@ NNI 提供了两种朴素压缩算法以及三种流行的压缩算法,包括 ...@@ -20,24 +25,26 @@ NNI 提供了两种朴素压缩算法以及三种流行的压缩算法,包括
通过简单的示例来展示如何修改 Trial 代码来使用压缩算法。 比如,需要通过 Level Pruner 来将权重剪枝 80%,首先在代码中训练模型前,添加以下内容([完整代码](https://github.com/microsoft/nni/tree/master/examples/model_compress))。 通过简单的示例来展示如何修改 Trial 代码来使用压缩算法。 比如,需要通过 Level Pruner 来将权重剪枝 80%,首先在代码中训练模型前,添加以下内容([完整代码](https://github.com/microsoft/nni/tree/master/examples/model_compress))。
TensorFlow 代码 TensorFlow 代码
```python ```python
from nni.compression.tensorflow import LevelPruner from nni.compression.tensorflow import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }] config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(config_list) pruner = LevelPruner(tf.get_default_graph(), config_list)
pruner(tf.get_default_graph()) pruner.compress()
``` ```
PyTorch 代码 PyTorch 代码
```python ```python
from nni.compression.torch import LevelPruner from nni.compression.torch import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }] config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(config_list) pruner = LevelPruner(model, config_list)
pruner(model) pruner.compress()
``` ```
可使用 `nni.compression` 中的其它压缩算法。 此算法分别在 `nni.compression.torch``nni.compression.tensorflow` 中实现,支持 PyTorch 和 TensorFlow。 参考 [Pruner](./Pruner.md)[Quantizer](./Quantizer.md) 进一步了解支持的算法。 可使用 `nni.compression` 中的其它压缩算法。 此算法分别在 `nni.compression.torch``nni.compression.tensorflow` 中实现,支持 PyTorch 和 TensorFlow。 参考 [Pruner](./Pruner.md)[Quantizer](./Quantizer.md) 进一步了解支持的算法。
函数调用 `pruner(model)` 接收用户定义的模型(在 Tensorflow 中,通过 `tf.get_default_graph()` 来获得模型,而 PyTorch 中 model 是定义的模型类),并修改模型来插入 mask。 然后运行模型时,这些 mask 即会生效。 mask 可在运行时通过算法来调整。 函数调用 `pruner.compress()` 来修改用户定义的模型(在 Tensorflow 中,通过 `tf.get_default_graph()` 来获得模型,而 PyTorch 中 model 是定义的模型类),并修改模型来插入 mask。 然后运行模型时,这些 mask 即会生效。 mask 可在运行时通过算法来调整。
实例化压缩算法时,会传入 `config_list`。 配置说明如下。 实例化压缩算法时,会传入 `config_list`。 配置说明如下。
...@@ -54,6 +61,7 @@ pruner(model) ...@@ -54,6 +61,7 @@ pruner(model)
`list` 中的 `dict` 会依次被应用,也就是说,如果一个操作出现在两个配置里,后面的 `dict` 会覆盖前面的配置。 `list` 中的 `dict` 会依次被应用,也就是说,如果一个操作出现在两个配置里,后面的 `dict` 会覆盖前面的配置。
配置的简单示例如下: 配置的简单示例如下:
```python ```python
[ [
{ {
...@@ -70,6 +78,7 @@ pruner(model) ...@@ -70,6 +78,7 @@ pruner(model)
} }
] ]
``` ```
其表示压缩操作的默认稀疏度为 0.8,但`op_name1``op_name2` 会使用 0.6,且不压缩 `op_name3` 其表示压缩操作的默认稀疏度为 0.8,但`op_name1``op_name2` 会使用 0.6,且不压缩 `op_name3`
### 其它 API ### 其它 API
...@@ -77,17 +86,30 @@ pruner(model) ...@@ -77,17 +86,30 @@ pruner(model)
一些压缩算法使用 Epoch 来控制压缩进度(如[AGP](./Pruner.md#agp-pruner)),一些算法需要在每个批处理步骤后执行一些逻辑。 因此提供了另外两个 API。 一个是 `update_epoch`,可参考下例使用: 一些压缩算法使用 Epoch 来控制压缩进度(如[AGP](./Pruner.md#agp-pruner)),一些算法需要在每个批处理步骤后执行一些逻辑。 因此提供了另外两个 API。 一个是 `update_epoch`,可参考下例使用:
TensorFlow 代码 TensorFlow 代码
```python ```python
pruner.update_epoch(epoch, sess) pruner.update_epoch(epoch, sess)
``` ```
PyTorch 代码 PyTorch 代码
```python ```python
pruner.update_epoch(epoch) pruner.update_epoch(epoch)
``` ```
另一个是 `step`,可在每个批处理后调用 `pruner.step()`。 注意,并不是所有的算法都需要这两个 API,对于不需要它们的算法,调用它们不会有影响。 另一个是 `step`,可在每个批处理后调用 `pruner.step()`。 注意,并不是所有的算法都需要这两个 API,对于不需要它们的算法,调用它们不会有影响。
__[TODO]__ 最后一个 API 可供用户导出压缩后的模型。 当完成训练后使用此 API,可得到压缩后的模型。 同时也可导出另一个文件用来存储 mask 的数值。 使用下列 API 可轻松将压缩后的模型导出,稀疏模型的 `state_dict` 会保存在 `model.pth` 文件中,可通过 `torch.load('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])
```
## 定制新的压缩算法 ## 定制新的压缩算法
...@@ -99,41 +121,49 @@ __[TODO]__ 最后一个 API 可供用户导出压缩后的模型。 当完成训 ...@@ -99,41 +121,49 @@ __[TODO]__ 最后一个 API 可供用户导出压缩后的模型。 当完成训
```python ```python
# TensorFlow 中定制 Pruner。 # TensorFlow 中定制 Pruner。
# 如果要在 PyTorch 中定制 Pruner, # PyTorch Pruner,只需
# nni.compression.tensorflow.Pruner 替换为 # nni.compression.tensorflow.Pruner 替换为
# nni.compression.torch.Pruner # nni.compression.torch.Pruner
class YourPruner(nni.compression.tensorflow.Pruner): class YourPruner(nni.compression.tensorflow.Pruner):
def __init__(self, config_list): def __init__(self, model, config_list):
# 建议使用 NNI 定义的规范来进行配置 """
super().__init__(config_list) 建议使用 NNI 定义的规范来配置
"""
def bind_model(self, model): super().__init__(model, config_list)
# 此函数可通过成员变量,来保存模型和其权重,
# 从而能在训练过程中获取这些信息。 def calc_mask(self, layer, config):
pass """
Pruner 需要重载此方法来为权重提供掩码
def calc_mask(self, weight, config, **kwargs): 掩码必须与权重有相同的形状和类型。
# weight 是目标的权重张量 将对权重执行 ``mul()`` 操作。
# config 是在 config_list 中为此层选定的 dict 对象 此方法会挂载到模型的 ``forward()`` 方法上。
# kwargs 包括 op, op_types, 和 op_name
# 实现定制的 mask 并返回 Parameters
----------
layer: LayerInfo
为 ``layer`` 的权重计算掩码
config: dict
生成权重所需要的掩码
"""
return your_mask return your_mask
# 注意, PyTorch 不需要 sess 参数 # PyTorch 版本不需要 sess 参数
def update_epoch(self, epoch_num, sess): def update_epoch(self, epoch_num, sess):
pass pass
# 注意, PyTorch 不需要 sess 参数 # PyTorch 版本不需要 sess 参数
def step(self, sess): def step(self, sess):
# 根据在 bind_model 函数中引用的模型或权重进行一些处理 """
根据需要可基于 bind_model 方法中的模型或权重进行操作
"""
pass pass
``` ```
对于最简单的算法,只需要重写 `calc_mask` 函数。 它接收每层的权重,并选择对应的配置和操作的信息。 可在此函数中为此权重生成 mask 并返回。 NNI 会应用此 mask。 对于最简单的算法,只需要重写 `calc_mask` 函数。 它接收需要压缩的层以及其压缩配置。 可在此函数中为此权重生成 mask 并返回。 NNI 会应用此 mask。
一些算法根据训练进度来生成 mask,如 Epoch 数量。 Pruner 可使用 `update_epoch` 来了解训练进度。 一些算法根据训练进度来生成 mask,如 Epoch 数量。 Pruner 可使用 `update_epoch` 来了解训练进度。 应在每个 Epoch 之前调用它。
一些算法可能需要全局的信息来生成 mask,例如模型的所有权重(用于生成统计信息),模型优化器的信息。 可使用 `bind_model` 来支持此类需求。 `bind_model` 接受完整模型作为参数,因而其记录了所有信息(例如,权重的引用)。 然后 `step` 可以根据算法来处理或更新信息。 可参考[内置算法的源码](https://github.com/microsoft/nni/tree/master/src/sdk/pynni/nni/compressors)作为示例。 一些算法可能需要全局的信息来生成 mask,例如模型的所有权重(用于生成统计信息). 可在 Pruner 类中通过 `self.bound_model` 来访问权重。 如果需要优化器的信息(如在 Pytorch 中),可重载 `__init__` 来接收优化器等参数。 然后 `step` 可以根据算法来处理或更新信息。 可参考[内置算法的源码](https://github.com/microsoft/nni/tree/master/src/sdk/pynni/nni/compressors)作为示例。
### 量化算法 ### 量化算法
...@@ -141,38 +171,79 @@ class YourPruner(nni.compression.tensorflow.Pruner): ...@@ -141,38 +171,79 @@ class YourPruner(nni.compression.tensorflow.Pruner):
```python ```python
# TensorFlow 中定制 Quantizer。 # TensorFlow 中定制 Quantizer。
# 如果要在 PyTorch 中定制 Quantizer, # PyTorch Quantizer,只需
# nni.compression.tensorflow.Quantizer 替换为 # nni.compression.tensorflow.Quantizer 替换为
# nni.compression.torch.Quantizer # nni.compression.torch.Quantizer
class YourQuantizer(nni.compression.tensorflow.Quantizer): class YourQuantizer(nni.compression.tensorflow.Quantizer):
def __init__(self, config_list): def __init__(self, model, config_list):
# 建议使用 NNI 定义的规范来进行配置 """
super().__init__(config_list) 建议使用 NNI 定义的规范来配置
"""
def bind_model(self, model): super().__init__(model, config_list)
# 此函数可通过成员变量,来保存模型和其权重,
# 从而能在训练过程中获取这些信息。
pass
def quantize_weight(self, weight, config, **kwargs): def quantize_weight(self, weight, config, **kwargs):
# weight 是目标的权重张量 """
# config 是在 config_list 中为此层选定的 dict 对象 quantize 需要重载此方法来为权重提供掩码
# kwargs 包括 op, op_types, 和 op_name 此方法挂载于模型的 :meth:`forward`。
# 实现定制的 Quantizer 并返回新的权重
Parameters
----------
weight : Tensor
要被量化的权重
config : dict
权重量化的配置
"""
# 此处逻辑生成 `new_weight`
return new_weight return new_weight
# 注意, PyTorch 不需要 sess 参数 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
# Pytorch 版本不需要 sess 参数
def update_epoch(self, epoch_num, sess): def update_epoch(self, epoch_num, sess):
pass pass
# 注意, PyTorch 不需要 sess 参数 # Pytorch 版本不需要 sess 参数
def step(self, sess): def step(self, sess):
# 根据在 bind_model 函数中引用的模型或权重进行一些处理 """
根据需要可基于 bind_model 方法中的模型或权重进行操作
"""
pass pass
``` ```
__[TODO]__ 添加成员函数 `quantize_layer_output`,用于支持量化层输出的量化算法。
### 使用用户自定义的压缩算法 ### 使用用户自定义的压缩算法
__[TODO]__ ... __[TODO]__ ...
...@@ -3,7 +3,7 @@ NNI Compressor 中的 Pruner ...@@ -3,7 +3,7 @@ NNI Compressor 中的 Pruner
## Level Pruner ## Level Pruner
这是个基本的 Pruner:可设置目标稀疏度(以分数表示,0.6 表示会剪除 60%)。 这是个基本的一次性 Pruner:可设置目标稀疏度(以分数表示,0.6 表示会剪除 60%)。
首先按照绝对值对指定层的权重排序。 然后按照所需的稀疏度,将值最小的权重屏蔽为 0。 首先按照绝对值对指定层的权重排序。 然后按照所需的稀疏度,将值最小的权重屏蔽为 0。
...@@ -13,16 +13,16 @@ TensorFlow 代码 ...@@ -13,16 +13,16 @@ TensorFlow 代码
``` ```
from nni.compression.tensorflow import LevelPruner from nni.compression.tensorflow import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }] config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(config_list) pruner = LevelPruner(model_graph, config_list)
pruner(model_graph) pruner.compress()
``` ```
PyTorch 代码 PyTorch 代码
``` ```
from nni.compression.torch import LevelPruner from nni.compression.torch import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }] config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(config_list) pruner = LevelPruner(model, config_list)
pruner(model) pruner.compress()
``` ```
#### Level Pruner 的用户配置 #### Level Pruner 的用户配置
...@@ -31,7 +31,7 @@ pruner(model) ...@@ -31,7 +31,7 @@ pruner(model)
*** ***
## AGP Pruner ## AGP 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 提出了一种逐渐修建权重的算法。 这是一种迭代的 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 提出了一种逐渐修建权重的算法。
> 我们引入了一种新的自动梯度剪枝算法。这种算法从初始的稀疏度值 si(一般为 0)开始,通过 n 步的剪枝操作,增加到最终所需的稀疏度 sf。从训练步骤 t0 开始,以 ∆t 为剪枝频率: ![](../../img/agp_pruner.png) 在神经网络训练时‘逐步增加网络稀疏度时,每训练 ∆t 步更新一次权重剪枝的二进制掩码。同时也允许训练步骤恢复因为剪枝而造成的精度损失。 根据我们的经验,∆t 设为 100 到 1000 个训练步骤之间时,对于模型最终精度的影响可忽略不计。 一旦模型达到了稀疏度目标 sf,权重掩码将不再更新。 公式背后的稀疏函数直觉。 > 我们引入了一种新的自动梯度剪枝算法。这种算法从初始的稀疏度值 si(一般为 0)开始,通过 n 步的剪枝操作,增加到最终所需的稀疏度 sf。从训练步骤 t0 开始,以 ∆t 为剪枝频率: ![](../../img/agp_pruner.png) 在神经网络训练时‘逐步增加网络稀疏度时,每训练 ∆t 步更新一次权重剪枝的二进制掩码。同时也允许训练步骤恢复因为剪枝而造成的精度损失。 根据我们的经验,∆t 设为 100 到 1000 个训练步骤之间时,对于模型最终精度的影响可忽略不计。 一旦模型达到了稀疏度目标 sf,权重掩码将不再更新。 公式背后的稀疏函数直觉。
### 用法 ### 用法
...@@ -50,8 +50,8 @@ config_list = [{ ...@@ -50,8 +50,8 @@ config_list = [{
'frequency': 1, 'frequency': 1,
'op_types': 'default' 'op_types': 'default'
}] }]
pruner = AGP_Pruner(config_list) pruner = AGP_Pruner(tf.get_default_graph(), config_list)
pruner(tf.get_default_graph()) pruner.compress()
``` ```
PyTorch 代码 PyTorch 代码
```python ```python
...@@ -62,10 +62,10 @@ config_list = [{ ...@@ -62,10 +62,10 @@ config_list = [{
'start_epoch': 0, 'start_epoch': 0,
'end_epoch': 10, 'end_epoch': 10,
'frequency': 1, 'frequency': 1,
'op_types': 'default' 'op_types': ['default']
}] }]
pruner = AGP_Pruner(config_list) pruner = AGP_Pruner(model, config_list)
pruner(model) pruner.compress()
``` ```
其次,在训练代码中每完成一个 Epoch,更新一下 Epoch 数值。 其次,在训练代码中每完成一个 Epoch,更新一下 Epoch 数值。
...@@ -89,3 +89,140 @@ pruner.update_epoch(epoch) ...@@ -89,3 +89,140 @@ pruner.update_epoch(epoch)
*** ***
## 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` 最好足够大。因为假设是在后几轮中具有较高稀疏度的性能(准确度)可与第一轮获得的相当。 [这是](./LotteryTicketHypothesis.md)简单的重现结果。
*稍后支持 TensorFlow 版本。*
#### LotteryTicketPruner 的用户配置
* **prune_iterations:** 迭代修剪的次数。
* **sparsity:** 压缩完成后的最终稀疏度。
***
## FPGM Pruner
这是一种一次性的 Pruner,FPGM Pruner 是论文 [Filter Pruning via Geometric Median for Deep Convolutional Neural Networks Acceleration](https://arxiv.org/pdf/1811.00250.pdf) 的实现
> 以前的方法使用 “smaller-norm-less-important” 准则来修剪卷积神经网络中规范值较小的。 本文中,分析了基于规范的准则,并指出其所依赖的两个条件不能总是满足:(1) 过滤器的规范偏差应该较大;(2) 过滤器的最小规范化值应该很小。 为了解决此问题,提出了新的过滤器修建方法,即 Filter Pruning via Geometric Median (FPGM),可不考虑这两个要求来压缩模型。 与以前的方法不同,FPGM 通过修剪冗余的,而不是相关性更小的部分来压缩 CNN 模型。
### 用法
首先,导入 Pruner 来为模型添加遮盖。
TensorFlow 代码
```python
from nni.compression.tensorflow import FPGMPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2D']
}]
pruner = FPGMPruner(model, config_list)
pruner.compress()
```
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 用于修剪深度神经网络中的卷积层,因此 `op_types` 字段仅支持卷积层。
另外,需要在每个 epoch 开始的地方添加下列代码来更新 epoch 的编号。
TensorFlow 代码
```python
pruner.update_epoch(epoch, sess)
```
PyTorch 代码
```python
pruner.update_epoch(epoch)
```
查看示例进一步了解
#### FPGM Pruner 的用户配置
* **sparsity:** 卷积过滤器要修剪的百分比。
***
## 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) 层创建新的内核举证,并保留剩余的内核 权重,并复制到新模型中。
```
from nni.compression.torch import L1FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'] }]
pruner = L1FilterPruner(model, config_list)
pruner.compress()
```
#### L1Filter Pruner 的用户配置
- **sparsity:**,指定压缩的稀疏度。
- **op_types:** 在 L1Filter Pruner 中仅支持 Conv2d。
## 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 代码
```
from nni.compression.torch import SlimPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['BatchNorm2d'] }]
pruner = SlimPruner(model, config_list)
pruner.compress()
```
#### Slim Pruner 的用户配置
- **sparsity:**,指定压缩的稀疏度。
- **op_types:** 在 Slim Pruner 中仅支持 BatchNorm2d。
...@@ -8,11 +8,11 @@ Naive Quantizer 将 Quantizer 权重默认设置为 8 位,可用它来测试 ...@@ -8,11 +8,11 @@ Naive Quantizer 将 Quantizer 权重默认设置为 8 位,可用它来测试
### 用法 ### 用法
Tensorflow Tensorflow
```python ```python
nni.compressors.tensorflow.NaiveQuantizer()(model_graph) nni.compressors.tensorflow.NaiveQuantizer(model_graph).compress()
``` ```
PyTorch PyTorch
```python ```python
nni.compressors.torch.NaiveQuantizer()(model) nni.compressors.torch.NaiveQuantizer(model).compress()
``` ```
*** ***
...@@ -25,27 +25,36 @@ nni.compressors.torch.NaiveQuantizer()(model) ...@@ -25,27 +25,36 @@ nni.compressors.torch.NaiveQuantizer()(model)
### 用法 ### 用法
可在训练代码前将模型量化为 8 位。 可在训练代码前将模型量化为 8 位。
TensorFlow 代码
```python
from nni.compressors.tensorflow import QAT_Quantizer
config_list = [{ 'q_bits': 8, 'op_types': ['default'] }]
quantizer = QAT_Quantizer(config_list)
quantizer(tf.get_default_graph())
```
PyTorch 代码 PyTorch 代码
```python ```python
from nni.compressors.torch import QAT_Quantizer from nni.compressors.torch import QAT_Quantizer
config_list = [{ 'q_bits': 8, 'op_types': ['default'] }] model = Mnist()
quantizer = QAT_Quantizer(config_list)
quantizer(model) 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 的用户配置 #### QAT Quantizer 的用户配置
* **q_bits:** 指定需要被量化的位数。 * **quant_types:**: 字符串列表 要应用的量化类型,当前支持 'weight', 'input', 'output'
* **quant_bits:** int 或 {str : int} 的 dict 量化的位长,主键是量化类型,键值为长度,例如。 {'weight', 8}, 当类型为 int 时,所有量化类型都用同样的位长
* **quant_start_step:** int 在运行到某步骤前,对模型禁用量化。这让网络在进入更稳定的 状态后再激活量化,这样不会配除掉一些分数显著的值,默认为 0
### 注意
当前不支持批处理规范化折叠。
*** ***
## DoReFa Quantizer ## DoReFa Quantizer
...@@ -58,18 +67,18 @@ TensorFlow 代码 ...@@ -58,18 +67,18 @@ TensorFlow 代码
```python ```python
from nni.compressors.tensorflow import DoReFaQuantizer from nni.compressors.tensorflow import DoReFaQuantizer
config_list = [{ 'q_bits': 8, 'op_types': 'default' }] config_list = [{ 'q_bits': 8, 'op_types': 'default' }]
quantizer = DoReFaQuantizer(config_list) quantizer = DoReFaQuantizer(tf.get_default_graph(), config_list)
quantizer(tf.get_default_graph()) quantizer.compress()
``` ```
PyTorch 代码 PyTorch 代码
```python ```python
from nni.compressors.torch import DoReFaQuantizer from nni.compressors.torch import DoReFaQuantizer
config_list = [{ 'q_bits': 8, 'op_types': 'default' }] config_list = [{ 'q_bits': 8, 'op_types': 'default' }]
quantizer = DoReFaQuantizer(config_list) quantizer = DoReFaQuantizer(model, config_list)
quantizer(model) quantizer.compress()
``` ```
查看示例进一步了解 查看示例进一步了解
#### QAT Quantizer 的用户配置 #### DoReFa Quantizer 的用户配置
* **q_bits:** 指定需要被量化的位数。 * **q_bits:** 指定需要被量化的位数。
NNI Compressor 中的 SlimPruner
===
## 1. Slim Pruner
SlimPruner 是一种结构化的修剪算法,通过修剪卷积层后对应的 BN 层相应的缩放因子来修剪通道。
['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 层的缩放因子在修剪时,是**全局排序的**,因此稀疏模型能自动找到给定的稀疏度。
## 2. 用法
PyTorch 代码
```
from nni.compression.torch import SlimPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['BatchNorm2d'] }]
pruner = SlimPruner(model, config_list)
pruner.compress()
```
#### Filter Pruner 的用户配置
- **sparsity:**,指定压缩的稀疏度。
- **op_types:** 在 Slim Pruner 中仅支持 BatchNorm2d。
## 3. 实验
我们实现了 ['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/)
## 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 个最好的特征。
## GradientFeatureSelector
GradinetFeatureSelector 的算法来源于 ["Feature Gradients: Scalable Feature Selection via Discrete Relaxation"](https://arxiv.org/pdf/1908.10382.pdf)
GradientFeatureSelector,基于梯度搜索算法的特征选择。
1) 该方法扩展了一个近期的结果,即在亚线性数据中通过展示计算能迭代的学习(即,在迷你批处理中),在**线性的时间空间中**的特征数量 D 及样本大小 N。
2) 这与在搜索领域的离散到连续的放松一起,可以在非常**大的数据集**上进行**高效、基于梯度**的搜索算法。
3) 最重要的是,此算法能在特征和目标间为 N > D 和 N < D 都找到**高阶相关性**,这与只考虑一种情况和交互式的方法所不同。
### 用法
```python
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)
# 初始化 Selector
fgs = FeatureGradientSelector()
# 拟合数据
fgs.fit(X_train, y_train)
# 获取重要的特征
# 此处会返回重要特征的索引。
print(fgs.get_selected_features())
...
```
也可在 `/examples/feature_engineering/gradient_feature_selector/` 目录找到示例。
**FeatureGradientSelector 构造函数的参数**
* **order** (int, 可选, 默认为 4) - 要包含的交互顺序。 较高的顺序可能会更准确,但会增加运行时间。 12 是允许的顺序的最大值。
* **penatly** (int, 可选, 默认为 1) - 乘以正则项的常数。
* **n_features** (int, 可选, 默认为 None) - 如果为 None,会自动根据搜索来选择特征的数量。 否则,表示要选择的最好特征的数量。
* **max_features** (int, 可选, 默认为 None) - 如果不为 None,会使用 'elbow method' 来确定以 max_features 为上限的特征数量。
* **learning_rate** (float, 可选, 默认为 1e-1) - 学习率
* **init** (*zero, on, off, onhigh, offhigh, 或 sklearn, 可选, 默认为zero*) - 如何初始化向量分数。 默认值为 'zero'。
* **n_epochs** (int, 可选, 默认为 1) - 要运行的 Epoch 数量
* **shuffle** (bool, 可选, 默认为 True) - 在 Epoch 之前需要随机化 "rows"。
* **batch_size** (int, 可选, 默认为 1000) - 一次处理的 "rows" 数量。
* **target_batch_size** (int, 可选, 默认为 1000) - 累计梯度的 "rows" 数量。 当行数过多无法读取到内存中,但估计精度所需。
* **classification** (bool, 可选, 默认为 True) - 如果为 True,为分类问题,否则是回归问题。
* **ordinal** (bool, 可选, 默认为 True) - 如果为 True,是有序的分类。 需要 classification 也为 True。
* **balanced** (bool, 可选, 默认为 True) - 如果为 True,优化中每类的权重都一样,否则需要通过支持来对每类加权。 需要 classification 也为 True。
* **prerocess** (str, 可选, 默认为 'zscore') - 'zscore' 是将数据中心化并归一化的党委方差,'center' 表示仅将数据均值调整到 0。
* **soft_grouping** (bool, 可选, 默认为 True) - 如果为 True,将同一来源的特征分组到一起。 用于支持分组或组内特征的稀疏性。
* **verbose** (int, 可选, 默认为 0) - 控制拟合时的信息详细程度。 设为 0 表示不打印,1 或更大值表示打印详细数量的步骤。
* **device** (str, 可选, 默认为 'cpu') - 'cpu' 表示在 CPU 上运行,'cuda' 表示在 GPU 上运行。 在 GPU 上运行得更快
**`fit` 函数参数要求**
* **X** (数组,必需) - 训练的输入样本,shape = [n_samples, n_features]
* **y** (数组,必需) - 目标值 (分类中为标签,回归中为实数),shape = [n_samples].
* **groups** (数组, 可选, 默认为 None) - 必需选择为一个单元的列的分组。 例如 [0,0,1,2] 指定前两列是组的一部分。 形状是 [n_features]。
**`get_selected_features` 函数参数的要求**
目前, `get_selected_features` 函数没有参数。
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