diff --git a/paddleslim/nas/search_space/mobilenet_block.py b/paddleslim/nas/search_space/mobilenet_block.py new file mode 100644 index 0000000000000000000000000000000000000000..b08a4c78dc364530539fa0dd01a2122e592b8d07 --- /dev/null +++ b/paddleslim/nas/search_space/mobilenet_block.py @@ -0,0 +1,356 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# 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 numpy as np +import paddle.fluid as fluid +from paddle.fluid.param_attr import ParamAttr +from .search_space_base import SearchSpaceBase +from .base_layer import conv_bn_layer +from .search_space_registry import SEARCHSPACE +from .utils import compute_downsample_num + +__all__ = ["MobileNetV1BlockSpace", "MobileNetV2BlockSpace"] + + +@SEARCHSPACE.register +class MobileNetV2BlockSpace(SearchSpaceBase): + def __init__(self, + input_size, + output_size, + block_num, + block_mask=None, + scale=1.0): + super(MobileNetV2BlockSpace, self).__init__(input_size, output_size, + block_num, block_mask) + + # use input_size and output_size to compute self.downsample_num + self.downsample_num = compute_downsample_num(self.input_size, self.output_size) + if self.block_num != None: + assert self.downsample_num <= self.block_num, 'downsample numeber must be LESS THAN OR EQUAL TO block_num, but NOW: downsample numeber is {}, block_num is {}'.format(self.downsample_num, self.block_num) + + # self.filter_num means channel number + self.filter_num = np.array([3, 4, 8, 12, 16, 24, 32, 48, 64, 80, 96, 128, 144, 160, 192, 224, 256, 320, 384, 512]) # 20 + # self.k_size means kernel size + self.k_size = np.array([3, 5]) #2 + # self.multiply means expansion_factor of each _inverted_residual_unit + self.multiply = np.array([1, 2, 3, 4, 5, 6]) #6 + # self.repeat means repeat_num _inverted_residual_unit in each _invresi_blocks + self.repeat = np.array([1, 2, 3, 4, 5, 6]) #6 + self.scale = scale + + def init_tokens(self): + if self.block_mask != None: + return [0] * (len(self.block_mask) * 4) + else: + return [0] * (self.block_num * 4) + + def range_table(self): + range_table_base = [] + if self.block_mask != None: + for i in range(len(self.block_mask)): + range_table_base.append(len(self.multiply)) + range_table_base.append(len(self.filter_num)) + range_table_base.append(len(self.repeat)) + range_table_base.append(len(self.k_size)) + else: + for i in range(block_num): + range_table_base.append(len(self.multiply)) + range_table_base.append(len(self.filter_num)) + range_table_base.append(len(self.repeat)) + range_table_base.append(len(self.k_size)) + return range_table_base + + def token2arch(self, tokens=None): + """ + return mobilenetv2 net_arch function + """ + + if tokens == None: + tokens = self.init_tokens() + + self.bottleneck_params_list = [] + if self.block_mask != None: + for i in range(len(self.block_mask)): + self.bottleneck_params_list.append( + (self.multiply[tokens[i * 4]], + self.filter_num[tokens[i * 4 + 1]], + self.repeat[tokens[i * 4 + 2]], 2 + if self.block_mask[i] == 1 else 1, + self.k_size[tokens[i * 4 + 3]])) + else: + repeat_num = self.block_num / self.downsample_num + num_minus = self.block_num % self.downsample_num + ### if block_num > downsample_num, add stride=1 block at last (block_num-downsample_num) layers + for i in range(self.downsample_num): + self.bottleneck_params_list.append(self.mutiply[tokens[i * 4], self.filter_num[tokens[i * 4 + 1]], + self.repeat[tokens[i * 4 + 2]], 2, self.k_size[tokens[i * 4 + 3]]) + + ### if block_num / downsample_num > 1, add (block_num / downsample_num) times stride=1 block + for k in range(repeat_num - 1): + kk = k * self.downsample_num + i + self.bottleneck_params_list.append(self.mutiply[tokens[kk * 4], self.filter_num[tokens[kk * 4 + 1]], + self.repeat[tokens[kk * 4 + 2]], 1, self.k_size[tokens[kk * 4 + 3]]) + + if self.downsample_num - i <= num_minus: + j = self.downsample_num * repeat_num + i + self.bottleneck_params_list.append(self.mutiply[tokens[j * 4], self.filter_num[tokens[j * 4 + 1]], + self.repeat[tokens[j * 4 + 2]], 1, self.k_size[tokens[j * 4 + 3]]) + + def net_arch(input, return_mid_layer=False): + # all padding is 'SAME' in the conv2d, can compute the actual padding automatic. + # bottleneck sequences + in_c = int(32 * self.scale) + for i, layer_setting in enumerate(self.bottleneck_params_list): + t, c, n, s, k = layer_setting + input, depthwise_conv = self._invresi_blocks( + input=input, + in_c=in_c, + t=t, + c=int(c * self.scale), + n=n, + s=s, + k=k, + name='mobilenetv2_conv' + str(i+1)) + in_c = int(c * self.scale) + + if return_mid_layer: + return input, depthwise_conv + else: + return input, + + return net_arch + + def _shortcut(self, input, data_residual): + """Build shortcut layer. + Args: + input(Variable): input. + data_residual(Variable): residual layer. + Returns: + Variable, layer output. + """ + return fluid.layers.elementwise_add(input, data_residual) + + def _inverted_residual_unit(self, + input, + num_in_filter, + num_filters, + ifshortcut, + stride, + filter_size, + expansion_factor, + reduction_ratio=4, + name=None): + """Build inverted residual unit. + Args: + input(Variable), input. + num_in_filter(int), number of in filters. + num_filters(int), number of filters. + ifshortcut(bool), whether using shortcut. + stride(int), stride. + filter_size(int), filter size. + padding(str|int|list), padding. + expansion_factor(float), expansion factor. + name(str), name. + Returns: + Variable, layers output. + """ + num_expfilter = int(round(num_in_filter * expansion_factor)) + channel_expand = conv_bn_layer( + input=input, + num_filters=num_expfilter, + filter_size=1, + stride=1, + padding='SAME', + num_groups=1, + act='relu6', + name=name + '_expand') + + bottleneck_conv = conv_bn_layer( + input=channel_expand, + num_filters=num_expfilter, + filter_size=filter_size, + stride=stride, + padding='SAME', + num_groups=num_expfilter, + act='relu6', + name=name + '_dwise', + use_cudnn=False) + + depthwise_output = bottleneck_conv + + linear_out = conv_bn_layer( + input=bottleneck_conv, + num_filters=num_filters, + filter_size=1, + stride=1, + padding='SAME', + num_groups=1, + act=None, + name=name + '_linear') + out = linear_out + if ifshortcut: + out = self._shortcut(input=input, data_residual=out) + return out, depthwise_output + + def _invresi_blocks(self, input, in_c, t, c, n, s, k, name=None): + """Build inverted residual blocks. + Args: + input: Variable, input. + in_c: int, number of in filters. + t: float, expansion factor. + c: int, number of filters. + n: int, number of layers. + s: int, stride. + k: int, filter size. + name: str, name. + Returns: + Variable, layers output. + """ + first_block, depthwise_output = self._inverted_residual_unit( + input=input, + num_in_filter=in_c, + num_filters=c, + ifshortcut=False, + stride=s, + filter_size=k, + expansion_factor=t, + name=name + '_1') + + last_residual_block = first_block + last_c = c + + for i in range(1, n): + last_residual_block, depthwise_output = self._inverted_residual_unit( + input=last_residual_block, + num_in_filter=last_c, + num_filters=c, + ifshortcut=True, + stride=1, + filter_size=k, + expansion_factor=t, + name=name + '_' + str(i + 1)) + return last_residual_block, depthwise_output + + + +@SEARCHSPACE.register +class MobileNetV1BlockSpace(SearchSpaceBase): + def __init__(self, input_size, output_size, block_num, block_mask=None, scale=1.0): + super(MobileNetV1BlockSpace, self).__init__(input_size, output_size, block_num, block_mask) + # use input_size and output_size to compute self.downsample_num + self.downsample_num = compute_downsample_num(self.input_size, self.output_size) + if self.block_num != None: + assert self.downsample_num <= self.block_num, 'downsample numeber must be LESS THAN OR EQUAL TO block_num, but NOW: downsample numeber is {}, block_num is {}'.format(self.downsample_num, self.block_num) + + # self.filter_num means channel number + self.filter_num = np.array([3, 4, 8, 12, 16, 24, 32, 48, 64, 80, 96, 128, 144, 160, 192, 224, 256, 320, 384, 512, 576, 640, 768, 1024, 1048]) + self.k_size = np.array([3, 5]) + self.scale = scale + + def init_tokens(self): + if self.block_mask != None: + return [0] * (len(self.block_mask) * 3) + else: + return [0] * (self.block_num * 3) + + def range_table(self): + range_table_base = [] + if self.block_mask != None: + for i in range(len(self.block_mask)): + range_table_base.append(len(self.filter_num)) + range_table_base.append(len(self.filter_num)) + range_table_base.append(len(self.k_size)) + else: + for i in range(self.block_num)): + range_table_base.append(len(self.filter_num)) + range_table_base.append(len(self.filter_num)) + range_table_base.append(len(self.k_size)) + + return range_table_base + + def token2arch(self, tokens=None): + if tokens == None: + tokens = self.init_tokens() + + self.bottleneck_param_list = [] + if self.block_mask != None + for i in range(len(self.block_mask)): + self.bottleneck_params_list.append(self.filter_num[tokens[i * 3]], self.filter_num[tokens[i * 3 + 1]], 2 if self.block_mask[i] == 1 else 1, self.k_size[tokens[i * 3 + 2]) + else: + repeat_num = self.block_num / self.downsample_num + num_minus = self.block_num % self.downsample_num + for i in range(self.block_num): + ### if block_num > downsample_num, add stride=1 block at last (block_num-downsample_num) layers + self.bottleneck_params_list.append(self.filter_num[tokens[i * 3]], self.filter_num[tokens[i * 3 + 1]], 2, self.k_size[tokens[i * 3 + 2]) + + ### if block_num / downsample_num > 1, add (block_num / downsample_num) times stride=1 block + for k in range(repeat_num - 1): + kk = k * self.downsample_num + i + self.bottleneck_params_list.append(self.filter_num[tokens[kk * 3], self.filter_num[tokens[kk * 3 + 1]], + 1, self.k_size[tokens[kk * 3 + 2]]) + + if self.downsample_num - i <= num_minus: + j = self.downsample_num * repeat_num + i + self.bottleneck_params_list.append(self.filter_num[tokens[j * 3], self.filter_num[tokens[j * 3 + 1]], + 1, self.k_size[tokens[j * 3 + 2]]) + + def net_arch(input, return_mid_layer=False): + + + def net_arch(input): + for i, layer_setting in enumerate(self.bottleneck_params_list): + filter_num1, filter_num2, stride, kernel_size = layer_setting + input = self._depthwise_separable( + input=input, + num_filters1=filter_num1, + num_filters2=filter_num2, + num_groups=filter_num1, + stride=stride, + scale=self.scale, + kernel_size=kernel_size, + name='mobilenetv1_{}'.format(str(i + 1))) + return input + + return net_arch + + def _depthwise_separable(self, + input, + num_filters1, + num_filters2, + num_groups, + stride, + scale, + kernel_size, + name=None): + depthwise_conv = conv_bn_layer( + input=input, + filter_size=kernel_size, + num_filters=int(num_filters1 * scale), + stride=stride, + num_groups=int(num_groups * scale), + use_cudnn=False, + name=name + '_dw') + pointwise_conv = conv_bn_layer( + input=depthwise_conv, + filter_size=1, + num_filters=int(num_filters2 * scale), + stride=1, + name=name + '_sep') + + return pointwise_conv + diff --git a/paddleslim/nas/search_space/mobilenetv2_block.py b/paddleslim/nas/search_space/mobilenetv2_block.py deleted file mode 100644 index 6f04e97f03beda2f891e8aea29111f083afff6dc..0000000000000000000000000000000000000000 --- a/paddleslim/nas/search_space/mobilenetv2_block.py +++ /dev/null @@ -1,261 +0,0 @@ -# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. -# -# 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 numpy as np -import paddle.fluid as fluid -from paddle.fluid.param_attr import ParamAttr -from .search_space_base import SearchSpaceBase -from .base_layer import conv_bn_layer -from .search_space_registry import SEARCHSPACE - -__all__ = ["MobileNetV2BlockSpace"] - - -@SEARCHSPACE.register -class MobileNetV2BlockSpace(SearchSpaceBase): - def __init__(self, - input_size, - output_size, - block_num, - block_mask=None, - scale=1.0): - super(MobileNetV2BlockSpace, self).__init__(input_size, output_size, - block_num, block_mask) - self.filter_num1 = np.array([3, 4, 8, 12, 16, 24, 32, 48]) - self.filter_num1 = np.array([3, 4, 8, 12, 16, 24, 32, 48]) #8 - self.filter_num2 = np.array([8, 12, 16, 24, 32, 48, 64, 80]) #8 - self.filter_num3 = np.array([16, 24, 32, 48, 64, 80, 96, 128]) #8 - self.filter_num4 = np.array( - [24, 32, 48, 64, 80, 96, 128, 144, 160, 192]) #10 - self.filter_num5 = np.array( - [32, 48, 64, 80, 96, 128, 144, 160, 192, 224]) #10 - self.filter_num6 = np.array( - [64, 80, 96, 128, 144, 160, 192, 224, 256, 320, 384, 512]) #12 - # self.k_size means kernel size - self.k_size = np.array([3, 5]) #2 - # self.multiply means expansion_factor of each _inverted_residual_unit - self.multiply = np.array([1, 2, 3, 4, 6]) #5 - # self.repeat means repeat_num _inverted_residual_unit in each _invresi_blocks - self.repeat = np.array([1, 2, 3, 4, 5, 6]) #6 - self.scale = scale - - def init_tokens(self): - return [0] * (len(self.block_mask) * 4) - - def range_table(self): - range_table_base = [] - if self.block_mask != None: - for i in range(len(self.block_mask)): - filter_num = self.__dict__['filter_num{}'.format(i + 1 if i < 6 - else 6)] - range_table_base.append(len(self.multiply)) - range_table_base.append(len(filter_num)) - range_table_base.append(len(self.repeat)) - range_table_base.append(len(self.k_size)) - #[len(self.multiply), len(self.filter_num1), len(self.repeat), len(self.k_size), - # len(self.multiply), len(self.filter_num1), len(self.repeat), len(self.k_size), - # len(self.multiply), len(self.filter_num2), len(self.repeat), len(self.k_size), - # len(self.multiply), len(self.filter_num3), len(self.repeat), len(self.k_size), - # len(self.multiply), len(self.filter_num4), len(self.repeat), len(self.k_size), - # len(self.multiply), len(self.filter_num5), len(self.repeat), len(self.k_size), - # len(self.multiply), len(self.filter_num6), len(self.repeat), len(self.k_size)] - return range_table_base - - def token2arch(self, tokens=None): - """ - return mobilenetv2 net_arch function - """ - - if tokens is None: - tokens = self.init_tokens() - print(tokens) - print(len(tokens)) - - bottleneck_params_list = [] - if self.block_mask == None: - if self.block_num >= 1: - bottleneck_params_list.append( - (1, self.head_num[tokens[0]], 1, 1, 3)) - if self.block_num >= 2: - bottleneck_params_list.append( - (self.multiply[tokens[1]], self.filter_num1[tokens[2]], - self.repeat[tokens[3]], 2, self.k_size[tokens[4]])) - if self.block_num >= 3: - bottleneck_params_list.append( - (self.multiply[tokens[5]], self.filter_num1[tokens[6]], - self.repeat[tokens[7]], 2, self.k_size[tokens[8]])) - if self.block_num >= 4: - bottleneck_params_list.append( - (self.multiply[tokens[9]], self.filter_num2[tokens[10]], - self.repeat[tokens[11]], 2, self.k_size[tokens[12]])) - if self.block_num >= 5: - bottleneck_params_list.append( - (self.multiply[tokens[13]], self.filter_num3[tokens[14]], - self.repeat[tokens[15]], 2, self.k_size[tokens[16]])) - bottleneck_params_list.append( - (self.multiply[tokens[17]], self.filter_num4[tokens[18]], - self.repeat[tokens[19]], 1, self.k_size[tokens[20]])) - if self.block_num >= 6: - bottleneck_params_list.append( - (self.multiply[tokens[21]], self.filter_num5[tokens[22]], - self.repeat[tokens[23]], 2, self.k_size[tokens[24]])) - bottleneck_params_list.append( - (self.multiply[tokens[25]], self.filter_num6[tokens[26]], - self.repeat[tokens[27]], 1, self.k_size[tokens[28]])) - else: - for i in range(len(self.block_mask)): - filter_num = self.__dict__['filter_num{}'.format(i + 1 if i < 6 - else 6)] - bottleneck_params_list.append( - (self.multiply[tokens[i * 4]], - filter_num[tokens[i * 4 + 1]], - self.repeat[tokens[i * 4 + 2]], 2 - if self.block_mask[i] == 1 else 1, - self.k_size[tokens[i * 4 + 3]])) - - def net_arch(input): - # all padding is 'SAME' in the conv2d, can compute the actual padding automatic. - # bottleneck sequences - i = 1 - in_c = int(32 * self.scale) - for layer_setting in bottleneck_params_list: - t, c, n, s, k = layer_setting - i += 1 - input = self._invresi_blocks( - input=input, - in_c=in_c, - t=t, - c=int(c * self.scale), - n=n, - s=s, - k=k, - name='mobilenetv2_conv' + str(i)) - in_c = int(c * self.scale) - - return input - - return net_arch - - def _shortcut(self, input, data_residual): - """Build shortcut layer. - Args: - input(Variable): input. - data_residual(Variable): residual layer. - Returns: - Variable, layer output. - """ - return fluid.layers.elementwise_add(input, data_residual) - - def _inverted_residual_unit(self, - input, - num_in_filter, - num_filters, - ifshortcut, - stride, - filter_size, - expansion_factor, - reduction_ratio=4, - name=None): - """Build inverted residual unit. - Args: - input(Variable), input. - num_in_filter(int), number of in filters. - num_filters(int), number of filters. - ifshortcut(bool), whether using shortcut. - stride(int), stride. - filter_size(int), filter size. - padding(str|int|list), padding. - expansion_factor(float), expansion factor. - name(str), name. - Returns: - Variable, layers output. - """ - num_expfilter = int(round(num_in_filter * expansion_factor)) - channel_expand = conv_bn_layer( - input=input, - num_filters=num_expfilter, - filter_size=1, - stride=1, - padding='SAME', - num_groups=1, - act='relu6', - name=name + '_expand') - - bottleneck_conv = conv_bn_layer( - input=channel_expand, - num_filters=num_expfilter, - filter_size=filter_size, - stride=stride, - padding='SAME', - num_groups=num_expfilter, - act='relu6', - name=name + '_dwise', - use_cudnn=False) - - linear_out = conv_bn_layer( - input=bottleneck_conv, - num_filters=num_filters, - filter_size=1, - stride=1, - padding='SAME', - num_groups=1, - act=None, - name=name + '_linear') - out = linear_out - if ifshortcut: - out = self._shortcut(input=input, data_residual=out) - return out - - def _invresi_blocks(self, input, in_c, t, c, n, s, k, name=None): - """Build inverted residual blocks. - Args: - input: Variable, input. - in_c: int, number of in filters. - t: float, expansion factor. - c: int, number of filters. - n: int, number of layers. - s: int, stride. - k: int, filter size. - name: str, name. - Returns: - Variable, layers output. - """ - first_block = self._inverted_residual_unit( - input=input, - num_in_filter=in_c, - num_filters=c, - ifshortcut=False, - stride=s, - filter_size=k, - 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=k, - expansion_factor=t, - name=name + '_' + str(i + 1)) - return last_residual_block diff --git a/paddleslim/nas/search_space/search_space_base.py b/paddleslim/nas/search_space/search_space_base.py index 6a83f86005a5fb2408f7f85f40dff8a9e5cba819..e75bc03555198e1fc22dff369ff4d58c8f85a4dd 100644 --- a/paddleslim/nas/search_space/search_space_base.py +++ b/paddleslim/nas/search_space/search_space_base.py @@ -24,6 +24,15 @@ class SearchSpaceBase(object): self.output_size = output_size self.block_num = block_num self.block_mask = block_mask + if self.block_mask != None: + assert isinstance(self.block_mask, + list), 'Block_mask must be a list.' + print( + "If block_mask is NOT None, we will use block_mask as major configs!" + ) + self.block_num = None + if self.block_mask == None and self.block_num == None: + print("block_mask and block num can NOT be None at the same time!") def init_tokens(self): """Get init tokens in search space. diff --git a/paddleslim/nas/search_space/utils.py b/paddleslim/nas/search_space/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..daed1d599a6d9d5f0d62ff118d85b3572398e498 --- /dev/null +++ b/paddleslim/nas/search_space/utils.py @@ -0,0 +1,12 @@ +import math + +def compute_downsample_num(input_size, output_size): + downsample_num = 0 + while input_size > output_size: + input_size = math.ceil(float(input_size) / 2.0) + downsample_num += 1 + + if input_size != output_size: + raise NotImplementedError('output_size must can downsample by input_size!!!') + + return downsample_num