"...git@developer.sourcefind.cn:gaoqiong/migraphx.git" did not exist on "f8a75f8a73ffcae1b36f216101941d9420972eba"
Unverified Commit 25db55ca authored by kvartet's avatar kvartet Committed by GitHub
Browse files

Update Chinese documents (#3243)

parent 53b565e4
自动调优 SVD(在推荐系统中使用 NNI)
==============================================
本教程中,会首先介绍 GitHub 存储库:`推荐系统 <https://github.com/Microsoft/Recommenders>`__。 它使用 Jupyter Notebook 提供了构建推荐系统的一些示例和实践技巧。 其中大量的模型被广泛的应用于推荐系统中。 为了提供完整的体验,每个示例都通过以下五个关键任务中展示:
* `准备数据 <https://github.com/microsoft/recommenders/tree/master/examples/01_prepare_data>`__\ : 为每个算法准备并读取数据。
* `模型 <https://github.com/Microsoft/Recommenders/blob/master/examples/02_model/README.md>`__\ :使用各种经典的以及深度学习推荐算法,如交替最小二乘法(\ `ALS <https://spark.apache.org/docs/latest/api/python/_modules/pyspark/ml/recommendation.html#ALS>`__\ )或极限深度分解机(\ `xDeepFM <https://arxiv.org/abs/1803.05170>`__\ )。
* `评估 <https://github.com/Microsoft/Recommenders/blob/master/examples/03_evaluate/README.md>`__\ :使用离线指标来评估算法。
* `模型选择和优化 <https://github.com/Microsoft/Recommenders/blob/master/examples/04_model_select_and_optimize/README.md>`__\ :为推荐算法模型调优超参。
* `运营 <https://github.com/Microsoft/Recommenders/blob/master/examples/05_operationalize/README.md>`__\ :在 Azure 的生产环境上运行模型。
在第四项调优模型超参的任务上,NNI 可以发挥作用。 在 NNI 上调优推荐模型的具体示例,采用了 `SVD <https://github.com/Microsoft/Recommenders/blob/master/examples/02_model/surprise_svd_deep_dive.ipynb>`__\ 算法,以及数据集 Movielens100k。 此模型有超过 10 个超参需要调优。
由 Recommenders 提供的 `示例 <https://github.com/Microsoft/Recommenders/blob/master/examples/04_model_select_and_optimize/nni_surprise_svd.ipynb>`__ 中有非常详细的一步步的教程。 其中使用了不同的调优函数,包括 ``Annealing``\ , ``SMAC``\ , ``Random Search``\ , ``TPE``\ , ``Hyperband``\ , ``Metis`` 和 ``Evolution``。 最后比较了不同调优算法的结果。 请参考此 Notebook,来学习如何使用 NNI 调优 SVD 模型,并可以继续使用 NNI 来调优 Recommenders 中的其它模型。
# 使用 NNI 为 SPTAG 自动调参 使用 NNI 为 SPTAG 自动调参
===================================
[SPTAG](https://github.com/microsoft/SPTAG) (Space Partition Tree And Graph) 是大规模向量的最近邻搜索的工具,由[微软研究院MSR](https://www.msra.cn/)[微软必应团队](https://www.bing.com/)联合发布。 `SPTAG <https://github.com/microsoft/SPTAG>`__ (Space Partition Tree And Graph) 是大规模向量的最近邻搜索的工具,由 `微软研究院 (MSR) <https://www.msra.cn/>`__ 和 `微软必应团队 <https://www.bing.com/>`__ 联合发布。
此工具假设样本可以表示为向量,并且能通过 L2 或余弦算法来比较距离。 输入一个查询向量,会返回与其 L2 或余弦距离最小的一组向量。 SPTAG 提供了两种方法:kd-tree 与其的相关近邻图 (SPTAG-KDT),以及平衡 k-means 树与其的相关近邻图 (SPTAG-BKT)。 SPTAG-KDT 在索引构建效率上较好,而 SPTAG-BKT 在搜索高维度数据的精度上较好。 此工具假设样本可以表示为向量,并且能通过 L2 或余弦算法来比较距离。 输入一个查询向量,会返回与其 L2 或余弦距离最小的一组向量。
SPTAG 提供了两种方法:kd-tree 与其的相关近邻图 (SPTAG-KDT),以及平衡 k-means 树与其的相关近邻图 (SPTAG-BKT)。 SPTAG-KDT 在索引构建效率上较好,而 SPTAG-BKT 在搜索高维度数据的精度上较好。
在 SPTAG中,有几十个参数可以根据特定的场景或数据集进行调优。 NNI 是用来自动化调优这些参数的绝佳工具。 SPTAG 的作者尝试了使用 NNI 来进行自动调优,并轻松找到了性能较好的参数组合,并在 SPTAG [文档](https://github.com/microsoft/SPTAG/blob/master/docs/Parameters.md)中进行了分享。 参考此文档了解详细教程。 在 SPTAG中,有几十个参数可以根据特定的场景或数据集进行调优。 NNI 是用来自动化调优这些参数的绝佳工具。 SPTAG 的作者尝试了使用 NNI 来进行自动调优,并轻松找到了性能较好的参数组合,并在 SPTAG `文档 <https://github.com/microsoft/SPTAG/blob/master/docs/Parameters.rst>`__ 中进行了分享。 参考此文档了解详细教程。
\ No newline at end of file
...@@ -8,6 +8,6 @@ NNI 可以应用于各种模型调优任务。 一些最先进的模型搜索算 ...@@ -8,6 +8,6 @@ NNI 可以应用于各种模型调优任务。 一些最先进的模型搜索算
:maxdepth: 1 :maxdepth: 1
SVD 自动调优 <RecommendersSvd> SVD 自动调优 <RecommendersSvd>
NNI 中的 EfficientNet <./TrialExample/EfficientNet> NNI 中的 EfficientNet <../TrialExample/EfficientNet>
用于阅读理解的自动模型架构搜索<../TrialExample/SquadEvolutionExamples> 用于阅读理解的自动模型架构搜索 <../TrialExample/SquadEvolutionExamples>
TPE 的并行优化<ParallelizingTpeSearch> TPE 的并行优化 <ParallelizingTpeSearch>
\ No newline at end of file \ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
自动系统调优 自动系统调优
####################### #######################
数据库、张量算子实现等系统的性能往往需要进行调优,以适应特定的硬件配置、目标工作负载等。 手动调优系统非常复杂,并且通常需要对硬件和工作负载有详细的了解。 NNI 可以使这些任务变得更容易,并帮助系统所有者自动找到系统的最佳配置。 自动系统调优的详细设计思想可以在[这篇文章](https://dl.acm.org/doi/10.1145/3352020.3352031)中找到。 以下是 NNI 可以发挥作用的一些典型案例。 数据库、张量算子实现等系统的性能往往需要进行调优,以适应特定的硬件配置、目标工作负载等。 手动调优系统非常复杂,并且通常需要对硬件和工作负载有详细的了解。 NNI 可以使这些任务变得更容易,并帮助系统所有者自动找到系统的最佳配置。 自动系统调优的详细设计思想可以在 `这篇文章 <https://dl.acm.org/doi/10.1145/3352020.3352031>`__ 中找到。 以下是 NNI 可以发挥作用的一些典型案例。
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
......
...@@ -13,3 +13,4 @@ ...@@ -13,3 +13,4 @@
特征工程<feature_engineering> 特征工程<feature_engineering>
性能测量,比较和分析<perf_compare> 性能测量,比较和分析<perf_compare>
在 Google Colab 中使用 NNI <NNI_colab_support> 在 Google Colab 中使用 NNI <NNI_colab_support>
自动补全 nnictl 命令 <AutoCompletion>
使用 NNI Tuners 自动进行模型压缩
========================================
使用 NNI 能轻松实现自动模型压缩
首先,使用 NNI 压缩模型
---------------------------------
可使用 NNI 轻松压缩模型。 以剪枝为例,可通过 LevelPruner 对预训练模型剪枝:
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
op_type 为 'default' 表示模块类型为 PyTorch 定义在了 :githublink:`default_layers.py <src/sdk/pynni/nni/compression/pytorch/default_layers.py>` 。
因此 ``{ 'sparsity': 0.8, 'op_types': ['default'] }`` 表示 **所有指定 op_types 的层都会被压缩到 0.8 的稀疏度**。 当调用 ``pruner.compress()`` 时,模型会通过掩码进行压缩。随后还可以微调模型,此时 **被剪除的权重不会被更新**。
然后,进行自动化
-------------------------
前面的示例人工选择了 LevelPruner,并对所有层使用了相同的稀疏度,显然这不是最佳方法,因为不同层会有不同的冗余度。 每层的稀疏度都应该仔细调整,以便减少模型性能的下降,可通过 NNI Tuner 来完成。
首先需要设计搜索空间,这里使用了嵌套的搜索空间,其中包含了选择的剪枝函数以及需要优化稀疏度的层。
.. code-block:: json
{
"prune_method": {
"_type": "choice",
"_value": [
{
"_name": "agp",
"conv0_sparsity": {
"_type": "uniform",
"_value": [
0.1,
0.9
]
},
"conv1_sparsity": {
"_type": "uniform",
"_value": [
0.1,
0.9
]
},
},
{
"_name": "level",
"conv0_sparsity": {
"_type": "uniform",
"_value": [
0.1,
0.9
]
},
"conv1_sparsity": {
"_type": "uniform",
"_value": [
0.01,
0.9
]
},
}
]
}
}
然后需要修改几行代码。
.. code-block:: python
import nni
from nni.algorithms.compression.pytorch.pruning import *
params = nni.get_parameters()
conv0_sparsity = params['prune_method']['conv0_sparsity']
conv1_sparsity = params['prune_method']['conv1_sparsity']
# 如果需要约束总稀疏度,则应缩放原始稀疏度
config_list_level = [{ 'sparsity': conv0_sparsity, 'op_name': 'conv0' },
{ 'sparsity': conv1_sparsity, 'op_name': 'conv1' }]
config_list_agp = [{'initial_sparsity': 0, 'final_sparsity': conv0_sparsity,
'start_epoch': 0, 'end_epoch': 3,
'frequency': 1,'op_name': 'conv0' },
{'initial_sparsity': 0, 'final_sparsity': conv1_sparsity,
'start_epoch': 0, 'end_epoch': 3,
'frequency': 1,'op_name': 'conv1' },]
PRUNERS = {'level':LevelPruner(model, config_list_level), 'agp':AGPPruner(model, config_list_agp)}
pruner = PRUNERS(params['prune_method']['_name'])
pruner.compress()
... # 微调
acc = evaluate(model) # evaluation
nni.report_final_results(acc)
最后,定义任务,并使用任务来自动修剪层稀疏度。
.. code-block:: yaml
authorName: default
experimentName: Auto_Compression
trialConcurrency: 2
maxExecDuration: 100h
maxTrialNum: 500
#choice: local, remote, pai
trainingServicePlatform: local
#choice: true, false
useAnnotation: False
searchSpacePath: search_space.json
tuner:
#choice: TPE, Random, Anneal...
builtinTunerName: TPE
classArgs:
#choice: maximize, minimize
optimize_mode: maximize
trial:
command: bash run_prune.sh
codeDir: .
gpuNum: 1
模型压缩 Python API 参考
=============================================
.. contents::
灵敏度工具
---------------------
.. autoclass:: nni.compression.pytorch.utils.sensitivity_analysis.SensitivityAnalysis
:members:
拓扑结构工具
------------------
.. autoclass:: nni.compression.pytorch.utils.shape_dependency.ChannelDependency
:members:
.. autoclass:: nni.compression.pytorch.utils.shape_dependency.GroupDependency
:members:
.. autoclass:: nni.compression.pytorch.utils.mask_conflict.CatMaskPadding
:members:
.. autoclass:: nni.compression.pytorch.utils.mask_conflict.GroupMaskConflict
:members:
.. autoclass:: nni.compression.pytorch.utils.mask_conflict.ChannelMaskConflict
:members:
模型 FLOPs 和参数计数器
------------------------------
.. autofunction:: nni.compression.pytorch.utils.counter.count_flops_params
# 模型压缩分析工具 模型压缩分析工具
====================================
```eval_rst
.. contents:: .. contents::
```
NNI 提供了几种易于使用的工具,在压缩时用于分析模型。 NNI 提供了几种易于使用的工具,在压缩时用于分析模型。
## 灵敏度分析 灵敏度分析
首先提供的是灵敏度分析工具 (**SensitivityAnalysis**),用于分析模型中每个卷积层的灵敏度。 具体来说,SensitiviyAnalysis 会为每层逐渐剪枝,同时测试模型的精度变化。 注意,敏感度分析一次只会对一层进行剪枝,其它层会使用它们原始的权重。 根据不同稀疏度下不同卷积层的精度,可以很容易的找出模型精度对哪些层的变化更敏感。 --------------------
### 用法 首先提供的是灵敏度分析工具 ( **SensitivityAnalysis** ),用于分析模型中每个卷积层的灵敏度。 具体来说,SensitiviyAnalysis 会为每层逐渐剪枝,同时测试模型的精度变化。 注意,敏感度分析一次只会对一层进行剪枝,其它层会使用它们原始的权重。 根据不同稀疏度下不同卷积层的精度,可以很容易的找出模型精度对哪些层的变化更敏感。
用法
^^^^^
下列代码是 SensitivityAnalysis 的基本用法。 下列代码是 SensitivityAnalysis 的基本用法。
```python
from nni.compression.torch.utils.sensitivity_analysis import SensitivityAnalysis .. code-block:: python
def val(model): from nni.compression.pytorch.utils.sensitivity_analysis import SensitivityAnalysis
model.eval()
total = 0 def val(model):
correct = 0 model.eval()
with torch.no_grad(): total = 0
for batchid, (data, label) in enumerate(val_loader): correct = 0
data, label = data.cuda(), label.cuda() with torch.no_grad():
out = model(data) for batchid, (data, label) in enumerate(val_loader):
_, predicted = out.max(1) data, label = data.cuda(), label.cuda()
total += data.size(0) out = model(data)
correct += predicted.eq(label).sum().item() _, predicted = out.max(1)
return correct / total total += data.size(0)
correct += predicted.eq(label).sum().item()
s_analyzer = SensitivityAnalysis(model=net, val_func=val) return correct / total
sensitivity = s_analyzer.analysis(val_args=[net])
os.makedir(outdir) s_analyzer = SensitivityAnalysis(model=net, val_func=val)
s_analyzer.export(os.path.join(outdir, filename)) sensitivity = s_analyzer.analysis(val_args=[net])
``` os.makedir(outdir)
s_analyzer.export(os.path.join(outdir, filename))
SensitivityAnalysis 的两个重要参数是 `model`, 和 `val_func``model` 是要分析的神经网络,`val_func` 是返回验证数据集的精度、损失或其它指标的验证函数。 根据不同的场景,可能需要不同的方法来计算损失和精度,因此用户需要定义能返回模型精度、损失的函数,并传给 SensitivityAnalysis。 上面的示例也展示了如何用 SensitivityAnalysis 将敏感度结果导出为 csv 文件。
SensitivityAnalysis 的两个重要参数是 ``model`` 和 ``val_func``。 ``model`` 是要分析的神经网络,``val_func`` 是返回验证数据集的精度、损失或其它指标的验证函数。 根据不同的场景,可能需要不同的方法来计算损失和精度,因此用户需要定义能返回模型精度、损失的函数,并传给 SensitivityAnalysis。
除此之外,还可以使用可选参数 `sparsities` 来为每一层设置稀疏度值。 上面的示例也展示了如何用 SensitivityAnalysis 将敏感度结果导出为 csv 文件。
```python
s_analyzer = SensitivityAnalysis(model=net, val_func=val, sparsities=[0.25, 0.5, 0.75]) 除此之外,还可以使用可选参数 ``sparsities`` 来为每一层设置稀疏度值。
```
.. code-block:: python
s_analyzer = SensitivityAnalysis(model=net, val_func=val, sparsities=[0.25, 0.5, 0.75])
SensitivityAnalysis 会为每一层逐渐剪枝 25% 50% 75% 的权重,并同时记录模型精度 (SensitivityAnalysis 一次只修建一层,其他层会使用原始权重)。 如果没有设置稀疏度,SensitivityAnalysis 会将 numpy.arange(0.1, 1.0, 0.1) 作为默认的稀疏度值。 SensitivityAnalysis 会为每一层逐渐剪枝 25% 50% 75% 的权重,并同时记录模型精度 (SensitivityAnalysis 一次只修建一层,其他层会使用原始权重)。 如果没有设置稀疏度,SensitivityAnalysis 会将 numpy.arange(0.1, 1.0, 0.1) 作为默认的稀疏度值。
还可以通过 early_stop_mode 和 early_stop_value 选项来加快灵敏度分析。 默认情况下,SensitivityAnalysis 会为每一层测试所有的稀疏度值下的精度。 而设置了 early_stop_mode 和 early_stop_value 后,当精度或损失值到了 early_stop_value 所设置的阈值时,会停止灵敏度分析。 支持的提前终止模式包括:minimize, maximize, dropped, raised。 还可以通过 early_stop_mode 和 early_stop_value 选项来加快灵敏度分析。 默认情况下,SensitivityAnalysis 会为每一层测试所有的稀疏度值下的精度。 而设置了 early_stop_mode 和 early_stop_value 后,当精度或损失值到了 early_stop_value 所设置的阈值时,会停止灵敏度分析。 支持的提前终止模式包括:minimize, maximize, dropped, raised。
minimize: 当 val_func 的返回值低于 `early_stop_value` 时,会停止分析。 minimize: 当 val_func 的返回值低于 ``early_stop_value`` 时,会停止分析。
maximize: 当 val_func 的返回值大于 ``early_stop_value`` 时,会停止分析。
dropped: 当验证指标下降 ``early_stop_value`` 时,会停止分析。
raised: 当验证指标增加 ``early_stop_value`` 时,会停止分析。
.. code-block:: python
s_analyzer = SensitivityAnalysis(model=net, val_func=val, sparsities=[0.25, 0.5, 0.75], early_stop_mode='dropped', early_stop_value=0.1)
如果只想分析部分卷积层,可在分析函数中通过 ``specified_layers`` 指定。 ``specified_layers`` 是卷积层的 Pytorch 模块名称。 例如:
.. code-block:: python
sensitivity = s_analyzer.analysis(val_args=[net], specified_layers=['Conv1'])
maximize: 当 val_func 的返回值大于 `early_stop_value` 时,会停止分析 在此例中,只会分析 ``Conv1`` 层。 另外,也可以通过并行启动多个进程,将同一个模型的不同层分给每个进程来加速
dropped: 当验证指标下降 `early_stop_value` 时,会停止分析。 输出示例
^^^^^^^^^^^^^^
raised: 当验证指标增加 `early_stop_value` 时,会停止分析。 下面是从 SensitivityAnalysis 中导出的 csv 文件示例。 第一行由 'layername' 和稀疏度值的列表组成。 稀疏度值表示 SensitivityAnalysis 为每一层剪枝的权重比例。 每行表示某层在不同稀疏度下的模型精度。 注意,根据 early_stop 选项,
某些层可能不会有所有稀疏度下的精度或损失值。比如,精度下降的值超过了定义的阈值。
```python .. code-block:: bash
s_analyzer = SensitivityAnalysis(model=net, val_func=val, sparsities=[0.25, 0.5, 0.75], early_stop_mode='dropped', early_stop_value=0.1)
```
如果只想分析部分卷积层,可在分析函数中通过 `specified_layers` 指定。 `specified_layers` 是卷积层的 Pytorch 模块名称。 例如:
```python
sensitivity = s_analyzer.analysis(val_args=[net], specified_layers=['Conv1'])
```
在此例中,只会分析 `Conv1` 层。 另外,也可以通过并行启动多个进程,将同一个模型的不同层分给每个进程来加速。
layername,0.05,0.1,0.2,0.3,0.4,0.5,0.7,0.85,0.95
features.0,0.54566,0.46308,0.06978,0.0374,0.03024,0.01512,0.00866,0.00492,0.00184
features.3,0.54878,0.51184,0.37978,0.19814,0.07178,0.02114,0.00438,0.00442,0.00142
features.6,0.55128,0.53566,0.4887,0.4167,0.31178,0.19152,0.08612,0.01258,0.00236
features.8,0.55696,0.54194,0.48892,0.42986,0.33048,0.2266,0.09566,0.02348,0.0056
features.10,0.55468,0.5394,0.49576,0.4291,0.3591,0.28138,0.14256,0.05446,0.01578
### 输出示例 拓扑结构分析
下面是从 SensitivityAnalysis 中导出的 csv 文件示例。 第一行由 'layername' 和稀疏度值的列表组成。 稀疏度值表示 SensitivityAnalysis 为每一层剪枝的权重比例。 每行表示某层在不同稀疏度下的模型精度。 注意,根据 early_stop 选项,某些层可能不会有所有稀疏度下的精度或损失值。比如,精度下降的值超过了定义的阈值。 -----------------
```
layername,0.05,0.1,0.2,0.3,0.4,0.5,0.7,0.85,0.95
features.0,0.54566,0.46308,0.06978,0.0374,0.03024,0.01512,0.00866,0.00492,0.00184
features.3,0.54878,0.51184,0.37978,0.19814,0.07178,0.02114,0.00438,0.00442,0.00142
features.6,0.55128,0.53566,0.4887,0.4167,0.31178,0.19152,0.08612,0.01258,0.00236
features.8,0.55696,0.54194,0.48892,0.42986,0.33048,0.2266,0.09566,0.02348,0.0056
features.10,0.55468,0.5394,0.49576,0.4291,0.3591,0.28138,0.14256,0.05446,0.01578
```
## 拓扑结构分析
NNI 还提供了在模型压缩过程中,进行模型拓扑分析的工具。 这些工具可帮助用户更好的压缩模型。 压缩模型时,因为网络结构的复杂性,经常需要花时间检查压缩配置是否合理。 因此,NNI 提供了这些工具用于模型拓扑分析,来减轻用户负担。 NNI 还提供了在模型压缩过程中,进行模型拓扑分析的工具。 这些工具可帮助用户更好的压缩模型。 压缩模型时,因为网络结构的复杂性,经常需要花时间检查压缩配置是否合理。 因此,NNI 提供了这些工具用于模型拓扑分析,来减轻用户负担。
### ChannelDependency 通道依赖
复杂模型中还会有残差或连接的操作。 对这些模型剪枝时,需要小心卷积层之间通道数量的依赖关系。 以 resnet18 中残差模块为例。 `layer2.0.conv2``layer2.0.downsample.0` 层输出的特征会加到一起,所以 `layer2.0.conv2``layer2.0.downsample.0` 的输出通道数量必须一样,否则会有 Tensor 形状的冲突。 ^^^^^^^^^^^^^^^^^
![](../../img/channel_dependency_example.jpg) 复杂模型中还会有残差或连接的操作。 对这些模型剪枝时,需要小心卷积层之间通道数量的依赖关系。 以 resnet18 中残差模块为例。 ``layer2.0.conv2`` 和 ``layer2.0.downsample.0`` 层输出的特征会加到一起,所以 ``layer2.0.conv2`` 和 ``layer2.0.downsample.0`` 的输出通道数量必须一样,否则会有 Tensor 形状的冲突。
.. image:: ../../img/channel_dependency_example.jpg
:target: ../../img/channel_dependency_example.jpg
:alt:
如果有通道依赖的图层,被分配了不同的稀疏度 (此处仅讨论 L1FilterPruner/L2FilterPruner 的结构化剪枝),就会造成形状冲突。 即使剪枝后的掩码模型也能正常使用,剪枝后的模型也因为模型在加和、连接这些层的输出时有冲突,不能在设备上加速。 此工具可用于查找有通道依赖的层,帮助更好的剪枝模型。 如果有通道依赖的图层,被分配了不同的稀疏度 (此处仅讨论 L1FilterPruner/L2FilterPruner 的结构化剪枝),就会造成形状冲突。 即使剪枝后的掩码模型也能正常使用,剪枝后的模型也因为模型在加和、连接这些层的输出时有冲突,不能在设备上加速。 此工具可用于查找有通道依赖的层,帮助更好的剪枝模型。
#### 用法 用法
```python ^^^^^
from nni.compression.torch.utils.shape_dependency import ChannelDependency
data = torch.ones(1, 3, 224, 224).cuda() .. code-block:: python
channel_depen = ChannelDependency(net, data)
channel_depen.export('dependency.csv') from nni.compression.pytorch.utils.shape_dependency import ChannelDependency
``` data = torch.ones(1, 3, 224, 224).cuda()
channel_depen = ChannelDependency(net, data)
#### Output Example channel_depen.export('dependency.csv')
下列代码是 由 ChannelDependency 导出的 torchvision.models.resnet18 示例。 每行上,有相互依赖的输出通道。 例如,layer1.1.conv2, conv1 和 layer1.0.conv2 相互间有输出依赖。这表示这三个层的输出通道(滤波器)数量需要一致,否则模型会产生形状冲突。
``` 输出示例
Dependency Set,Convolutional Layers ^^^^^^^^^^^^^^
Set 1,layer1.1.conv2,layer1.0.conv2,conv1
Set 2,layer1.0.conv1 下列代码是 由 ChannelDependency 导出的 torchvision.models.resnet18 示例。 每行上,有相互依赖的输出通道。 例如,layer1.1.conv2, conv1 和 layer1.0.conv2 相互间有输出依赖。这表示这三个层的输出通道(滤波器)数量需要一致,否则模型会产生形状冲突。
Set 3,layer1.1.conv1
Set 4,layer2.0.conv1 .. code-block:: bash
Set 5,layer2.1.conv2,layer2.0.conv2,layer2.0.downsample.0
Set 6,layer2.1.conv1 Dependency Set,Convolutional Layers
Set 7,layer3.0.conv1 Set 1,layer1.1.conv2,layer1.0.conv2,conv1
Set 8,layer3.0.downsample.0,layer3.1.conv2,layer3.0.conv2 Set 2,layer1.0.conv1
Set 9,layer3.1.conv1 Set 3,layer1.1.conv1
Set 10,layer4.0.conv1 Set 4,layer2.0.conv1
Set 11,layer4.0.downsample.0,layer4.1.conv2,layer4.0.conv2 Set 5,layer2.1.conv2,layer2.0.conv2,layer2.0.downsample.0
Set 12,layer4.1.conv1 Set 6,layer2.1.conv1
``` Set 7,layer3.0.conv1
Set 8,layer3.0.downsample.0,layer3.1.conv2,layer3.0.conv2
### 掩码冲突 Set 9,layer3.1.conv1
Set 10,layer4.0.conv1
Set 11,layer4.0.downsample.0,layer4.1.conv2,layer4.0.conv2
Set 12,layer4.1.conv1
掩码冲突
^^^^^^^^^^^^
当不同层的掩码有冲突时,(例如,为通道依赖的层设置了不同的稀疏度),可通过 MaskConflict 来修复。 即,MaskConflict 可加载由 (L1FilterPruner, 等) 导出的掩码,并检查是否有掩码冲突。如果有 MaskConflict 会将冲突的掩码设置为相同的值。 当不同层的掩码有冲突时,(例如,为通道依赖的层设置了不同的稀疏度),可通过 MaskConflict 来修复。 即,MaskConflict 可加载由 (L1FilterPruner, 等) 导出的掩码,并检查是否有掩码冲突。如果有 MaskConflict 会将冲突的掩码设置为相同的值。
``` .. code-block:: bash
from nni.compression.torch.utils.mask_conflict import fix_mask_conflict
fixed_mask = fix_mask_conflict('./resnet18_mask', net, data) from nni.compression.pytorch.utils.mask_conflict import fix_mask_conflict
``` fixed_mask = fix_mask_conflict('./resnet18_mask', net, data)
模型 FLOPs 和参数计数器
------------------------------
NNI 提供了模型计数器,用于计算模型的 FLOPs 和参数。 此计数器支持计算没有掩码模型的 FLOPs、参数,也可以计算有掩码模型的 FLOPs、参数,这有助于在模型压缩过程中检查模型的复杂度。 注意,对于结构化的剪枝,仅根据掩码来标识保留的滤波器,不会考虑剪枝的输入通道,因此,计算出的 FLOPs 会比实际数值要大(即,模型加速后的计算值)。
我们支持两种模式来收集模块信息。 第一种是 ``default`` 模式,它只采集卷积操作和线性操作的信息。 第二种是 ``full`` 模式,它还会收集其他操作的信息。 用户可以轻松地使用我们收集的 ``results`` 进行进一步的分析。
用法
^^^^^
.. code-block:: python
from nni.compression.pytorch.utils.counter import count_flops_params
# 给定的输入大小 (1, 1, 28, 28)
flops, params, results = count_flops_params(model, (1, 1, 28, 28))
### 模型 FLOPs 和参数计数器 # 给定大小为 (1, 1, 28, 28) 的张量,切换成 full 模式
NNI 提供了模型计数器,用于计算模型的 FLOPs 和参数。 此计数器支持计算没有掩码模型的 FLOPs、参数,也可以计算有掩码模型的 FLOPs、参数,这有助于在模型压缩过程中检查模型的复杂度。 注意,对于结构化的剪枝,仅根据掩码来标识保留的滤波器,不会考虑剪枝的输入通道,因此,计算出的 FLOPs 会比实际数值要大(即,模型加速后的计算值)。 x = torch.randn(1, 1, 28, 28)
### 用法 flops, params, results = count_flops_params(model, (x,) mode='full') # tuple of tensor as input
```
from nni.compression.torch.utils.counter import count_flops_params
# 给定输入大小 (1, 1, 28, 28) # 格式化输出大小为M(例如,10^6)
flops, params = count_flops_params(model, (1, 1, 28, 28)) print(f'FLOPs: {flops/1e6:.3f}M, Params: {params/1e6:.3f}M)
# 将输出大小格式化为 M (例如, 10^6) print(results)
print(f'FLOPs: {flops/1e6:.3f}M, Params: {params/1e6:.3f}M) {
``` 'conv': {'flops': [60], 'params': [20], 'weight_size': [(5, 3, 1, 1)], 'input_size': [(1, 3, 2, 2)], 'output_size': [(1, 5, 2, 2)], 'module_type': ['Conv2d']},
\ No newline at end of file 'conv2': {'flops': [100], 'params': [30], 'weight_size': [(5, 5, 1, 1)], 'input_size': [(1, 5, 2, 2)], 'output_size': [(1, 5, 2, 2)], 'module_type': ['Conv2d']}
}
自定义压缩算法
===================================
.. contents::
为了简化实现新压缩算法的过程,NNI 设计了简单灵活,同时支持剪枝和量化的接口。 首先会介绍如何自定义新的剪枝算法,然后介绍如何自定义新的量化算法。
**重要说明**,为了更好的理解如何定制新的剪枝、量化算法,应先了解 NNI 中支持各种剪枝算法的框架。 参考 `模型压缩框架概述 </Compression/Framework.html>`__。
自定义剪枝算法
---------------------------------
要实现新的剪枝算法,需要实现 ``权重掩码`` 类,它是 ``WeightMasker`` 的子类,以及 ``Pruner`` 类,它是 ``Pruner`` 的子类。
``权重掩码`` 的实现如下:
.. code-block:: 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 提供的 :githublink:`权重掩码 <src/sdk/pynni/nni/compression/pytorch/pruning/structured_pruning.py>` 来实现自己的权重掩码。
基础的 ``Pruner`` 如下所示:
.. code-block:: 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 提供的 :githublink:`Pruner <src/sdk/pynni/nni/compression/pytorch/pruning/one_shot.py>` 来实现自己的 Pruner。
----
自定义量化算法
--------------------------------------
要实现新的量化算法,需要继承 ``nni.compression.pytorch.Quantizer``。 然后,根据算法逻辑来重写成员函数。 需要重载的成员函数是 ``quantize_weight``。 ``quantize_weight`` 直接返回量化后的权重,而不是 mask。这是因为对于量化算法,量化后的权重不能通过应用 mask 来获得。
.. code-block:: python
from nni.compression.pytorch 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`。
参数
----------
weight : Tensor
要被量化的权重
config : dict
输出量化的配置
"""
# 此处逻辑生成 `new_weight`
return new_weight
def quantize_output(self, output, config, **kwargs):
"""
重载此方法量化输入
此方法挂载于模型的 `:meth:`forward`。
参数量
----------
output : Tensor
需要被量化的输出
config : dict
输出量化的配置
"""
# 生成 `new_output` 的代码
return new_output
def quantize_input(self, *inputs, config, **kwargs):
"""
重载此方法量化输入
此方法挂载于模型的 :meth:`forward`。
参数量
----------
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 函数:
.. code-block:: python
from nni.compression.pytorch.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。
*编写中*……
滤波器剪枝的依赖感知模式
========================================
目前,我们有几种针对卷积层的滤波剪枝算法,分别为:FPGM Pruner, L1Filter Pruner, L2Filter Pruner, Activation APoZ Rank Filter Pruner, Activation Mean Rank Filter Pruner, Taylor FO On Weight Pruner。 在这些滤波器剪枝算法中,剪枝器将分别对每个卷积层进行剪枝。 在裁剪卷积层时,算法会根据一些特定的规则(如l1-norm)量化每个滤波器的重要性,并裁剪不太重要的滤波器。
就像 `dependency analysis utils <./CompressionUtils.md>`__ 显示,如果将两个卷积层(conv1,conv2)的输出通道相加,这两个卷积层之间将具有通道依赖关系(更多详细信息参见 `Compression Utils <./CompressionUtils.rst>`__\ )。 以下图为例。
.. image:: ../../img/mask_conflict.jpg
:target: ../../img/mask_conflict.jpg
:alt:
如果我们为 conv1 修剪前50%的输出通道(滤波器),并为 conv2 修剪后50%的输出通道, 虽然这两个层都删减了50%的滤波器,但加速模块仍然需要添加零来对齐输出通道。 在这种情况下,我们无法从模型剪枝中获得速度效益。
为了更好地发挥模型剪枝的速度优势,我们为滤波器剪枝添加了一个依赖感知模式。 在依赖感知模式下,剪枝器不仅根据每个滤波器的l1范数对模型进行剪枝,而且还会基于整个网络结构的拓扑结构对模型进行剪枝。
在依赖关系感知模式下(``dependency_aware`` 设置为``True``),剪枝器将尝试为彼此具有通道依赖关系的层修剪相同的输出通道,如下图所示。
.. image:: ../../img/dependency-aware.jpg
:target: ../../img/dependency-aware.jpg
:alt:
以 L1Filter Pruner 的依赖感知模式为例。 具体来说,剪枝器将为每个通道计算依赖集中所有层的L1范数和。 显然,最终可以从这个依赖集中删除的通道数量是由这个依赖集中,层的最小稀疏性决定的(记作 ``min_sparsity``)。 根据每个通道的 L1 范数和,剪枝器将对所有层修剪相同的 ``min_sparsity`` 通道。 接下来,剪枝器将根据每个卷积层自己的 L1 范数为它们额外修剪 ``sparsity`` - ``min_sparsity`` 通道。 例如,假设 ``conv1``、``conv2`` 的输出通道相加,分别配置稀疏度为0.3和0.2。 在这种情况下,``依赖感知的剪枝器`` 将会
.. code-block:: bash
- 首先,根据 `conv1` 和 `conv2` 的 L1 范数和,修剪 `conv1` 和 `conv2` 相同的20%通道。
- 其次,剪枝器将根据 `conv1` 每个通道的 L1 范数为 `conv1` 额外修剪10%的通道。
此外,对于具有多个滤波器组的卷积层,``依赖感知剪枝器`` 也将尝试为每个滤波器组修剪相同数量的通道。 总体而言,该剪枝器将根据每个滤波器的 L1 范数对模型进行剪枝,并尝试满足拓扑约束(通道依赖等),以提高加速过程后的最终速度增益。
在依赖关系感知模式下,剪枝器将为模型修剪提供更好的速度增益。
用法
-----
在本节中,我们将说明如何使用滤波剪枝器的依赖感知模式。 目前,我们有几种 One-Shot 的 Pruner,比如 FPGM Pruner, L1Filter Pruner, L2Filter Pruner, Activation APoZ Rank Filter Pruner, Activation Mean Rank Filter Pruner, Taylor FO On Weight Pruner 支持依赖感知模式。
以为 ``L1FilterPruner`` 启动依赖感知模式为例:
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import L1FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'] }]
# dummy_input 对于依赖感知模式是必需的
dummy_input = torch.ones(1, 3, 224, 224).cuda()
pruner = L1FilterPruner(model, config_list, dependency_aware=True, dummy_input=dummy_input)
# 对于 L2FilterPruner
# pruner = L2FilterPruner(model, config_list, dependency_aware=True, dummy_input=dummy_input)
# 对于 FPGMPruner
# pruner = FPGMPruner(model, config_list, dependency_aware=True, dummy_input=dummy_input)
# 对于 ActivationAPoZRankFilterPruner
# pruner = ActivationAPoZRankFilterPruner(model, config_list, statistics_batch_num=1, , dependency_aware=True, dummy_input=dummy_input)
# 对于 ActivationMeanRankFilterPruner
# pruner = ActivationMeanRankFilterPruner(model, config_list, statistics_batch_num=1, dependency_aware=True, dummy_input=dummy_input)
# 对于 TaylorFOWeightFilterPruner
# pruner = TaylorFOWeightFilterPruner(model, config_list, statistics_batch_num=1, dependency_aware=True, dummy_input=dummy_input)
pruner.compress()
评估
----------
为了比较有无依赖感知模式的剪枝器的性能,我们使用 L1FilterPruner 分别在依赖感知模式打开和关闭下修剪 Mobilenet_v2。 为了简化实验,我们将统一剪枝,这意味着我们为模型中的所有卷积层分配相同的稀疏度。
我们在cifar10数据集上训练了Mobilenet_v2模型,并根据此预先训练的检查点对模型进行裁剪。 下图显示了不同剪枝器剪枝后模型的准确率和 FLOPs。
.. image:: ../../img/mobilev2_l1_cifar.jpg
:target: ../../img/mobilev2_l1_cifar.jpg
:alt:
在图中,``依赖感知`` 表示 L1FilterPruner 启用了依赖感知模式。 ``L1 Filter`` 是没有依赖感知模式的普通 ``L1FilterPruner``,而 ``No-Dependency`` 意味着剪枝器只修剪了与其他层没有通道依赖的层。 如图所示,当启用依赖感知模式时,剪枝器可以在相同的浮点运算率下带来更高的精度。
模型压缩框架概述
=======================================
.. contents::
下图展示了模型压缩框架的组件概览。
.. image:: ../../img/compressor_framework.jpg
:target: ../../img/compressor_framework.jpg
:alt:
NNI 模型压缩框架中主要有三个组件/类: ``Compressor``\ , ``Pruner`` ``Quantizer`` 下面会逐个详细介绍:
Compressor
----------
Compressor Pruner Quantizer 的基类,提供了统一的接口,可用同样的方式使用它们。 例如,使用 Pruner
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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()
查看 :githublink:`示例代码 <examples/model_compress>` 了解更多信息。
``Compressor`` 类提供了一些工具函数:
设置包装的属性
^^^^^^^^^^^^^^^^^^^^^
有时,``calc_mask`` 需要保存一些状态数据,可以像 PyTorch module 一样,使用 ``set_wrappers_attribute`` API 来注册属性。 这些缓存会注册到 ``module 包装`` 中。 用户可以通过 ``module 包装``来直接访问这些缓存。
在上述示例中,使用了 ``set_wrappers_attribute`` 类设置缓冲 ``if_calculated``,它用来标识某层的掩码是否已经计算过了。
forward 时收集数据
^^^^^^^^^^^^^^^^^^^^^^^^^^^
有时,需要在 forward 方法中收集数据,例如,需要激活的平均值。 可通过向 module 中添加定制的 Collector 来做到。
.. code-block:: 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 方法运行时调用。
还可这样来移除收集方法:
.. code-block:: python
# 保存 Collector 的标识
collector_id = self.pruner.add_activation_collector(collector)
# Collector 不再需要后,
# 可以通过保存的 Collector 标识来删除
self.pruner.remove_activation_collector(collector_id)
----
Pruner
------
Pruner 接收 ``模型````配置`` ``优化器`` 作为参数。 通过往 ``optimizer.step()`` 上增加回调,在训练过程中根据 ``config_list`` 来对模型剪枝。
Pruner 类是 Compressor 的子类,因此它包含了 Compressor 的所有功能,并添加了剪枝所需要的组件,包括:
权重掩码
^^^^^^^^^^^^^
``权重掩码`` 是剪枝算法的实现,可将由 ``module 包装`` 所包装起来的一层根据稀疏度进行修建。
剪枝模块包装
^^^^^^^^^^^^^^^^^^^^^^
``剪枝 module 的包装`` 包含:
#. 原始的 module
#. ``calc_mask`` 使用的一些缓存
#. 新的 forward 方法,用于在运行原始的 forward 方法前应用掩码。
使用 ``module 包装`` 的原因:
#. 计算掩码所需要的 ``calc_mask`` 方法需要一些缓存,这些缓存需要注册在 ``module 包装`` 里,这样就不需要修改原始的 module
#. 新的 ``forward`` 方法用来在原始 ``forward`` 调用前,将掩码应用到权重上。
剪枝回调
^^^^^^^^^^^^
Pruner 构造时会添加剪枝的回调,用来在 ``optimizer.step()`` 被调用时,调用 Pruner calc_mask
----
Quantizer
---------
Quantizer 也是 ``Compressor`` 的子类,用来通过减少权重或激活值的位宽来压缩模型,这样可以减少模型推理时的计算时间。 它包含:
量化 module 包装
^^^^^^^^^^^^^^^^^^^^^^^^^^^
模型中每个要量化的模块和层,都需要量化包装,它通过提供 ``forward`` 方法来量化原始模型的权重、输入和输出。
量化回调
^^^^^^^^^^^^^^^^^
量化回调会在调用 ``optimizer.step()`` 时设置。
量化相关函数
^^^^^^^^^^^^^^^^^^^^
``Quantizer`` 类为子类提供一下方法来实现量化算法:
.. code-block:: python
class Quantizer(Compressor):
"""
PyTorch 的量化基类
"""
def quantize_weight(self, weight, wrapper, **kwargs):
"""
重载此方法实现权重的量化。
此方法挂载于模型的 :meth:`forward`。
参数量
----------
weight : Tensor
要被量化的权重
wrapper : QuantizerModuleWrapper
原始 module 的包装
"""
raise NotImplementedError('Quantizer must overload quantize_weight()')
def quantize_output(self, output, wrapper, **kwargs):
"""
重载此方法实现输出的量化。
此方法挂载于模型的 :meth:`forward`。
参数量
----------
output : Tensor
需要被量化的输出
wrapper : QuantizerModuleWrapper
原始 module 的包装
"""
raise NotImplementedError('Quantizer must overload quantize_output()')
def quantize_input(self, *inputs, wrapper, **kwargs):
"""
重载此方法量化输入
此方法挂载于模型的 :meth:`forward`。
参数量
----------
inputs : Tensor
需要被量化的张量
wrapper : QuantizerModuleWrapper
原始 module 的包装
"""
raise NotImplementedError('Quantizer must overload quantize_input()')
----
GPU 支持
-----------------
在多 GPU 训练中,缓存和参数会在每次 ``forward`` 方法被调用时,复制到多个 GPU 上。 如果缓存和参数要在 ``forward`` 更新,就需要通过 ``原地`` 更新来提高效率。
因为 ``calc_mask`` 会在 ``optimizer.step`` 方法中的调用,会在 ``forward`` 方法后才被调用,且只会发生在单 GPU 上,因此它天然的就支持多 GPU 的情况。
# 加速掩码的模型 加速掩码的模型
=====================
*此功能处于测试阶段。* *此功能处于测试阶段。*
## 介绍 介绍
------------
剪枝算法通常都用权重掩码来模拟实际的剪枝。 掩码可以用来检查某个剪枝(或稀疏)算法的模型性能,但还没有真正加速。 模型加速才是模型剪枝的最终目标。因此提供了此工具,来帮助基于用户提供的掩码(掩码来自于剪枝算法),将已有模型转换成小模型。 剪枝算法通常都用权重掩码来模拟实际的剪枝。 掩码可以用来
检查某个剪枝(或稀疏)算法的模型性能,但还没有真正加速。
模型加速才是模型剪枝的最终目标。
因此提供了此工具,来帮助基于用户提供的掩码(掩码来自于剪枝算法),
将已有模型转换成小模型。
有两种剪枝算法。 一种是细粒度的剪枝,不改变权重形状,和输入输出的张量。 稀疏内核会被用来加速细粒度剪枝的层。 另一类是粗粒度的剪枝(例如,通道),通常,权重形状,输入输出张量会有所改变。 要加速这类剪枝算法,不需要使用系数内核,只需要用更小的层来替换。 由于开源社区中对稀疏内核的支持还比较有限,当前仅支持粗粒度剪枝,会在将来再支持细粒度的剪枝算法。 有两种剪枝算法。 一种是细粒度的剪枝,不改变权重形状,和输入输出的张量。 稀疏内核会被用来加速细粒度剪枝的层。 另一类是粗粒度的剪枝(例如,通道),通常,权重形状,输入输出张量会有所改变。 要加速这类剪枝算法,不需要使用系数内核,只需要用更小的层来替换。 由于开源社区中对稀疏内核的支持还比较有限,当前仅支持粗粒度剪枝,会在将来再支持细粒度的剪枝算法。
## 设计和实现 设计和实现
-------------------------
为了加速模型,被剪枝的层应该被替换掉,要么为粗粒度掩码使用较小的层,要么用稀疏内核来替换细粒度的掩码。 粗粒度掩码通常会改变权重的形状,或输入输出张量,因此,应该通过形状推断,来检查是否其它未被剪枝的层由于形状变化而需要改变形状。 因此,在设计中,主要有两个步骤:第一,做形状推理,找出所有应该替换的模块;第二,替换模块。 第一步需要模型的拓扑(即连接),我们使用了 `jit.trace` 来获取 PyTorch 的模型图。 为了加速模型,被剪枝的层应该被替换掉,要么为粗粒度掩码使用较小的层,要么用稀疏内核来替换细粒度的掩码。 粗粒度掩码通常会改变权重的形状,或输入输出张量,因此,应该通过形状推断,来检查是否其它未被剪枝的层由于形状变化而需要改变形状。 因此,在设计中,主要有两个步骤:第一,做形状推理,找出所有应该替换的模块;第二,替换模块。 第一步需要模型的拓扑(即连接),我们使用了 ``jit.trace`` 来获取 PyTorch 的模型图。
对于每个模块,要准备四个函数,三个用于形状推理,一个用于模块替换。 三个形状推理函数是:给定权重形状推断输入/输出形状,给定输入形状推断权重/输出形状,给定输出形状推断权重/输入形状。 模块替换功能返回一个较小的新创建的模块。 对于每个模块,要准备四个函数,三个用于形状推理,一个用于模块替换。 三个形状推理函数是:给定权重形状推断输入/输出形状,给定输入形状推断权重/输出形状,给定输出形状推断权重/输入形状。 模块替换功能返回一个较小的新创建的模块。
## 用法 用法
-----
```python
from nni.compression.torch import ModelSpeedup
# model: 要加速的模型
# dummy_input: 模型的示例输入,传给 `jit.trace`
# masks_file: 剪枝算法创建的掩码文件
m_speedup = ModelSpeedup(model, dummy_input.to(device), masks_file)
m_speedup.speedup_model()
dummy_input = dummy_input.to(device)
start = time.time()
out = model(dummy_input)
print('elapsed time: ', time.time() - start)
```
完整示例参考[这里](https://github.com/microsoft/nni/tree/master/examples/model_compress/model_speedup.py)
注意:当前支持 PyTorch 1.3.1 或更高版本。 .. code-block:: python
## 局限性
由于每个模块需要 4 个函数用于形状推理和模块替换,因此工作量较大,当前仅实现了示例所需的函数。 如果要加速自己的模型,但当前不支持,欢迎贡献。
对于 PyTorch,仅提供了替换模块,如果是在 `forward` 中的函数,当前不支持。 一种解决方案是将函数变为 PyTorch 模块。
## 示例的加速结果
实验代码可在[这里](https://github.com/microsoft/nni/tree/master/examples/model_compress/model_speedup.py)找到。
### slim Pruner 示例 from nni.compression.pytorch import ModelSpeedup
# model: 要加速的模型
# dummy_input: 模型的示例输入,传给 `jit.trace`
# masks_file: 剪枝算法创建的掩码文件
m_speedup = ModelSpeedup(model, dummy_input.to(device), masks_file)
m_speedup.speedup_model()
dummy_input = dummy_input.to(device)
start = time.time()
out = model(dummy_input)
print('elapsed time: ', time.time() - start)
在一块 V100 GPU 上, 输入张量:`torch.randn(64, 3, 32, 32)` 完整示例参考 :githublink:`这里 <examples/model_compress/model_speedup.py>`。
| 次数 | 掩码时延 | 加速后的时延 | 注意:当前支持 PyTorch 1.3.1 或更高版本。
| -- | ------- | -------- |
| 1 | 0.01197 | 0.005107 |
| 2 | 0.02019 | 0.008769 |
| 4 | 0.02733 | 0.014809 |
| 8 | 0.04310 | 0.027441 |
| 16 | 0.07731 | 0.05008 |
| 32 | 0.14464 | 0.10027 |
### fpgm Pruner 示例
在 CPU 上, 输入张量:`torch.randn(64, 1, 28, 28)`, 方差较大
| 次数 | 掩码时延 | 加速后的时延 |
| --- | ------- | -------- |
| 1 | 0.01383 | 0.01839 |
| 2 | 0.01167 | 0.003558 |
| 4 | 0.01636 | 0.01088 |
| 40 | 0.14412 | 0.08268 |
| 40 | 1.29385 | 0.14408 |
| 40 | 0.41035 | 0.46162 |
| 400 | 6.29020 | 5.82143 |
### l1filter Pruner 示例
在一块 V100 GPU 上, 输入张量:`torch.randn(64, 3, 32, 32)`
| 次数 | 掩码时延 | 加速后的时延 | 局限性
| -- | ------- | -------- | -----------
| 1 | 0.01026 | 0.003677 |
| 2 | 0.01657 | 0.008161 |
| 4 | 0.02458 | 0.020018 |
| 8 | 0.03498 | 0.025504 |
| 16 | 0.06757 | 0.047523 |
| 32 | 0.10487 | 0.086442 |
### APoZ Pruner 示例 由于每个模块需要 4 个函数用于形状推理和模块替换,因此工作量较大,当前仅实现了示例所需的函数。 如果要加速自己的模型,但当前不支持,欢迎贡献。
在一块 V100 GPU 上, 输入张量:`torch.randn(64, 3, 32, 32)` 对于 PyTorch,仅提供了替换模块,如果是在 ``forward`` 中的函数,当前不支持。 一种解决方案是将函数变为 PyTorch 模块。
示例的加速结果
---------------------------
实验代码在 :githublink:`这里 <examples/model_compress/model_speedup.py>`。
slim Pruner 示例
^^^^^^^^^^^^^^^^^^^
在一块 V100 GPU 上
输入张量: ``torch.randn(64, 3, 32, 32)``
.. list-table::
:header-rows: 1
:widths: auto
* - 次数
- 掩码时延
- 加速后的时延
* - 1
- 0.01197
- 0.005107
* - 2
- 0.02019
- 0.008769
* - 4
- 0.02733
- 0.014809
* - 8
- 0.04310
- 0.027441
* - 16
- 0.07731
- 0.05008
* - 32
- 0.14464
- 0.10027
fpgm Pruner 示例
^^^^^^^^^^^^^^^^^^^
在 cpu 上
输入张量: ``torch.randn(64, 1, 28, 28)``\ ,
方差较大
.. list-table::
:header-rows: 1
:widths: auto
* - 次数
- 掩码时延
- 加速后的时延
* - 1
- 0.01383
- 0.01839
* - 2
- 0.01167
- 0.003558
* - 4
- 0.01636
- 0.01088
* - 40
- 0.14412
- 0.08268
* - 40
- 1.29385
- 0.14408
* - 40
- 0.41035
- 0.46162
* - 400
- 6.29020
- 5.82143
l1filter Pruner 示例
^^^^^^^^^^^^^^^^^^^^^^^
在一块 V100 GPU 上
输入张量: ``torch.randn(64, 3, 32, 32)``
.. list-table::
:header-rows: 1
:widths: auto
* - 次数
- 掩码时延
- 加速后的时延
* - 1
- 0.01026
- 0.003677
* - 2
- 0.01657
- 0.008161
* - 4
- 0.02458
- 0.020018
* - 8
- 0.03498
- 0.025504
* - 16
- 0.06757
- 0.047523
* - 32
- 0.10487
- 0.086442
APoZ Pruner 示例
^^^^^^^^^^^^^^^^^^^
在一块 V100 GPU 上
输入张量: ``torch.randn(64, 3, 32, 32)``
.. list-table::
:header-rows: 1
:widths: auto
* - Times
- Mask Latency
- Speedup Latency
* - 1
- 0.01389
- 0.004208
* - 2
- 0.01628
- 0.008310
* - 4
- 0.02521
- 0.014008
* - 8
- 0.03386
- 0.023923
* - 16
- 0.06042
- 0.046183
* - 32
- 0.12421
- 0.087113
| 次数 | 掩码时延 | 加速后的时延 |
| -- | ------- | -------- |
| 1 | 0.01389 | 0.004208 |
| 2 | 0.01628 | 0.008310 |
| 4 | 0.02521 | 0.014008 |
| 8 | 0.03386 | 0.023923 |
| 16 | 0.06042 | 0.046183 |
| 32 | 0.12421 | 0.087113 |
使用 NNI 进行模型压缩
==========================
.. contents::
随着更多层和节点大型神经网络的使用,降低其存储和计算成本变得至关重要,尤其是对于某些实时应用程序。 模型压缩可用于解决此问题。
NNI 的模型压缩工具包,提供了最先进的模型压缩算法和策略,帮助压缩并加速模型。 NNI 模型压缩支持的主要功能有:
* 支持多种流行的剪枝和量化算法。
* 通过 NNI 强大的自动调优功能,可使用最先进的策略来自动化模型的剪枝和量化过程。
* 加速压缩的模型,使其在推理时有更低的延迟,同时文件也会变小。
* 提供优化且易用的压缩工具,帮助用户深入了解压缩过程和结果。
* 提供简洁的接口,帮助用户实现自己的压缩算法。
* 注意,PyTorch 和 TensorFlow 有统一的 API 接口,当前仅支持 PyTorch 版本,未来会提供 TensorFlow 的支持。
支持的算法
--------------------
包括剪枝和量化算法。
剪枝算法
^^^^^^^^^^^^^^^^^^
剪枝算法通过删除冗余权重或层通道来压缩原始网络,从而降低模型复杂性并解决过拟合问题。
.. list-table::
:header-rows: 1
:widths: auto
* - 名称
- 算法简介
* - `Level Pruner </Compression/Pruner.html#level-pruner>`__
- 根据权重的绝对值,来按比例修剪权重。
* - `AGP Pruner </Compression/Pruner.html#agp-pruner>`__
- 自动的逐步剪枝(是否剪枝的判断:基于对模型剪枝的效果)`参考论文 <https://arxiv.org/abs/1710.01878>`__
* - `Lottery Ticket Pruner </Compression/Pruner.html#lottery-ticket-hypothesis>`__
- "The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks" 提出的剪枝过程。 它会反复修剪模型。 `参考论文 <https://arxiv.org/abs/1803.03635>`__
* - `FPGM Pruner </Compression/Pruner.html#fpgm-pruner>`__
- Filter Pruning via Geometric Median for Deep Convolutional Neural Networks Acceleration `参考论文 <https://arxiv.org/pdf/1811.00250.pdf>`__
* - `L1Filter Pruner </Compression/Pruner.html#l1filter-pruner>`__
- 在卷积层中具有最小 L1 权重规范的剪枝滤波器(用于 Efficient Convnets 的剪枝滤波器) `参考论文 <https://arxiv.org/abs/1608.08710>`__
* - `L2Filter Pruner </Compression/Pruner.html#l2filter-pruner>`__
- 在卷积层中具有最小 L2 权重规范的剪枝滤波器
* - `ActivationAPoZRankFilterPruner </Compression/Pruner.html#activationapozrankfilterpruner>`__
- 基于指标 APoZ(平均百分比零)的剪枝滤波器,该指标测量(卷积)图层激活中零的百分比。 `参考论文 <https://arxiv.org/abs/1607.03250>`__
* - `ActivationMeanRankFilterPruner </Compression/Pruner.html#activationmeanrankfilterpruner>`__
- 基于计算输出激活最小平均值指标的剪枝滤波器
* - `Slim Pruner </Compression/Pruner.html#slim-pruner>`__
- 通过修剪 BN 层中的缩放因子来修剪卷积层中的通道 (Learning Efficient Convolutional Networks through Network Slimming) `参考论文 <https://arxiv.org/abs/1708.06519>`__
* - `TaylorFO Pruner </Compression/Pruner.html#taylorfoweightfilterpruner>`__
- 基于一阶泰勒展开的权重对滤波器剪枝 (Importance Estimation for Neural Network Pruning) `参考论文 <http://jankautz.com/publications/Importance4NNPruning_CVPR19.pdf>`__
* - `ADMM Pruner </Compression/Pruner.html#admm-pruner>`__
- 基于 ADMM 优化技术的剪枝 `参考论文 <https://arxiv.org/abs/1804.03294>`__
* - `NetAdapt Pruner </Compression/Pruner.html#netadapt-pruner>`__
- 在满足计算资源预算的情况下,对预训练的网络迭代剪枝 `参考论文 <https://arxiv.org/abs/1804.03230>`__
* - `SimulatedAnnealing Pruner </Compression/Pruner.html#simulatedannealing-pruner>`__
- 通过启发式的模拟退火算法进行自动剪枝 `参考论文 <https://arxiv.org/abs/1907.03141>`__
* - `AutoCompress Pruner </Compression/Pruner.html#autocompress-pruner>`__
- 通过迭代调用 SimulatedAnnealing Pruner 和 ADMM Pruner 进行自动剪枝 `参考论文 - <https://arxiv.org/abs/1907.03141>`__
* - `AMC Pruner </Compression/Pruner.html#amc-pruner>`__
- AMC:移动设备的模型压缩和加速 `参考论文 <https://arxiv.org/pdf/1802.03494.pdf>`__
参考此 `基准测试 <../CommunitySharings/ModelCompressionComparison.rst>`__ 来查看这些剪枝器在一些基准问题上的表现。
量化算法
^^^^^^^^^^^^^^^^^^^^^^^
量化算法通过减少表示权重或激活所需的精度位数来压缩原始网络,这可以减少计算和推理时间。
.. list-table::
:header-rows: 1
:widths: auto
* - 名称
- 算法简介
* - `Naive Quantizer </Compression/Quantizer.html#naive-quantizer>`__
- 默认将权重量化为 8 位
* - `QAT Quantizer </Compression/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 </Compression/Quantizer.html#dorefa-quantizer>`__
- DoReFa-Net: 通过低位宽的梯度算法来训练低位宽的卷积神经网络。 `参考论文 <https://arxiv.org/abs/1606.06160>`__
* - `BNN Quantizer </Compression/Quantizer.html#bnn-quantizer>`__
- 二进制神经网络:使用权重和激活限制为 +1 或 -1 的深度神经网络。 `参考论文 <https://arxiv.org/abs/1602.02830>`__
自动模型压缩
---------------------------
有时,给定的目标压缩率很难通过一次压缩就得到最好的结果。 自动模型压缩算法,通常需要通过对不同层采用不同的稀疏度来探索可压缩的空间。 NNI 提供了这样的算法,来帮助用户在模型中为每一层指定压缩度。 此外,还可利用 NNI 的自动调参功能来自动的压缩模型。 详细文档参考 `这里 <./AutoPruningUsingTuners.rst>`__。
模型加速
-------------
模型压缩的目的是减少推理延迟和模型大小。 但现有的模型压缩算法主要通过模拟的方法来检查压缩模型性能(如精度)。例如,剪枝算法中使用掩码,而量化算法中量化值仍然是以 32 位浮点数来存储。 只要给出这些算法产生的掩码和量化位,NNI 可真正的加速模型。 模型加速的详细文档参考 `这里 <./ModelSpeedup.rst>`__。
压缩工具
---------------------
压缩工具包括了一些有用的工具,能帮助用户理解并分析要压缩的模型。 例如,可检查每层对剪枝的敏感度。 可很容易的计算模型的 FLOPs 和参数数量。 `点击这里 <./CompressionUtils.rst>`__,查看压缩工具的完整列表。
自定义压缩算法
-----------------------------------------
NNI 模型压缩提供了简洁的接口,用于自定义新的压缩算法。 接口的设计理念是,将框架相关的实现细节包装起来,让用户能聚焦于压缩逻辑。 点击 `这里 <./Framework.rst>`__,查看自定义新压缩算法(包括剪枝和量化算法)的详细教程。
参考和反馈
----------------------
* 在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 中的 `特征工程 <../FeatureEngineering/Overview.rst>`__\ ;
* 了解更多关于 NNI 中的 `NAS <../NAS/Overview.rst>`__\ ;
* 了解更多关于 NNI 中的 `超参调优 <../Tuner/BuiltinTuner.rst>`__\ ;
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>`__
* `AMC Pruner <#amc-pruner>`__
* `Sensitivity Pruner <#sensitivity-pruner>`__
**其它**
* `ADMM Pruner <#admm-pruner>`__
* `Lottery Ticket Hypothesis <#lottery-ticket-hypothesis>`__
Level Pruner
------------
这是个基本的一次性 Pruner:可设置目标稀疏度(以分数表示,0.6 表示会剪除 60%)。
首先按照绝对值对指定层的权重排序。 然后按照所需的稀疏度,将值最小的权重屏蔽为 0。
用法
^^^^^
TensorFlow 代码
.. code-block:: python
from nni.algorithms.compression.tensorflow.pruning import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
Level Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PyTorch
.. autoclass:: nni.algorithms.compression.pytorch.pruning.LevelPruner
TensorFlow
""""""""""
.. autoclass:: nni.algorithms.compression.tensorflow.pruning.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。
.. image:: ../../img/slim_pruner.png
:target: ../../img/slim_pruner.png
:alt:
..
Slim Pruner **会遮盖卷据层通道之后 BN 层对应的缩放因子**,训练时在缩放因子上的 L1 正规化应在批量正规化 (BN) 层之后来做。BN 层的缩放因子在修剪时,是 **全局排序的**,因此稀疏模型能自动找到给定的稀疏度。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import SlimPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['BatchNorm2d'] }]
pruner = SlimPruner(model, config_list)
pruner.compress()
Slim Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.SlimPruner
复现实验
^^^^^^^^^^^^^^^^^^^^^
我们复现了 `Learning Efficient Convolutional Networks through Network Slimming <https://arxiv.org/pdf/1708.06519.pdf>`__ 中的一项实验。根据论文,对 CIFAR-10 上的 **VGGNet** 剪除了 ``70%`` 的通道,即约 ``88.5%`` 的参数。 我们的实验结果如下:
.. list-table::
:header-rows: 1
:widths: auto
* - 模型
- 错误率(论文/我们的)
- 参数量
- 剪除率
* - VGGNet
- 6.34/6.40
- 20.04M
-
* - Pruned-VGGNet
- 6.20/6.26
- 2.03M
- 88.5%
实验代码在 :githublink:`这里 <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 修剪滤波器。
.. image:: ../../img/fpgm_fig1.png
:target: ../../img/fpgm_fig1.png
:alt:
..
以前的方法使用 “smaller-norm-less-important” 准则来修剪卷积神经网络中规范值较小的。 本文中,分析了基于规范的准则,并指出其所依赖的两个条件不能总是满足:(1) 过滤器的规范偏差应该较大;(2) 过滤器的最小规范化值应该很小。 为了解决此问题,提出了新的过滤器修建方法,即 Filter Pruning via Geometric Median (FPGM),可不考虑这两个要求来压缩模型。 与以前的方法不同,FPGM 通过修剪冗余的,而不是相关性更小的部分来压缩 CNN 模型。
我们还为这个 Pruner 提供了一个依赖感知模式,以更好地提高修剪的速度。 请参考 `dependency-aware <./DependencyAware.rst>`__ 获取更多信息。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import FPGMPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = FPGMPruner(model, config_list)
pruner.compress()
FPGM Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.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。
.. image:: ../../img/l1filter_pruner.png
:target: ../../img/l1filter_pruner.png
:alt:
..
L1Filter Pruner 修剪 **卷积层** 中的过滤器
从第 i 个卷积层修剪 m 个过滤器的过程如下:
#. 对于每个滤波器 :math:`F_{i,j}`,计算其绝对内核权重之和 :math:`s_j=\sum_{l=1}^{n_i}\sum|K_l|`.
#. 将滤波器按 by :math:`s_j` 排序
#. 修剪 :math:`m` 具有最小求和值及其相应特征图的筛选器。 在
下一个卷积层中,被剪除的特征图所对应的内核也被移除。
#. 为第 :math:`i` 层和第 :math:`i+1` 层创建新的内核权重,
并保留剩余的内核 权重,复制到新模型中。
此外,我们还为 L1FilterPruner 提供了依赖感知模式。 参考 `dependency-aware mode <./DependencyAware.rst>`__ 获取依赖感知模式的更多细节。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import L1FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'] }]
pruner = L1FilterPruner(model, config_list)
pruner.compress()
L1Filter Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.L1FilterPruner
复现实验
^^^^^^^^^^^^^^^^^^^^^
我们通过 **L1FilterPruner** 实现了 `PRUNING FILTERS FOR EFFICIENT CONVNETS <https://arxiv.org/abs/1608.08710>`__ 中的一项实验, 即论文中,在 CIFAR-10 数据集上修剪 **VGG-16** 的 **VGG-16-pruned-A**,其中大约剪除了 ``64%`` 的参数。 实验结果如下:
.. list-table::
:header-rows: 1
:widths: auto
* - 模型
- 错误率(论文/我们的)
- 参数量
- 剪除率
* - VGG-16
- 6.75/6.49
- 1.5x10^7
-
* - VGG-16-pruned-A
- 6.60/6.47
- 5.4x10^6
- 64.0%
实验代码在 :githublink:`这里 <examples/model_compress/>`
----
L2Filter Pruner
---------------
这是一种结构化剪枝算法,用于修剪权重的最小 L2 规范筛选器。 它被实现为一次性修剪器。
我们还为这个 Pruner 提供了一个依赖感知模式,以更好地提高修剪的速度。 请参考 `dependency-aware <./DependencyAware.rst>`__ 获取更多信息。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import L2FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'] }]
pruner = L2FilterPruner(model, config_list)
pruner.compress()
L2Filter Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.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 定义为:
.. image:: ../../img/apoz.png
:target: ../../img/apoz.png
:alt:
我们还为这个 Pruner 提供了一个依赖感知模式,以更好地提高修剪的速度。 请参考 `dependency-aware <./DependencyAware.rst>`__ 获取更多信息。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import ActivationAPoZRankFilterPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = ActivationAPoZRankFilterPruner(model, config_list, statistics_batch_num=1)
pruner.compress()
注意:ActivationAPoZRankFilterPruner 用于修剪深度神经网络中的卷积层,因此 ``op_types`` 字段仅支持卷积层。
参考 :githublink:`示例 <examples/model_compress/model_prune_torch.py>` 了解更多信息。
ActivationAPoZRankFilterPruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.ActivationAPoZRankFilterPruner
----
ActivationMeanRankFilter Pruner
-------------------------------
ActivationMeanRankFilterPruner 是从卷积层激活的输出,用最小的重要性标准
``平均激活`` 来修剪滤波器,来达到预设的网络稀疏度。 剪枝标准 ``平均激活``,在论文 `Pruning Convolutional Neural Networks for Resource Efficient Inference <https://arxiv.org/abs/1611.06440>`__ 的 2.2 节中进行了介绍。 本文中提到的其他修剪标准将在以后的版本中支持。
我们还为这个 Pruner 提供了一个依赖感知模式,以更好地提高修剪的速度。 请参考 `dependency-aware <./DependencyAware.rst>`__ 获取更多信息。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import ActivationMeanRankFilterPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = ActivationMeanRankFilterPruner(model, config_list, statistics_batch_num=1)
pruner.compress()
注意:ActivationMeanRankFilterPruner 用于修剪深度神经网络中的卷积层,因此 ``op_types`` 字段仅支持卷积层。
参考 :githublink:`示例 <examples/model_compress/model_prune_torch.py>` 了解更多信息。
ActivationMeanRankFilterPruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.ActivationMeanRankFilterPruner
----
TaylorFOWeightFilter Pruner
---------------------------
TaylorFOWeightFilter Pruner 根据权重上的一阶泰勒展开式,来估计重要性并进行剪枝,从而达到预设的网络稀疏度。 过滤器的估计重要性在论文 `Importance Estimation for Neural Network Pruning <http://jankautz.com/publications/Importance4NNPruning_CVPR19.pdf>`__ 中有定义。 本文中提到的其他修剪标准将在以后的版本中支持。
..
.. image:: ../../img/importance_estimation_sum.png
:target: ../../img/importance_estimation_sum.png
:alt:
我们还为这个 Pruner 提供了一个依赖感知模式,以更好地提高修剪的速度。 请参考 `dependency-aware <./DependencyAware.rst>`__ 获取更多信息。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import TaylorFOWeightFilterPruner
config_list = [{
'sparsity': 0.5,
'op_types': ['Conv2d']
}]
pruner = TaylorFOWeightFilterPruner(model, config_list, statistics_batch_num=1)
pruner.compress()
TaylorFOWeightFilter Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.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:
.. image:: ../../img/agp_pruner.png
:target: ../../img/agp_pruner.png
:alt:
在训练网络时,每隔 ∆t 步更新二值权重掩码,以逐渐增加网络的稀疏性,同时允许网络训练步骤从任何剪枝导致的精度损失中恢复。 根据我们的经验,∆t 设为 100 到 1000 个训练步骤之间时,对于模型最终精度的影响可忽略不计。 一旦模型达到了稀疏度目标 sf,权重掩码将不再更新。 背后的稀疏函数直觉在公式(1)。
用法
^^^^^
通过下列代码,可以在 10 个 Epoch 中将权重稀疏度从 0% 剪枝到 80%。
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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 代码
.. code-block:: python
pruner.update_epoch(epoch)
参考 :githublink:`示例 <examples/model_compress/model_prune_torch.py>` 了解更多信息。
AGP Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.AGPPruner
----
NetAdapt Pruner
---------------
NetAdapt 在满足资源预算的情况下,自动简化预训练的网络。
给定整体稀疏度,NetAdapt 可通过迭代剪枝自动为不同层生成不同的稀疏分布。
参考 `NetAdapt: Platform-Aware Neural Network Adaptation for Mobile Applications <https://arxiv.org/abs/1804.03230>`__ 了解详细信息。
.. image:: ../../img/algo_NetAdapt.png
:target: ../../img/algo_NetAdapt.png
:alt:
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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()
参考 :githublink:`示例 <examples/model_compress/auto_pruners_torch.py>` 了解更多信息。
NetAdapt Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.NetAdaptPruner
SimulatedAnnealing Pruner
-------------------------
此 Pruner 基于先验经验,实现了引导式的启发搜索方法,模拟退火(SA)算法。
增强的模拟退火算法基于以下发现:具有更多权重的深度神经网络层通常具有较高的可压缩度,对整体精度的影响更小。
* 随机初始化剪枝率的分布(稀疏度)。
* 当 current_temperature < stop_temperature 时:
#. 对当前分布生成扰动
#. 对扰动的分布进行快速评估
#. 根据性能和概率来决定是否接受扰动,如果不接受,返回步骤 1
#. 冷却,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 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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()
参考 :githublink:`示例 <examples/model_compress/auto_pruners_torch.py>` 了解更多信息。
SimulatedAnnealing Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.SimulatedAnnealingPruner
AutoCompress Pruner
-------------------
每一轮中,AutoCompressPruner 会用相同的稀疏度对模型进行剪枝,从而达到总体的稀疏度:
.. code-block:: bash
1. 使用 SimulatedAnnealingPruner 生成稀疏度分布
2. 执行基于 ADMM 的结构化剪枝,为下一轮生成剪枝结果。
这里会使用 `speedup` 来执行真正的剪枝。
更多信息请参考 `AutoCompress: An Automatic DNN Structured Pruning Framework for Ultra-High Compression Rates <https://arxiv.org/abs/1907.03141>`__。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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()
参考 :githublink:`示例 <examples/model_compress/auto_pruners_torch.py>` 了解更多信息。
AutoCompress Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.AutoCompressPruner
AMC Pruner
----------
AMC Pruner 利用强化学习来提供模型压缩策略。
这种基于学习的压缩策略比传统的基于规则的压缩策略有更高的压缩比,
更好地保存了精度,节省了人力。
.. image:: ../../img/amc_pruner.jpg
:target: ../../img/amc_pruner.jpg
:alt:
更多信息请参考 `AMC: AutoML for Model Compression and Acceleration on Mobile Devices <https://arxiv.org/pdf/1802.03494.pdf>`__。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import AMCPruner
config_list = [{
'op_types': ['Conv2d', 'Linear']
}]
pruner = AMCPruner(model, config_list, evaluator, val_loader, flops_ratio=0.5)
pruner.compress()
你可以参考 :githublink:`示例 <examples/model_compress/amc/>` 获取更多信息。
AutoCompress Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.AMCPruner
复现实验
^^^^^^^^^^^^^^^^^^^^^
我们复现了 `AMC: AutoML for Model Compression and Acceleration on Mobile Devices <https://arxiv.org/pdf/1802.03494.pdf>`__ 中的实验,对于 ImageNet 数据集,压缩后 **MobileNet** 的 FLOPS 降至50%。 我们的实验结果如下:
.. list-table::
:header-rows: 1
:widths: auto
* - 模型
- Top 1 准确率(论文的/我们的)
- Top 5 准确率 (论文的/我们的)
- FLOPS
* - MobileNet
- 70.5% / 69.9%
- 89.3% / 89.1%
- 50%
实验代码在 :githublink:`这里 <examples/model_compress/amc/>`。
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 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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()
参考 :githublink:`示例 <examples/model_compress/auto_pruners_torch.py>` 了解更多信息。
ADMM Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.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*) -- 在单独训练时 -- 在相似的迭代次数后达到了与原始网络相似的准确度。
本文中,作者使用叫做 *迭代* 修剪的方法:
..
#. 随机初始化一个神经网络 f(x;theta_0) (其中 theta\ *0 follows D*\ {theta})。
#. 将网络训练 j 次,得出参数 theta_j。
#. 在 theta_j 修剪参数的 p%,创建掩码 m。
#. 将其余参数重置为 theta_0 的值,创建获胜彩票 f(x;m*theta_0)。
#. 重复步骤 2、3 和 4。
如果配置的最终稀疏度为 P (e.g., 0.8) 并且有 n 次修建迭代,每次迭代修剪前一轮中剩余权重的 1-(1-P)^(1/n)。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.LotteryTicketPruner
复现实验
^^^^^^^^^^^^^^^^^^^^^
在重现时,在 MNIST 使用了与论文相同的配置。 实验代码在 :githublink:`这里 <examples/model_compress/lottery_torch_mnist_fc.py>`。 在次实验中,修剪了10次,在每次修剪后,训练了 50 个 epoch。
.. image:: ../../img/lottery_ticket_mnist_fc.png
:target: ../../img/lottery_ticket_mnist_fc.png
:alt:
上图展示了全连接网络的结果。 ``round0-sparsity-0.0`` 是没有剪枝的性能。 与论文一致,修剪约 80% 也能获得与不修剪时相似的性能,收敛速度也会更快。 如果修剪过多(例如,大于 94%),则精度会降低,收敛速度会稍慢。 与本文稍有不同,论文中数据的趋势比较明显。
Sensitivity Pruner
------------------
在每一轮,SensitivityPruner 根据对每一层准确率的敏感度对模型进行剪枝,直到满足整个模型最终配置的稀疏度:
.. code-block:: bash
1. 分析模型当前状态下各层的敏感度。
2. 根据敏感度对每一层剪枝。
参考 `Learning both Weights and Connections for Efficient Neural Networks <https://arxiv.org/abs/1506.02626>`__ 了解更多信息。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning 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 分别是传给 evaluator 和 finetuner 的参数
pruner.compress(eval_args=[model], finetune_args=[model])
Sensitivity Pruner 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**PyTorch**
.. autoclass:: nni.algorithms.compression.pytorch.pruning.SensitivityPruner
支持的量化算法
========================================
支持的量化算法列表
* `Naive Quantizer <#naive-quantizer>`__
* `QAT Quantizer <#qat-quantizer>`__
* `DoReFa Quantizer <#dorefa-quantizer>`__
* `BNN Quantizer <#bnn-quantizer>`__
Naive Quantizer
---------------
Naive Quantizer Quantizer 权重默认设置为 8 位,可用它来测试量化算法。
用法
^^^^^
PyTorch
.. code-block:: python
model = nni.algorithms.compression.pytorch.quantization.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)。
用法
^^^^^
可在训练代码前将模型量化为 8 位。
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.quantization 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.rst>`__ 中找到。
此算法所需的配置:
* **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 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.quantization 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.rst>`__ 中找到。
此算法所需的配置:
----
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 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.quantization 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()
可以查看 :githublink:`示例 <examples/model_compress/BNN_quantizer_cifar10.py>` 了解更多信息。
BNN Quantizer 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
压缩算法的公共配置可在 `config_list 说明 <./QuickStart.rst>`__ 中找到。
此算法所需的配置:
实验
^^^^^^^^^^
我们实现了 `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** 进行了量化操作。 我们的实验结果如下:
.. list-table::
:header-rows: 1
:widths: auto
* - 模型
- 准确率
* - VGGNet
- 86.93%
实验代码在 :githublink:`examples/model_compress/BNN_quantizer_cifar10.py <examples/model_compress/BNN_quantizer_cifar10.py>`
模型压缩教程
==============================
.. contents::
本教程中,`第一部分 <#quick-start-to-compress-a-model>`__ 会简单介绍 NNI 上模型压缩的用法。 `第二部分 <#detailed-usage-guide>`__ 会进行详细介绍。
模型压缩快速入门
-------------------------------
NNI 为模型压缩提供了非常简单的 API 压缩包括剪枝和量化算法。 它们的用法相同,这里通过 `slim pruner </Compression/Pruner.html#slim-pruner>`__ 来演示如何使用。
编写配置
^^^^^^^^^^^^^^^^^^^
编写配置来指定要剪枝的层。 以下配置表示剪枝所有的 ``BatchNorm2d``,稀疏度设为 0.7,其它层保持不变。
.. code-block:: python
configure_list = [{
'sparsity': 0.7,
'op_types': ['BatchNorm2d'],
}]
配置说明在 `这里 <#specification-of-config-list>`__ 注意,不同的 Pruner 可能有自定义的配置字段,例如,AGP Pruner ``start_epoch`` 详情参考每个 Pruner `用法 <./Pruner.rst>`__,来调整相应的配置。
选择压缩算法
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
选择 Pruner 来修剪模型。 首先,使用模型来初始化 Pruner,并将配置作为参数传入,然后调用 ``compress()`` 来压缩模型。
.. code-block:: python
pruner = SlimPruner(model, configure_list)
model = pruner.compress()
然后,使用正常的训练方法来训练模型 (如,SGD),剪枝在训练过程中是透明的。 一些 Pruner 只在最开始剪枝一次,接下来的训练可被看作是微调优化。 有些 Pruner 会迭代的对模型剪枝,在训练过程中逐步修改掩码。
导出压缩结果
^^^^^^^^^^^^^^^^^^^^^^^^^
训练完成后,可获得剪枝后模型的精度。 可将模型权重到处到文件,同时将生成的掩码也导出到文件, 也支持导出 ONNX 模型。
.. code-block:: python
pruner.export_model(model_path='pruned_vgg19_cifar10.pth', mask_path='mask_vgg19_cifar10.pth')
模型完整的示例代码在 :githublink:`这里 <examples/model_compress/model_prune_torch.py>`.
加速模型
^^^^^^^^^^^^^^^^^^
掩码实际上并不能加速模型。 要基于导出的掩码,来对模型加速,因此,NNI 提供了 API 来加速模型。 在模型上调用 ``apply_compression_results`` 后,模型会变得更小,推理延迟也会减小。
.. code-block:: python
from nni.compression.pytorch import apply_compression_results
apply_compression_results(model, 'mask_vgg19_cifar10.pth')
参考 `这里 <ModelSpeedup.rst>`__,了解详情。
使用指南
--------------------
将压缩应用到模型的示例代码如下:
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
TensorFlow 代码
.. code-block:: python
from nni.algorithms.compression.tensorflow.pruning import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(tf.get_default_graph(), config_list)
pruner.compress()
可使用 ``nni.compression`` 中的其它压缩算法。 此算法分别在 ``nni.compression.torch`` ``nni.compression.tensorflow`` 中实现,支持 PyTorch TensorFlow(部分支持)。 参考 `Pruner <./Pruner.md>`__ `Quantizer <./Quantizer.md>`__ 进一步了解支持的算法。 此外,如果要使用知识蒸馏算法,可参考 `KD 示例 <../TrialExample/KDExample.rst>`__
压缩算法首先通过传入 ``config_list`` 来实例化。 ``config_list`` 会稍后介绍。
函数调用 ``pruner.compress()`` 来修改用户定义的模型(在 Tensorflow 中,通过 ``tf.get_default_graph()`` 来获得模型,而 PyTorch model 是定义的模型类),并修改模型来插入 mask 然后运行模型时,这些 mask 即会生效。 掩码可在运行时通过算法来调整。
*注意,``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.rst>`__,查看每个算法的键值。
配置的简单示例如下:
.. code-block:: python
[
{
'sparsity': 0.8,
'op_types': ['default']
},
{
'sparsity': 0.6,
'op_names': ['op_name1', 'op_name2']
},
{
'exclude': True,
'op_names': ['op_name3']
}
]
其表示压缩操作的默认稀疏度为 0.8,但 ``op_name1`` ``op_name2`` 会使用 0.6,且不压缩 ``op_name3``
其它量化算法字段
^^^^^^^^^^^^^^^^^^^^^^^^^^
如果使用量化算法,则需要设置下面的 ``config_list`` 如果使用剪枝算法,则可以忽略这些键值。
* **quant_types** : 字符串列表。
要应用量化的类型,当前支持 "权重""输入""输出" "权重"是指将量化操作
应用到 module 的权重参数上。 "输入" 是指对 module forward 方法的输入应用量化操作。 "输出"是指将量化运法应用于模块 forward 方法的输出,在某些论文中,这种方法称为"激活"
* **quant_bits**int dict {str : int}
量化的位宽,键是量化类型,值是量化位宽度,例如:
.. code-block:: bash
{
quant_bits: {
'weight': 8,
'output': 4,
},
}
当值为 int 类型时,所有量化类型使用相同的位宽。 例如:
.. code-block:: bash
{
quant_bits: 8, # weight or output quantization are all 8 bits
}
下面的示例展示了一个更完整的 ``config_list``,它使用 ``op_names``(或者 ``op_types``)指定目标层以及这些层的量化位数。
.. code-block:: bash
configure_list = [{
'quant_types': ['weight'],
'quant_bits': 8,
'op_names': ['conv1']
}, {
'quant_types': ['weight'],
'quant_bits': 4,
'quant_start_step': 0,
'op_names': ['conv2']
}, {
'quant_types': ['weight'],
'quant_bits': 3,
'op_names': ['fc1']
},
{
'quant_types': ['weight'],
'quant_bits': 2,
'op_names': ['fc2']
}
]
在这个示例中,'op_names' 是层的名字,四个层将被量化为不同的 quant_bits
更新优化状态的 API
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
一些压缩算法使用 Epoch 来控制压缩进度 (如 `AGP </Compression/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`` 中,被掩码遮盖的权重为零。
.. code-block:: bash
pruner.export_model(model_path='model.pth')
``mask_dict`` ``onnx`` 格式的剪枝模型(需要指定 ``input_shape``)可这样导出:
.. code-block:: python
pruner.export_model(model_path='model.pth', mask_path='mask.pth', onnx_path='model.onnx', input_shape=[1, 1, 28, 28])
如果需要实际加速压缩后的模型,参考 `NNI 模型加速 <./ModelSpeedup.rst>`__
#################
剪枝
#################
剪枝是一种常用的神经网络模型压缩技术。
剪枝算法探索模型权重(参数)中的冗余,并尝试去除冗余和非关键权重,
将它们的值归零,确保其不参与反向传播过程。
从剪枝粒度的角度来看,细粒度剪枝或非结构化剪枝是指分别对每个权重进行剪枝。
粗粒度剪枝或结构化剪枝是修剪整组权重,例如卷积滤波器。
NNI 提供了多种非结构化和结构化剪枝算法。
其使用了统一的接口来支持 TensorFlow 和 PyTorch。
只需要添加几行代码即可压缩模型。
对于结构化滤波器剪枝,NNI 还提供了依赖感知模式。 在依赖感知模式下,
滤波器剪枝在加速后会获得更好的速度增益。
详细信息,参考以下教程:
.. toctree::
:maxdepth: 2
Pruners <Pruner>
Dependency Aware Mode <DependencyAware>
Model Speedup <ModelSpeedup>
Automatic Model Pruning with NNI Tuners <AutoPruningUsingTuners>
#################
量化
#################
量化是指通过减少权重表示或激活所需的比特数来压缩模型,
从而减少计算量和推理时间。 在深度神经网络的背景下,模型权重主要的数据
格式是32位浮点数。 许多研究工作表明,在不显着降低精度的情况下,权重和激活
可以使用8位整数表示, 更低的比特位数,例如4/2/1比特,
是否能够表示权重也是目前非常活跃的研究方向。
一个 Quantizer 是指一种 NNI 实现的量化算法,NNI 提供了多个 Quantizer,如下所示。 你也可以
使用 NNI 模型压缩的接口来创造你的 Quantizer。
.. toctree::
:maxdepth: 2
Quantizers <Quantizer>
# NNI 上的自动模型压缩
使用 NNI 的压缩和 Tuner 能轻松实现自动模型压缩
## 首先,使用 NNI 压缩模型
可使用 NNI 轻松压缩模型。 以剪枝为例,可通过 LevelPruner 对预训练模型剪枝:
```python
from nni.compression.torch import LevelPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
pruner = LevelPruner(model, config_list)
pruner.compress()
```
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.compress()` 时,模型会通过掩码进行压缩。随后还可以微调模型,此时**被剪除的权重不会被更新**
## 然后,进行自动化
前面的示例手工选择了 LevelPruner,并对所有层使用了相同的稀疏度,显然这不是最佳方法,因为不同层会有不同的冗余度。 每层的稀疏度都应该仔细调整,以便减少模型性能的下降,可通过 NNI Tuner 来完成。
首先需要设计搜索空间,这里使用了嵌套的搜索空间,其中包含了选择的剪枝函数以及需要优化稀疏度的层。
```json
{
"prune_method": {
"_type": "choice",
"_value": [
{
"_name": "agp",
"conv0_sparsity": {
"_type": "uniform",
"_value": [
0.1,
0.9
]
},
"conv1_sparsity": {
"_type": "uniform",
"_value": [
0.1,
0.9
]
},
},
{
"_name": "level",
"conv0_sparsity": {
"_type": "uniform",
"_value": [
0.1,
0.9
]
},
"conv1_sparsity": {
"_type": "uniform",
"_value": [
0.01,
0.9
]
},
}
]
}
}
```
然后需要修改几行代码。
```python
import nni
from nni.compression.torch import *
params = nni.get_parameters()
conv0_sparsity = params['prune_method']['conv0_sparsity']
conv1_sparsity = params['prune_method']['conv1_sparsity']
# 如果对总稀疏度有要求,这些原始稀疏度就需要调整。
config_list_level = [{ 'sparsity': conv0_sparsity, 'op_name': 'conv0' },
{ 'sparsity': conv1_sparsity, 'op_name': 'conv1' }]
config_list_agp = [{'initial_sparsity': 0, 'final_sparsity': conv0_sparsity,
'start_epoch': 0, 'end_epoch': 3,
'frequency': 1,'op_name': 'conv0' },
{'initial_sparsity': 0, 'final_sparsity': conv1_sparsity,
'start_epoch': 0, 'end_epoch': 3,
'frequency': 1,'op_name': 'conv1' },]
PRUNERS = {'level':LevelPruner(model, config_list_level), 'agp':AGPPruner(model, config_list_agp)}
pruner = PRUNERS(params['prune_method']['_name'])
pruner.compress()
... # fine tuning
acc = evaluate(model) # evaluation
nni.report_final_results(acc)
```
最后,定义任务,并使用任务来自动修剪层稀疏度。
```yaml
authorName: default
experimentName: Auto_Compression
trialConcurrency: 2
maxExecDuration: 100h
maxTrialNum: 500
# 可选项: local, remote, pai
trainingServicePlatform: local
# 可选项: true, false
useAnnotation: False
searchSpacePath: search_space.json
tuner:
# 可选项: TPE, Random, Anneal...
builtinTunerName: TPE
classArgs:
# 可选项: maximize, minimize
optimize_mode: maximize
trial:
command: bash run_prune.sh
codeDir: .
gpuNum: 1
```
# 模型压缩 Python API 参考
```eval_rst
.. contents::
```
## 灵敏度工具
```eval_rst
.. autoclass:: nni.compression.torch.utils.sensitivity_analysis.SensitivityAnalysis
:members:
```
## 拓扑结构工具
```eval_rst
.. autoclass:: nni.compression.torch.utils.shape_dependency.ChannelDependency
:members:
.. autoclass:: nni.compression.torch.utils.shape_dependency.GroupDependency
:members:
.. autoclass:: nni.compression.torch.utils.mask_conflict.CatMaskPadding
:members:
.. autoclass:: nni.compression.torch.utils.mask_conflict.GroupMaskConflict
:members:
.. autoclass:: nni.compression.torch.utils.mask_conflict.ChannelMaskConflict
:members:
```
## 模型 FLOPs 和参数计数器
```eval_rst
.. autofunction:: nni.compression.torch.utils.counter.count_flops_params
```
\ No newline at end of file
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