ppyoloe_head.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. # Copyright (c) 2022 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. import paddle
  15. import paddle.nn as nn
  16. import paddle.nn.functional as F
  17. from ppdet.core.workspace import register
  18. from paddle import ParamAttr
  19. from paddle.nn.initializer import KaimingNormal
  20. from paddle.nn.initializer import Normal, Constant
  21. from ..bbox_utils import batch_distance2bbox
  22. from ..losses import GIoULoss
  23. from ..initializer import bias_init_with_prob, constant_, normal_
  24. from ..assigners.utils import generate_anchors_for_grid_cell
  25. from ppdet.modeling.backbones.cspresnet import ConvBNLayer, RepVggBlock
  26. from ppdet.modeling.ops import get_static_shape, get_act_fn
  27. from ppdet.modeling.layers import MultiClassNMS
  28. __all__ = ['PPYOLOEHead', 'SimpleConvHead']
  29. class ESEAttn(nn.Layer):
  30. def __init__(self, feat_channels, act='swish', attn_conv='convbn'):
  31. super(ESEAttn, self).__init__()
  32. self.fc = nn.Conv2D(feat_channels, feat_channels, 1)
  33. if attn_conv == 'convbn':
  34. self.conv = ConvBNLayer(feat_channels, feat_channels, 1, act=act)
  35. else:
  36. self.conv = RepVggBlock(feat_channels, feat_channels, act=act)
  37. self._init_weights()
  38. def _init_weights(self):
  39. normal_(self.fc.weight, std=0.001)
  40. def forward(self, feat, avg_feat):
  41. weight = F.sigmoid(self.fc(avg_feat))
  42. return self.conv(feat * weight)
  43. @register
  44. class PPYOLOEHead(nn.Layer):
  45. __shared__ = [
  46. 'num_classes', 'eval_size', 'trt', 'exclude_nms',
  47. 'exclude_post_process', 'use_shared_conv'
  48. ]
  49. __inject__ = ['static_assigner', 'assigner', 'nms']
  50. def __init__(self,
  51. in_channels=[1024, 512, 256],
  52. num_classes=80,
  53. act='swish',
  54. fpn_strides=(32, 16, 8),
  55. grid_cell_scale=5.0,
  56. grid_cell_offset=0.5,
  57. reg_max=16,
  58. reg_range=None,
  59. static_assigner_epoch=4,
  60. use_varifocal_loss=True,
  61. static_assigner='ATSSAssigner',
  62. assigner='TaskAlignedAssigner',
  63. nms='MultiClassNMS',
  64. eval_size=None,
  65. loss_weight={
  66. 'class': 1.0,
  67. 'iou': 2.5,
  68. 'dfl': 0.5,
  69. },
  70. trt=False,
  71. attn_conv='convbn',
  72. exclude_nms=False,
  73. exclude_post_process=False,
  74. use_shared_conv=True):
  75. super(PPYOLOEHead, self).__init__()
  76. assert len(in_channels) > 0, "len(in_channels) should > 0"
  77. self.in_channels = in_channels
  78. self.num_classes = num_classes
  79. self.fpn_strides = fpn_strides
  80. self.grid_cell_scale = grid_cell_scale
  81. self.grid_cell_offset = grid_cell_offset
  82. if reg_range:
  83. self.sm_use = True
  84. self.reg_range = reg_range
  85. else:
  86. self.sm_use = False
  87. self.reg_range = (0, reg_max + 1)
  88. self.reg_channels = self.reg_range[1] - self.reg_range[0]
  89. self.iou_loss = GIoULoss()
  90. self.loss_weight = loss_weight
  91. self.use_varifocal_loss = use_varifocal_loss
  92. self.eval_size = eval_size
  93. self.static_assigner_epoch = static_assigner_epoch
  94. self.static_assigner = static_assigner
  95. self.assigner = assigner
  96. self.nms = nms
  97. if isinstance(self.nms, MultiClassNMS) and trt:
  98. self.nms.trt = trt
  99. self.exclude_nms = exclude_nms
  100. self.exclude_post_process = exclude_post_process
  101. self.use_shared_conv = use_shared_conv
  102. # stem
  103. self.stem_cls = nn.LayerList()
  104. self.stem_reg = nn.LayerList()
  105. act = get_act_fn(
  106. act, trt=trt) if act is None or isinstance(act,
  107. (str, dict)) else act
  108. for in_c in self.in_channels:
  109. self.stem_cls.append(ESEAttn(in_c, act=act, attn_conv=attn_conv))
  110. self.stem_reg.append(ESEAttn(in_c, act=act, attn_conv=attn_conv))
  111. # pred head
  112. self.pred_cls = nn.LayerList()
  113. self.pred_reg = nn.LayerList()
  114. for in_c in self.in_channels:
  115. self.pred_cls.append(
  116. nn.Conv2D(
  117. in_c, self.num_classes, 3, padding=1))
  118. self.pred_reg.append(
  119. nn.Conv2D(
  120. in_c, 4 * self.reg_channels, 3, padding=1))
  121. # projection conv
  122. self.proj_conv = nn.Conv2D(self.reg_channels, 1, 1, bias_attr=False)
  123. self.proj_conv.skip_quant = True
  124. self._init_weights()
  125. @classmethod
  126. def from_config(cls, cfg, input_shape):
  127. return {'in_channels': [i.channels for i in input_shape], }
  128. def _init_weights(self):
  129. bias_cls = bias_init_with_prob(0.01)
  130. for cls_, reg_ in zip(self.pred_cls, self.pred_reg):
  131. constant_(cls_.weight)
  132. constant_(cls_.bias, bias_cls)
  133. constant_(reg_.weight)
  134. constant_(reg_.bias, 1.0)
  135. proj = paddle.linspace(self.reg_range[0], self.reg_range[1] - 1,
  136. self.reg_channels).reshape(
  137. [1, self.reg_channels, 1, 1])
  138. self.proj_conv.weight.set_value(proj)
  139. self.proj_conv.weight.stop_gradient = True
  140. if self.eval_size:
  141. anchor_points, stride_tensor = self._generate_anchors()
  142. self.anchor_points = anchor_points
  143. self.stride_tensor = stride_tensor
  144. def forward_train(self, feats, targets, aux_pred=None):
  145. anchors, anchor_points, num_anchors_list, stride_tensor = \
  146. generate_anchors_for_grid_cell(
  147. feats, self.fpn_strides, self.grid_cell_scale,
  148. self.grid_cell_offset)
  149. cls_score_list, reg_distri_list = [], []
  150. for i, feat in enumerate(feats):
  151. avg_feat = F.adaptive_avg_pool2d(feat, (1, 1))
  152. cls_logit = self.pred_cls[i](self.stem_cls[i](feat, avg_feat) +
  153. feat)
  154. reg_distri = self.pred_reg[i](self.stem_reg[i](feat, avg_feat))
  155. # cls and reg
  156. cls_score = F.sigmoid(cls_logit)
  157. cls_score_list.append(cls_score.flatten(2).transpose([0, 2, 1]))
  158. reg_distri_list.append(reg_distri.flatten(2).transpose([0, 2, 1]))
  159. cls_score_list = paddle.concat(cls_score_list, axis=1)
  160. reg_distri_list = paddle.concat(reg_distri_list, axis=1)
  161. return self.get_loss([
  162. cls_score_list, reg_distri_list, anchors, anchor_points,
  163. num_anchors_list, stride_tensor
  164. ], targets, aux_pred)
  165. def _generate_anchors(self, feats=None, dtype='float32'):
  166. # just use in eval time
  167. anchor_points = []
  168. stride_tensor = []
  169. for i, stride in enumerate(self.fpn_strides):
  170. if feats is not None:
  171. _, _, h, w = feats[i].shape
  172. else:
  173. h = int(self.eval_size[0] / stride)
  174. w = int(self.eval_size[1] / stride)
  175. shift_x = paddle.arange(end=w) + self.grid_cell_offset
  176. shift_y = paddle.arange(end=h) + self.grid_cell_offset
  177. shift_y, shift_x = paddle.meshgrid(shift_y, shift_x)
  178. anchor_point = paddle.cast(
  179. paddle.stack(
  180. [shift_x, shift_y], axis=-1), dtype=dtype)
  181. anchor_points.append(anchor_point.reshape([-1, 2]))
  182. stride_tensor.append(paddle.full([h * w, 1], stride, dtype=dtype))
  183. anchor_points = paddle.concat(anchor_points)
  184. stride_tensor = paddle.concat(stride_tensor)
  185. return anchor_points, stride_tensor
  186. def forward_eval(self, feats):
  187. if self.eval_size:
  188. anchor_points, stride_tensor = self.anchor_points, self.stride_tensor
  189. else:
  190. anchor_points, stride_tensor = self._generate_anchors(feats)
  191. cls_score_list, reg_dist_list = [], []
  192. for i, feat in enumerate(feats):
  193. _, _, h, w = feat.shape
  194. l = h * w
  195. avg_feat = F.adaptive_avg_pool2d(feat, (1, 1))
  196. cls_logit = self.pred_cls[i](self.stem_cls[i](feat, avg_feat) +
  197. feat)
  198. reg_dist = self.pred_reg[i](self.stem_reg[i](feat, avg_feat))
  199. reg_dist = reg_dist.reshape(
  200. [-1, 4, self.reg_channels, l]).transpose([0, 2, 3, 1])
  201. if self.use_shared_conv:
  202. reg_dist = self.proj_conv(F.softmax(
  203. reg_dist, axis=1)).squeeze(1)
  204. else:
  205. reg_dist = F.softmax(reg_dist, axis=1)
  206. # cls and reg
  207. cls_score = F.sigmoid(cls_logit)
  208. cls_score_list.append(cls_score.reshape([-1, self.num_classes, l]))
  209. reg_dist_list.append(reg_dist)
  210. cls_score_list = paddle.concat(cls_score_list, axis=-1)
  211. if self.use_shared_conv:
  212. reg_dist_list = paddle.concat(reg_dist_list, axis=1)
  213. else:
  214. reg_dist_list = paddle.concat(reg_dist_list, axis=2)
  215. reg_dist_list = self.proj_conv(reg_dist_list).squeeze(1)
  216. return cls_score_list, reg_dist_list, anchor_points, stride_tensor
  217. def forward(self, feats, targets=None, aux_pred=None):
  218. assert len(feats) == len(self.fpn_strides), \
  219. "The size of feats is not equal to size of fpn_strides"
  220. if self.training:
  221. return self.forward_train(feats, targets, aux_pred)
  222. else:
  223. return self.forward_eval(feats)
  224. @staticmethod
  225. def _focal_loss(score, label, alpha=0.25, gamma=2.0):
  226. weight = (score - label).pow(gamma)
  227. if alpha > 0:
  228. alpha_t = alpha * label + (1 - alpha) * (1 - label)
  229. weight *= alpha_t
  230. loss = F.binary_cross_entropy(
  231. score, label, weight=weight, reduction='sum')
  232. return loss
  233. @staticmethod
  234. def _varifocal_loss(pred_score, gt_score, label, alpha=0.75, gamma=2.0):
  235. weight = alpha * pred_score.pow(gamma) * (1 - label) + gt_score * label
  236. loss = F.binary_cross_entropy(
  237. pred_score, gt_score, weight=weight, reduction='sum')
  238. return loss
  239. def _bbox_decode(self, anchor_points, pred_dist):
  240. _, l, _ = get_static_shape(pred_dist)
  241. pred_dist = F.softmax(pred_dist.reshape([-1, l, 4, self.reg_channels]))
  242. pred_dist = self.proj_conv(pred_dist.transpose([0, 3, 1, 2])).squeeze(1)
  243. return batch_distance2bbox(anchor_points, pred_dist)
  244. def _bbox2distance(self, points, bbox):
  245. x1y1, x2y2 = paddle.split(bbox, 2, -1)
  246. lt = points - x1y1
  247. rb = x2y2 - points
  248. return paddle.concat([lt, rb], -1).clip(self.reg_range[0],
  249. self.reg_range[1] - 1 - 0.01)
  250. def _df_loss(self, pred_dist, target, lower_bound=0):
  251. target_left = paddle.cast(target.floor(), 'int64')
  252. target_right = target_left + 1
  253. weight_left = target_right.astype('float32') - target
  254. weight_right = 1 - weight_left
  255. loss_left = F.cross_entropy(
  256. pred_dist, target_left - lower_bound,
  257. reduction='none') * weight_left
  258. loss_right = F.cross_entropy(
  259. pred_dist, target_right - lower_bound,
  260. reduction='none') * weight_right
  261. return (loss_left + loss_right).mean(-1, keepdim=True)
  262. def _bbox_loss(self, pred_dist, pred_bboxes, anchor_points, assigned_labels,
  263. assigned_bboxes, assigned_scores, assigned_scores_sum):
  264. # select positive samples mask
  265. mask_positive = (assigned_labels != self.num_classes)
  266. num_pos = mask_positive.sum()
  267. # pos/neg loss
  268. if num_pos > 0:
  269. # l1 + iou
  270. bbox_mask = mask_positive.unsqueeze(-1).tile([1, 1, 4])
  271. pred_bboxes_pos = paddle.masked_select(pred_bboxes,
  272. bbox_mask).reshape([-1, 4])
  273. assigned_bboxes_pos = paddle.masked_select(
  274. assigned_bboxes, bbox_mask).reshape([-1, 4])
  275. bbox_weight = paddle.masked_select(
  276. assigned_scores.sum(-1), mask_positive).unsqueeze(-1)
  277. loss_l1 = F.l1_loss(pred_bboxes_pos, assigned_bboxes_pos)
  278. loss_iou = self.iou_loss(pred_bboxes_pos,
  279. assigned_bboxes_pos) * bbox_weight
  280. loss_iou = loss_iou.sum() / assigned_scores_sum
  281. dist_mask = mask_positive.unsqueeze(-1).tile(
  282. [1, 1, self.reg_channels * 4])
  283. pred_dist_pos = paddle.masked_select(
  284. pred_dist, dist_mask).reshape([-1, 4, self.reg_channels])
  285. assigned_ltrb = self._bbox2distance(anchor_points, assigned_bboxes)
  286. assigned_ltrb_pos = paddle.masked_select(
  287. assigned_ltrb, bbox_mask).reshape([-1, 4])
  288. loss_dfl = self._df_loss(pred_dist_pos, assigned_ltrb_pos,
  289. self.reg_range[0]) * bbox_weight
  290. loss_dfl = loss_dfl.sum() / assigned_scores_sum
  291. else:
  292. loss_l1 = paddle.zeros([1])
  293. loss_iou = paddle.zeros([1])
  294. loss_dfl = pred_dist.sum() * 0.
  295. return loss_l1, loss_iou, loss_dfl
  296. def get_loss(self, head_outs, gt_meta, aux_pred=None):
  297. pred_scores, pred_distri, anchors,\
  298. anchor_points, num_anchors_list, stride_tensor = head_outs
  299. anchor_points_s = anchor_points / stride_tensor
  300. pred_bboxes = self._bbox_decode(anchor_points_s, pred_distri)
  301. if aux_pred is not None:
  302. pred_scores_aux = aux_pred[0]
  303. pred_bboxes_aux = self._bbox_decode(anchor_points_s, aux_pred[1])
  304. gt_labels = gt_meta['gt_class']
  305. gt_bboxes = gt_meta['gt_bbox']
  306. pad_gt_mask = gt_meta['pad_gt_mask']
  307. # label assignment
  308. if gt_meta['epoch_id'] < self.static_assigner_epoch:
  309. assigned_labels, assigned_bboxes, assigned_scores = \
  310. self.static_assigner(
  311. anchors,
  312. num_anchors_list,
  313. gt_labels,
  314. gt_bboxes,
  315. pad_gt_mask,
  316. bg_index=self.num_classes,
  317. pred_bboxes=pred_bboxes.detach() * stride_tensor)
  318. alpha_l = 0.25
  319. else:
  320. if self.sm_use:
  321. # only used in smalldet of PPYOLOE-SOD model
  322. assigned_labels, assigned_bboxes, assigned_scores = \
  323. self.assigner(
  324. pred_scores.detach(),
  325. pred_bboxes.detach() * stride_tensor,
  326. anchor_points,
  327. stride_tensor,
  328. gt_labels,
  329. gt_bboxes,
  330. pad_gt_mask,
  331. bg_index=self.num_classes)
  332. else:
  333. if aux_pred is None:
  334. assigned_labels, assigned_bboxes, assigned_scores = \
  335. self.assigner(
  336. pred_scores.detach(),
  337. pred_bboxes.detach() * stride_tensor,
  338. anchor_points,
  339. num_anchors_list,
  340. gt_labels,
  341. gt_bboxes,
  342. pad_gt_mask,
  343. bg_index=self.num_classes)
  344. else:
  345. assigned_labels, assigned_bboxes, assigned_scores = \
  346. self.assigner(
  347. pred_scores_aux.detach(),
  348. pred_bboxes_aux.detach() * stride_tensor,
  349. anchor_points,
  350. num_anchors_list,
  351. gt_labels,
  352. gt_bboxes,
  353. pad_gt_mask,
  354. bg_index=self.num_classes)
  355. alpha_l = -1
  356. # rescale bbox
  357. assigned_bboxes /= stride_tensor
  358. assign_out_dict = self.get_loss_from_assign(
  359. pred_scores, pred_distri, pred_bboxes, anchor_points_s,
  360. assigned_labels, assigned_bboxes, assigned_scores, alpha_l)
  361. if aux_pred is not None:
  362. assign_out_dict_aux = self.get_loss_from_assign(
  363. aux_pred[0], aux_pred[1], pred_bboxes_aux, anchor_points_s,
  364. assigned_labels, assigned_bboxes, assigned_scores, alpha_l)
  365. loss = {}
  366. for key in assign_out_dict.keys():
  367. loss[key] = assign_out_dict[key] + assign_out_dict_aux[key]
  368. else:
  369. loss = assign_out_dict
  370. return loss
  371. def get_loss_from_assign(self, pred_scores, pred_distri, pred_bboxes,
  372. anchor_points_s, assigned_labels, assigned_bboxes,
  373. assigned_scores, alpha_l):
  374. # cls loss
  375. if self.use_varifocal_loss:
  376. one_hot_label = F.one_hot(assigned_labels,
  377. self.num_classes + 1)[..., :-1]
  378. loss_cls = self._varifocal_loss(pred_scores, assigned_scores,
  379. one_hot_label)
  380. else:
  381. loss_cls = self._focal_loss(pred_scores, assigned_scores, alpha_l)
  382. assigned_scores_sum = assigned_scores.sum()
  383. if paddle.distributed.get_world_size() > 1:
  384. paddle.distributed.all_reduce(assigned_scores_sum)
  385. assigned_scores_sum /= paddle.distributed.get_world_size()
  386. assigned_scores_sum = paddle.clip(assigned_scores_sum, min=1.)
  387. loss_cls /= assigned_scores_sum
  388. loss_l1, loss_iou, loss_dfl = \
  389. self._bbox_loss(pred_distri, pred_bboxes, anchor_points_s,
  390. assigned_labels, assigned_bboxes, assigned_scores,
  391. assigned_scores_sum)
  392. loss = self.loss_weight['class'] * loss_cls + \
  393. self.loss_weight['iou'] * loss_iou + \
  394. self.loss_weight['dfl'] * loss_dfl
  395. out_dict = {
  396. 'loss': loss,
  397. 'loss_cls': loss_cls,
  398. 'loss_iou': loss_iou,
  399. 'loss_dfl': loss_dfl,
  400. 'loss_l1': loss_l1,
  401. }
  402. return out_dict
  403. def post_process(self, head_outs, scale_factor):
  404. pred_scores, pred_dist, anchor_points, stride_tensor = head_outs
  405. pred_bboxes = batch_distance2bbox(anchor_points, pred_dist)
  406. pred_bboxes *= stride_tensor
  407. if self.exclude_post_process:
  408. return paddle.concat(
  409. [pred_bboxes, pred_scores.transpose([0, 2, 1])], axis=-1), None
  410. else:
  411. # scale bbox to origin
  412. scale_y, scale_x = paddle.split(scale_factor, 2, axis=-1)
  413. scale_factor = paddle.concat(
  414. [scale_x, scale_y, scale_x, scale_y],
  415. axis=-1).reshape([-1, 1, 4])
  416. pred_bboxes /= scale_factor
  417. if self.exclude_nms:
  418. # `exclude_nms=True` just use in benchmark
  419. return pred_bboxes, pred_scores
  420. else:
  421. bbox_pred, bbox_num, _ = self.nms(pred_bboxes, pred_scores)
  422. return bbox_pred, bbox_num
  423. def get_activation(name="LeakyReLU"):
  424. if name == "silu":
  425. module = nn.Silu()
  426. elif name == "relu":
  427. module = nn.ReLU()
  428. elif name in ["LeakyReLU", 'leakyrelu', 'lrelu']:
  429. module = nn.LeakyReLU(0.1)
  430. elif name is None:
  431. module = nn.Identity()
  432. else:
  433. raise AttributeError("Unsupported act type: {}".format(name))
  434. return module
  435. class ConvNormLayer(nn.Layer):
  436. def __init__(self,
  437. in_channels,
  438. out_channels,
  439. kernel_size,
  440. stride=1,
  441. padding=0,
  442. dilation=1,
  443. groups=1,
  444. norm_type='gn',
  445. activation="LeakyReLU"):
  446. super(ConvNormLayer, self).__init__()
  447. assert norm_type in ['bn', 'sync_bn', 'syncbn', 'gn', None]
  448. self.conv = nn.Conv2D(
  449. in_channels,
  450. out_channels,
  451. kernel_size,
  452. stride=stride,
  453. padding=padding,
  454. dilation=dilation,
  455. groups=groups,
  456. bias_attr=False,
  457. weight_attr=ParamAttr(initializer=KaimingNormal()))
  458. if norm_type in ['bn', 'sync_bn', 'syncbn']:
  459. self.norm = nn.BatchNorm2D(out_channels)
  460. elif norm_type == 'gn':
  461. self.norm = nn.GroupNorm(num_groups=32, num_channels=out_channels)
  462. else:
  463. self.norm = None
  464. self.act = get_activation(activation)
  465. def forward(self, x):
  466. y = self.conv(x)
  467. if self.norm is not None:
  468. y = self.norm(y)
  469. y = self.act(y)
  470. return y
  471. class ScaleReg(nn.Layer):
  472. """
  473. Parameter for scaling the regression outputs.
  474. """
  475. def __init__(self, scale=1.0):
  476. super(ScaleReg, self).__init__()
  477. scale = paddle.to_tensor(scale)
  478. self.scale = self.create_parameter(
  479. shape=[1],
  480. dtype='float32',
  481. default_initializer=nn.initializer.Assign(scale))
  482. def forward(self, x):
  483. return x * self.scale
  484. @register
  485. class SimpleConvHead(nn.Layer):
  486. __shared__ = ['num_classes']
  487. def __init__(self,
  488. num_classes=80,
  489. feat_in=288,
  490. feat_out=288,
  491. num_convs=1,
  492. fpn_strides=[32, 16, 8, 4],
  493. norm_type='gn',
  494. act='LeakyReLU',
  495. prior_prob=0.01,
  496. reg_max=16):
  497. super(SimpleConvHead, self).__init__()
  498. self.num_classes = num_classes
  499. self.feat_in = feat_in
  500. self.feat_out = feat_out
  501. self.num_convs = num_convs
  502. self.fpn_strides = fpn_strides
  503. self.reg_max = reg_max
  504. self.cls_convs = nn.LayerList()
  505. self.reg_convs = nn.LayerList()
  506. for i in range(self.num_convs):
  507. in_c = feat_in if i == 0 else feat_out
  508. self.cls_convs.append(
  509. ConvNormLayer(
  510. in_c,
  511. feat_out,
  512. 3,
  513. stride=1,
  514. padding=1,
  515. norm_type=norm_type,
  516. activation=act))
  517. self.reg_convs.append(
  518. ConvNormLayer(
  519. in_c,
  520. feat_out,
  521. 3,
  522. stride=1,
  523. padding=1,
  524. norm_type=norm_type,
  525. activation=act))
  526. bias_cls = bias_init_with_prob(prior_prob)
  527. self.gfl_cls = nn.Conv2D(
  528. feat_out,
  529. self.num_classes,
  530. kernel_size=3,
  531. stride=1,
  532. padding=1,
  533. weight_attr=ParamAttr(initializer=Normal(
  534. mean=0.0, std=0.01)),
  535. bias_attr=ParamAttr(initializer=Constant(value=bias_cls)))
  536. self.gfl_reg = nn.Conv2D(
  537. feat_out,
  538. 4 * (self.reg_max + 1),
  539. kernel_size=3,
  540. stride=1,
  541. padding=1,
  542. weight_attr=ParamAttr(initializer=Normal(
  543. mean=0.0, std=0.01)),
  544. bias_attr=ParamAttr(initializer=Constant(value=0)))
  545. self.scales = nn.LayerList()
  546. for i in range(len(self.fpn_strides)):
  547. self.scales.append(ScaleReg(1.0))
  548. def forward(self, feats):
  549. cls_scores = []
  550. bbox_preds = []
  551. for x, scale in zip(feats, self.scales):
  552. cls_feat = x
  553. reg_feat = x
  554. for cls_conv in self.cls_convs:
  555. cls_feat = cls_conv(cls_feat)
  556. for reg_conv in self.reg_convs:
  557. reg_feat = reg_conv(reg_feat)
  558. cls_score = self.gfl_cls(cls_feat)
  559. cls_score = F.sigmoid(cls_score)
  560. cls_score = cls_score.flatten(2).transpose([0, 2, 1])
  561. cls_scores.append(cls_score)
  562. bbox_pred = scale(self.gfl_reg(reg_feat))
  563. bbox_pred = bbox_pred.flatten(2).transpose([0, 2, 1])
  564. bbox_preds.append(bbox_pred)
  565. cls_scores = paddle.concat(cls_scores, axis=1)
  566. bbox_preds = paddle.concat(bbox_preds, axis=1)
  567. return cls_scores, bbox_preds