未验证 提交 0ba185ef 编写于 作者: W whs 提交者: GitHub

Add document for slim demo. (#1967) (#2126)

* Add README.md

* Update url of images.

* Add images.

* Add usage.md

* Add demo.md

* Fix demo.md

* Add model_zoo.md

* Fix pretrain models download in model zoo.

* Refine documents.
1. Rename python to Python
2. Fix README.md: vgg->VGG, resnet->ResNet, mobilenet->MobileNet
3. Rename flops to FLOPS

* Move docs of PaddleSlim from models.fluid.PaddleSlim to models.PaddleSlim

* 1. Introduce model format in README.md
2. Merge results of combined strategies into one table.

* Refine README.md

* Refine README
1. Fix some terms in README.md
2. Fix description of model format in README.md
3. Add Channel-Wise into feature list
4. Rename 'paddle mobile' to 'Paddle Mobile'
5. Add comments for dynamic quantization and static quantization
6. Add entry url for Model Zoo in README.md

* Refine README.
上级 fef56494
<div align="center">
<h3>
<a href="docs/tutorial.md">
算法原理介绍
</a>
<span> | </span>
<a href="docs/usage.md">
使用文档
</a>
<span> | </span>
<a href="docs/demo.md">
示例文档
</a>
<span> | </span>
<a href="docs/model_zoo.md">
Model Zoo
</a>
</h3>
</div>
---
# PaddleSlim模型压缩工具库
PaddleSlim是PaddlePaddle框架的一个子模块。PaddleSlim首次在PaddlePaddle 1.4版本中发布。在PaddleSlim中,实现了目前主流的网络剪枝、量化、蒸馏三种压缩策略,主要用于压缩图像领域模型。在后续版本中,会添加更多的压缩策略,以及完善对NLP领域模型的支持。
## 目录
- [特色](#特色)
- [架构介绍](#架构介绍)
- [功能列表](#功能列表)
- [实验结果与ModelZoo](#简要实验结果)
- [模型导出格式](#模型导出格式)
## 主要特点
Paddle-Slim工具库有以下特点:
### 接口简单
- 以配置文件方式集中管理可配参数,方便实验管理
- 在普通模型训练脚本上,添加极少代码即可完成模型压缩
详见:[使用示例](docs/demo.md)
### 效果好
- 对于冗余信息较少的MobileNetV1模型,卷积核剪切策略依然可缩减模型大小,并保持尽量少的精度损失。
- 蒸馏压缩策略可明显提升原始模型的精度。
- 量化训练与蒸馏的组合使用,可同时做到缩减模型大小和提升模型精度。
详见:[效果数据与ModelZoo](docs/model_zoo.md)
### 功能更强更灵活
- 剪切压缩过程自动化
- 剪切压缩策略支持更多网络结构
- 蒸馏支持多种方式,用户可自定义组合loss
- 支持快速配置多种压缩策略组合使用
详见:[使用说明](docs/usage.md)
## 架构介绍
这里简要介绍模型压缩工具实现的整体原理,便于理解使用流程。
**图 1**为模型压缩工具的架构图,从上到下为API依赖关系。蒸馏模块、量化模块和剪切模块都间接依赖底层的paddle框架。目前,模型压缩工具作为了PaddlePaddle框架的一部分,所以已经安装普通版本paddle的用户需要重新下载安装支持模型压缩功能的paddle,才能使用压缩功能。
<p align="center">
<img src="docs/images/framework_0.png" height=452 width=706 hspace='10'/> <br />
<strong>图 1</strong>
</p>
**图 1**所示,最上层的紫色模块为用户接口,在Python脚本中调用模型压缩功能时,只需要构造一个Compressor对象即可,在[使用文档](docs/usage.md)中会有详细说明。
我们将每个压缩算法称为压缩策略,在迭代训练模型的过程中调用用户注册的压缩策略完成模型压缩,如**图2**所示。其中,模型压缩工具封装好了模型训练逻辑,用户只需要提供训练模型需要的网络结构、数据、优化策略(optimizer)等,在[使用文档](docs/usage.md)会对此详细介绍。
<p align="center">
<img src="docs/images/framework_1.png" height=255 width=646 hspace='10'/> <br />
<strong>图 2</strong>
</p>
## 功能列表
### 剪切
- 支持敏感度和uniform两种方式
- 支持VGG、ResNet、MobileNet等各种类型的网络
- 支持用户自定义剪切范围
### 量化训练
- 支持动态和静态两种量化训练方式
- 动态策略: 在推理过程中,动态统计激活的量化参数。
- 静态策略: 在推理过程中,对不同的输入,采用相同的从训练数据中统计得到的量化参数。
- 支持对权重全局量化和Channel-Wise量化
- 支持以兼容Paddle Mobile的格式保存模型
### 蒸馏
- 支持在teacher网络和student网络任意层添加组合loss
- 支持FSP loss
- 支持L2 loss
- 支持softmax with cross-entropy loss
### 其它功能
- 支持配置文件管理压缩任务超参数
- 支持多种压缩策略组合使用
- 蒸馏和剪切压缩过程支持checkpoints功能
## 简要实验结果
本节列出了PaddleSlim模型压缩工具库的一些实验结果,更多实验数据和预训练模型的下载,请参考:[详细实验结果与ModelZoo](docs/model_zoo.md)
### 量化训练
评估实验所使用数据集为ImageNet1000类数据,且以top-1准确率为衡量指标:
| Model | FP32| int8(X:abs_max, W:abs_max) | int8, (X:moving_average_abs_max, W:abs_max) |int8, (X:abs_max, W:channel_wise_abs_max) |
|:---|:---:|:---:|:---:|:---:|
|MobileNetV1|89.54%/70.91%|89.64%/71.01%|89.58%/70.86%|89.75%/71.13%|
|ResNet50|92.80%/76.35%|93.12%/76.77%|93.07%/76.65%|93.15%/76.80%|
### 卷积核剪切
数据:ImageNet 1000类
模型:MobileNetV1
原始模型大小:17M
原始精度(top5/top1): 89.54% / 70.91%
#### Uniform剪切
| FLOPS |model size| 精度损失(top5/top1)|精度(top5/top1) |
|---|---|---|---|
| -50%|-47.0%(9.0M)|-0.41% / -1.08%|89.13% / 69.83%|
| -60%|-55.9%(7.5M)|-1.34% / -2.67%|88.22% / 68.24%|
| -70%|-65.3%(5.9M)|-2.55% / -4.34%|86.99% / 66.57%|
#### 基于敏感度迭代剪切
| FLOPS |精度(top5/top1)|
|---|---|
| -0% |89.54% / 70.91% |
| -20% |90.08% / 71.48% |
| -36% |89.62% / 70.83%|
| -50% |88.77% / 69.31%|
### 蒸馏
数据:ImageNet 1000类
模型:MobileNetV1
|- |精度(top5/top1) |收益(top5/top1)|
|---|---|---|
| 单独训| 89.54% / 70.91%| - |
| ResNet50蒸馏训| 90.92% / 71.97%| +1.28% / +1.06%|
### 组合实验
数据:ImageNet 1000类
模型:MobileNetV1
|压缩策略 |精度(top5/top1) |模型大小|
|---|---|---|
| Baseline|89.54% / 70.91%|17.0M|
| ResNet50蒸馏|90.92% / 71.97%|17.0M|
| ResNet50蒸馏训 + 量化|90.94% / 72.08%|4.2M|
| 剪切-50% FLOPS|89.13% / 69.83%|9.0M|
| 剪切-50% FLOPS + 量化|89.11% / 69.70%|2.3M|
## 模型导出格式
压缩框架支持导出以下格式的模型:
- **Paddle Fluid模型格式:** Paddle Fluid模型格式,可通过Paddle框架加载使用。
- **Paddle Mobile模型格式:** 仅在量化训练策略时使用,兼容[Paddle Mobile](https://github.com/PaddlePaddle/paddle-mobile)的模型格式。
<div align="center">
<h3>
<a href="usage.md">
使用文档
</a>
<span> | </span>
<a href="tutorial.md">
算法原理介绍
</a>
<span> | </span>
<a href="model_zoo.md">
Model Zoo
</a>
</h3>
</div>
---
# Paddle模型压缩工具库使用示例
## 目录
- [概述](#0-概述)
- [数据准备](#1-数据准备)
- [压缩脚本准备](#2-压缩脚本介绍)
- [蒸馏示例](#31-蒸馏)
- [剪切示例](#32-uniform剪切)
- [量化示例](#34-int8量化训练)
- [蒸馏后量化示例](#35-蒸馏后int8量化)
- [剪切后量化示例](#36-剪切后int8量化)
## 0. 概述
该示例参考[PaddlePaddle/models/fluid/PaddleCV/image_classification](https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleCV/image_classification)下代码,分别实现了以下策略:
1. <a href="#31-蒸馏">蒸馏</a>:用ResNet50对MobileNetV1的在ImageNet 1000数据上的蒸馏训练。
2. <a href="#32-uniform剪切">剪切</a>:对预训练好的MobileNetV1进行剪切
3. <a href="#34-int8量化训练">量化</a>:对预训练好的MobileNetV1进行int8量化训练
4. <a href="#35-蒸馏后int8量化">蒸馏量化组合</a>:先用ResNet50对MobileNetV1进行蒸馏,再对蒸馏后得到的模型进行int8量化训练。
5. <a href="#36-剪切后int8量化">剪切量化组合</a>:先用Uniform剪切策略对MobileNetV1进行剪切,再对剪切后的模型进行int8量化训练
本示例完整代码链接:https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleSlim
使用方式:
克隆[PaddlePaddle/models](https://github.com/PaddlePaddle/models)到本地,并进入models/fluid/PaddleSlim路径。
**文件结构**
```
/.
|-configs # 压缩任务的配置文件,包括:蒸馏、int8量化量化、filter剪切和组合策略的配置文件。
|-data # 存放训练数据和pretrain model
|-models # MobileNetV1和ResNet50网络结构的定义
|-quant_low_level_api # 量化训练的底层API, 用于处理特殊情况,用户可暂时忽略该内容
|-compress.py # 模型压缩任务主脚本,示例中多个压缩策略共用这一个脚本。定义了压缩任务需要的模型相关的信息。
|-reader.py # 定义数据处理逻辑
|-run.sh # 模型压缩任务启动脚本
|-utility.py # 定义了常用的工具方法
```
本示例中的五个压缩策略使用相同的训练数据和压缩Python脚本`compress.py`,每种策略对应独立的配置文件。
第1章介绍数据准备,第2章介绍脚本compress.py中几个关键步骤。第3章分别介绍了如何执行各种压缩策略的示例。
## 1. 数据准备
### 1.1 训练数据准备
参考[models/fluid/PaddleCV/image_classification](https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleCV/image_classification#data-preparation)下的数据准备教程准备训练数据,并放入PaddleSlim/data路径下。
### 1.2 预训练模型准备
脚本run.sh会自动从[models/fluid/PaddleCV/image_classification](https://github.com/PaddlePaddle/models/tree/develop/fluid/PaddleCV/image_classification#supported-models-and-performances)下载ResNet50和MobileNetV1的预训练模型,并放入PaddleSlim/pretrain路径下。
## 2. 压缩脚本介绍
`compress.py`中定义了执行压缩任务需要的所有模型相关的信息,这里对几个关键的步骤进行简要介绍:
### 2.1 目标网络的定义
compress.py的以下代码片段定义了train program, 这里train program只有前向计算操作。
```
out = model.net(input=image, class_dim=args.class_dim)
cost = fluid.layers.cross_entropy(input=out, label=label)
avg_cost = fluid.layers.mean(x=cost)
acc_top1 = fluid.layers.accuracy(input=out, label=label, k=1)
acc_top5 = fluid.layers.accuracy(input=out, label=label, k=5)
```
然后,通过clone方法得到eval_program, 用来在压缩过程中评估模型精度,如下:
```
val_program = fluid.default_main_program().clone()
```
定义完目标网络结构,需要对其初始化,并根据需要加载预训练模型。
### 2.2 定义feed_list和fetch_list
对于train program, 定义train_feed_list用于指定从train data reader中取的数据feed给哪些variable。定义train_fetch_list用于指定在训练时,需要在log中展示的结果。如果需要在训练过程中在log中打印accuracy信心,则将('acc_top1', acc_top1.name)添加到train_fetch_list中即可。
```
train_feed_list = [('image', image.name), ('label', label.name)]
train_fetch_list = [('loss', avg_cost.name)]
```
>注意: 在train_fetch_list里必须有loss这一项。
对于eval program. 同上定义eval_feed_list和train_fetch_list:
```
val_feed_list = [('image', image.name), ('label', label.name)]
val_fetch_list = [('acc_top1', acc_top1.name), ('acc_top5', acc_top5.name)]
```
### 2.3 定义teacher网络
以下代码片段定义了teacher网络,并对其进行了初始化操作。
```
teacher_program = fluid.Program()
startup_program = fluid.Program()
with fluid.program_guard(teacher_program, startup_program):
img = teacher_program.global_block()._clone_variable(image, force_persistable=False)
predict = teacher_model.net(img, class_dim=args.class_dim)
exe.run(startup_program)
```
需要注意的是:
- teacher网络只有一个输入,直接clone在train program(fluid.default_main_program) 中定义的image变量即可。
- teacher网络的输出只需要到predict即可,不用加loss和accuracy等操作
- teacher网络需要初始化并加载预训练模型。
>注意: ResNet50和MobileNetV1的fc layer的weight parameter的名称都为‘fc_1.weight’,所以需要到PaddleSlim/models/resnet.py中修改一下ResNet fc layer的名称, 同时,修改ResNet50 pretrain model中响应weight的文件名,使其与resnet.py中的名称保持一致。
## 3. 执行压缩策略示例
所有示例的执行命令都放在`run.sh`文件中,用户可以修改run.sh后,执行不同的压缩策略示例。
### 3.1 蒸馏
在该示例中,用预训练好的ResNet50模型监督训练MobileNetV1模型。
修改run.sh, 执行以下命令,执行蒸馏压缩示例:
```
# for distillation
#--------------------
export CUDA_VISIBLE_DEVICES=0
python compress.py \
--model "MobileNet" \
--teacher_model "ResNet50" \
--teacher_pretrained_model ./data/pretrain/ResNet50_pretrained \
--compress_config ./configs/mobilenetv1_resnet50_distillation.yaml
```
该示例在评估数据集上的准确率结果如下:
|- |精度(top5/top1) |
|---|---|
| ResNet50蒸馏训| 90.92% / 71.97%|
<p align="center">
<img src="images/demo/distillation_result.png" height=300 width=400 hspace='10'/> <br />
<strong>图1</strong>
</p>
### 3.2 Uniform剪切
在该示例中,将MobileNetV1模型剪掉50%的FLOPS.
修改run.sh, 执行以下命令,执行Uniform卷积核剪切模型压缩示例:
```
# for uniform filter pruning
#---------------------------
export CUDA_VISIBLE_DEVICES=0
python compress.py \
--model "MobileNet" \
--pretrained_model ./data/pretrain/MobileNetV1_pretrained \
--compress_config ./configs/filter_pruning_uniform.yaml
```
该示例在评估数据集上的准确率结果如下:
| FLOPS |模型大小|精度(top5/top1) |
|---|---|---|
| -50%|-47.0%(9.0M) |89.13% / 69.83%|
<p align="center">
<img src="images/demo/pruning_uni_result.png" height=300 width=400 hspace='10'/> <br />
<strong>图2</strong>
</p>
### 3.3 敏感度剪切
在该示例中,将MobileNetV1模型剪掉50%的FLOPS.
修改run.sh, 执行以下命令,执行敏感度卷积核剪切压缩示例:
```
# for sensitivity filter pruning
#---------------------------
export CUDA_VISIBLE_DEVICES=0
python compress.py \
--model "MobileNet" \
--pretrained_model ./data/pretrain/MobileNetV1_pretrained \
--compress_config ./configs/filter_pruning_sen.yaml
```
该示例在评估数据集上的准确率结果如下:
| FLOPS |模型大小| 精度(top5/top1) |
|---|---|---|
| -50%|-61.2%(6.6M) |88.47% / 68.68%|
<p align="center">
<img src="images/demo/pruning_sen_result.png" height=300 width=400 hspace='10'/> <br />
<strong>图3</strong>
</p>
### 3.4 int8量化训练
修改run.sh, 执行以下命令,执行int8量化训练示例:
```
# for quantization
#---------------------------
export CUDA_VISIBLE_DEVICES=0
python compress.py \
--batch_size 64 \
--model "MobileNet" \
--pretrained_model ./pretrain/MobileNetV1_pretrained \
--compress_config ./configs/quantization.yaml
```
该示例结果如下:
| Model | int8量化(top1_acc)|
|---|---|
|MobileNetV1|71.00%|
### 3.5 蒸馏后int8量化
本示例先用ResNet50模型对MobileNetV1蒸馏训练120个epochs,然后再对MobileNetV1模型进行动态int8量化训练。
修改run.sh, 执行以下命令,执行蒸馏与int8量化训练结合的模型压缩示例:
```
# for distillation with quantization
#-----------------------------------
export CUDA_VISIBLE_DEVICES=0
python compress.py \
--model "MobileNet" \
--teacher_model "ResNet50" \
--teacher_pretrained_model ./data/pretrain/ResNet50_pretrained \
--compress_config ./configs/quantization_dist.yaml
```
该示例结果如下:
|- |精度(top5/top1) |
|---|---|
| ResNet50蒸馏训+量化|90.94% / 72.08%|
### 3.6 剪切后int8量化
本示例先将预训练好的MobileNetV1模型剪掉50% FLOPS, 让后再对其进行动态int8量化训练。
修改run.sh, 执行以下命令,执行剪切与int8量化训练结合的模型压缩示例:
```
# for uniform filter pruning with quantization
#---------------------------------------------
export CUDA_VISIBLE_DEVICES=0
python compress.py \
--model "MobileNet" \
--pretrained_model ./data/pretrain/MobileNetV1_pretrained \
--compress_config ./configs/quantization_pruning.yaml
```
该示例结果如下:
| 剪切FLOPS |剪切+量化(dynamic)
|---|---|
| -50%|89.11% / 69.70%|
<div align="center">
<h3>
<a href="usage.md">
使用文档
</a>
<span> | </span>
<a href="demo.md">
示例文档
</a>
<span> | </span>
<a href="tutorial.md">
算法原理介绍
</a>
</h3>
</div>
---
# Paddle模型压缩工具实验与模型库
## 目录
- [量化实验](#int8量化训练)
- [剪切实验](#剪切实验)
- [蒸馏实验](蒸馏实验)
- [组合实验](组合实验)
## 1. int8量化训练
评估实验所使用数据集为ImageNet 1000类,且以top-1准确率为衡量指标:
| Model | FP32| int8(A:abs_max, W:abs_max) | int8, (A:moving_average_abs_max, W:abs_max) |int8, (A:abs_max, W:channel_wise_abs_max) |
|:---|:---:|:---:|:---:|:---:|
|MobileNetV1|[70.916%]()|[71.008%]()|[70.84%]()|[71.00%]()|
|ResNet50|[76.352%]()|[76.612%]()|[76.456%]()|[76.73%]()|
点击表中超链接即可下载预训练模型。
模型大小变化:
| Model | FP32| int8(A:abs_max, W:abs_max) | int8, (A:moving_average_abs_max, W:abs_max) |int8, (A:abs_max, W:channel_wise_abs_max) |
|:---|:---:|:---:|:---:|:---:|
|MobileNetV1|17M|4.8M(1/3.54)|5.1M(1/3.33)|4.9M(1/3.47)|
|ResNet50|99M|26M(1/3.81)|27M(1/3.67)|27M(1/3.67)|
## 2. 剪切实验
数据: ImageNet 1000类
模型:MobileNetV1
原始模型大小:17M
原始精度(top5/top1): 89.54% / 70.91%
### 2.1 基于敏感度迭代剪切
#### 实验说明
分步剪切,每步剪掉模型7%的FLOPS.
optimizer配置如下:
```
epoch_size=5000
boundaries = [30, 60, 90, 120] * epoch_size # for -50% FLOPS
#boundaries = [35, 65, 95, 125] * epoch_size # for -60% FLOPS
#boundaries = [50, 80, 110, 140] * epoch_size # for -70% FLOPS
values = [0.01, 0.1, 0.01, 0.001, 0.0001]
optimizer = fluid.optimizer.Momentum(
momentum=0.9,
learning_rate=fluid.layers.piecewise_decay(boundaries=boundaries, values=values),
regularization=fluid.regularizer.L2Decay(1e-4))
```
#### 实验结果
| FLOPS |model size| 精度(top5/top1) |下载模型|
|---|---|---|---|
| -50%|-59.4%(6.9M) |88.22% / 68.41% |[点击下载](https://paddle-slim-models.bj.bcebos.com/sensitive_filter_pruning_0.5_model.tar.gz)|
| -60%|-70.6%(5.0M)|87.01% / 66.31% |[点击下载](https://paddle-slim-models.bj.bcebos.com/sensitive_filter_pruning_0.6_model.tar.gz)|
| -70%|-78.8%(3.6M)|85.30% / 63.41% |[点击下载](https://paddle-slim-models.bj.bcebos.com/sensitive_filter_pruning_0.7_model.tar.gz)|
### 2.2 基于敏感度一次性剪切
#### 实验说明
一步剪切掉50%FLOPS, 然后fine-tune 120个epoch.
optimizer配置如下:
```
epoch_size=5000
boundaries = [30, 60, 90] * epoch_size
values = [0.1, 0.01, 0.001, 0.0001]
optimizer = fluid.optimizer.Momentum(
momentum=0.9,
learning_rate=fluid.layers.piecewise_decay(boundaries=boundaries, values=values),
regularization=fluid.regularizer.L2Decay(1e-4))
```
#### 实验结果
| FLOPS |model size|精度(top5/top1) |模型下载|
|---|---|---|---|
| -50%|-61.2%(6.6M)| 88.47% / 68.68% |[点击下载](https://paddle-slim-models.bj.bcebos.com/sensitive_filter_pruning_0.5-1step.tar.gz)|
### 2.3 基于敏感度分步剪切
#### 实验说明
1. 一次剪掉20%FLOPS, fine-tune 120个epoch
2. 在上一步基础上,一次剪掉20%FLOPS, fine-tune 120个epoch
3. 在上一步基础上,一次剪掉20%FLOPS, fine-tune 120个epoch
optimizer配置如下:
```
epoch_size=5000
boundaries = [30, 60, 90] * epoch_size
values = [0.1, 0.01, 0.001, 0.0001]
optimizer = fluid.optimizer.Momentum(
momentum=0.9,
learning_rate=fluid.layers.piecewise_decay(boundaries=boundaries, values=values),
regularization=fluid.regularizer.L2Decay(1e-4))
```
#### 实验结果
| FLOPS |精度(top5/top1)|模型下载 |
|---|---|---|
| -20%|90.08% / 71.48% |[点击下载](https://paddle-slim-models.bj.bcebos.com/sensitive_filter_pruning_3step_0.2_model.tar.gz)|
| -36%|89.62% / 70.83%|[点击下载](https://paddle-slim-models.bj.bcebos.com/sensitive_filter_pruning_3step_0.36_model.tar.gz)|
| -50%|88.77% / 69.31%|[点击下载](https://paddle-slim-models.bj.bcebos.com/sensitive_filter_pruning_3step_0.5_model.tar.gz)|
### 2.4 Uniform剪切
#### 实验说明
一次剪掉指定比例的FLOPS,然后fine-tune 120个epoch.
optimizer配置如下:
```
epoch_size=5000
boundaries = [30, 60, 90] * epoch_size
values = [0.1, 0.01, 0.001, 0.0001]
optimizer = fluid.optimizer.Momentum(
momentum=0.9,
learning_rate=fluid.layers.piecewise_decay(boundaries=boundaries, values=values),
regularization=fluid.regularizer.L2Decay(1e-4))
```
#### 实验结果
| FLOPS |model size|精度(top5/top1) |模型下载 |
|---|---|---|---|
| -50%|-47.0%(9.0M) | 89.13% / 69.83%|[点击下载](https://paddle-slim-models.bj.bcebos.com/uniform_filter_pruning_0.5_model.tar.gz)|
| -60%|-55.9%(7.5M)|88.22% / 68.24%| [点击下载](https://paddle-slim-models.bj.bcebos.com/uniform_filter_pruning_0.6_model.tar.gz)|
| -70%|-65.3%(5.9M)|86.99% / 66.57%| [点击下载](https://paddle-slim-models.bj.bcebos.com/uniform_filter_pruning_0.7_model.tar.gz)|
## 3. 蒸馏
数据: ImageNet 1000类
模型:MobileNetV1
原始模型大小:17M
原始精度(top5/top1): 89.54% / 70.91%
#### 实验说明
用训练好的ResNet50蒸馏训练MobileNetV1, 训练120个epoch. 对第一个block加FSP loss; 对softmax layer的输入加L2-loss.
optimizer配置如下:
```
epoch_size=5000
boundaries = [30, 60, 90] * epoch_size
values = [0.1, 0.01, 0.001, 0.0001]
optimizer = fluid.optimizer.Momentum(
momentum=0.9,
learning_rate=fluid.layers.piecewise_decay(boundaries=boundaries, values=values),
regularization=fluid.regularizer.L2Decay(1e-4))
```
#### 实验结果
|- |精度(top5/top1) |收益(top5/top1)|模型下载 |
|---|---|---|---|
| ResNet50蒸馏训| 90.92% / 71.97%| +1.28% / +1.06%| [点击下载](https://paddle-slim-models.bj.bcebos.com/mobilenetv1_resnet50_distillation_model.tar.gz)|
## 4. 组合实验
### 4.1 蒸馏后量化
#### 实验说明
#### 实验结果
|- |精度(top5/top1) |模型下载 |
|---|---|---|
| ResNet50蒸馏训+量化|90.94% / 72.08%| [点击下载]()|
### 4.2 剪切后量化
#### 实验说明
#### 实验结果
| 剪切FLOPS |剪切+量化(dynamic)|模型下载 |
|---|---|---|
| -50%|89.11% / 69.70%| [点击下载]()|
<div align="center">
<h3>
<a href="usage.md">
使用文档
</a>
<span> | </span>
<a href="demo.md">
示例文档
</a>
<span> | </span>
<a href="model_zoo.md">
Model Zoo
</a>
</h3>
</div>
---
# Paddle模型压缩工具库算法原理介绍
## 目录
- [量化原理介绍](#1-quantization-aware-training量化介绍)
- [剪切原理介绍](#2-卷积核剪切原理)
- [蒸馏原理介绍](#3-蒸馏)
## 1. Quantization Aware Training量化介绍
### 1.1 背景
近年来,定点量化使用更少的比特数(如8-bit、3-bit、2-bit等)表示神经网络的权重和激活已被验证是有效的。定点量化的优点包括低内存带宽、低功耗、低计算资源占用以及低模型存储需求等。
<p align="center">
<img src="images/tutorial/quan_table_0.png" height=258 width=600 hspace='10'/> <br />
<strong>表1: 不同类型操作的开销对比</strong>
</p>
由表1可知,低精度定点数操作的硬件面积大小及能耗比高精度浮点数要少几个数量级。 使用定点量化可带来4倍的模型压缩、4倍的内存带宽提升,以及更高效的cache利用(很多硬件设备,内存访问是主要能耗)。除此之外,计算速度也会更快(通常具有2x-3x的性能提升)。由表2可知,在很多场景下,定点量化操作对精度并不会造成损失。另外,定点量化对神经网络于嵌入式设备上的推断来说是极其重要的。
<p align="center">
<img src="images/tutorial/quan_table_1.png" height=155 width=500 hspace='10'/> <br />
<strong>表2:模型量化前后精度对比</strong>
</p>
目前,学术界主要将量化分为两大类:`Post Training Quantization``Quantization Aware Training``Post Training Quantization`是指使用KL散度、滑动平均等方法确定量化参数且不需要重新训练的定点量化方法。`Quantization Aware Training`是在训练过程中对量化进行建模以确定量化参数,它与`Post Training Quantization`模式相比可以提供更高的预测精度。本文主要针对`Quantization Aware Training`量化模式进行阐述说明。
### 1.2 量化原理
#### 1.2.1 量化方式
目前,存在着许多方法可以将浮点数量化成定点数。例如:
$$ r = min(max(x, a), b)$$ $$ s = \frac{b - a}{n - 1} $$ $$ q = \left \lfloor \frac{r - a}{s} \right \rceil $$
式中,$x$是待量化的浮点值,$[a, b]$是量化范围,$a$是待量化浮点数中的最小值, $b$ 是待量化浮点数中的最大值。$\left \lfloor \right \rceil$ 表示将结果四舍五入到最近的整数。如果量化级别为$k$,则$n$为$2^k$。例如,若$k$为8,则$n$为256。$q$是量化得到的整数。
PaddleSlim框架中选择的量化方法为最大绝对值量化(`max-abs`),具体描述如下:
$$ M = max(abs(x)) $$ $$ q = \left \lfloor \frac{x}{M} * (n - 1) \right \rceil $$
式中,$x$是待被量化的浮点值,$M$是待量化浮点数中的绝对值最大值。$\left \lfloor \right \rceil$表示将结果四舍五入到最近的整数。对于8bit量化,PaddleSlim采用`int8_t`,即$n=2^7=128$。$q$是量化得到的整数。
无论是`min-max量化`还是`max-abs量化`,他们都可以表示为如下形式:
$q = scale * r + b$
其中`min-max``max-abs`被称为量化参数或者量化比例或者量化范围。
#### 1.2.2 量化训练框架
##### 1.2.2.1 前向传播
前向传播过程采用模拟量化的方式,具体描述如下:
<p align="center">
<img src="images/tutorial/quan_forward.png" height=433 width=335 hspace='10'/> <br />
<strong>图1:基于模拟量化训练的前向过程</strong>
</p>
由图1可知,基于模拟量化训练的前向过程可被描述为以下四个部分:
1) 输入和权重均被量化成8-bit整数。
2) 在8-bit整数上执行矩阵乘法或者卷积操作。
3) 反量化矩阵乘法或者卷积操作的输出结果为32-bit浮点型数据。
4) 在32-bit浮点型数据上执行偏置加法操作。此处,偏置并未被量化。
对于通用矩阵乘法(`GEMM`),输入$X$和权重$W$的量化操作可被表述为如下过程:
$$ X_q = \left \lfloor \frac{X}{X_m} * (n - 1) \right \rceil $$ $$ W_q = \left \lfloor \frac{W}{W_m} * (n - 1) \right \rceil $$
执行通用矩阵乘法:
$$ Y = X_q * W_q $$
反量化$Y$:
$$
\begin{align}
Y_{dq} = \frac{Y}{(n - 1) * (n - 1)} * X_m * W_m \
=\frac{X_q * W_q}{(n - 1) * (n - 1)} * X_m * W_m \
=(\frac{X_q}{n - 1} * X_m) * (\frac{W_q}{n - 1} * W_m) \
\end{align}
$$
上述公式表明反量化操作可以被移动到`GEMM`之前,即先对$Xq$和$Wq$执行反量化操作再做`GEMM`操作。因此,前向传播的工作流亦可表示为如下方式:
<p align="center">
<img src="images/tutorial/quan_fwd_1.png" height=435 width=341 hspace='10'/> <br />
<strong>图2:基于模拟量化训练前向过程的等价工作流</strong>
</p>
训练过程中,PaddleSlim使用图2中所示的等价工作流。在设计中,量化Pass在IrGraph中插入量化op和反量化op。因为在连续的量化、反量化操作之后输入仍然为32-bit浮点型数据。因此,PaddleSlim量化训练框架所采用的量化方式被称为模拟量化。
##### 1.2.2.2 反向传播
由图3可知,权重更新所需的梯度值可以由量化后的权重和量化后的激活求得。反向传播过程中的所有输入和输出均为32-bit浮点型数据。注意,梯度更新操作需要在原始权重上进行,即计算出的梯度将被加到原始权重上而非量化后或反量化后的权重上。
<p align="center">
<img src="images/tutorial/quan_bwd.png" height=300 width=650 hspace='10'/> <br />
<strong>图3:基于模拟量化训练的反向传播和权重更新过程</strong>
</p>
因此,量化Pass也会改变相应反向算子的某些输入。
#### 1.2.3 确定量化参数
存在着两种策略可以计算求取量化比例系数,即动态策略和静态策略。动态策略会在每次迭代过程中计算量化比例系数的值。静态策略则对不同的输入采用相同的量化比例系数。
对于权重而言,在训练过程中采用动态策略。换句话说,在每次迭代过程中量化比例系数均会被重新计算得到直至训练过程结束。
对于激活而言,可以选择动态策略也可以选择静态策略。若选择使用静态策略,则量化比例系数会在训练过程中被评估求得,且在推断过程中被使用(不同的输入均保持不变)。静态策略中的量化比例系数可于训练过程中通过如下三种方式进行评估:
1. 在一个窗口中计算激活最大绝对值的平均值。
2. 在一个窗口中计算激活最大绝对值的最大值。
3. 在一个窗口中计算激活最大绝对值的滑动平均值,计算公式如下:
$$ Vt = (1 - k) * V + k * V_{t-1} $$
式中,$V$ 是当前batch的最大绝对值, $Vt$是滑动平均值。$k$是一个因子,例如其值可取为0.9。
## 2. 卷积核剪切原理
该策略参考paper: [Pruning Filters for Efficient ConvNets](https://arxiv.org/pdf/1608.08710.pdf)
该策略通过减少卷积层中卷积核的数量,来减小模型大小和降低模型计算复杂度。
### 2.1 剪切卷积核
**剪切注意事项1**
剪切一个conv layer的filter,需要修改后续conv layer的filter. 如**图4**所示,剪掉Xi的一个filter,会导致$X_{i+1}$少一个channel, $X_{i+1}$对应的filter在input_channel纬度上也要减1.
<p align="center">
<img src="images/tutorial/pruning_0.png" height=200 width=600 hspace='10'/> <br />
<strong>图4</strong>
</p>
**剪切注意事项2**
**图5**所示,剪切完$X_i$之后,根据注意事项1我们从$X_{i+1}$的filter中删除了一行(图中蓝色行),在计算$X_{i+1}$的filters的l1_norm(图中绿色一列)的时候,有两种选择:
算上被删除的一行:independent pruning
减去被删除的一行:greedy pruning
<p align="center">
<img src="images/tutorial/pruning_1.png" height=200 width=450 hspace='10'/> <br />
<strong>图5</strong>
</p>
**剪切注意事项3**
在对ResNet等复杂网络剪切的时候,还要考虑到后当前卷积层的修改对上一层卷积层的影响。
**图6**所示,在对residual block剪切时,$X_{i+1}$层如何剪切取决于project shortcut的剪切结果,因为我们要保证project shortcut的output和$X_{i+1}$的output能被正确的concat.
<p align="center">
<img src="images/tutorial/pruning_2.png" height=240 width=600 hspace='10'/> <br />
<strong>图6</strong>
</p>
### 2.2 Uniform剪切卷积网络
每层剪切一样比例的卷积核。
在剪切一个卷积核之前,按l1_norm对filter从高到低排序,越靠后的filter越不重要,优先剪掉靠后的filter.
### 2.3 基于敏感度剪切卷积网络
根据每个卷积层敏感度的不同,剪掉不同比例的卷积核。
#### 两个假设
- 在一个conv layer的parameter内部,按l1_norm对filter从高到低排序,越靠后的filter越不重要。
- 两个layer剪切相同的比例的filters,我们称对模型精度影响更大的layer的敏感度相对高。
#### 剪切filter的指导原则
- layer的剪切比例与其敏感度成反比
- 优先剪切layer内l1_norm相对低的filter
#### 敏感度的理解
<p align="center">
<img src="images/tutorial/pruning_3.png" height=200 width=400 hspace='10'/> <br />
<strong>图7</strong>
</p>
**图7**所示,横坐标是将filter剪切掉的比例,竖坐标是精度的损失,每条彩色虚线表示的是网络中的一个卷积层。
以不同的剪切比例**单独**剪切一个卷积层,并观察其在验证数据集上的精度损失,并绘出**图7**中的虚线。虚线上升较慢的,对应的卷积层相对不敏感,我们优先剪不敏感的卷积层的filter.
#### 选择最优的剪切率组合
我们将**图7**中的折线拟合为**图8**中的曲线,每在竖坐标轴上选取一个精度损失值,就在横坐标轴上对应着一组剪切率,如**图8**中黑色实线所示。
用户给定一个模型整体的剪切率,我们通过移动**图5**中的黑色实线来找到一组满足条件的且合法的剪切率。
<p align="center">
<img src="images/tutorial/pruning_4.png" height=200 width=400 hspace='10'/> <br />
<strong>图8</strong>
</p>
#### 迭代剪切
考虑到多个卷积层间的相关性,一个卷积层的修改可能会影响其它卷积层的敏感度,我们采取了多次剪切的策略,步骤如下:
- step1: 统计各卷积层的敏感度信息
- step2: 根据当前统计的敏感度信息,对每个卷积层剪掉少量filter, 并统计FLOPS,如果FLOPS已满足要求,进入step4,否则进行step3。
- step3: 对网络进行简单的fine-tune,进入step1
- step4: fine-tune训练至收敛
## 3. 蒸馏
一般情况下,模型参数量越多,结构越复杂,其性能越好,但参数也越允余,运算量和资源消耗也越大;模型蒸馏是将复杂网络中的有用信息将复杂网络中的有用信息提取出来提取出来,迁移到一个更小的网络中去,在我们的工具包中,支持两种蒸馏的方法。
第一种是传统的蒸馏方法(参考论文:[Distilling the Knowledge in a Neural Network](https://arxiv.org/pdf/1503.02531.pdf)
使用复杂的网络作为teacher模型去监督训练一个参数量和运算量更少的student模型。teacher模型可以是一个或者多个提前训练好的高性能模型。student模型的训练有两个目标:一个是原始的目标函数,为student模型输出的类别概率和label的交叉熵,记为hard-target;另一个是student模型输出的类别概率和teacher模型输出的类别概率的交叉熵,记为soft target,这两个loss加权后得到最终的训练loss,共同监督studuent模型的训练。
第二种是基于FSP的蒸馏方法(参考论文:[A Gift from Knowledge Distillation:
Fast Optimization, Network Minimization and Transfer Learning](http://openaccess.thecvf.com/content_cvpr_2017/papers/Yim_A_Gift_From_CVPR_2017_paper.pdf)
相比传统的蒸馏方法直接用小模型去拟合大模型的输出,该方法用小模型去拟合大模型不同层特征之间的转换关系,其用一个FSP矩阵(特征的内积)来表示不同层特征之间的关系,大模型和小模型不同层之间分别获得多个FSP矩阵,然后使用L2 loss让小模型的对应层FSP矩阵和大模型对应层的FSP矩阵尽量一致,具体如下图所示。这种方法的优势,通俗的解释是,比如将蒸馏类比成teacher(大模型)教student(小模型)解决一个问题,传统的蒸馏是直接告诉小模型问题的答案,让小模型学习,而学习FSP矩阵是让小模型学习解决问题的中间过程和方法,因此其学到的信息更多。
<p align="center">
<img src="images/tutorial/distillation_0.png" height=300 width=600 hspace='10'/> <br />
<strong>图9</strong>
</p>
由于小模型和大模型之间通过L2 loss进行监督,必须保证两个FSP矩阵的维度必须相同,而FSP矩阵的维度为M*N,其中M、N分别为输入和输出特征的channel数,因此大模型和小模型的FSP矩阵需要一一对应。
## 4. 参考文献
1. [High-Performance Hardware for Machine Learning](https://media.nips.cc/Conferences/2015/tutorialslides/Dally-NIPS-Tutorial-2015.pdf)
2. [Quantizing deep convolutional networks for efficient inference: A whitepaper](https://arxiv.org/pdf/1806.08342.pdf)
3. [Pruning Filters for Efficient ConvNets](https://arxiv.org/pdf/1608.08710.pdf)
4. [Distilling the Knowledge in a Neural Network](https://arxiv.org/pdf/1503.02531.pdf)
5. [A Gift from Knowledge Distillation: Fast Optimization, Network Minimization and Transfer Learning](http://openaccess.thecvf.com/content_cvpr_2017/papers/Yim_A_Gift_From_CVPR_2017_paper.pdf)
<div align="center">
<h3>
<a href="tutorial.md">
算法原理介绍
</a>
<span> | </span>
<a href="demo.md">
示例文档
</a>
<span> | </span>
<a href="model_zoo.md">
Model Zoo
</a>
</h3>
</div>
---
# Paddle模型压缩工具库使用说明
本文第一章介绍PaddleSlim模块通用功能的使用,不涉及具体压缩策略的细节。第二、三、四章分别介绍量化训练、剪切、蒸馏三种压缩策略的使用方式。
建议在看具体策略使用方式之前,先浏览下对应的原理介绍:<a href="tutorial.md">算法原理介绍</a>
>在本文中不区分operator和layer的概念。不区分loss和cost的概念。
## 目录
- [通用功能使用说明](#1-paddleslim通用功能使用介绍)
- [量化使用说明](#21-量化训练)
- [剪切使用说明](#22-卷积核剪切)
- [蒸馏使用说明](#23-蒸馏)
## 1. PaddleSlim通用功能使用介绍
## 1.1 使用压缩工具库的前提
### 1.1.1 安装paddle
**版本:** PaddlePaddle >= 1.4
**安装教程:** [安装说明](http://paddlepaddle.org/documentation/docs/zh/1.3/beginners_guide/install/index_cn.html)
### 1.1.2 搭建好网络结构
用户需要搭建好前向网络,并可以正常执行。
一个正常可执行的网络一般需要以下内容或操作:
- 网络结构的定义
- data_reader
- optimizer
- 初始化,load pretrain model
- feed list与fetch list
#### 1.1.2.1 网络结构的定义
首先参考以下文档,配置网络:
[《Paddle使用指南:配置简单的网络》](http://paddlepaddle.org/documentation/docs/zh/1.3/user_guides/howto/configure_simple_model/index.html)
这一步的产出应该是两个[Program](http://paddlepaddle.org/documentation/docs/zh/1.3/api_cn/fluid_cn.html#program)实例:
- **train_program:** 用于在压缩过程中迭代训练模型,该program必须包含loss。一般改program不要有backward op和weights update op,否则不能使用蒸馏策略。
- **eval_program:** 用于在压缩过程中评估模型的精度,一般会包含accuracy、IoU等评估指标的计算layer。
>在量化训练策略中,会根据eval_program进行网络结构剪枝并保存一个用于inference的量化模型。这时候,就要求inference网络是eval_program的一个子网络。
#### 1.1.2.2. data_reader
按照以下文档准备数据:
[《Paddle使用指南:准备数据》](http://paddlepaddle.org/documentation/docs/zh/1.3/user_guides/howto/prepare_data/index.html)
这一步需要产出两个DataReader:
**train_reader:** 用于给train_program的执行提供数据
**eval_reader:** 用于给eval_program的执行提供数据
#### 1.1.2.3. optimizer
[fluid.optimizer API](http://www.paddlepaddle.org/documentation/docs/zh/1.3/api_cn/optimizer_cn.html)
在不同的使用场景下,用户需要提供0个、1个或2两个optimizer:
- **0个optimizer:** 在模型搭建阶段的train_program已经是一个包含了反向op和模型weight更新op的网络,则不用再提供optimizer
- **1个optimizer:** train_program只有前向计算op, 则需要提供一个optimizer,用于优化训练train_program.
- **2个optimizer:** 在使用蒸馏策略时,且蒸馏训练阶段和单独fine-tune阶段用不同的优化策略。一个optimizer用于优化训练teacher网络和student网络组成的蒸馏训练网络,另一个optimizer用于单独优化student网络。更多细节会在蒸馏策略使用文档中介绍。
#### 1.1.2.4. load pretrain model
- 剪切:需要加载pretrain model
- 蒸馏:根据需要选择是否加载pretrain model
- 量化训练:需要加载pretrain model
#### 1.1.2.5. feed list与fetch list
feed list和fetch list是两个有序的字典, 示例如下:
```
feed_list = [('image', image.name), ('label', label.name)]
fetch_list = [('loss', avg_cost.name)]
```
其中,feed_list中的key为自定义的有一定含义的字符串,value是[Variable](http://paddlepaddle.org/documentation/docs/zh/1.3/api_guides/low_level/program.html#variable)的名称, feed_list中的顺序需要和DataReader提供的数据的顺序对应。
对于train_program和eval_program都需要有与其对应的feed_list和fetch_list。
>注意: 在train_program对应的fetch_list中,loss variable(loss layer的输出)对应的key一定要是‘‘loss’’
## 1.2 压缩工具库的使用
经过1.1节的准备,所以压缩工具用到的关于目标模型的信息已经就绪,执行以下步骤配置并启动压缩任务:
- 改写模型训练脚本,加入模型压缩逻辑
- 编写配置文件
- 执行训练脚本进行模型压缩
### 1.2.1 如何改写普通训练脚本
在1.1节得到的模型脚本基础上做如下修改:
第一步: 构造`paddle.fluid.contrib.slim.Compressor`对象, Compressor构造方法参数说明如下:
```
Compressor(place,
scope,
train_program,
train_reader=None,
train_feed_list=None,
train_fetch_list=None,
eval_program=None,
eval_reader=None,
eval_feed_list=None,
eval_fetch_list=None,
teacher_programs=[],
checkpoint_path='./checkpoints',
train_optimizer=None,
distiller_optimizer=None)
```
- **place:** 压缩任务使用的device。GPU请使用[paddle.fluid.CUDAPlace(0)](http://paddlepaddle.org/documentation/docs/zh/1.3/api_cn/fluid_cn.html#paddle.fluid.CUDAPlace)
- **scope:** 如果在网络配置阶段没有构造scope,则用的是[global scope](http://paddlepaddle.org/documentation/docs/zh/1.3/api_cn/executor_cn.html#paddle.fluid.global_scope),该参数设置为`paddle.fluid.global_scope()`. 如果有自己构造scope,则设置为自己构造的scope.
- **train_program:** 该program内的网络只有前向operator,而且必须带有loss. 关于program的概念,请参考:[Program API](http://paddlepaddle.org/documentation/docs/zh/1.3/api_cn/fluid_cn.html#program)
- **train_reader:** 提供训练数据的[data reader](http://paddlepaddle.org/documentation/docs/zh/1.3/user_guides/howto/prepare_data/reader_cn.html)
- **train_feed_list:** 用于指定train program的输入节点, 详见:1.1.2.5节。
- **train_fetch_list:** 用于指定train program的输出节点,详见:1.1.2.5节。
- **eval_program:** 用于评估模型精度的program
- **eval_reader:** 提供评估数据的[data reader](http://paddlepaddle.org/documentation/docs/zh/1.3/user_guides/howto/prepare_data/reader_cn.html)
- **eval_feed_list:** 用于指定eval program的输入节点,详见:1.1.2.5节。
- **eval_fetch_list:** 用于指定eval program的输出节点, 格式同train_fetch_list, 详见:1.1.2.5节。
- **teacher_programs:** 用于蒸馏的programs, 这些program需要和train program共用同一个scope.
- **train_optimizer:** 用于训练train program的优化器
- **distiller_optimizer:** 用于蒸馏训练的优化器
第2步:读取配置文件和调用run方法,示例如下
```python
compressor.config('./compress.yaml')
compressor.run()
```
其中,compress.yaml文件是压缩策略配置文件,集中了压缩策略的所有可调节参数,在1.2.2节中会详细介绍其格式和内容。
完成该节操作后的完整示例见:[compress.py]()
### 1.2.2 配置文件的使用
模型压缩模块用[yaml](https://zh.wikipedia.org/wiki/YAML)文件集中管理可调节的压缩策略参数。我们以filter pruning为例,说明配置文件的编写方式。
第一步:注册pruners, 如下所示,指定pruner的类别和一些属性,后文**第5节**会详细介绍可选类别和属性的含义。
```python
pruners:
pruner_1:
class: 'StructurePruner'
pruning_axis:
'*': 0
criterions:
'*': 'l1_norm'
```
第二步:注册剪切策略
如下所示,我们注册两个uniform剪切策略,分别在第0个epoch和第10个epoch将模型的FLOPS剪掉10%.
```python
strategies:
pruning_strategy_0:
class: 'UniformPruneStrategy'
pruner: 'pruner_1'
start_epoch: 0
target_ratio: 0.10
pruned_params: '.*_sep_weights'
metric_name: 'acc_top1'
pruning_strategy_1:
class: 'UniformPruneStrategy'
pruner: 'pruner_1'
start_epoch: 10
target_ratio: 0.10
pruned_params: '.*_sep_weights'
metric_name: 'acc_top1'
```
第三步:配置通用参数
我们在compress_pass下配置整个压缩任务的参数,如下所示,整个压缩任务会执行120个epoch, 压缩过程中的checkpoint保存在./checkpoints路径下。compress_pass.strategies下为生效的压缩策略,如果生效的多个策略的start_epoch参数一样,则按compress_pass.strategies下列出的先后顺序被调用。
```python
compress_pass:
epoch: 120
checkpoint_path: './checkpoints/'
strategies:
- pruning_strategy_0
- pruning_strategy_1
```
## 2. 模型压缩策略使用介绍
本章依次介绍量化训练、卷积核剪切和蒸馏三种策略的使用方式,在此之前建议先浏览相应策略的原理介绍:
- [量化训练原理](tutorial.md#1-quantization-aware-training量化介绍)
- [卷积核剪切原理](tutorial.md#2-卷积核剪切原理)
- [蒸馏原理](tutorial.md#3-蒸馏)
### 2.1 量化训练
>注意:多个压缩策略组合使用时,量化训练策略必须放在最后。
```
class Compressor(object):
def __init__(self,
place,
scope,
train_program,
train_reader=None,
train_feed_list=None,
train_fetch_list=None,
eval_program=None,
eval_reader=None,
eval_feed_list=None,
eval_fetch_list=None,
teacher_programs=[],
checkpoint_path='./checkpoints',
train_optimizer=None,
distiller_optimizer=None):
```
在定义Compressor对象时,需要注意以下问题:
- train program如果带反向operators和优化更新相关的operators, train_optimizer需要设置为None.
- eval_program中parameter的名称需要与train_program中的parameter的名称完全一致。
- 最终保存的量化后的int8模型,是在eval_program网络基础上进行剪枝保存的,所以,如果用户希望最终保存的模型可以用于inference, 则eval program需要包含infer需要的各种operators.
- checkpoint保存的是float数据类型的模型
在配置文件中,配置量化训练策略发方法如下:
```
strategies:
quantization_strategy:
class: 'QuantizationStrategy'
start_epoch: 0
end_epoch: 10
float_model_save_path: './output/float'
mobile_model_save_path: './output/mobile'
int8_model_save_path: './output/int8'
weight_bits: 8
activation_bits: 8
weight_quantize_type: 'abs_max'
activation_quantize_type: 'abs_max'
save_in_nodes: ['image']
save_out_nodes: ['quan.tmp_2']
compressor:
epoch: 20
checkpoint_path: './checkpoints_quan/'
strategies:
- quantization_strategy
```
可配置参数有:
- **class:** 量化策略的类名称,目前仅支持`QuantizationStrategy`
- **start_epoch:** 在start_epoch开始之前,量化训练策略会往train_program和eval_program插入量化operators和反量化operators. 从start_epoch开始,进入量化训练阶段。
- **end_epoch:** 在end_epoch结束之后,会保存用户指定格式的模型。注意:end_epoch之后并不会停止量化训练,而是继续训练到compressor.epoch为止。
- **float_model_save_path:** 保存float数据格式模型的路径。模型weight的实际大小在int8可表示范围内,但是是以float格式存储的。如果设置为None, 则不存储float格式的模型。默认为None.
- **int8_model_save_path:** 保存int8数据格式模型的路径。如果设置为None, 则不存储int8格式的模型。默认为None.
- **mobile_model_save_path:** 保存兼容paddle-mobile框架的模型的路径。如果设置为None, 则不存储mobile格式的模型。默认为None.
- **weight_bits:** 量化weight的bit数,bias不会被量化。
- **activation_bits:** 量化activation的bit数。
- **weight_quantize_type:** 对于weight的量化方式,目前支持'abs_max', 'channel_wise_abs_max'.
- **activation_quantize_type:** 对activation的量化方法,目前可选`abs_max``range_abs_max``abs_max`意为在训练的每个step和inference阶段动态的计算量化范围。`range_abs_max`意为在训练阶段计算出一个静态的范围,并将其用于inference阶段。
- **save_in_nodes:** variable名称列表。在保存量化后模型的时候,需要根据save_in_nodes对eval programg 网络进行前向遍历剪枝。默认为eval_feed_list内指定的variable的名称列表。
- **save_out_nodes:** varibale名称列表。在保存量化后模型的时候,需要根据save_out_nodes对eval programg 网络进行回溯剪枝。默认为eval_fetch_list内指定的variable的名称列表。
### 2.2 卷积核剪切
该策略通过减少指定卷积层中卷积核的数量,达到缩减模型大小和计算复杂度的目的。根据选取剪切比例的策略的不同,又细分为以下两个方式:
- uniform pruning: 每层剪切掉相同比例的卷积核数量。
- sensitive pruning: 根据每层敏感度,剪切掉不同比例的卷积核数量。
两种剪切方式都需要加载预训练模型。
卷积核剪切是基于结构剪切,所以在配置文件中需要注册一个`StructurePruner`, 如下所示:
```
pruners:
pruner_1:
class: 'StructurePruner'
pruning_axis:
'*': 0
criterions:
'*': 'l1_norm'
```
其中,一个配置文件可注册多个pruners, 所有pruner需要放在`pruners`关键字下, `pruner`的可配置参数有:
- **class:** pruner 的类型,目前只支持`StructurePruner`
- **pruning_axis:** 剪切的纬度;'`conv*': 0`表示对所有的卷积层filter weight的第0维进行剪切,即对卷积层filter的数量进行剪切。
- **criterions**: 通过通配符指定剪切不同parameter时用的排序方式。目前仅支持`l1_norm`.
#### 2.2.1 uniform pruning
uniform pruning剪切策略需要在配置文件的`strategies`关键字下注册`UniformPruneStrategy`实例,并将其添加至compressor的strategies列表中。
如下所示:
```
strategies:
uniform_pruning_strategy:
class: 'UniformPruneStrategy'
pruner: 'pruner_1'
start_epoch: 0
target_ratio: 0.5
pruned_params: '.*_sep_weights'
compressor:
epoch: 100
strategies:
- uniform_pruning_strategy
```
UniformPruneStrategy的可配置参数有:
- **class:** 如果使用Uniform剪切策略,请设置为`UniformPruneStrategy`
- **pruner:** StructurePruner实例的名称,需要在配置文件中注册。在pruner中指定了对单个parameter的剪切方式。
- **start_epoch:** 开始剪切策略的epoch. 在start_epoch开始之前,该策略会对网络中的filter数量进行剪切,从start_epoch开始对被剪切的网络进行fine-tune训练,直到整个压缩任务结束。
- **target_ratio:** 将目标网络的FLOPS剪掉的比例。
- **pruned_params:** 被剪切的parameter的名称,支持通配符。如,‘*’为对所有parameter进行剪切,‘conv*’意为对所有名义以‘conv’开头的parameter进行剪切。
#### 2.2.2 sensitive pruning
sensitive剪切策略需要在配置文件的`strategies`关键字下注册`SensitivePruneStrategy`实例,并将其添加至compressor的strategies列表中。
如下所示:
```
strategies:
sensitive_pruning_strategy:
class: 'SensitivePruneStrategy'
pruner: 'pruner_1'
start_epoch: 0
delta_rate: 0.1
target_ratio: 0.5
num_steps: 1
eval_rate: 0.2
pruned_params: '.*_sep_weights'
sensitivities_file: 'mobilenet_acc_top1_sensitive.data'
metric_name: 'acc_top1'
compressor:
epoch: 200
strategies:
- sensitive_pruning_strategy
```
SensitivePruneStrategy可配置的参数有:
- **class:** 如果使用敏感度剪切策略,请设置为`SensitivePruneStrategy`
- **pruner:** StructurePruner实例的名称,需要在配置文件中注册。在pruner中指定了对单个parameter的剪切方式。
- **start_epoch:** 开始剪切策略的epoch。 在start_epoch开始之前,该策略会对网络中的filter数量进行第一次剪切。
- **delta_rate:** 统计敏感度信息时,剪切率从0到1,依次递增delta_rate. 具体细节可参考[原理介绍文档]()
- **target_ratio:** 将目标网络的FLOPS剪掉的比例。
- **num_steps:** 整个剪切过程的步数。每次迭代剪掉的比例为:$step = 1 - (1-target\_ratio)^{\frac{1}{num\_steps}}$
- **eval_rate:** 计算敏感度时,随机抽取使用的验证数据的比例。在迭代剪切中,为了快速重新计算每一步的每个parameter的敏感度,建议随机选取部分验证数据进行计算。当`num_steps`等于1时,建议使用全量数据进行计算。
### 2.3 蒸馏
PaddleSlim支持`FSP_loss`, `L2_loss``softmax_with_cross_entropy_loss`, 用户可以在配置文件中,用这三种loss组合teacher net和student net的任意一层。
与其它策略不同,如果要使用蒸馏策略,用户在脚本中构造Compressor对象时,需要指定teacher program 和distiller optimizer.
其中,teacher program有以下要求:
- teacher program需要加载预训练好的模型。
- teacher program中的变量不能与student program中的变量有命名冲突。
- teacher program中只有前向计算operators, 不能有backward operators。
- 用户不必手动设置teacher program的stop_gradient属性(不计算gradient和不更新weight),PaddleSlim会自动将其设置为True.
distiller optimizer用来为student net和teacher net组合而成的网络添加反向operators和优化相关的operators, 仅用于蒸馏训练阶段。
在配置文件中,配置蒸馏策略方式如下:
```
strategies:
distillation_strategy:
class: 'DistillationStrategy'
distillers: ['fsp_distiller', 'l2_distiller']
start_epoch: 0
end_epoch: 130
```
其中, 需要在关键字`strategies`下注册策略实例,可配置参数有:
- **class:** 策略类的名称,蒸馏策略请设置为DistillationStrategy。
- **distillers:** 一个distiller列表,列表中每一个distiller代表了student net和teacher net之间的一个组合loss。该策略会将这个列表中定义的loss加在一起作为蒸馏训练阶段优化的目标。 distiller需要提前在当前配置文件中进行注册,下文会详细介绍其注册方法。
- **start_epoch:** 在start_epoch开始之前,该策略会根据用户定义的losses将teacher net合并到student net中,并根据合并后的loss添加反向计算操作和优化更新操作。
- **end_epoch:** 在 end_epoch结束之后,该策略去将teacher net从student net中删除,并回复student net的loss. 在次之后,进入单独fine-tune student net的阶段。
distiller的配置方式如下:
**FSPDistiller**
```
distillers:
fsp_distiller:
class: 'FSPDistiller'
teacher_pairs: [['res2a_branch2a.conv2d.output.1.tmp_0', 'res3a_branch2a.conv2d.output.1.tmp_0']]
student_pairs: [['depthwise_conv2d_1.tmp_0', 'conv2d_3.tmp_0']]
distillation_loss_weight: 1
```
- **class:** distiller类名称,可选:`FSPDistiller``L2Distiller``SoftLabelDistiller`
- **teacher_pairs:** teacher网络对应的sections. 列表中每一个section由两个variable name表示,这两个variable代表网络中的两个feature map. 这两个feature map可以有不同的channel数量,但是必须有相同的长和宽。
- **student_pairs:** student网络对应的sections. student_pairs[i]与teacher_pairs[i]计算出一个fsp loss.
- **distillation_loss_weight:** 当前定义的fsp loss对应的权重。默认为1.0
**L2-loss**
```
distillers:
l2_distiller:
class: 'L2Distiller'
teacher_feature_map: 'fc_1.tmp_0'
student_feature_map: 'fc_0.tmp_0'
distillation_loss_weight: 1
```
- **teacher_feature_map:** teacher网络中用于计算l2 loss的feature map
- **student_feature_map:** student网络中用于计算l2 loss的feature map, shape必须与`teacher_feature_map`完全一致。
**SoftLabelDistiller**
```
distillers:
soft_label_distiller:
class: 'SoftLabelDistiller'
student_temperature: 1.0
teacher_temperature: 1.0
teacher_feature_map: 'teacher.tmp_1'
student_feature_map: 'student.tmp_1'
distillation_loss_weight: 0.001
```
- **teacher_feature_map:** teacher网络中用于计算softmax_with_cross_entropy的feature map。
- **student_feature_map:** student网络中用于计算softmax_with_cross_entropy的feature map。shape必须与`teacher_feature_map`完全一致。
- **student_temperature:** 在计算softmax_with_cross_entropy之前,用该系数除student_feature_map。
- **teacher_temperature:** 在计算softmax_with_cross_entropy之前,用该系数除teacher_feature_map。
- **distillation_loss_weight:** 当前定义的loss对应的权重。默认为1.0
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册