提交 9a10a366 编写于 作者: L Liufang Sang 提交者: whs

fix icafe 3107 3104 3085 about quantization (#3889)

上级 532ebad1
......@@ -4,7 +4,7 @@
## 概述
该示例使用PaddleSlim提供的[量化压缩策略](https://github.com/PaddlePaddle/models/blob/develop/PaddleSlim/docs/tutorial.md#1-quantization-aware-training%E9%87%8F%E5%8C%96%E4%BB%8B%E7%BB%8D)分类模型进行压缩。
该示例使用PaddleSlim提供的[量化压缩策略](https://github.com/PaddlePaddle/models/blob/develop/PaddleSlim/docs/tutorial.md#1-quantization-aware-training%E9%87%8F%E5%8C%96%E4%BB%8B%E7%BB%8D)检测模型进行压缩。
在阅读该示例前,建议您先了解以下内容:
- [检测模型的常规训练方法](https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/PaddleDetection)
......@@ -41,10 +41,11 @@
step1: 设置gpu卡
```
export CUDA_VISIBLE_DEVICES=0
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
```
step2: 开始训练
使用PaddleDetection提供的配置文件在用8卡进行训练:
使用PaddleDetection提供的配置文件用8卡进行训练:
```
python compress.py \
......
......@@ -49,7 +49,7 @@ from ppdet.data.data_feed import create_reader
from ppdet.utils.eval_utils import parse_fetches, eval_results
from ppdet.utils.stats import TrainingStats
from ppdet.utils.cli import ArgsParser, print_total_cfg
from ppdet.utils.check import check_gpu, check_version
from ppdet.utils.check import check_gpu
import ppdet.utils.checkpoint as checkpoint
from ppdet.modeling.model_input import create_feed
......@@ -121,8 +121,7 @@ def main():
# check if set use_gpu=True in paddlepaddle cpu version
check_gpu(cfg.use_gpu)
# print_total_cfg(cfg)
#check_version()
if cfg.use_gpu:
devices_num = fluid.core.get_cuda_device_count()
else:
......
......@@ -195,19 +195,6 @@ def main():
model_filename='model',
params_filename='weights')
logger.info("convert the freezed pass to paddle-lite execution")
mobile_pass = TransformForMobilePass()
mobile_pass.apply(test_graph)
mobile_program = test_graph.to_program()
fluid.io.save_inference_model(
dirname=os.path.join(FLAGS.save_path, 'mobile'),
feeded_var_names=feed_names,
target_vars=fetch_targets,
executor=exe,
main_program=mobile_program,
model_filename='model',
params_filename='weights')
if __name__ == '__main__':
parser = ArgsParser()
......
......@@ -5,7 +5,6 @@ strategies:
start_epoch: 0
end_epoch: 4
float_model_save_path: './output/yolov3/float'
mobile_model_save_path: './output/yolov3/mobile'
int8_model_save_path: './output/yolov3/int8'
weight_bits: 8
activation_bits: 8
......
......@@ -33,20 +33,23 @@ add_arg('model_name', str, "__model__", "model filename for inference model")
add_arg('params_name', str, "__params__", "params filename for inference model")
# yapf: enable
def eval(args):
# parameters from arguments
place = fluid.CUDAPlace(0) if args.use_gpu else fluid.CPUPlace()
exe = fluid.Executor(place)
val_program, feed_target_names, fetch_targets = fluid.io.load_inference_model(args.model_path,
exe,
model_filename=args.model_name,
params_filename=args.params_name)
val_program, feed_target_names, fetch_targets = fluid.io.load_inference_model(
args.model_path,
exe,
model_filename=args.model_name,
params_filename=args.params_name)
val_reader = paddle.batch(reader.val(), batch_size=128)
feeder = fluid.DataFeeder(place=place, feed_list=feed_target_names, program=val_program)
feeder = fluid.DataFeeder(
place=place, feed_list=feed_target_names, program=val_program)
results=[]
results = []
for batch_id, data in enumerate(val_reader()):
# top1_acc, top5_acc
......@@ -56,8 +59,8 @@ def eval(args):
label = [[d[1]] for d in data]
feed_data = feeder.feed(image)
pred = exe.run(val_program,
feed=feed_data,
fetch_list=fetch_targets)
feed=feed_data,
fetch_list=fetch_targets)
pred = np.array(pred[0])
label = np.array(label)
sort_array = pred.argsort(axis=1)
......@@ -68,23 +71,25 @@ def eval(args):
for i in range(len(label)):
if label[i][0] in top_5_pred[i]:
acc_num += 1
top_5 = acc_num / len(label)
top_5 = float(acc_num) / len(label)
results.append([top_1, top_5])
else:
# eval "eval model", which inputs are image and label, output is top1 and top5 accuracy
result = exe.run(val_program,
feed=feeder.feed(data),
fetch_list=fetch_targets)
feed=feeder.feed(data),
fetch_list=fetch_targets)
result = [np.mean(r) for r in result]
results.append(result)
result = np.mean(np.array(results), axis=0)
print("top1_acc/top5_acc= {}".format(result))
sys.stdout.flush()
def main():
args = parser.parse_args()
print_arguments(args)
eval(args)
if __name__ == '__main__':
main()
>运行该示例前请安装Paddle1.6或更高版本
>运行该示例前请安装Paddle1.6或更高版本。 本示例中的run.sh脚本仅适用于linux系统,在windows环境下,请参考run.sh内容编写适合windows环境的脚本。
# 分类模型量化压缩示例
......@@ -40,7 +40,7 @@ cost = fluid.layers.cross_entropy(input=out, label=label)
- use_gpu: 是否使用gpu。如果选择使用GPU,请确保当前环境和Paddle版本支持GPU。默认为True。
- batch_size: 在量化之后,对模型进行fine-tune训练时用的batch size。
- model: 要压缩的目标模型,该示例支持'MobileNet', 'MobileNetV2'和'ResNet50'。
- model: 要压缩的目标模型,该示例支持'MobileNet', 'MobileNetV2'和'ResNet34'。
- pretrained_model: 预训练模型的路径,可以从[这里](https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification#%E5%B7%B2%E5%8F%91%E5%B8%83%E6%A8%A1%E5%9E%8B%E5%8F%8A%E5%85%B6%E6%80%A7%E8%83%BD)下载。
- config_file: 压缩策略的配置文件。
......@@ -49,7 +49,7 @@ cost = fluid.layers.cross_entropy(input=out, label=label)
### 训练时的模型结构
这部分介绍来源于[量化low-level API介绍](https://github.com/PaddlePaddle/models/tree/develop/PaddleSlim/quant_low_level_api#1-%E9%87%8F%E5%8C%96%E8%AE%AD%E7%BB%83low-level-apis%E4%BB%8B%E7%BB%8D)
PaddlePaddle框架中有四个和量化相关的IrPass, 分别是QuantizationTransformPass、QuantizationFreezePass、ConvertToInt8Pass以及TransformForMobilePass。在训练时,对网络应用了QuantizationTransformPass,作用是在网络中的conv2d、depthwise_conv2d、mul等算子的各个输入前插入连续的量化op和反量化op,并改变相应反向算子的某些输入。示例图如下:
PaddlePaddle框架中和量化相关的IrPass有QuantizationTransformPass、QuantizationFreezePass、ConvertToInt8Pass。在训练时,对网络应用了QuantizationTransformPass,作用是在网络中的conv2d、depthwise_conv2d、mul等算子的各个输入前插入连续的量化op和反量化op,并改变相应反向算子的某些输入。示例图如下:
<p align="center">
<img src="../../docs/images/usage/TransformPass.png" height=400 width=520 hspace='10'/> <br />
......@@ -65,10 +65,10 @@ PaddlePaddle框架中有四个和量化相关的IrPass, 分别是QuantizationTra
>注意:配置文件中的信息不会保存在断点中,重启前对配置文件的修改将会生效。
### 保存评估和预测模型
如果在配置文件的量化策略中设置了`float_model_save_path`, `int8_model_save_path`, `mobile_model_save_path`, 在训练结束后,会保存模型量化压缩之后用于评估和预测的模型。接下来介绍这三种模型的区别。
如果在配置文件的量化策略中设置了`float_model_save_path`, `int8_model_save_path`,在训练结束后,会保存模型量化压缩之后用于评估和预测的模型。接下来介绍这2种模型的区别。
#### FP32模型
在介绍量化训练时的模型结构时介绍了PaddlePaddle框架中有四个和量化相关的IrPass, 分别是QuantizationTransformPass、QuantizationFreezePass、ConvertToInt8Pass以及TransformForMobilePass。FP32预测模型是在应用QuantizationFreezePass并删除eval_program中多余的operators之后,保存的模型。
在介绍量化训练时的模型结构时介绍了PaddlePaddle框架中和量化相关的IrPass, 有QuantizationTransformPass、QuantizationFreezePass、ConvertToInt8Pass。FP32预测模型是在应用QuantizationFreezePass并删除eval_program中多余的operators之后,保存的模型。
QuantizationFreezePass主要用于改变IrGraph中量化op和反量化op的顺序,即将类似图1中的量化op和反量化op顺序改变为图2中的布局。除此之外,QuantizationFreezePass还会将`conv2d``depthwise_conv2d``mul`等算子的权重离线量化为int8_t范围内的值(但数据类型仍为float32),以减少预测过程中对权重的量化操作,示例如图2:
......@@ -86,20 +86,13 @@ QuantizationFreezePass主要用于改变IrGraph中量化op和反量化op的顺
<strong>图3:应用ConvertToInt8Pass后的结果</strong>
</p>
#### mobile模型
经TransformForMobilePass转换后,用户可得到兼容[paddle-lite](https://github.com/PaddlePaddle/Paddle-Lite)移动端预测库的量化模型。paddle-mobile中的量化op和反量化op的名称分别为`quantize``dequantize``quantize`算子和PaddlePaddle框架中的`fake_quantize_abs_max`算子簇的功能类似,`dequantize` 算子和PaddlePaddle框架中的`fake_dequantize_max_abs`算子簇的功能相同。若选择paddle-mobile执行量化训练输出的模型,则需要将`fake_quantize_abs_max`等算子改为`quantize`算子以及将`fake_dequantize_max_abs`等算子改为`dequantize`算子,示例如图4:
<p align="center">
<img src="../../docs/images/usage/TransformForMobilePass.png" height=400 width=400 hspace='10'/> <br />
<strong>图4:应用TransformForMobilePass后的结果</strong>
</p>
> 综上,可得在量化过程中有以下几种模型结构:
1. 原始模型
2. 经QuantizationTransformPass之后得到的适用于训练的量化模型结构,在${checkpoint_path}下保存的`eval_model`是这种结构,在训练过程中每个epoch结束时也使用这个网络结构进行评估,虽然这个模型结构不是最终想要的模型结构,但是每个epoch的评估结果可用来挑选模型。
3. 经QuantizationFreezePass之后得到的FP32模型结构,具体结构已在上面进行介绍。本文档中列出的数据集的评估结果是对FP32模型结构进行评估得到的结果。这种模型结构在训练过程中只会保存一次,也就是在量化配置文件中设置的`end_epoch`结束时进行保存,如果想将其他epoch的训练结果转化成FP32模型,可使用脚本 <a href='./freeze.py'>PaddleSlim/classification/quantization/freeze.py</a>进行转化,具体使用方法在[评估](#评估)中介绍。
4. 经ConvertToInt8Pass之后得到的8-bit模型结构,具体结构已在上面进行介绍。这种模型结构在训练过程中只会保存一次,也就是在量化配置文件中设置的`end_epoch`结束时进行保存,如果想将其他epoch的训练结果转化成8-bit模型,可使用脚本 <a href='./freeze.py'>PaddleSlim/classification/quantization/freeze.py</a>进行转化,具体使用方法在[评估](#评估)中介绍。
5. 经TransformForMobilePass之后得到的mobile模型结构,具体结构已在上面进行介绍。这种模型结构在训练过程中只会保存一次,也就是在量化配置文件中设置的`end_epoch`结束时进行保存,如果想将其他epoch的训练结果转化成mobile模型,可使用脚本 <a href='./freeze.py'>PaddleSlim/classification/quantization/freeze.py</a>进行转化,具体使用方法在[评估](#评估)中介绍。
## 评估
......@@ -120,11 +113,11 @@ python eval.py \
--model_path ${checkpoint_path}/${epoch_id}/eval_model
```
在评估之后,选取效果最好的epoch的模型,可使用脚本 <a href='./freeze.py'>PaddleSlim/classification/quantization/freeze.py</a>将该模型转化为以上介绍的三种模型:FP32模型,8-bit模型,mobile模型,需要配置的参数为:
在评估之后,选取效果最好的epoch的模型,可使用脚本 <a href='./freeze.py'>PaddleSlim/classification/quantization/freeze.py</a>将该模型转化为以上介绍的2种模型:FP32模型,8-bit模型,需要配置的参数为:
- model_path, 加载的模型路径,`为${checkpoint_path}/${epoch_id}/eval_model/`
- weight_quant_type 模型参数的量化方式,和配置文件中的类型保持一致
- save_path `FP32`, `8-bit`, `mobile`模型的保存路径,分别为 `${save_path}/float/`, `${save_path}/int8/`, `${save_path}/mobile/`
- save_path `FP32`, `8-bit`模型的保存路径,分别为 `${save_path}/float/`, `${save_path}/int8/`
运行命令示例:
```
......@@ -166,11 +159,43 @@ python infer.py \
### PaddleLite预测
FP32模型可使用Paddle-Lite进行加载预测,可参见教程[Paddle-Lite如何加载运行量化模型](https://github.com/PaddlePaddle/Paddle-Lite/wiki/model_quantization)
mobile预测模型兼容Paddle-Lite(Paddle-Mobile的升级版), 使用方法可参考[Paddle-Lite文档](https://paddlepaddle.github.io/Paddle-Lite/).
## 如何进行部分量化
通过在定义op时指定 ``name_scope````skip_quant``可对这个op跳过量化。比如在<a href="../models/resnet.py">PaddleSlim/classification/models/resnet.py</a>中,将某个conv的定义作如下改变:
原定义:
```
conv = self.conv_bn_layer(
input=input,
num_filters=64,
filter_size=7,
stride=2,
act='relu',
name=prefix_name + conv1_name)
```
跳过量化时的定义:
```
with fluid.name_scope('skip_quant'):
conv = self.conv_bn_layer(
input=input,
num_filters=64,
filter_size=7,
stride=2,
act='relu',
name=prefix_name + conv1_name)
```
在脚本 <a href="./compress.py">PaddleSlim/classification/quantization/compress.py</a> 中,统计了``conv`` op的数量和以``fake_quantize``开头的量化op的数量,在对一些``conv`` op跳过之后,可发现以``fake_quantize``开头的量化op的数量变少。
## 示例结果
>当前release的结果并非超参调优后的最好结果,仅做示例参考,后续我们会优化当前结果。
### MobileNetV1
| weight量化方式 | activation量化方式| top1_acc/top5_acc |Paddle Fluid inference time(ms)| Paddle Lite inference time(ms)| 模型下载|
......@@ -203,14 +228,23 @@ fluid.optimizer.Momentum(momentum=0.9,
>训练超参:
### ResNet50
优化器
```
fluid.optimizer.Momentum(momentum=0.9,
learning_rate=fluid.layers.piecewise_decay(
boundaries=[5000 * 12],
values=[0.0001, 0.00001]),
regularization=fluid.regularizer.L2Decay(1e-4))
```
8卡,batch size 1024,epoch 30, 挑选好的结果
### ResNet34
| weight量化方式 | activation量化方式| top1_acc/top5_acc |Paddle Fluid inference time(ms)| Paddle Lite inference time(ms)|模型下载|
|---|---|---|---|---|---|
|baseline|- |76.50%/93.00%|- |-|[下载模型](http://paddle-imagenet-models-name.bj.bcebos.com/ResNet50_pretrained.tar)|
|abs_max|abs_max|76.71%/93.10% |- |-|[下载模型](https://paddle-slim-models.bj.bcebos.com/quantization%2Fresnet50_w_abs_a_abs_7670_9310.tar.gz)|
|abs_max|moving_average_abs_max|76.65%/93.12% |- |-|[下载模型](https://paddle-slim-models.bj.bcebos.com/quantization%2Fresnet50_w_abs_a_move_7665_9312.tar.gz) |
|channel_wise_abs_max|abs_max|76.56%/93.05% |- |-| [下载模型](https://paddle-slim-models.bj.bcebos.com/quantization%2Fresnet50_w_chan_a_abs_7656_9304.tar.gz)|
|baseline|- |74.57%/92.14%|- |-|-|
|abs_max|abs_max||- |-|-|
|abs_max|moving_average_abs_max||- |-|-|
|channel_wise_abs_max|abs_max||- |-| -|
>训练超参:
......
......@@ -38,8 +38,9 @@ def compress(args):
image_shape = "3,224,224"
image_shape = [int(m) for m in image_shape.split(",")]
image = fluid.layers.data(name='image', shape=image_shape, dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
image = fluid.data(
name='image', shape=[None] + image_shape, dtype='float32')
label = fluid.data(name='label', shape=[None, 1], dtype='int64')
# model definition
model = models.__dict__[args.model]()
......@@ -95,10 +96,21 @@ def compress(args):
eval_fetch_list=val_fetch_list,
teacher_programs=[],
train_optimizer=opt,
prune_infer_model=[[image.name], [out.name]],
distiller_optimizer=None)
com_pass.config(args.config_file)
com_pass.run()
conv_op_num = 0
fake_quant_op_num = 0
for op in com_pass.context.eval_graph.ops():
if op._op.type == 'conv2d':
conv_op_num += 1
elif op._op.type.startswith('fake_quantize'):
fake_quant_op_num += 1
print('conv op num {}'.format(conv_op_num))
print('fake quant op num {}'.format(fake_quant_op_num))
def main():
args = parser.parse_args()
......
......@@ -5,7 +5,6 @@ strategies:
start_epoch: 0
end_epoch: 29
float_model_save_path: './output/mobilenet_v1/float'
mobile_model_save_path: './output/mobilenet_v1/mobile'
int8_model_save_path: './output/mobilenet_v1/int8'
weight_bits: 8
activation_bits: 8
......
......@@ -5,7 +5,6 @@ strategies:
start_epoch: 0
end_epoch: 29
float_model_save_path: './output/mobilenet_v2/float'
mobile_model_save_path: './output/mobilenet_v2/mobile'
int8_model_save_path: './output/mobilenet_v2/int8'
weight_bits: 8
activation_bits: 8
......
......@@ -3,9 +3,8 @@ strategies:
quantization_strategy:
class: 'QuantizationStrategy'
start_epoch: 0
end_epoch: 29
end_epoch: 0
float_model_save_path: './output/resnet34/float'
mobile_model_save_path: './output/resnet34/mobile'
int8_model_save_path: './output/resnet34/int8'
weight_bits: 8
activation_bits: 8
......@@ -14,7 +13,7 @@ strategies:
save_in_nodes: ['image']
save_out_nodes: ['fc_0.tmp_2']
compressor:
epoch: 30
epoch: 1
checkpoint_path: './checkpoints/resnet34/'
strategies:
- quantization_strategy
......@@ -45,81 +45,83 @@ add_arg('save_path', str, './output', 'Path to save inference model')
add_arg('weight_quant_type', str, 'abs_max', 'quantization type for weight')
# yapf: enable
def eval(args):
# parameters from arguments
place = fluid.CUDAPlace(0) if args.use_gpu else fluid.CPUPlace()
exe = fluid.Executor(place)
val_program, feed_names, fetch_targets = fluid.io.load_inference_model(args.model_path,
exe,
model_filename="__model__",
params_filename="__params__")
val_program, feed_names, fetch_targets = fluid.io.load_inference_model(
args.model_path,
exe,
model_filename="__model__.infer",
params_filename="__params__")
val_reader = paddle.batch(reader.val(), batch_size=128)
feeder = fluid.DataFeeder(place=place, feed_list=feed_names, program=val_program)
feeder = fluid.DataFeeder(
place=place, feed_list=feed_names, program=val_program)
results=[]
results = []
for batch_id, data in enumerate(val_reader()):
image = [[d[0]] for d in data]
label = [[d[1]] for d in data]
feed_data = feeder.feed(image)
pred = exe.run(val_program, feed=feed_data, fetch_list=fetch_targets)
pred = np.array(pred[0])
label = np.array(label)
sort_array = pred.argsort(axis=1)
top_1_pred = sort_array[:, -1:][:, ::-1]
top_1 = np.mean(label == top_1_pred)
top_5_pred = sort_array[:, -5:][:, ::-1]
acc_num = 0
for i in range(len(label)):
if label[i][0] in top_5_pred[i]:
acc_num += 1
top_5 = acc_num / len(label)
results.append([top_1, top_5])
# top1_acc, top5_acc
result = exe.run(val_program,
feed=feeder.feed(data),
fetch_list=fetch_targets)
result = [np.mean(r) for r in result]
results.append(result)
result = np.mean(np.array(results), axis=0)
print("top1_acc/top5_acc= {}".format(result))
sys.stdout.flush()
_logger.info("freeze the graph for inference")
test_graph = IrGraph(core.Graph(val_program.desc), for_test=True)
freeze_pass = QuantizationFreezePass(
scope=fluid.global_scope(),
place=place,
weight_quantize_type=args.weight_quant_type)
scope=fluid.global_scope(),
place=place,
weight_quantize_type=args.weight_quant_type)
freeze_pass.apply(test_graph)
server_program = test_graph.to_program()
fluid.io.save_inference_model(
dirname=os.path.join(args.save_path, 'float'),
feeded_var_names=feed_names,
target_vars=fetch_targets,
executor=exe,
main_program=server_program,
model_filename='model',
params_filename='weights')
dirname=os.path.join(args.save_path, 'float'),
feeded_var_names=feed_names,
target_vars=fetch_targets,
executor=exe,
main_program=server_program,
model_filename='model',
params_filename='weights')
_logger.info("convert the weights into int8 type")
convert_int8_pass = ConvertToInt8Pass(
scope=fluid.global_scope(),
place=place)
scope=fluid.global_scope(), place=place)
convert_int8_pass.apply(test_graph)
server_int8_program = test_graph.to_program()
fluid.io.save_inference_model(
dirname=os.path.join(args.save_path, 'int8'),
feeded_var_names=feed_names,
target_vars=fetch_targets,
executor=exe,
main_program=server_int8_program,
model_filename='model',
params_filename='weights')
_logger.info("convert the freezed pass to paddle-lite execution")
mobile_pass = TransformForMobilePass()
mobile_pass.apply(test_graph)
mobile_program = test_graph.to_program()
fluid.io.save_inference_model(
dirname=os.path.join(args.save_path, 'mobile'),
feeded_var_names=feed_names,
target_vars=fetch_targets,
executor=exe,
main_program=mobile_program,
model_filename='model',
params_filename='weights')
dirname=os.path.join(args.save_path, 'int8'),
feeded_var_names=feed_names,
target_vars=fetch_targets,
executor=exe,
main_program=server_int8_program,
model_filename='model',
params_filename='weights')
def main():
args = parser.parse_args()
print_arguments(args)
eval(args)
if __name__ == '__main__':
main()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册