From 6dae3b7d31bfb13299c33abacf5a0f4ecc72e8b9 Mon Sep 17 00:00:00 2001 From: whs Date: Wed, 25 Sep 2019 12:16:42 +0800 Subject: [PATCH] [PaddleSlim]Add pruning demo for classification (#3383) * Add pruning demo for classification --- PaddleSlim/__init__.py | 0 PaddleSlim/classification/__init__.py | 0 PaddleSlim/classification/eval.py | 66 +++++ PaddleSlim/classification/imagenet_reader.py | 193 +++++++++++++ PaddleSlim/classification/infer.py | 63 +++++ PaddleSlim/classification/models/__init__.py | 5 + PaddleSlim/classification/models/mobilenet.py | 197 ++++++++++++++ .../classification/models/mobilenet_v2.py | 253 ++++++++++++++++++ PaddleSlim/classification/models/resnet.py | 165 ++++++++++++ PaddleSlim/classification/pruning/README.md | 155 +++++++++++ PaddleSlim/classification/pruning/__init__.py | 0 PaddleSlim/classification/pruning/compress.py | 103 +++++++ .../pruning/configs/mobilenet_v1.yaml | 20 ++ .../pruning/configs/mobilenet_v2.yaml | 22 ++ .../pruning/configs/resnet50.yaml | 20 ++ .../pruning/images/mobilenetv2.jpg | Bin 0 -> 66688 bytes PaddleSlim/classification/pruning/run.sh | 32 +++ 17 files changed, 1294 insertions(+) create mode 100644 PaddleSlim/__init__.py create mode 100644 PaddleSlim/classification/__init__.py create mode 100644 PaddleSlim/classification/eval.py create mode 100644 PaddleSlim/classification/imagenet_reader.py create mode 100644 PaddleSlim/classification/infer.py create mode 100644 PaddleSlim/classification/models/__init__.py create mode 100644 PaddleSlim/classification/models/mobilenet.py create mode 100644 PaddleSlim/classification/models/mobilenet_v2.py create mode 100644 PaddleSlim/classification/models/resnet.py create mode 100644 PaddleSlim/classification/pruning/README.md create mode 100644 PaddleSlim/classification/pruning/__init__.py create mode 100644 PaddleSlim/classification/pruning/compress.py create mode 100644 PaddleSlim/classification/pruning/configs/mobilenet_v1.yaml create mode 100644 PaddleSlim/classification/pruning/configs/mobilenet_v2.yaml create mode 100644 PaddleSlim/classification/pruning/configs/resnet50.yaml create mode 100644 PaddleSlim/classification/pruning/images/mobilenetv2.jpg create mode 100644 PaddleSlim/classification/pruning/run.sh diff --git a/PaddleSlim/__init__.py b/PaddleSlim/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/PaddleSlim/classification/__init__.py b/PaddleSlim/classification/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/PaddleSlim/classification/eval.py b/PaddleSlim/classification/eval.py new file mode 100644 index 00000000..43e10659 --- /dev/null +++ b/PaddleSlim/classification/eval.py @@ -0,0 +1,66 @@ +#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +import os +import sys +import numpy as np +import argparse +import functools + +import paddle +import paddle.fluid as fluid +import imagenet_reader as reader +sys.path.append("../") +from utility import add_arguments, print_arguments + +parser = argparse.ArgumentParser(description=__doc__) +# yapf: disable +add_arg = functools.partial(add_arguments, argparser=parser) +add_arg('use_gpu', bool, False, "Whether to use GPU or not.") +add_arg('model_path', str, "./pruning/checkpoints/resnet50/2/eval_model/", "Whether to use pretrained 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="__model__", + params_filename="__params__") + val_reader = paddle.batch(reader.val(), batch_size=128) + feeder = fluid.DataFeeder(place=place, feed_list=feed_target_names, program=val_program) + + results=[] + for batch_id, data in enumerate(val_reader()): + + # 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() + +def main(): + args = parser.parse_args() + print_arguments(args) + eval(args) + +if __name__ == '__main__': + main() diff --git a/PaddleSlim/classification/imagenet_reader.py b/PaddleSlim/classification/imagenet_reader.py new file mode 100644 index 00000000..f1f99096 --- /dev/null +++ b/PaddleSlim/classification/imagenet_reader.py @@ -0,0 +1,193 @@ +import os +import math +import random +import functools +import numpy as np +import paddle +from PIL import Image, ImageEnhance + +random.seed(0) +np.random.seed(0) + +DATA_DIM = 224 + +THREAD = 16 +BUF_SIZE = 10240 + +DATA_DIR = '../data/ILSVRC2012' +DATA_DIR = os.path.join(os.path.split(os.path.realpath(__file__))[0], DATA_DIR) + + +img_mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1)) +img_std = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1)) + + +def resize_short(img, target_size): + percent = float(target_size) / min(img.size[0], img.size[1]) + resized_width = int(round(img.size[0] * percent)) + resized_height = int(round(img.size[1] * percent)) + img = img.resize((resized_width, resized_height), Image.LANCZOS) + return img + + +def crop_image(img, target_size, center): + width, height = img.size + size = target_size + if center == True: + w_start = (width - size) / 2 + h_start = (height - size) / 2 + else: + w_start = np.random.randint(0, width - size + 1) + h_start = np.random.randint(0, height - size + 1) + w_end = w_start + size + h_end = h_start + size + img = img.crop((w_start, h_start, w_end, h_end)) + return img + + +def random_crop(img, size, scale=[0.08, 1.0], ratio=[3. / 4., 4. / 3.]): + aspect_ratio = math.sqrt(np.random.uniform(*ratio)) + w = 1. * aspect_ratio + h = 1. / aspect_ratio + + bound = min((float(img.size[0]) / img.size[1]) / (w**2), + (float(img.size[1]) / img.size[0]) / (h**2)) + scale_max = min(scale[1], bound) + scale_min = min(scale[0], bound) + + target_area = img.size[0] * img.size[1] * np.random.uniform(scale_min, + scale_max) + target_size = math.sqrt(target_area) + w = int(target_size * w) + h = int(target_size * h) + + i = np.random.randint(0, img.size[0] - w + 1) + j = np.random.randint(0, img.size[1] - h + 1) + + img = img.crop((i, j, i + w, j + h)) + img = img.resize((size, size), Image.LANCZOS) + return img + + +def rotate_image(img): + angle = np.random.randint(-10, 11) + img = img.rotate(angle) + return img + + +def distort_color(img): + def random_brightness(img, lower=0.5, upper=1.5): + e = np.random.uniform(lower, upper) + return ImageEnhance.Brightness(img).enhance(e) + + def random_contrast(img, lower=0.5, upper=1.5): + e = np.random.uniform(lower, upper) + return ImageEnhance.Contrast(img).enhance(e) + + def random_color(img, lower=0.5, upper=1.5): + e = np.random.uniform(lower, upper) + return ImageEnhance.Color(img).enhance(e) + + ops = [random_brightness, random_contrast, random_color] + np.random.shuffle(ops) + + img = ops[0](img) + img = ops[1](img) + img = ops[2](img) + + return img + + +def process_image(sample, mode, color_jitter, rotate): + img_path = sample[0] + + img = Image.open(img_path) + if mode == 'train': + if rotate: img = rotate_image(img) + img = random_crop(img, DATA_DIM) + else: + img = resize_short(img, target_size=256) + img = crop_image(img, target_size=DATA_DIM, center=True) + if mode == 'train': + if color_jitter: + img = distort_color(img) + if np.random.randint(0, 2) == 1: + img = img.transpose(Image.FLIP_LEFT_RIGHT) + + if img.mode != 'RGB': + img = img.convert('RGB') + + img = np.array(img).astype('float32').transpose((2, 0, 1)) / 255 + img -= img_mean + img /= img_std + + if mode == 'train' or mode == 'val': + return img, sample[1] + elif mode == 'test': + return [img] + + +def _reader_creator(file_list, + mode, + shuffle=False, + color_jitter=False, + rotate=False, + data_dir=DATA_DIR, + batch_size=1): + def reader(): + try: + with open(file_list) as flist: + full_lines = [line.strip() for line in flist] + if shuffle: + np.random.shuffle(full_lines) + if mode == 'train' and os.getenv('PADDLE_TRAINING_ROLE'): + # distributed mode if the env var `PADDLE_TRAINING_ROLE` exits + trainer_id = int(os.getenv("PADDLE_TRAINER_ID", "0")) + trainer_count = int(os.getenv("PADDLE_TRAINERS", "1")) + per_node_lines = len(full_lines) // trainer_count + lines = full_lines[trainer_id * per_node_lines:( + trainer_id + 1) * per_node_lines] + print( + "read images from %d, length: %d, lines length: %d, total: %d" + % (trainer_id * per_node_lines, per_node_lines, + len(lines), len(full_lines))) + else: + lines = full_lines + + for line in lines: + if mode == 'train' or mode == 'val': + img_path, label = line.split() + img_path = os.path.join(data_dir, img_path) + yield img_path, int(label) + elif mode == 'test': + img_path = os.path.join(data_dir, line) + yield [img_path] + except Exception as e: + print("Reader failed!\n{}".format(str(e))) + os._exit(1) + + mapper = functools.partial( + process_image, mode=mode, color_jitter=color_jitter, rotate=rotate) + + return paddle.reader.xmap_readers(mapper, reader, THREAD, BUF_SIZE) + + +def train(data_dir=DATA_DIR): + file_list = os.path.join(data_dir, 'train_list.txt') + return _reader_creator( + file_list, + 'train', + shuffle=True, + color_jitter=False, + rotate=False, + data_dir=data_dir) + + +def val(data_dir=DATA_DIR): + file_list = os.path.join(data_dir, 'val_list.txt') + return _reader_creator(file_list, 'val', shuffle=False, data_dir=data_dir) + + +def test(data_dir=DATA_DIR): + file_list = os.path.join(data_dir, 'test_list.txt') + return _reader_creator(file_list, 'test', shuffle=False, data_dir=data_dir) diff --git a/PaddleSlim/classification/infer.py b/PaddleSlim/classification/infer.py new file mode 100644 index 00000000..127b1404 --- /dev/null +++ b/PaddleSlim/classification/infer.py @@ -0,0 +1,63 @@ +#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +import os +import sys +import numpy as np +import argparse +import functools + +import paddle +import paddle.fluid as fluid +import imagenet_reader as reader +sys.path.append("..") +from utility import add_arguments, print_arguments + +parser = argparse.ArgumentParser(description=__doc__) +# yapf: disable +add_arg = functools.partial(add_arguments, argparser=parser) +add_arg('use_gpu', bool, False, "Whether to use GPU or not.") +add_arg('model_path', str, "./pruning/checkpoints/resnet50/2/eval_model/", "Whether to use pretrained model.") +# yapf: enable + +def infer(args): + # parameters from arguments + + place = fluid.CUDAPlace(0) if args.use_gpu else fluid.CPUPlace() + exe = fluid.Executor(place) + + test_program, feed_target_names, fetch_targets = fluid.io.load_inference_model(args.model_path, + exe, + model_filename="__model__.infer", + params_filename="__params__") + test_reader = paddle.batch(reader.test(), batch_size=1) + feeder = fluid.DataFeeder(place=place, feed_list=feed_target_names, program=test_program) + + results=[] + for batch_id, data in enumerate(test_reader()): + + # top1_acc, top5_acc + result = exe.run(test_program, + feed=feeder.feed(data), + fetch_list=fetch_targets) + print result + sys.stdout.flush() + +def main(): + args = parser.parse_args() + print_arguments(args) + infer(args) + +if __name__ == '__main__': + main() diff --git a/PaddleSlim/classification/models/__init__.py b/PaddleSlim/classification/models/__init__.py new file mode 100644 index 00000000..b5b54739 --- /dev/null +++ b/PaddleSlim/classification/models/__init__.py @@ -0,0 +1,5 @@ +from .mobilenet import MobileNet +from .resnet import ResNet50 +from .mobilenet_v2 import MobileNetV2 + +__all__=['MobileNet', 'ResNet50', 'MobileNetV2'] diff --git a/PaddleSlim/classification/models/mobilenet.py b/PaddleSlim/classification/models/mobilenet.py new file mode 100644 index 00000000..921d6226 --- /dev/null +++ b/PaddleSlim/classification/models/mobilenet.py @@ -0,0 +1,197 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import paddle.fluid as fluid +from paddle.fluid.initializer import MSRA +from paddle.fluid.param_attr import ParamAttr + +__all__ = ['MobileNet'] + +train_parameters = { + "input_size": [3, 224, 224], + "input_mean": [0.485, 0.456, 0.406], + "input_std": [0.229, 0.224, 0.225], + "learning_strategy": { + "name": "piecewise_decay", + "batch_size": 256, + "epochs": [10, 16, 30], + "steps": [0.1, 0.01, 0.001, 0.0001] + } +} + + +class MobileNet(): + def __init__(self): + self.params = train_parameters + + def net(self, input, class_dim=1000, scale=1.0): + # conv1: 112x112 + input = self.conv_bn_layer( + input, + filter_size=3, + channels=3, + num_filters=int(32 * scale), + stride=2, + padding=1, + name="conv1") + + # 56x56 + input = self.depthwise_separable( + input, + num_filters1=32, + num_filters2=64, + num_groups=32, + stride=1, + scale=scale, + name="conv2_1") + + input = self.depthwise_separable( + input, + num_filters1=64, + num_filters2=128, + num_groups=64, + stride=2, + scale=scale, + name="conv2_2") + + # 28x28 + input = self.depthwise_separable( + input, + num_filters1=128, + num_filters2=128, + num_groups=128, + stride=1, + scale=scale, + name="conv3_1") + + input = self.depthwise_separable( + input, + num_filters1=128, + num_filters2=256, + num_groups=128, + stride=2, + scale=scale, + name="conv3_2") + + # 14x14 + input = self.depthwise_separable( + input, + num_filters1=256, + num_filters2=256, + num_groups=256, + stride=1, + scale=scale, + name="conv4_1") + + input = self.depthwise_separable( + input, + num_filters1=256, + num_filters2=512, + num_groups=256, + stride=2, + scale=scale, + name="conv4_2") + + # 14x14 + for i in range(5): + input = self.depthwise_separable( + input, + num_filters1=512, + num_filters2=512, + num_groups=512, + stride=1, + scale=scale, + name="conv5" + "_" + str(i + 1)) + # 7x7 + input = self.depthwise_separable( + input, + num_filters1=512, + num_filters2=1024, + num_groups=512, + stride=2, + scale=scale, + name="conv5_6") + + input = self.depthwise_separable( + input, + num_filters1=1024, + num_filters2=1024, + num_groups=1024, + stride=1, + scale=scale, + name="conv6") + + input = fluid.layers.pool2d( + input=input, + pool_size=0, + pool_stride=1, + pool_type='avg', + global_pooling=True) + + output = fluid.layers.fc(input=input, + size=class_dim, + act='softmax', + param_attr=ParamAttr( + initializer=MSRA(), name="fc7_weights"), + bias_attr=ParamAttr(name="fc7_offset")) + + return output + + def conv_bn_layer(self, + input, + filter_size, + num_filters, + stride, + padding, + channels=None, + num_groups=1, + act='relu', + use_cudnn=True, + name=None): + conv = fluid.layers.conv2d( + input=input, + num_filters=num_filters, + filter_size=filter_size, + stride=stride, + padding=padding, + groups=num_groups, + act=None, + use_cudnn=use_cudnn, + param_attr=ParamAttr( + initializer=MSRA(), name=name + "_weights"), + bias_attr=False) + bn_name = name + "_bn" + return fluid.layers.batch_norm( + input=conv, + act=act, + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(name=bn_name + "_offset"), + moving_mean_name=bn_name + '_mean', + moving_variance_name=bn_name + '_variance') + + def depthwise_separable(self, + input, + num_filters1, + num_filters2, + num_groups, + stride, + scale, + name=None): + depthwise_conv = self.conv_bn_layer( + input=input, + filter_size=3, + num_filters=int(num_filters1 * scale), + stride=stride, + padding=1, + num_groups=int(num_groups * scale), + use_cudnn=False, + name=name + "_dw") + + pointwise_conv = self.conv_bn_layer( + input=depthwise_conv, + filter_size=1, + num_filters=int(num_filters2 * scale), + stride=1, + padding=0, + name=name + "_sep") + return pointwise_conv diff --git a/PaddleSlim/classification/models/mobilenet_v2.py b/PaddleSlim/classification/models/mobilenet_v2.py new file mode 100644 index 00000000..1855996a --- /dev/null +++ b/PaddleSlim/classification/models/mobilenet_v2.py @@ -0,0 +1,253 @@ +#copyright (c) 2019 PaddlePaddle Authors. All Rights Reserve. +# +#Licensed under the Apache License, Version 2.0 (the "License"); +#you may not use this file except in compliance with the License. +#You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +#Unless required by applicable law or agreed to in writing, software +#distributed under the License is distributed on an "AS IS" BASIS, +#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#See the License for the specific language governing permissions and +#limitations under the License. + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import paddle.fluid as fluid +from paddle.fluid.initializer import MSRA +from paddle.fluid.param_attr import ParamAttr + +__all__ = ['MobileNetV2', 'MobileNetV2_x0_25, ''MobileNetV2_x0_5', 'MobileNetV2_x1_0', 'MobileNetV2_x1_5', 'MobileNetV2_x2_0', + 'MobileNetV2_scale'] + +train_parameters = { + "input_size": [3, 224, 224], + "input_mean": [0.485, 0.456, 0.406], + "input_std": [0.229, 0.224, 0.225], + "learning_strategy": { + "name": "piecewise_decay", + "batch_size": 256, + "epochs": [30, 60, 90], + "steps": [0.1, 0.01, 0.001, 0.0001] + } +} + + +class MobileNetV2(): + def __init__(self, scale=1.0, change_depth=False): + self.params = train_parameters + self.scale = scale + self.change_depth=change_depth + + + def net(self, input, class_dim=1000): + scale = self.scale + change_depth = self.change_depth + #if change_depth is True, the new depth is 1.4 times as deep as before. + bottleneck_params_list = [ + (1, 16, 1, 1), + (6, 24, 2, 2), + (6, 32, 3, 2), + (6, 64, 4, 2), + (6, 96, 3, 1), + (6, 160, 3, 2), + (6, 320, 1, 1), + ] if change_depth == False else [ + (1, 16, 1, 1), + (6, 24, 2, 2), + (6, 32, 5, 2), + (6, 64, 7, 2), + (6, 96, 5, 1), + (6, 160, 3, 2), + (6, 320, 1, 1), + ] + + #conv1 + input = self.conv_bn_layer( + input, + num_filters=int(32 * scale), + filter_size=3, + stride=2, + padding=1, + if_act=True, + name='conv1_1') + + # bottleneck sequences + i = 1 + in_c = int(32 * scale) + for layer_setting in bottleneck_params_list: + t, c, n, s = layer_setting + i += 1 + input = self.invresi_blocks( + input=input, + in_c=in_c, + t=t, + c=int(c * scale), + n=n, + s=s, + name='conv' + str(i)) + in_c = int(c * scale) + #last_conv + input = self.conv_bn_layer( + input=input, + num_filters=int(1280 * scale) if scale > 1.0 else 1280, + filter_size=1, + stride=1, + padding=0, + if_act=True, + name='conv9') + + input = fluid.layers.pool2d( + input=input, + pool_size=7, + pool_stride=1, + pool_type='avg', + global_pooling=True) + + output = fluid.layers.fc(input=input, + size=class_dim, + act='softmax', + param_attr=ParamAttr(name='fc10_weights'), + bias_attr=ParamAttr(name='fc10_offset')) + return output + + def conv_bn_layer(self, + input, + filter_size, + num_filters, + stride, + padding, + channels=None, + num_groups=1, + if_act=True, + name=None, + use_cudnn=True): + conv = fluid.layers.conv2d( + input=input, + num_filters=num_filters, + filter_size=filter_size, + stride=stride, + padding=padding, + groups=num_groups, + act=None, + use_cudnn=use_cudnn, + param_attr=ParamAttr(name=name + '_weights'), + bias_attr=False) + bn_name = name + '_bn' + bn = fluid.layers.batch_norm( + input=conv, + param_attr=ParamAttr(name=bn_name + "_scale"), + bias_attr=ParamAttr(name=bn_name + "_offset"), + moving_mean_name=bn_name + '_mean', + moving_variance_name=bn_name + '_variance') + if if_act: + return fluid.layers.relu6(bn) + else: + return bn + + def shortcut(self, input, data_residual): + return fluid.layers.elementwise_add(input, data_residual) + + def inverted_residual_unit(self, + input, + num_in_filter, + num_filters, + ifshortcut, + stride, + filter_size, + padding, + expansion_factor, + name=None): + num_expfilter = int(round(num_in_filter * expansion_factor)) + + channel_expand = self.conv_bn_layer( + input=input, + num_filters=num_expfilter, + filter_size=1, + stride=1, + padding=0, + num_groups=1, + if_act=True, + name=name + '_expand') + + bottleneck_conv = self.conv_bn_layer( + input=channel_expand, + num_filters=num_expfilter, + filter_size=filter_size, + stride=stride, + padding=padding, + num_groups=num_expfilter, + if_act=True, + name=name + '_dwise', + use_cudnn=False) + + linear_out = self.conv_bn_layer( + input=bottleneck_conv, + num_filters=num_filters, + filter_size=1, + stride=1, + padding=0, + num_groups=1, + if_act=False, + name=name + '_linear') + if ifshortcut: + out = self.shortcut(input=input, data_residual=linear_out) + return out + else: + return linear_out + + def invresi_blocks(self, input, in_c, t, c, n, s, name=None): + first_block = self.inverted_residual_unit( + input=input, + num_in_filter=in_c, + num_filters=c, + ifshortcut=False, + stride=s, + filter_size=3, + padding=1, + expansion_factor=t, + name=name + '_1') + + last_residual_block = first_block + last_c = c + + for i in range(1, n): + last_residual_block = self.inverted_residual_unit( + input=last_residual_block, + num_in_filter=last_c, + num_filters=c, + ifshortcut=True, + stride=1, + filter_size=3, + padding=1, + expansion_factor=t, + name=name + '_' + str(i + 1)) + return last_residual_block + + + +def MobileNetV2_x0_25(): + model = MobileNetV2(scale=0.25) + return model + +def MobileNetV2_x0_5(): + model = MobileNetV2(scale=0.5) + return model + +def MobileNetV2_x1_0(): + model = MobileNetV2(scale=1.0) + return model + +def MobileNetV2_x1_5(): + model = MobileNetV2(scale=1.5) + return model + +def MobileNetV2_x2_0(): + model = MobileNetV2(scale=2.0) + return model + +def MobileNetV2_scale(): + model = MobileNetV2(scale=1.2, change_depth=True) + return model diff --git a/PaddleSlim/classification/models/resnet.py b/PaddleSlim/classification/models/resnet.py new file mode 100644 index 00000000..a27bd52d --- /dev/null +++ b/PaddleSlim/classification/models/resnet.py @@ -0,0 +1,165 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function +import paddle +import paddle.fluid as fluid +import math +from paddle.fluid.param_attr import ParamAttr + +__all__ = ["ResNet", "ResNet50", "ResNet101", "ResNet152"] + +train_parameters = { + "input_size": [3, 224, 224], + "input_mean": [0.485, 0.456, 0.406], + "input_std": [0.229, 0.224, 0.225], + "learning_strategy": { + "name": "piecewise_decay", + "batch_size": 256, + "epochs": [10, 16, 30], + "steps": [0.1, 0.01, 0.001, 0.0001] + } +} + + +class ResNet(): + def __init__(self, layers=50): + self.params = train_parameters + self.layers = layers + + def net(self, input, class_dim=1000, conv1_name='conv1', fc_name=None): + layers = self.layers + supported_layers = [50, 101, 152] + assert layers in supported_layers, \ + "supported layers are {} but input layer is {}".format(supported_layers, layers) + + if layers == 50: + depth = [3, 4, 6, 3] + elif layers == 101: + depth = [3, 4, 23, 3] + elif layers == 152: + depth = [3, 8, 36, 3] + num_filters = [64, 128, 256, 512] + + # TODO(wanghaoshuang@baidu.com): + # fix name("conv1") conflict between student and teacher in distillation. + conv = self.conv_bn_layer( + input=input, + num_filters=64, + filter_size=7, + stride=2, + act='relu', + name=conv1_name) + conv = fluid.layers.pool2d( + input=conv, + pool_size=3, + pool_stride=2, + pool_padding=1, + pool_type='max') + + for block in range(len(depth)): + for i in range(depth[block]): + if layers in [101, 152] and block == 2: + if i == 0: + conv_name = "res" + str(block + 2) + "a" + else: + conv_name = "res" + str(block + 2) + "b" + str(i) + else: + conv_name = "res" + str(block + 2) + chr(97 + i) + conv = self.bottleneck_block( + input=conv, + num_filters=num_filters[block], + stride=2 if i == 0 and block != 0 else 1, + name=conv_name) + + pool = fluid.layers.pool2d( + input=conv, pool_size=7, pool_type='avg', global_pooling=True) + stdv = 1.0 / math.sqrt(pool.shape[1] * 1.0) + out = fluid.layers.fc(input=pool, + size=class_dim, + act='softmax', + name=fc_name, + param_attr=fluid.param_attr.ParamAttr( + initializer=fluid.initializer.Uniform(-stdv, + stdv))) + return out + + def conv_bn_layer(self, + input, + num_filters, + filter_size, + stride=1, + groups=1, + act=None, + name=None): + conv = fluid.layers.conv2d( + input=input, + num_filters=num_filters, + filter_size=filter_size, + stride=stride, + padding=(filter_size - 1) // 2, + groups=groups, + act=None, + param_attr=ParamAttr(name=name + "_weights"), + bias_attr=False, + name=name + '.conv2d.output.1') + if name == "conv1": + bn_name = "bn_" + name + else: + bn_name = "bn" + name[3:] + return fluid.layers.batch_norm( + input=conv, + act=act, + name=bn_name + '.output.1', + param_attr=ParamAttr(name=bn_name + '_scale'), + bias_attr=ParamAttr(bn_name + '_offset'), + moving_mean_name=bn_name + '_mean', + moving_variance_name=bn_name + '_variance', ) + + def shortcut(self, input, ch_out, stride, name): + ch_in = input.shape[1] + if ch_in != ch_out or stride != 1: + return self.conv_bn_layer(input, ch_out, 1, stride, name=name) + else: + return input + + def bottleneck_block(self, input, num_filters, stride, name): + conv0 = self.conv_bn_layer( + input=input, + num_filters=num_filters, + filter_size=1, + act='relu', + name=name + "_branch2a") + conv1 = self.conv_bn_layer( + input=conv0, + num_filters=num_filters, + filter_size=3, + stride=stride, + act='relu', + name=name + "_branch2b") + conv2 = self.conv_bn_layer( + input=conv1, + num_filters=num_filters * 4, + filter_size=1, + act=None, + name=name + "_branch2c") + + short = self.shortcut( + input, num_filters * 4, stride, name=name + "_branch1") + + return fluid.layers.elementwise_add( + x=short, y=conv2, act='relu', name=name + ".add.output.5") + + +def ResNet50(): + model = ResNet(layers=50) + return model + + +def ResNet101(): + model = ResNet(layers=101) + return model + + +def ResNet152(): + model = ResNet(layers=152) + return model diff --git a/PaddleSlim/classification/pruning/README.md b/PaddleSlim/classification/pruning/README.md new file mode 100644 index 00000000..ad92c3be --- /dev/null +++ b/PaddleSlim/classification/pruning/README.md @@ -0,0 +1,155 @@ +>运行该示例前请安装Paddle1.6或更高版本 + +# 分类模型卷积通道剪裁示例 + +## 概述 + +该示例使用PaddleSlim提供的[卷积通道剪裁压缩策略](https://github.com/PaddlePaddle/models/blob/develop/PaddleSlim/docs/tutorial.md#2-%E5%8D%B7%E7%A7%AF%E6%A0%B8%E5%89%AA%E8%A3%81%E5%8E%9F%E7%90%86)对分类模型进行压缩。 +在阅读该示例前,建议您先了解以下内容: + +- [分类模型的常规训练方法](https://github.com/PaddlePaddle/models/tree/develop/PaddleCV/image_classification) +- [PaddleSlim使用文档](https://github.com/PaddlePaddle/models/blob/develop/PaddleSlim/docs/usage.md) + + +## 配置文件说明 + +关于配置文件如何编写您可以参考: + +- [PaddleSlim配置文件编写说明](https://github.com/PaddlePaddle/models/blob/develop/PaddleSlim/docs/usage.md#122-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E7%9A%84%E4%BD%BF%E7%94%A8) +- [裁剪策略配置文件编写说明](https://github.com/PaddlePaddle/models/blob/develop/PaddleSlim/docs/usage.md#22-%E6%A8%A1%E5%9E%8B%E9%80%9A%E9%81%93%E5%89%AA%E8%A3%81) + +其中,配置文件中的`pruned_params`需要根据当前模型的网络结构特点设置,它用来指定要裁剪的parameters. + +这里以MobileNetV2模型为例,MobileNetV2的主要结构为Inverted residuals, 如图1所示: + + +

+
+图1 +

+ +PaddleSlim暂时无法对`depthwise convolution`直接进行剪裁, 因为`depthwise convolution`的`channel`的变化会同时影响到前后的卷积层。 +另外,`depthwise convolution`的参数(parameters)量占整个模型的比例并不高,所以,我们直接剪裁depthwise convolution的前后相邻的普通卷积层。 +通过以下命令观察目标卷积层的参数(parameters)的名称: + +``` +for param in fluid.default_main_program().global_block().all_parameters(): + if 'weights' in param.name: + print param.name, param.shape +``` + +结果如下: + +``` +conv1_1_weights (32L, 3L, 3L, 3L) +conv2_1_expand_weights (32L, 32L, 1L, 1L) +conv2_1_dwise_weights (32L, 1L, 3L, 3L) +conv2_1_linear_weights (16L, 32L, 1L, 1L) +conv3_1_expand_weights (96L, 16L, 1L, 1L) +conv3_1_dwise_weights (96L, 1L, 3L, 3L) +conv3_1_linear_weights (24L, 96L, 1L, 1L) +... +conv8_1_expand_weights (960L, 160L, 1L, 1L) +conv8_1_dwise_weights (960L, 1L, 3L, 3L) +conv8_1_linear_weights (320L, 960L, 1L, 1L) +conv9_weights (1280L, 320L, 1L, 1L) +fc10_weights (1280L, 1000L) +``` + +观察可知,普通卷积的参数名称为`.*expand_weights`或`.*linear_weights`, 用以下正则表达式`.*[r|d]_weights`对其进行匹配。 + +综上,我们将MobileNetV2配置文件中的`pruned_params`设置为`.*[r|d]_weights`。 + +我们可以用上述操作观察MobileNetV1和ResNet50的参数名称规律,然后设置合适的正则表达式来剪裁合适的参数。 + + +## 训练 + +根据[PaddleCV/image_classification/train.py](https://github.com/PaddlePaddle/models/blob/develop/PaddleCV/image_classification/train.py)编写压缩脚本compress.py。 +在该脚本中定义了Compressor对象,用于执行压缩任务。 + +可以通过命令`python compress.py`用默认参数执行压缩任务,通过`python compress.py --help`查看可配置参数,简述如下: + +- use_gpu: 是否使用gpu。如果选择使用GPU,请确保当前环境和Paddle版本支持GPU。默认为True。 +- batch_size: 在剪裁之后,对模型进行fine-tune训练时用的batch size。 +- model: 要压缩的目标模型,该示例支持'MobileNetV1', 'MobileNetV2'和'ResNet50'。 +- 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: 压缩策略的配置文件。 + +您可以通过运行脚本`run.sh`运行改示例,请确保已正确下载[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)。 + + +### 保存断点(checkpoint) + +如果在配置文件中设置了`checkpoint_path`, 则在压缩任务执行过程中会自动保存断点,当任务异常中断时, +重启任务会自动从`checkpoint_path`路径下按数字顺序加载最新的checkpoint文件。如果不想让重启的任务从断点恢复, +需要修改配置文件中的`checkpoint_path`,或者将`checkpoint_path`路径下文件清空。 + +>注意:配置文件中的信息不会保存在断点中,重启前对配置文件的修改将会生效。 + + +## 评估 + +如果在配置文件中设置了`checkpoint_path`,则每个epoch会保存一个压缩后的用于评估的模型, +该模型会保存在`${checkpoint_path}/${epoch_id}/eval_model/`路径下,包含`__model__`和`__params__`两个文件。 +其中,`__model__`用于保存模型结构信息,`__params__`用于保存参数(parameters)信息。 + +如果不需要保存评估模型,可以在定义Compressor对象时,将`save_eval_model`选项设置为False(默认为True)。 + +脚本PaddleSlim/classification/eval.py中为使用该模型在评估数据集上做评估的示例。 + +## 预测 + +如果在配置文件中设置了`checkpoint_path`,并且在定义Compressor对象时指定了`prune_infer_model`选项,则每个epoch都会 +保存一个`inference model`。该模型是通过删除eval_program中多余的operators而得到的。 + +该模型会保存在`${checkpoint_path}/${epoch_id}/eval_model/`路径下,包含`__model__.infer`和`__params__`两个文件。 +其中,`__model__.infer`用于保存模型结构信息,`__params__`用于保存参数(parameters)信息。 + +更多关于`prune_infer_model`选项的介绍,请参考:[Compressor介绍](https://github.com/PaddlePaddle/models/blob/develop/PaddleSlim/docs/usage.md#121-%E5%A6%82%E4%BD%95%E6%94%B9%E5%86%99%E6%99%AE%E9%80%9A%E8%AE%AD%E7%BB%83%E8%84%9A%E6%9C%AC) + +### python预测 + +在脚本PaddleSlim/classification/infer.py中展示了如何使用fluid python API加载使用预测模型进行预测。 + +### PaddleLite + +该示例中产出的预测(inference)模型可以直接用PaddleLite进行加载使用。 +关于PaddleLite如何使用,请参考:[PaddleLite使用文档](https://github.com/PaddlePaddle/Paddle-Lite/wiki#%E4%BD%BF%E7%94%A8) + +## 示例结果 + +### MobileNetV1 + +| FLOPS |top1_acc/top5_acc| model_size |Paddle Fluid inference time(ms)| Paddle Lite inference time(ms)| +|---|---|---|---|---| +|baseline|70.99%/89.68% |- |- |-| +|-10%|- |- |- |-| +|-30%|- |- |- |-| +|-50%|- |- |- |-| + +>训练超参: + +### MobileNetV2 + +| FLOPS |top1_acc/top5_acc| model_size |Paddle Fluid inference time(ms)| Paddle Lite inference time(ms)| +|---|---|---|---|---| +|baseline|72.15%/90.65% |- |- |-| +|-10%|- |- |- |-| +|-30%|- |- |- |-| +|-50%|- |- |- |-| + +>训练超参: + +### ResNet50 + +| FLOPS |top1_acc/top5_acc| model_size |Paddle Fluid inference time(ms)| Paddle Lite inference time(ms)| +|---|---|---|---|---| +|baseline|76.50%/93.00% |- |- |-| +|-10%|- |- |- |-| +|-30%|- |- |- |-| +|-50%|- |- |- |-| + +>训练超参: + +## FAQ diff --git a/PaddleSlim/classification/pruning/__init__.py b/PaddleSlim/classification/pruning/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/PaddleSlim/classification/pruning/compress.py b/PaddleSlim/classification/pruning/compress.py new file mode 100644 index 00000000..b40d8a1c --- /dev/null +++ b/PaddleSlim/classification/pruning/compress.py @@ -0,0 +1,103 @@ +import os +import sys +import logging +import paddle +import argparse +import functools +import paddle.fluid as fluid +sys.path.append("..") +import imagenet_reader as reader +import models +sys.path.append("../../") +from utility import add_arguments, print_arguments + +from paddle.fluid.contrib.slim import Compressor + +logging.basicConfig(format='%(asctime)s-%(levelname)s: %(message)s') +_logger = logging.getLogger(__name__) +_logger.setLevel(logging.INFO) + +parser = argparse.ArgumentParser(description=__doc__) +add_arg = functools.partial(add_arguments, argparser=parser) +# yapf: disable +add_arg('batch_size', int, 64*4, "Minibatch size.") +add_arg('use_gpu', bool, True, "Whether to use GPU or not.") +add_arg('model', str, None, "The target model.") +add_arg('pretrained_model', str, None, "Whether to use pretrained model.") +add_arg('config_file', str, None, "The config file for compression with yaml format.") +# yapf: enable + + +model_list = [m for m in dir(models) if "__" not in m] + +def compress(args): + class_dim=1000 + image_shape="3,224,224" + image_shape = [int(m) for m in image_shape.split(",")] + assert args.model in model_list, "{} is not in lists: {}".format(args.model, model_list) + image = fluid.layers.data(name='image', shape=image_shape, dtype='float32') + label = fluid.layers.data(name='label', shape=[1], dtype='int64') + # model definition + model = models.__dict__[args.model]() + out = model.net(input=image, class_dim=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) + val_program = fluid.default_main_program().clone() +# for param in fluid.default_main_program().global_block().all_parameters(): +# print param.name, param.shape +# return + opt = fluid.optimizer.Momentum( + momentum=0.9, + learning_rate=fluid.layers.piecewise_decay( + boundaries=[5000 * 30, 5000 * 60, 5000 * 90], + values=[0.1, 0.01, 0.001, 0.0001]), + regularization=fluid.regularizer.L2Decay(4e-5)) + + place = fluid.CUDAPlace(0) if args.use_gpu else fluid.CPUPlace() + exe = fluid.Executor(place) + exe.run(fluid.default_startup_program()) + + if args.pretrained_model: + + def if_exist(var): + return os.path.exists(os.path.join(args.pretrained_model, var.name)) + + fluid.io.load_vars(exe, args.pretrained_model, predicate=if_exist) + + val_reader = paddle.batch(reader.val(), batch_size=args.batch_size) + val_feed_list = [('image', image.name), ('label', label.name)] + val_fetch_list = [('acc_top1', acc_top1.name), ('acc_top5', acc_top5.name)] + + train_reader = paddle.batch( + reader.train(), batch_size=args.batch_size, drop_last=True) + train_feed_list = [('image', image.name), ('label', label.name)] + train_fetch_list = [('loss', avg_cost.name)] + + com_pass = Compressor( + place, + fluid.global_scope(), + fluid.default_main_program(), + train_reader=train_reader, + train_feed_list=train_feed_list, + train_fetch_list=train_fetch_list, + eval_program=val_program, + eval_reader=val_reader, + eval_feed_list=val_feed_list, + eval_fetch_list=val_fetch_list, + save_eval_model=True, + prune_infer_model=[[image.name], [out.name]], + train_optimizer=opt) + com_pass.config(args.config_file) + com_pass.run() + + +def main(): + args = parser.parse_args() + print_arguments(args) + compress(args) + + +if __name__ == '__main__': + main() diff --git a/PaddleSlim/classification/pruning/configs/mobilenet_v1.yaml b/PaddleSlim/classification/pruning/configs/mobilenet_v1.yaml new file mode 100644 index 00000000..de0b427f --- /dev/null +++ b/PaddleSlim/classification/pruning/configs/mobilenet_v1.yaml @@ -0,0 +1,20 @@ +version: 1.0 +pruners: + pruner_1: + class: 'StructurePruner' + pruning_axis: + '*': 0 + criterions: + '*': 'l1_norm' +strategies: + uniform_pruning_strategy: + class: 'UniformPruneStrategy' + pruner: 'pruner_1' + start_epoch: 0 + target_ratio: 0.5 + pruned_params: '.*_sep_weights' +compressor: + epoch: 3 + checkpoint_path: './checkpoints/mobilenet_v1/' + strategies: + - uniform_pruning_strategy diff --git a/PaddleSlim/classification/pruning/configs/mobilenet_v2.yaml b/PaddleSlim/classification/pruning/configs/mobilenet_v2.yaml new file mode 100644 index 00000000..4df7edcc --- /dev/null +++ b/PaddleSlim/classification/pruning/configs/mobilenet_v2.yaml @@ -0,0 +1,22 @@ +version: 1.0 +pruners: + pruner_1: + class: 'StructurePruner' + pruning_axis: + '*': 0 + criterions: + '*': 'l1_norm' +strategies: + uniform_pruning_strategy: + class: 'UniformPruneStrategy' + pruner: 'pruner_1' + start_epoch: 0 + target_ratio: 0.5 + pruned_params: '.*[r|d]_weights' +# pruned_params: '.*linear_weights' +# pruned_params: '.*expand_weights' +compressor: + epoch: 2 + checkpoint_path: './checkpoints/' + strategies: + - uniform_pruning_strategy diff --git a/PaddleSlim/classification/pruning/configs/resnet50.yaml b/PaddleSlim/classification/pruning/configs/resnet50.yaml new file mode 100644 index 00000000..2519c433 --- /dev/null +++ b/PaddleSlim/classification/pruning/configs/resnet50.yaml @@ -0,0 +1,20 @@ +version: 1.0 +pruners: + pruner_1: + class: 'StructurePruner' + pruning_axis: + '*': 0 + criterions: + '*': 'l1_norm' +strategies: + uniform_pruning_strategy: + class: 'UniformPruneStrategy' + pruner: 'pruner_1' + start_epoch: 0 + target_ratio: 0.5 + pruned_params: '.*branch.*_weights' +compressor: + epoch: 4 + checkpoint_path: './checkpoints/resnet50/' + strategies: + - uniform_pruning_strategy diff --git a/PaddleSlim/classification/pruning/images/mobilenetv2.jpg b/PaddleSlim/classification/pruning/images/mobilenetv2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c0dd3824dc77f8d3c87d719c13ceca16a3472e3c GIT binary patch literal 66688 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 literal 0 HcmV?d00001 diff --git a/PaddleSlim/classification/pruning/run.sh b/PaddleSlim/classification/pruning/run.sh new file mode 100644 index 00000000..24cc4ece --- /dev/null +++ b/PaddleSlim/classification/pruning/run.sh @@ -0,0 +1,32 @@ +export CUDA_VISIBLE_DEVICES=0 + +nohup python compress.py \ +--model "MobileNet" \ +--use_gpu 0 \ +--batch_size 1 \ +--pretrained_model ../pretrain/MobileNetV1_pretrained \ +--config_file "./configs/mobilenet_v1.yaml" \ +> mobilenet_v1.log 2>&1 & +tailf mobilenet_v1.log + +# for compression of mobilenet_v2 +#nohup python compress.py \ +#--model "MobileNetV2" \ +#--use_gpu 0 \ +#--batch_size 1 \ +#--pretrained_model ../pretrain/MobileNetV2_pretrained \ +#--config_file "./configs/mobilenet_v2.yaml" \ +#> mobilenet_v2.log 2>&1 & +#tailf mobilenet_v2.log + + +# for compression of resnet50 +#python compress.py \ +#--model "ResNet50" \ +#--use_gpu 0 \ +#--batch_size 1 \ +#--pretrained_model ../pretrain/ResNet50_pretrained \ +#--config_file "./configs/resnet50.yaml" \ +#> resnet50.log 2>&1 & +#tailf resnet50.log + -- GitLab