ghostnet.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. # copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import math
  15. import paddle
  16. from paddle import ParamAttr
  17. import paddle.nn as nn
  18. import paddle.nn.functional as F
  19. from paddle.nn import Conv2D, BatchNorm, AdaptiveAvgPool2D, Linear
  20. from paddle.regularizer import L2Decay
  21. from paddle.nn.initializer import Uniform, KaimingNormal
  22. __all__ = ["GhostNet_x0_5", "GhostNet_x1_0", "GhostNet_x1_3"]
  23. class ConvBNLayer(nn.Layer):
  24. def __init__(self,
  25. in_channels,
  26. out_channels,
  27. kernel_size,
  28. stride=1,
  29. groups=1,
  30. act="relu",
  31. name=None):
  32. super(ConvBNLayer, self).__init__()
  33. self._conv = Conv2D(
  34. in_channels=in_channels,
  35. out_channels=out_channels,
  36. kernel_size=kernel_size,
  37. stride=stride,
  38. padding=(kernel_size - 1) // 2,
  39. groups=groups,
  40. weight_attr=ParamAttr(
  41. initializer=KaimingNormal(), name=name + "_weights"),
  42. bias_attr=False)
  43. bn_name = name + "_bn"
  44. self._batch_norm = BatchNorm(
  45. num_channels=out_channels,
  46. act=act,
  47. param_attr=ParamAttr(
  48. name=bn_name + "_scale", regularizer=L2Decay(0.0)),
  49. bias_attr=ParamAttr(
  50. name=bn_name + "_offset", regularizer=L2Decay(0.0)),
  51. moving_mean_name=bn_name + "_mean",
  52. moving_variance_name=bn_name + "_variance")
  53. def forward(self, inputs):
  54. y = self._conv(inputs)
  55. y = self._batch_norm(y)
  56. return y
  57. class SEBlock(nn.Layer):
  58. def __init__(self, num_channels, reduction_ratio=4, name=None):
  59. super(SEBlock, self).__init__()
  60. self.pool2d_gap = AdaptiveAvgPool2D(1)
  61. self._num_channels = num_channels
  62. stdv = 1.0 / math.sqrt(num_channels * 1.0)
  63. med_ch = num_channels // reduction_ratio
  64. self.squeeze = Linear(
  65. num_channels,
  66. med_ch,
  67. weight_attr=ParamAttr(
  68. initializer=Uniform(-stdv, stdv), name=name + "_1_weights"),
  69. bias_attr=ParamAttr(name=name + "_1_offset"))
  70. stdv = 1.0 / math.sqrt(med_ch * 1.0)
  71. self.excitation = Linear(
  72. med_ch,
  73. num_channels,
  74. weight_attr=ParamAttr(
  75. initializer=Uniform(-stdv, stdv), name=name + "_2_weights"),
  76. bias_attr=ParamAttr(name=name + "_2_offset"))
  77. def forward(self, inputs):
  78. pool = self.pool2d_gap(inputs)
  79. pool = paddle.squeeze(pool, axis=[2, 3])
  80. squeeze = self.squeeze(pool)
  81. squeeze = F.relu(squeeze)
  82. excitation = self.excitation(squeeze)
  83. excitation = paddle.clip(x=excitation, min=0, max=1)
  84. excitation = paddle.unsqueeze(excitation, axis=[2, 3])
  85. out = paddle.multiply(inputs, excitation)
  86. return out
  87. class GhostModule(nn.Layer):
  88. def __init__(self,
  89. in_channels,
  90. output_channels,
  91. kernel_size=1,
  92. ratio=2,
  93. dw_size=3,
  94. stride=1,
  95. relu=True,
  96. name=None):
  97. super(GhostModule, self).__init__()
  98. init_channels = int(math.ceil(output_channels / ratio))
  99. new_channels = int(init_channels * (ratio - 1))
  100. self.primary_conv = ConvBNLayer(
  101. in_channels=in_channels,
  102. out_channels=init_channels,
  103. kernel_size=kernel_size,
  104. stride=stride,
  105. groups=1,
  106. act="relu" if relu else None,
  107. name=name + "_primary_conv")
  108. self.cheap_operation = ConvBNLayer(
  109. in_channels=init_channels,
  110. out_channels=new_channels,
  111. kernel_size=dw_size,
  112. stride=1,
  113. groups=init_channels,
  114. act="relu" if relu else None,
  115. name=name + "_cheap_operation")
  116. def forward(self, inputs):
  117. x = self.primary_conv(inputs)
  118. y = self.cheap_operation(x)
  119. out = paddle.concat([x, y], axis=1)
  120. return out
  121. class GhostBottleneck(nn.Layer):
  122. def __init__(self,
  123. in_channels,
  124. hidden_dim,
  125. output_channels,
  126. kernel_size,
  127. stride,
  128. use_se,
  129. name=None):
  130. super(GhostBottleneck, self).__init__()
  131. self._stride = stride
  132. self._use_se = use_se
  133. self._num_channels = in_channels
  134. self._output_channels = output_channels
  135. self.ghost_module_1 = GhostModule(
  136. in_channels=in_channels,
  137. output_channels=hidden_dim,
  138. kernel_size=1,
  139. stride=1,
  140. relu=True,
  141. name=name + "_ghost_module_1")
  142. if stride == 2:
  143. self.depthwise_conv = ConvBNLayer(
  144. in_channels=hidden_dim,
  145. out_channels=hidden_dim,
  146. kernel_size=kernel_size,
  147. stride=stride,
  148. groups=hidden_dim,
  149. act=None,
  150. name=name +
  151. "_depthwise_depthwise" # looks strange due to an old typo, will be fixed later.
  152. )
  153. if use_se:
  154. self.se_block = SEBlock(num_channels=hidden_dim, name=name + "_se")
  155. self.ghost_module_2 = GhostModule(
  156. in_channels=hidden_dim,
  157. output_channels=output_channels,
  158. kernel_size=1,
  159. relu=False,
  160. name=name + "_ghost_module_2")
  161. if stride != 1 or in_channels != output_channels:
  162. self.shortcut_depthwise = ConvBNLayer(
  163. in_channels=in_channels,
  164. out_channels=in_channels,
  165. kernel_size=kernel_size,
  166. stride=stride,
  167. groups=in_channels,
  168. act=None,
  169. name=name +
  170. "_shortcut_depthwise_depthwise" # looks strange due to an old typo, will be fixed later.
  171. )
  172. self.shortcut_conv = ConvBNLayer(
  173. in_channels=in_channels,
  174. out_channels=output_channels,
  175. kernel_size=1,
  176. stride=1,
  177. groups=1,
  178. act=None,
  179. name=name + "_shortcut_conv")
  180. def forward(self, inputs):
  181. x = self.ghost_module_1(inputs)
  182. if self._stride == 2:
  183. x = self.depthwise_conv(x)
  184. if self._use_se:
  185. x = self.se_block(x)
  186. x = self.ghost_module_2(x)
  187. if self._stride == 1 and self._num_channels == self._output_channels:
  188. shortcut = inputs
  189. else:
  190. shortcut = self.shortcut_depthwise(inputs)
  191. shortcut = self.shortcut_conv(shortcut)
  192. return paddle.add(x=x, y=shortcut)
  193. class GhostNet(nn.Layer):
  194. def __init__(self, scale, class_dim=1000):
  195. super(GhostNet, self).__init__()
  196. self.cfgs = [
  197. # k, t, c, SE, s
  198. [3, 16, 16, 0, 1],
  199. [3, 48, 24, 0, 2],
  200. [3, 72, 24, 0, 1],
  201. [5, 72, 40, 1, 2],
  202. [5, 120, 40, 1, 1],
  203. [3, 240, 80, 0, 2],
  204. [3, 200, 80, 0, 1],
  205. [3, 184, 80, 0, 1],
  206. [3, 184, 80, 0, 1],
  207. [3, 480, 112, 1, 1],
  208. [3, 672, 112, 1, 1],
  209. [5, 672, 160, 1, 2],
  210. [5, 960, 160, 0, 1],
  211. [5, 960, 160, 1, 1],
  212. [5, 960, 160, 0, 1],
  213. [5, 960, 160, 1, 1]
  214. ]
  215. self.scale = scale
  216. output_channels = int(self._make_divisible(16 * self.scale, 4))
  217. self.conv1 = ConvBNLayer(
  218. in_channels=3,
  219. out_channels=output_channels,
  220. kernel_size=3,
  221. stride=2,
  222. groups=1,
  223. act="relu",
  224. name="conv1")
  225. # build inverted residual blocks
  226. idx = 0
  227. self.ghost_bottleneck_list = []
  228. for k, exp_size, c, use_se, s in self.cfgs:
  229. in_channels = output_channels
  230. output_channels = int(self._make_divisible(c * self.scale, 4))
  231. hidden_dim = int(self._make_divisible(exp_size * self.scale, 4))
  232. ghost_bottleneck = self.add_sublayer(
  233. name="_ghostbottleneck_" + str(idx),
  234. sublayer=GhostBottleneck(
  235. in_channels=in_channels,
  236. hidden_dim=hidden_dim,
  237. output_channels=output_channels,
  238. kernel_size=k,
  239. stride=s,
  240. use_se=use_se,
  241. name="_ghostbottleneck_" + str(idx)))
  242. self.ghost_bottleneck_list.append(ghost_bottleneck)
  243. idx += 1
  244. # build last several layers
  245. in_channels = output_channels
  246. output_channels = int(self._make_divisible(exp_size * self.scale, 4))
  247. self.conv_last = ConvBNLayer(
  248. in_channels=in_channels,
  249. out_channels=output_channels,
  250. kernel_size=1,
  251. stride=1,
  252. groups=1,
  253. act="relu",
  254. name="conv_last")
  255. self.pool2d_gap = AdaptiveAvgPool2D(1)
  256. in_channels = output_channels
  257. self._fc0_output_channels = 1280
  258. self.fc_0 = ConvBNLayer(
  259. in_channels=in_channels,
  260. out_channels=self._fc0_output_channels,
  261. kernel_size=1,
  262. stride=1,
  263. act="relu",
  264. name="fc_0")
  265. self.dropout = nn.Dropout(p=0.2)
  266. stdv = 1.0 / math.sqrt(self._fc0_output_channels * 1.0)
  267. self.fc_1 = Linear(
  268. self._fc0_output_channels,
  269. class_dim,
  270. weight_attr=ParamAttr(
  271. name="fc_1_weights", initializer=Uniform(-stdv, stdv)),
  272. bias_attr=ParamAttr(name="fc_1_offset"))
  273. def forward(self, inputs):
  274. x = self.conv1(inputs)
  275. for ghost_bottleneck in self.ghost_bottleneck_list:
  276. x = ghost_bottleneck(x)
  277. x = self.conv_last(x)
  278. x = self.pool2d_gap(x)
  279. x = self.fc_0(x)
  280. x = self.dropout(x)
  281. x = paddle.reshape(x, shape=[-1, self._fc0_output_channels])
  282. x = self.fc_1(x)
  283. return x
  284. def _make_divisible(self, v, divisor, min_value=None):
  285. """
  286. This function is taken from the original tf repo.
  287. It ensures that all layers have a channel number that is divisible by 8
  288. It can be seen here:
  289. https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
  290. """
  291. if min_value is None:
  292. min_value = divisor
  293. new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
  294. # Make sure that round down does not go down by more than 10%.
  295. if new_v < 0.9 * v:
  296. new_v += divisor
  297. return new_v
  298. def GhostNet_x0_5(**args):
  299. model = GhostNet(scale=0.5)
  300. return model
  301. def GhostNet_x1_0(**args):
  302. model = GhostNet(scale=1.0)
  303. return model
  304. def GhostNet_x1_3(**args):
  305. model = GhostNet(scale=1.3)
  306. return model