resnet_eb.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. # Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
  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. from __future__ import absolute_import
  15. from __future__ import division
  16. from __future__ import print_function
  17. from collections import OrderedDict
  18. from paddle import fluid
  19. from paddle.fluid.param_attr import ParamAttr
  20. from paddle.fluid.framework import Variable
  21. from paddle.fluid.regularizer import L2Decay
  22. from paddle.fluid.initializer import Constant
  23. from ppdet.core.workspace import register, serializable
  24. from numbers import Integral
  25. from .name_adapter import NameAdapter
  26. __all__ = ['ResNet_EB']
  27. @register
  28. @serializable
  29. class ResNet_EB(object):
  30. """
  31. modified ResNet, especially for EdgeBoard: https://ai.baidu.com/ai-doc/HWCE/Yk3b86gvp
  32. """
  33. __shared__ = ['norm_type', 'freeze_norm', 'weight_prefix_name']
  34. def __init__(self,
  35. depth=50,
  36. freeze_at=2,
  37. norm_type='affine_channel',
  38. freeze_norm=True,
  39. norm_decay=0.,
  40. variant='b',
  41. feature_maps=[2, 3, 4, 5],
  42. weight_prefix_name='',
  43. lr_mult_list=[1., 1., 1., 1.]):
  44. super(ResNet_EB, self).__init__()
  45. if isinstance(feature_maps, Integral):
  46. feature_maps = [feature_maps]
  47. assert depth in [18, 34, 50, 101, 152, 200], \
  48. "depth {} not in [18, 34, 50, 101, 152, 200]"
  49. assert variant in ['a', 'b', 'c', 'd'], "invalid ResNet variant"
  50. assert 0 <= freeze_at <= 4, "freeze_at should be 0, 1, 2, 3 or 4"
  51. assert len(feature_maps) > 0, "need one or more feature maps"
  52. assert norm_type in ['bn', 'sync_bn', 'affine_channel']
  53. assert len(lr_mult_list
  54. ) == 4, "lr_mult_list length must be 4 but got {}".format(
  55. len(lr_mult_list))
  56. self.depth = depth
  57. self.freeze_at = freeze_at
  58. self.norm_type = norm_type
  59. self.norm_decay = norm_decay
  60. self.freeze_norm = freeze_norm
  61. self.variant = variant
  62. self._model_type = 'ResNet'
  63. self.feature_maps = feature_maps
  64. self.depth_cfg = {
  65. 18: ([2, 2, 2, 2], self.basicblock),
  66. 34: ([3, 4, 6, 3], self.basicblock),
  67. 50: ([3, 4, 6, 3], self.bottleneck),
  68. 101: ([3, 4, 23, 3], self.bottleneck),
  69. 152: ([3, 8, 36, 3], self.bottleneck),
  70. 200: ([3, 12, 48, 3], self.bottleneck),
  71. }
  72. self.stage_filters = [64, 128, 256, 512]
  73. self._c1_out_chan_num = 64
  74. self.na = NameAdapter(self)
  75. self.prefix_name = weight_prefix_name
  76. self.lr_mult_list = lr_mult_list
  77. # var denoting curr stage
  78. self.stage_num = -1
  79. def _conv_offset(self,
  80. input,
  81. filter_size,
  82. stride,
  83. padding,
  84. act=None,
  85. name=None):
  86. out_channel = filter_size * filter_size * 3
  87. out = fluid.layers.conv2d(
  88. input,
  89. num_filters=out_channel,
  90. filter_size=filter_size,
  91. stride=stride,
  92. padding=padding,
  93. param_attr=ParamAttr(
  94. initializer=Constant(0.0), name=name + ".w_0"),
  95. bias_attr=ParamAttr(
  96. initializer=Constant(0.0), name=name + ".b_0"),
  97. act=act,
  98. name=name)
  99. return out
  100. def _conv_norm(self,
  101. input,
  102. num_filters,
  103. filter_size,
  104. stride=1,
  105. groups=1,
  106. act=None,
  107. name=None,
  108. dcn_v2=False):
  109. _name = self.prefix_name + name if self.prefix_name != '' else name
  110. # need fine lr for distilled model, default as 1.0
  111. lr_mult = 1.0
  112. mult_idx = max(self.stage_num - 2, 0)
  113. mult_idx = min(self.stage_num - 2, 3)
  114. lr_mult = self.lr_mult_list[mult_idx]
  115. if not dcn_v2:
  116. conv = fluid.layers.conv2d(
  117. input=input,
  118. num_filters=num_filters,
  119. filter_size=filter_size,
  120. stride=stride,
  121. padding=(filter_size - 1) // 2,
  122. groups=groups,
  123. act=None,
  124. param_attr=ParamAttr(
  125. name=_name + "_weights", learning_rate=lr_mult),
  126. bias_attr=False,
  127. name=_name + '.conv2d.output.1')
  128. else:
  129. # select deformable conv"
  130. offset_mask = self._conv_offset(
  131. input=input,
  132. filter_size=filter_size,
  133. stride=stride,
  134. padding=(filter_size - 1) // 2,
  135. act=None,
  136. name=_name + "_conv_offset")
  137. offset_channel = filter_size**2 * 2
  138. mask_channel = filter_size**2
  139. offset, mask = fluid.layers.split(
  140. input=offset_mask,
  141. num_or_sections=[offset_channel, mask_channel],
  142. dim=1)
  143. mask = fluid.layers.sigmoid(mask)
  144. conv = fluid.layers.deformable_conv(
  145. input=input,
  146. offset=offset,
  147. mask=mask,
  148. num_filters=num_filters,
  149. filter_size=filter_size,
  150. stride=stride,
  151. padding=(filter_size - 1) // 2,
  152. groups=groups,
  153. deformable_groups=1,
  154. im2col_step=1,
  155. param_attr=ParamAttr(
  156. name=_name + "_weights", learning_rate=lr_mult),
  157. bias_attr=False,
  158. name=_name + ".conv2d.output.1")
  159. bn_name = self.na.fix_conv_norm_name(name)
  160. bn_name = self.prefix_name + bn_name if self.prefix_name != '' else bn_name
  161. norm_lr = 0. if self.freeze_norm else lr_mult
  162. norm_decay = self.norm_decay
  163. pattr = ParamAttr(
  164. name=bn_name + '_scale',
  165. learning_rate=norm_lr,
  166. regularizer=L2Decay(norm_decay))
  167. battr = ParamAttr(
  168. name=bn_name + '_offset',
  169. learning_rate=norm_lr,
  170. regularizer=L2Decay(norm_decay))
  171. if self.norm_type in ['bn', 'sync_bn']:
  172. global_stats = True if self.freeze_norm else False
  173. out = fluid.layers.batch_norm(
  174. input=conv,
  175. act=act,
  176. name=bn_name + '.output.1',
  177. param_attr=pattr,
  178. bias_attr=battr,
  179. moving_mean_name=bn_name + '_mean',
  180. moving_variance_name=bn_name + '_variance',
  181. use_global_stats=global_stats)
  182. scale = fluid.framework._get_var(pattr.name)
  183. bias = fluid.framework._get_var(battr.name)
  184. elif self.norm_type == 'affine_channel':
  185. scale = fluid.layers.create_parameter(
  186. shape=[conv.shape[1]],
  187. dtype=conv.dtype,
  188. attr=pattr,
  189. default_initializer=fluid.initializer.Constant(1.))
  190. bias = fluid.layers.create_parameter(
  191. shape=[conv.shape[1]],
  192. dtype=conv.dtype,
  193. attr=battr,
  194. default_initializer=fluid.initializer.Constant(0.))
  195. out = fluid.layers.affine_channel(
  196. x=conv, scale=scale, bias=bias, act=act)
  197. if self.freeze_norm:
  198. scale.stop_gradient = True
  199. bias.stop_gradient = True
  200. return out
  201. def _shortcut(self, input, ch_out, stride, is_first, name):
  202. max_pooling_in_short_cut = self.variant == 'd'
  203. ch_in = input.shape[1]
  204. # the naming rule is same as pretrained weight
  205. name = self.na.fix_shortcut_name(name)
  206. std_senet = getattr(self, 'std_senet', False)
  207. if ch_in != ch_out or stride != 1 or (self.depth < 50 and is_first):
  208. if std_senet:
  209. if is_first:
  210. return self._conv_norm(input, ch_out, 1, stride, name=name)
  211. else:
  212. return self._conv_norm(input, ch_out, 3, stride, name=name)
  213. if max_pooling_in_short_cut and not is_first:
  214. input1 = fluid.layers.pool2d(
  215. input=input,
  216. pool_size=2,
  217. pool_stride=2,
  218. pool_padding=0,
  219. ceil_mode=True,
  220. pool_type='max')
  221. input2 = fluid.layers.pool2d(
  222. input=input,
  223. pool_size=2,
  224. pool_stride=2,
  225. pool_padding=0,
  226. ceil_mode=True,
  227. pool_type='avg')
  228. input = fluid.layers.elementwise_add(
  229. x=input1, y=input2, name=name + ".pool.add")
  230. return self._conv_norm(input, ch_out, 1, 1, name=name)
  231. return self._conv_norm(input, ch_out, 1, stride, name=name)
  232. else:
  233. return input
  234. def bottleneck(self,
  235. input,
  236. num_filters,
  237. stride,
  238. is_first,
  239. name,
  240. dcn_v2=False,
  241. gcb=False,
  242. gcb_name=None):
  243. assert dcn_v2 is False, "Not implemented in EdgeBoard yet."
  244. assert gcb is False, "Not implemented in EdgeBoard yet."
  245. if self.variant == 'a':
  246. stride1, stride2 = stride, 1
  247. else:
  248. stride1, stride2 = 1, stride
  249. # ResNeXt
  250. groups = getattr(self, 'groups', 1)
  251. group_width = getattr(self, 'group_width', -1)
  252. if groups == 1:
  253. expand = 4
  254. elif (groups * group_width) == 256:
  255. expand = 1
  256. else: # FIXME hard code for now, handles 32x4d, 64x4d and 32x8d
  257. num_filters = num_filters // 2
  258. expand = 2
  259. conv_name1, conv_name2, conv_name3, \
  260. shortcut_name = self.na.fix_bottleneck_name(name)
  261. std_senet = getattr(self, 'std_senet', False)
  262. if std_senet:
  263. conv_def = [
  264. [int(num_filters / 2), 1, stride1, 'relu', 1, conv_name1],
  265. [num_filters, 3, stride2, 'relu', groups, conv_name2],
  266. [num_filters * expand, 1, 1, None, 1, conv_name3]
  267. ]
  268. else:
  269. conv_def = [[num_filters, 1, stride1, 'relu', 1, conv_name1],
  270. [num_filters, 3, stride2, 'relu', groups, conv_name2],
  271. [num_filters * expand, 1, 1, None, 1, conv_name3]]
  272. residual = input
  273. for i, (c, k, s, act, g, _name) in enumerate(conv_def):
  274. residual = self._conv_norm(
  275. input=residual,
  276. num_filters=c,
  277. filter_size=k,
  278. stride=s,
  279. act=act,
  280. groups=g,
  281. name=_name,
  282. dcn_v2=False)
  283. short = self._shortcut(
  284. input,
  285. num_filters * expand,
  286. stride,
  287. is_first=is_first,
  288. name=shortcut_name)
  289. # Squeeze-and-Excitation
  290. if callable(getattr(self, '_squeeze_excitation', None)):
  291. residual = self._squeeze_excitation(
  292. input=residual, num_channels=num_filters, name='fc' + name)
  293. return fluid.layers.elementwise_add(
  294. x=short, y=residual, act='relu', name=name + ".add.output.5")
  295. def basicblock(self,
  296. input,
  297. num_filters,
  298. stride,
  299. is_first,
  300. name,
  301. dcn_v2=False,
  302. gcb=False,
  303. gcb_name=None):
  304. assert dcn_v2 is False, "Not implemented in EdgeBoard yet."
  305. assert gcb is False, "Not implemented EdgeBoard yet."
  306. conv0 = self._conv_norm(
  307. input=input,
  308. num_filters=num_filters,
  309. filter_size=3,
  310. act='relu',
  311. stride=stride,
  312. name=name + "_branch2a")
  313. conv1 = self._conv_norm(
  314. input=conv0,
  315. num_filters=num_filters,
  316. filter_size=3,
  317. act=None,
  318. name=name + "_branch2b")
  319. short = self._shortcut(
  320. input, num_filters, stride, is_first, name=name + "_branch1")
  321. return fluid.layers.elementwise_add(x=short, y=conv1, act='relu')
  322. def layer_warp(self, input, stage_num):
  323. """
  324. Args:
  325. input (Variable): input variable.
  326. stage_num (int): the stage number, should be 2, 3, 4, 5
  327. Returns:
  328. The last variable in endpoint-th stage.
  329. """
  330. assert stage_num in [2, 3, 4, 5]
  331. self.stage_num = stage_num
  332. stages, block_func = self.depth_cfg[self.depth]
  333. count = stages[stage_num - 2]
  334. ch_out = self.stage_filters[stage_num - 2]
  335. is_first = False if stage_num != 2 else True
  336. # Make the layer name and parameter name consistent
  337. # with ImageNet pre-trained model
  338. conv = input
  339. for i in range(count):
  340. conv_name = self.na.fix_layer_warp_name(stage_num, count, i)
  341. if self.depth < 50:
  342. is_first = True if i == 0 and stage_num == 2 else False
  343. conv = block_func(
  344. input=conv,
  345. num_filters=ch_out,
  346. stride=2 if i == 0 and stage_num != 2 else 1,
  347. is_first=is_first,
  348. name=conv_name,
  349. dcn_v2=False,
  350. gcb=False,
  351. gcb_name=None)
  352. return conv
  353. def c1_stage(self, input):
  354. out_chan = self._c1_out_chan_num
  355. conv1_name = self.na.fix_c1_stage_name()
  356. if self.variant in ['c', 'd']:
  357. conv_def = [
  358. [out_chan // 2, 3, 2, "conv1_1"],
  359. [out_chan // 2, 3, 1, "conv1_2"],
  360. [out_chan, 3, 1, "conv1_3"],
  361. ]
  362. else:
  363. conv_def = [[out_chan, 7, 2, conv1_name]]
  364. for (c, k, s, _name) in conv_def:
  365. input = self._conv_norm(
  366. input=input,
  367. num_filters=c,
  368. filter_size=k,
  369. stride=s,
  370. act='relu',
  371. name=_name)
  372. output = fluid.layers.pool2d(
  373. input=input,
  374. pool_size=3,
  375. pool_stride=2,
  376. pool_padding=1,
  377. pool_type='max')
  378. return output
  379. def __call__(self, input):
  380. assert isinstance(input, Variable)
  381. assert not (set(self.feature_maps) - set([2, 3, 4, 5])), \
  382. "feature maps {} not in [2, 3, 4, 5]".format(self.feature_maps)
  383. res_endpoints = []
  384. res = input
  385. feature_maps = self.feature_maps
  386. severed_head = getattr(self, 'severed_head', False)
  387. if not severed_head:
  388. res = self.c1_stage(res)
  389. feature_maps = range(2, max(self.feature_maps) + 1)
  390. for i in feature_maps:
  391. res = self.layer_warp(res, i)
  392. if i in self.feature_maps:
  393. res_endpoints.append(res)
  394. if self.freeze_at >= i:
  395. res.stop_gradient = True
  396. return OrderedDict([('res{}_sum'.format(self.feature_maps[idx]), feat)
  397. for idx, feat in enumerate(res_endpoints)])