ghostnet.py 11.3 KB
Newer Older
W
weishengyu 已提交
1
# copyright (c) 2020 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

L
littletomatodonkey 已提交
24 25
__all__ = ["GhostNet_x0_5", "GhostNet_x1_0", "GhostNet_x1_3"]

W
weishengyu 已提交
26 27

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

W
weishengyu 已提交
49
        self._batch_norm = BatchNorm(
W
weishengyu 已提交
50
            num_channels=out_channels,
W
weishengyu 已提交
51
            act=act,
W
weishengyu 已提交
52
            param_attr=ParamAttr(
53
                name=bn_name + "_scale", regularizer=L2Decay(0.0)),
W
weishengyu 已提交
54
            bias_attr=ParamAttr(
55
                name=bn_name + "_offset", regularizer=L2Decay(0.0)),
W
weishengyu 已提交
56
            moving_mean_name=bn_name + "_mean",
L
littletomatodonkey 已提交
57
            moving_variance_name=bn_name + "_variance")
W
weishengyu 已提交
58 59 60 61 62 63 64 65

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


class SEBlock(nn.Layer):
W
weishengyu 已提交
66
    def __init__(self, num_channels, reduction_ratio=4, name=None):
W
weishengyu 已提交
67
        super(SEBlock, self).__init__()
68
        self.pool2d_gap = AdaptiveAvgPool2D(1)
W
weishengyu 已提交
69 70 71 72 73 74
        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 已提交
75 76 77
            weight_attr=ParamAttr(
                initializer=Uniform(-stdv, stdv), name=name + "_1_weights"),
            bias_attr=ParamAttr(name=name + "_1_offset"))
W
weishengyu 已提交
78 79 80 81
        stdv = 1.0 / math.sqrt(med_ch * 1.0)
        self.excitation = Linear(
            med_ch,
            num_channels,
W
weishengyu 已提交
82 83 84
            weight_attr=ParamAttr(
                initializer=Uniform(-stdv, stdv), name=name + "_2_weights"),
            bias_attr=ParamAttr(name=name + "_2_offset"))
W
weishengyu 已提交
85 86 87

    def forward(self, inputs):
        pool = self.pool2d_gap(inputs)
L
littletomatodonkey 已提交
88
        pool = paddle.squeeze(pool, axis=[2, 3])
W
weishengyu 已提交
89 90 91
        squeeze = self.squeeze(pool)
        squeeze = F.relu(squeeze)
        excitation = self.excitation(squeeze)
92
        excitation = paddle.clip(x=excitation, min=0, max=1)
L
littletomatodonkey 已提交
93
        excitation = paddle.unsqueeze(excitation, axis=[2, 3])
W
weishengyu 已提交
94 95 96 97 98
        out = inputs * excitation
        return out


class GhostModule(nn.Layer):
W
weishengyu 已提交
99 100 101 102 103 104 105 106 107
    def __init__(self,
                 in_channels,
                 output_channels,
                 kernel_size=1,
                 ratio=2,
                 dw_size=3,
                 stride=1,
                 relu=True,
                 name=None):
W
weishengyu 已提交
108 109 110 111
        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 已提交
112 113 114
            in_channels=in_channels,
            out_channels=init_channels,
            kernel_size=kernel_size,
W
weishengyu 已提交
115 116 117
            stride=stride,
            groups=1,
            act="relu" if relu else None,
W
weishengyu 已提交
118
            name=name + "_primary_conv")
W
weishengyu 已提交
119
        self.cheap_operation = ConvBNLayer(
W
weishengyu 已提交
120 121 122
            in_channels=init_channels,
            out_channels=new_channels,
            kernel_size=dw_size,
W
weishengyu 已提交
123 124 125
            stride=1,
            groups=init_channels,
            act="relu" if relu else None,
W
weishengyu 已提交
126
            name=name + "_cheap_operation")
W
weishengyu 已提交
127 128 129 130 131 132 133 134 135

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

    def forward(self, inputs):
W
weishengyu 已提交
196
        x = self.ghost_module_1(inputs)
W
weishengyu 已提交
197 198 199 200 201 202 203 204 205 206
        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)
207
        return paddle.add(x=x, y=shortcut)
W
weishengyu 已提交
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234


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 已提交
235 236 237
            in_channels=3,
            out_channels=output_channels,
            kernel_size=3,
W
weishengyu 已提交
238 239 240
            stride=2,
            groups=1,
            act="relu",
W
weishengyu 已提交
241
            name="conv1")
W
weishengyu 已提交
242 243 244 245
        # build inverted residual blocks
        idx = 0
        self.ghost_bottleneck_list = []
        for k, exp_size, c, use_se, s in self.cfgs:
W
weishengyu 已提交
246
            in_channels = output_channels
W
weishengyu 已提交
247
            output_channels = int(self._make_divisible(c * self.scale, 4))
W
dbg  
weishengyu 已提交
248
            hidden_dim = int(self._make_divisible(exp_size * self.scale, 4))
W
weishengyu 已提交
249 250 251
            ghost_bottleneck = self.add_sublayer(
                name="_ghostbottleneck_" + str(idx),
                sublayer=GhostBottleneck(
W
weishengyu 已提交
252
                    in_channels=in_channels,
W
weishengyu 已提交
253 254 255 256 257
                    hidden_dim=hidden_dim,
                    output_channels=output_channels,
                    kernel_size=k,
                    stride=s,
                    use_se=use_se,
W
weishengyu 已提交
258
                    name="_ghostbottleneck_" + str(idx)))
W
weishengyu 已提交
259 260 261
            self.ghost_bottleneck_list.append(ghost_bottleneck)
            idx += 1
        # build last several layers
W
weishengyu 已提交
262
        in_channels = output_channels
W
weishengyu 已提交
263 264
        output_channels = int(self._make_divisible(exp_size * self.scale, 4))
        self.conv_last = ConvBNLayer(
W
weishengyu 已提交
265 266 267
            in_channels=in_channels,
            out_channels=output_channels,
            kernel_size=1,
W
weishengyu 已提交
268 269 270
            stride=1,
            groups=1,
            act="relu",
W
weishengyu 已提交
271
            name="conv_last")
272
        self.pool2d_gap = AdaptiveAvgPool2D(1)
W
weishengyu 已提交
273
        in_channels = output_channels
W
weishengyu 已提交
274
        self._fc0_output_channels = 1280
W
weishengyu 已提交
275
        self.fc_0 = ConvBNLayer(
W
weishengyu 已提交
276 277 278
            in_channels=in_channels,
            out_channels=self._fc0_output_channels,
            kernel_size=1,
W
weishengyu 已提交
279 280
            stride=1,
            act="relu",
W
weishengyu 已提交
281
            name="fc_0")
W
weishengyu 已提交
282
        self.dropout = nn.Dropout(p=0.2)
W
weishengyu 已提交
283
        stdv = 1.0 / math.sqrt(self._fc0_output_channels * 1.0)
W
weishengyu 已提交
284
        self.fc_1 = Linear(
W
weishengyu 已提交
285
            self._fc0_output_channels,
W
weishengyu 已提交
286
            class_dim,
W
weishengyu 已提交
287 288 289
            weight_attr=ParamAttr(
                name="fc_1_weights", initializer=Uniform(-stdv, stdv)),
            bias_attr=ParamAttr(name="fc_1_offset"))
W
weishengyu 已提交
290 291 292 293 294 295 296 297 298

    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 已提交
299
        x = paddle.reshape(x, shape=[-1, self._fc0_output_channels])
W
weishengyu 已提交
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
        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 已提交
317 318


W
weishengyu 已提交
319
def GhostNet_x0_5(**args):
W
weishengyu 已提交
320 321 322 323
    model = GhostNet(scale=0.5)
    return model


W
weishengyu 已提交
324
def GhostNet_x1_0(**args):
W
weishengyu 已提交
325 326 327 328
    model = GhostNet(scale=1.0)
    return model


W
weishengyu 已提交
329
def GhostNet_x1_3(**args):
W
weishengyu 已提交
330
    model = GhostNet(scale=1.3)
W
weishengyu 已提交
331
    return model