ghostnet.py 12.6 KB
Newer Older
C
cuicheng01 已提交
1
# copyright (c) 2021 PaddlePaddle Authors. All Rights Reserve.
W
weishengyu 已提交
2
#
W
weishengyu 已提交
3 4 5
# 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
W
weishengyu 已提交
6 7 8
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
W
weishengyu 已提交
9 10 11 12 13
# 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.
W
weishengyu 已提交
14 15 16 17 18 19

import math
import paddle
from paddle import ParamAttr
import paddle.nn as nn
import paddle.nn.functional as F
20 21 22
from paddle.nn import Conv2D, BatchNorm, AdaptiveAvgPool2D, Linear
from paddle.regularizer import L2Decay
from paddle.nn.initializer import Uniform, KaimingNormal
W
weishengyu 已提交
23

C
cuicheng01 已提交
24 25 26 27 28 29 30 31
from ppcls.utils.save_load import load_dygraph_pretrain, load_dygraph_pretrain_from_url

MODEL_URLS = {"GhostNet_x0_5": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x0_5_pretrained.pdparams",
              "GhostNet_x1_0": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x1_0_pretrained.pdparams",
              "GhostNet_x1_3": "https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/GhostNet_x1_3_pretrained.pdparams",
             }

__all__ = list(MODEL_URLS.keys())
L
littletomatodonkey 已提交
32

W
weishengyu 已提交
33 34

class ConvBNLayer(nn.Layer):
W
weishengyu 已提交
35 36 37 38 39 40 41 42
    def __init__(self,
                 in_channels,
                 out_channels,
                 kernel_size,
                 stride=1,
                 groups=1,
                 act="relu",
                 name=None):
W
weishengyu 已提交
43
        super(ConvBNLayer, self).__init__()
44
        self._conv = Conv2D(
W
weishengyu 已提交
45 46 47
            in_channels=in_channels,
            out_channels=out_channels,
            kernel_size=kernel_size,
W
weishengyu 已提交
48
            stride=stride,
W
weishengyu 已提交
49
            padding=(kernel_size - 1) // 2,
W
weishengyu 已提交
50
            groups=groups,
W
weishengyu 已提交
51
            weight_attr=ParamAttr(
52
                initializer=KaimingNormal(), name=name + "_weights"),
W
weishengyu 已提交
53
            bias_attr=False)
W
weishengyu 已提交
54
        bn_name = name + "_bn"
W
weishengyu 已提交
55

W
weishengyu 已提交
56
        self._batch_norm = BatchNorm(
W
weishengyu 已提交
57
            num_channels=out_channels,
W
weishengyu 已提交
58
            act=act,
W
weishengyu 已提交
59
            param_attr=ParamAttr(
60
                name=bn_name + "_scale", regularizer=L2Decay(0.0)),
W
weishengyu 已提交
61
            bias_attr=ParamAttr(
62
                name=bn_name + "_offset", regularizer=L2Decay(0.0)),
W
weishengyu 已提交
63
            moving_mean_name=bn_name + "_mean",
L
littletomatodonkey 已提交
64
            moving_variance_name=bn_name + "_variance")
W
weishengyu 已提交
65 66 67 68 69 70 71 72

    def forward(self, inputs):
        y = self._conv(inputs)
        y = self._batch_norm(y)
        return y


class SEBlock(nn.Layer):
W
weishengyu 已提交
73
    def __init__(self, num_channels, reduction_ratio=4, name=None):
W
weishengyu 已提交
74
        super(SEBlock, self).__init__()
75
        self.pool2d_gap = AdaptiveAvgPool2D(1)
W
weishengyu 已提交
76 77 78 79 80 81
        self._num_channels = num_channels
        stdv = 1.0 / math.sqrt(num_channels * 1.0)
        med_ch = num_channels // reduction_ratio
        self.squeeze = Linear(
            num_channels,
            med_ch,
W
weishengyu 已提交
82 83 84
            weight_attr=ParamAttr(
                initializer=Uniform(-stdv, stdv), name=name + "_1_weights"),
            bias_attr=ParamAttr(name=name + "_1_offset"))
W
weishengyu 已提交
85 86 87 88
        stdv = 1.0 / math.sqrt(med_ch * 1.0)
        self.excitation = Linear(
            med_ch,
            num_channels,
W
weishengyu 已提交
89 90 91
            weight_attr=ParamAttr(
                initializer=Uniform(-stdv, stdv), name=name + "_2_weights"),
            bias_attr=ParamAttr(name=name + "_2_offset"))
W
weishengyu 已提交
92 93 94

    def forward(self, inputs):
        pool = self.pool2d_gap(inputs)
L
littletomatodonkey 已提交
95
        pool = paddle.squeeze(pool, axis=[2, 3])
W
weishengyu 已提交
96 97 98
        squeeze = self.squeeze(pool)
        squeeze = F.relu(squeeze)
        excitation = self.excitation(squeeze)
99
        excitation = paddle.clip(x=excitation, min=0, max=1)
L
littletomatodonkey 已提交
100
        excitation = paddle.unsqueeze(excitation, axis=[2, 3])
101
        out = paddle.multiply(inputs, excitation)
W
weishengyu 已提交
102 103 104 105
        return out


class GhostModule(nn.Layer):
W
weishengyu 已提交
106 107 108 109 110 111 112 113 114
    def __init__(self,
                 in_channels,
                 output_channels,
                 kernel_size=1,
                 ratio=2,
                 dw_size=3,
                 stride=1,
                 relu=True,
                 name=None):
W
weishengyu 已提交
115 116 117 118
        super(GhostModule, self).__init__()
        init_channels = int(math.ceil(output_channels / ratio))
        new_channels = int(init_channels * (ratio - 1))
        self.primary_conv = ConvBNLayer(
W
weishengyu 已提交
119 120 121
            in_channels=in_channels,
            out_channels=init_channels,
            kernel_size=kernel_size,
W
weishengyu 已提交
122 123 124
            stride=stride,
            groups=1,
            act="relu" if relu else None,
W
weishengyu 已提交
125
            name=name + "_primary_conv")
W
weishengyu 已提交
126
        self.cheap_operation = ConvBNLayer(
W
weishengyu 已提交
127 128 129
            in_channels=init_channels,
            out_channels=new_channels,
            kernel_size=dw_size,
W
weishengyu 已提交
130 131 132
            stride=1,
            groups=init_channels,
            act="relu" if relu else None,
W
weishengyu 已提交
133
            name=name + "_cheap_operation")
W
weishengyu 已提交
134 135 136 137 138 139 140 141 142

    def forward(self, inputs):
        x = self.primary_conv(inputs)
        y = self.cheap_operation(x)
        out = paddle.concat([x, y], axis=1)
        return out


class GhostBottleneck(nn.Layer):
W
weishengyu 已提交
143 144 145 146 147 148 149 150
    def __init__(self,
                 in_channels,
                 hidden_dim,
                 output_channels,
                 kernel_size,
                 stride,
                 use_se,
                 name=None):
W
weishengyu 已提交
151 152 153
        super(GhostBottleneck, self).__init__()
        self._stride = stride
        self._use_se = use_se
W
weishengyu 已提交
154
        self._num_channels = in_channels
W
weishengyu 已提交
155 156
        self._output_channels = output_channels
        self.ghost_module_1 = GhostModule(
W
weishengyu 已提交
157
            in_channels=in_channels,
W
weishengyu 已提交
158 159 160 161
            output_channels=hidden_dim,
            kernel_size=1,
            stride=1,
            relu=True,
W
weishengyu 已提交
162
            name=name + "_ghost_module_1")
W
weishengyu 已提交
163 164
        if stride == 2:
            self.depthwise_conv = ConvBNLayer(
W
weishengyu 已提交
165 166 167
                in_channels=hidden_dim,
                out_channels=hidden_dim,
                kernel_size=kernel_size,
W
weishengyu 已提交
168 169 170
                stride=stride,
                groups=hidden_dim,
                act=None,
W
weishengyu 已提交
171 172
                name=name +
                "_depthwise_depthwise"  # looks strange due to an old typo, will be fixed later.
W
weishengyu 已提交
173 174
            )
        if use_se:
W
weishengyu 已提交
175
            self.se_block = SEBlock(num_channels=hidden_dim, name=name + "_se")
W
weishengyu 已提交
176
        self.ghost_module_2 = GhostModule(
W
weishengyu 已提交
177
            in_channels=hidden_dim,
W
weishengyu 已提交
178 179 180
            output_channels=output_channels,
            kernel_size=1,
            relu=False,
W
weishengyu 已提交
181
            name=name + "_ghost_module_2")
W
weishengyu 已提交
182
        if stride != 1 or in_channels != output_channels:
W
weishengyu 已提交
183
            self.shortcut_depthwise = ConvBNLayer(
W
weishengyu 已提交
184 185 186
                in_channels=in_channels,
                out_channels=in_channels,
                kernel_size=kernel_size,
W
weishengyu 已提交
187
                stride=stride,
W
weishengyu 已提交
188
                groups=in_channels,
W
weishengyu 已提交
189
                act=None,
W
weishengyu 已提交
190 191
                name=name +
                "_shortcut_depthwise_depthwise"  # looks strange due to an old typo, will be fixed later.
W
weishengyu 已提交
192 193
            )
            self.shortcut_conv = ConvBNLayer(
W
weishengyu 已提交
194 195 196
                in_channels=in_channels,
                out_channels=output_channels,
                kernel_size=1,
W
weishengyu 已提交
197 198 199
                stride=1,
                groups=1,
                act=None,
W
weishengyu 已提交
200
                name=name + "_shortcut_conv")
W
weishengyu 已提交
201 202

    def forward(self, inputs):
W
weishengyu 已提交
203
        x = self.ghost_module_1(inputs)
W
weishengyu 已提交
204 205 206 207 208 209 210 211 212 213
        if self._stride == 2:
            x = self.depthwise_conv(x)
        if self._use_se:
            x = self.se_block(x)
        x = self.ghost_module_2(x)
        if self._stride == 1 and self._num_channels == self._output_channels:
            shortcut = inputs
        else:
            shortcut = self.shortcut_depthwise(inputs)
            shortcut = self.shortcut_conv(shortcut)
214
        return paddle.add(x=x, y=shortcut)
W
weishengyu 已提交
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241


class GhostNet(nn.Layer):
    def __init__(self, scale, class_dim=1000):
        super(GhostNet, self).__init__()
        self.cfgs = [
            # k, t, c, SE, s
            [3, 16, 16, 0, 1],
            [3, 48, 24, 0, 2],
            [3, 72, 24, 0, 1],
            [5, 72, 40, 1, 2],
            [5, 120, 40, 1, 1],
            [3, 240, 80, 0, 2],
            [3, 200, 80, 0, 1],
            [3, 184, 80, 0, 1],
            [3, 184, 80, 0, 1],
            [3, 480, 112, 1, 1],
            [3, 672, 112, 1, 1],
            [5, 672, 160, 1, 2],
            [5, 960, 160, 0, 1],
            [5, 960, 160, 1, 1],
            [5, 960, 160, 0, 1],
            [5, 960, 160, 1, 1]
        ]
        self.scale = scale
        output_channels = int(self._make_divisible(16 * self.scale, 4))
        self.conv1 = ConvBNLayer(
W
weishengyu 已提交
242 243 244
            in_channels=3,
            out_channels=output_channels,
            kernel_size=3,
W
weishengyu 已提交
245 246 247
            stride=2,
            groups=1,
            act="relu",
W
weishengyu 已提交
248
            name="conv1")
W
weishengyu 已提交
249 250 251 252
        # build inverted residual blocks
        idx = 0
        self.ghost_bottleneck_list = []
        for k, exp_size, c, use_se, s in self.cfgs:
W
weishengyu 已提交
253
            in_channels = output_channels
W
weishengyu 已提交
254
            output_channels = int(self._make_divisible(c * self.scale, 4))
W
dbg  
weishengyu 已提交
255
            hidden_dim = int(self._make_divisible(exp_size * self.scale, 4))
W
weishengyu 已提交
256 257 258
            ghost_bottleneck = self.add_sublayer(
                name="_ghostbottleneck_" + str(idx),
                sublayer=GhostBottleneck(
W
weishengyu 已提交
259
                    in_channels=in_channels,
W
weishengyu 已提交
260 261 262 263 264
                    hidden_dim=hidden_dim,
                    output_channels=output_channels,
                    kernel_size=k,
                    stride=s,
                    use_se=use_se,
W
weishengyu 已提交
265
                    name="_ghostbottleneck_" + str(idx)))
W
weishengyu 已提交
266 267 268
            self.ghost_bottleneck_list.append(ghost_bottleneck)
            idx += 1
        # build last several layers
W
weishengyu 已提交
269
        in_channels = output_channels
W
weishengyu 已提交
270 271
        output_channels = int(self._make_divisible(exp_size * self.scale, 4))
        self.conv_last = ConvBNLayer(
W
weishengyu 已提交
272 273 274
            in_channels=in_channels,
            out_channels=output_channels,
            kernel_size=1,
W
weishengyu 已提交
275 276 277
            stride=1,
            groups=1,
            act="relu",
W
weishengyu 已提交
278
            name="conv_last")
279
        self.pool2d_gap = AdaptiveAvgPool2D(1)
W
weishengyu 已提交
280
        in_channels = output_channels
W
weishengyu 已提交
281
        self._fc0_output_channels = 1280
W
weishengyu 已提交
282
        self.fc_0 = ConvBNLayer(
W
weishengyu 已提交
283 284 285
            in_channels=in_channels,
            out_channels=self._fc0_output_channels,
            kernel_size=1,
W
weishengyu 已提交
286 287
            stride=1,
            act="relu",
W
weishengyu 已提交
288
            name="fc_0")
W
weishengyu 已提交
289
        self.dropout = nn.Dropout(p=0.2)
W
weishengyu 已提交
290
        stdv = 1.0 / math.sqrt(self._fc0_output_channels * 1.0)
W
weishengyu 已提交
291
        self.fc_1 = Linear(
W
weishengyu 已提交
292
            self._fc0_output_channels,
W
weishengyu 已提交
293
            class_dim,
W
weishengyu 已提交
294 295 296
            weight_attr=ParamAttr(
                name="fc_1_weights", initializer=Uniform(-stdv, stdv)),
            bias_attr=ParamAttr(name="fc_1_offset"))
W
weishengyu 已提交
297 298 299 300 301 302 303 304 305

    def forward(self, inputs):
        x = self.conv1(inputs)
        for ghost_bottleneck in self.ghost_bottleneck_list:
            x = ghost_bottleneck(x)
        x = self.conv_last(x)
        x = self.pool2d_gap(x)
        x = self.fc_0(x)
        x = self.dropout(x)
W
weishengyu 已提交
306
        x = paddle.reshape(x, shape=[-1, self._fc0_output_channels])
W
weishengyu 已提交
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
        x = self.fc_1(x)
        return x

    def _make_divisible(self, v, divisor, min_value=None):
        """
        This function is taken from the original tf repo.
        It ensures that all layers have a channel number that is divisible by 8
        It can be seen here:
        https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
        """
        if min_value is None:
            min_value = divisor
        new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
        # Make sure that round down does not go down by more than 10%.
        if new_v < 0.9 * v:
            new_v += divisor
        return new_v
W
weishengyu 已提交
324

C
cuicheng01 已提交
325 326 327 328 329 330 331 332 333 334 335 336 337
    
def _load_pretrained(pretrained, model, model_url, use_ssld=False):
    if pretrained is False:
        pass
    elif pretrained is True:
        load_dygraph_pretrain_from_url(model, model_url, use_ssld=use_ssld)
    elif isinstance(pretrained, str):
        load_dygraph_pretrain(model, pretrained)
    else:
        raise RuntimeError(
            "pretrained type is not available. Please use `string` or `boolean` type."
        )

W
weishengyu 已提交
338

C
cuicheng01 已提交
339 340 341
def GhostNet_x0_5(pretrained=False, use_ssld=False, **kwargs):
    model = GhostNet(scale=0.5, **kwargs)
    _load_pretrained(pretrained, model, MODEL_URLS["GhostNet_x0_5"], use_ssld=use_ssld)
W
weishengyu 已提交
342 343 344
    return model


C
cuicheng01 已提交
345 346 347
def GhostNet_x1_0(pretrained=False, use_ssld=False, **kwargs):
    model = GhostNet(scale=1.0, **kwargs)
    _load_pretrained(pretrained, model, MODEL_URLS["GhostNet_x1_0"], use_ssld=use_ssld)
W
weishengyu 已提交
348 349 350
    return model


C
cuicheng01 已提交
351 352 353
def GhostNet_x1_3(pretrained=False, use_ssld=False, **kwargs):
    model = GhostNet(scale=1.3, **kwargs)
    _load_pretrained(pretrained, model, MODEL_URLS["GhostNet_x1_3"], use_ssld=use_ssld)
W
weishengyu 已提交
354
    return model