diff --git a/regnet.py b/regnet.py new file mode 100644 index 0000000000000000000000000000000000000000..1c426735870c689f93c63beb5402929dfc6bd963 --- /dev/null +++ b/regnet.py @@ -0,0 +1,282 @@ +#copyright (c) 2020 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 math + +import paddle +import paddle.fluid as fluid +from paddle.fluid.param_attr import ParamAttr +import math +import numpy as np + +__all__ = ["RegNetX_200MF", "RegNetX_4GF", "RegNetX_32GF", "RegNetY_200MF", "RegNetY_4GF", "RegNetY_32GF"] + + +class RegNet(): + def __init__(self, w_a, w_0, w_m, d, group_w, bot_mul, q=8, se_on=False): + self.w_a = w_a + self.w_0 = w_0 + self.w_m = w_m + self.d = d + self.q = q + self.group_w = group_w + self.bot_mul = bot_mul + # Stem type + self.stem_type = "simple_stem_in" + # Stem width + self.stem_w = 32 + # Block type + self.block_type = "res_bottleneck_block" + # Stride of each stage + self.stride = 2 + # Squeeze-and-Excitation (RegNetY) + self.se_on = se_on + self.se_r = 0.25 + + def quantize_float(self, f, q): + """Converts a float to closest non-zero int divisible by q.""" + return int(round(f / q) * q) + + def adjust_ws_gs_comp(self, ws, bms, gs): + """Adjusts the compatibility of widths and groups.""" + ws_bot = [int(w * b) for w, b in zip(ws, bms)] + gs = [min(g, w_bot) for g, w_bot in zip(gs, ws_bot)] + ws_bot = [self.quantize_float(w_bot, g) for w_bot, g in zip(ws_bot, gs)] + ws = [int(w_bot / b) for w_bot, b in zip(ws_bot, bms)] + return ws, gs + + def get_stages_from_blocks(self, ws, rs): + """Gets ws/ds of network at each stage from per block values.""" + ts = [ + w != wp or r != rp + for w, wp, r, rp in zip(ws + [0], [0] + ws, rs + [0], [0] + rs) + ] + s_ws = [w for w, t in zip(ws, ts[:-1]) if t] + s_ds = np.diff([d for d, t in zip(range(len(ts)), ts) if t]).tolist() + return s_ws, s_ds + + def generate_regnet(self, w_a, w_0, w_m, d, q=8): + """Generates per block ws from RegNet parameters.""" + assert w_a >= 0 and w_0 > 0 and w_m > 1 and w_0 % q == 0 + ws_cont = np.arange(d) * w_a + w_0 + ks = np.round(np.log(ws_cont / w_0) / np.log(w_m)) + ws = w_0 * np.power(w_m, ks) + ws = np.round(np.divide(ws, q)) * q + num_stages, max_stage = len(np.unique(ws)), ks.max() + 1 + ws, ws_cont = ws.astype(int).tolist(), ws_cont.tolist() + return ws, num_stages, max_stage, ws_cont + + def init_weights(self, op_type, filter_size=0, num_channels=0, name=None): + if op_type == 'conv': + fan_out = num_channels * filter_size * filter_size + param_attr = ParamAttr( + name=name + "_weights", + initializer=fluid.initializer.NormalInitializer( + loc=0.0, scale=math.sqrt(2.0 / fan_out))) + bias_attr = False + elif op_type == 'bn': + param_attr = ParamAttr( + name=name + "_scale",initializer=fluid.initializer.Constant(0.0)) + bias_attr = ParamAttr( + name=name + "_offset", initializer=fluid.initializer.Constant(0.0)) + elif op_type == 'final_bn': + param_attr = ParamAttr( + name=name + "_scale",initializer=fluid.initializer.Constant(1.0)) + bias_attr = ParamAttr( + name=name + "_offset", initializer=fluid.initializer.Constant(0.0)) + return param_attr, bias_attr + + def net(self, input, class_dim=1000): + # Generate RegNet ws per block + b_ws, num_s, max_s, ws_cont = self.generate_regnet(self.w_a, self.w_0, self.w_m, self.d, self.q) + #print('generate_regnet func done!') + #print('b_ws={}, num_stages={}, max_stage={}, ws_cont={}'.format(b_ws, num_s, max_s, ws_cont)) + # Convert to per stage format + ws, ds = self.get_stages_from_blocks(b_ws, b_ws) + #print('get_stages_from_blocks func done!') + #print('ws={}, ds={}'.format(ws, ds)) + # Generate group widths and bot muls + gws = [self.group_w for _ in range(num_s)] + bms = [self.bot_mul for _ in range(num_s)] + #print('gws={}'.format(gws)) + #print('bms={}'.format(bms)) + # Adjust the compatibility of ws and gws + #print('adjust_ws_gs_comp func done!') + ws, gws = self.adjust_ws_gs_comp(ws, bms, gws) + print('ws={}'.format(ws)) + print('gws={}'.format(gws)) + # Use the same stride for each stage + ss = [self.stride for _ in range(num_s)] + # Use SE for RegNetY + se_r = self.se_r + + # Construct the model + # Group params by stage + stage_params = list(zip(ds, ws, ss, bms, gws)) + # Construct the stem + conv = self.conv_bn_layer( + input=input, + num_filters=self.stem_w, + filter_size=3, + stride=2, + padding=1, + act='relu', + name="stem_conv") + # Construct the stages + for block, (d, w_out, stride, bm, gw) in enumerate(stage_params): + for i in range(d): + # Stride apply to the first block of the stage + b_stride = stride if i == 0 else 1 + conv_name = 's' + str(block + 1) + '_b' + str(i + 1) #chr(97 + i) + conv = self.bottleneck_block( + input=conv, + num_filters=w_out, + stride=b_stride, + bm=bm, + gw=gw, + se_r=self.se_r, + name=conv_name) + pool = fluid.layers.pool2d( + input=conv, pool_type='avg', global_pooling=True) + out = fluid.layers.fc( + input=pool, + size=class_dim, + param_attr=ParamAttr( + name = "fc_0.w_0", + initializer=fluid.initializer.NormalInitializer(loc=0.0, scale=0.01)), + bias_attr=ParamAttr( + name="fc_0.b_0", + initializer=fluid.initializer.Constant(0.0))) + return out + + def conv_bn_layer(self, + input, + num_filters, + filter_size, + stride=1, + groups=1, + padding=0, + act=None, + name=None, + final_bn=False): + conv = fluid.layers.conv2d( + input=input, + num_filters=num_filters, + filter_size=filter_size, + stride=stride, + padding=padding, + groups=groups, + act=None, + name=name + '.conv2d.output.1') + bn_name = name + '_bn' + + return fluid.layers.batch_norm( + input=conv, + act=act, + name=bn_name + '.output.1', + moving_mean_name=bn_name + '_mean', + moving_variance_name=bn_name + '_variance', ) + #todo: to check the bn layer's eps and momentum, and relu_inplace + + 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=input, num_filters=ch_out, filter_size=1, stride=stride, padding=0, act=None, name=name) + else: + return input + + def bottleneck_block(self, input, num_filters, stride, bm, gw, se_r, name): + # Compute the bottleneck width + w_b = int(round(num_filters * bm)) + # Compute the number of groups + num_gs = w_b // gw + conv0 = self.conv_bn_layer( + input=input, num_filters=w_b, filter_size=1, stride=1, padding=0, act='relu', name=name+"_branch2a") + conv1 = self.conv_bn_layer( + input=conv0, + num_filters=w_b, + filter_size=3, + stride=stride, + padding=1, + groups=num_gs, + act='relu', + name=name+"_branch2b") + # Squeeze-and-Excitation (SE) + if self.se_on: + w_se = int(round(input.shape[1] * se_r)) + conv1 = self.squeeze_excitation( + input=conv1, + num_channels=w_b, + reduction_channels=w_se, + name=name+"_branch2se") + + conv2 =self.conv_bn_layer( + input=conv1, num_filters=num_filters, filter_size=1, stride=1, padding=0, act=None, name=name+"_branch2c", final_bn=True) + + short = self.shortcut(input, num_filters, stride, name=name + "_branch1") + + return fluid.layers.elementwise_add(x=short, y=conv2, act='relu') + + def squeeze_excitation(self, input, num_channels, reduction_channels, name=None): + pool = fluid.layers.pool2d( + input=input, pool_size=0, pool_type='avg', global_pooling=True) + fan_out = num_channels + squeeze = fluid.layers.conv2d(input=pool, + num_filters=reduction_channels, + filter_size=1, + act='relu', + param_attr=ParamAttr( + initializer=fluid.initializer.NormalInitializer(loc=0.0, scale=math.sqrt(2.0 / fan_out)), + name=name+'_sqz_weights'), + bias_attr=ParamAttr(name=name+'_sqz_offset')) + excitation = fluid.layers.conv2d(input=squeeze, + num_filters=num_channels, + filter_size=1, + act='sigmoid', + param_attr=ParamAttr( + initializer=fluid.initializer.NormalInitializer(loc=0.0, scale=math.sqrt(2.0 / fan_out)), + name=name+'_exc_weights'), + bias_attr=ParamAttr(name=name+'_exc_offset')) + scale = fluid.layers.elementwise_mul(x=input, y=excitation, axis=0) + return scale + + + +def RegNetX_200MF(): + model = RegNet(w_a=36.44, w_0=24, w_m=2.49, d=13, group_w=8, bot_mul=1.0, q=8) + return model + +def RegNetX_4GF(): + model = RegNet(w_a=38.65, w_0=96, w_m=2.43, d=23, group_w=40, bot_mul=1.0, q=8) + return model + +def RegNetX_32GF(): + model = RegNet(w_a=69.86, w_0=320, w_m=2.0, d=23, group_w=168, bot_mul=1.0, q=8) + return model + +def RegNetY_200MF(): + model = RegNet(w_a=36.44, w_0=24, w_m=2.49, d=13, group_w=8, bot_mul=1.0, q=8, se_on=True) + return model + +def RegNetY_4GF(): + model = RegNet(w_a=31.41, w_0=96, w_m=2.24, d=22, group_w=64, bot_mul=1.0, q=8, se_on=True) + return model + +def RegNetY_32GF(): + model = RegNet(w_a=115.89, w_0=232, w_m=2.53, d=20, group_w=232, bot_mul=1.0, q=8, se_on=True) + return model