Batching Loss — Focal Head
This page describes the batched loss computation used by FocalHeadBatched.
- You can find the implementation in the repository here:
FocalHeadBatched:
demos/streampetr/projects/mmdet3d_plugin/models/dense_heads/focal_head_batched.pyFocalHead (reference):
demos/streampetr/projects/mmdet3d_plugin/models/dense_heads/focal_head.py
Overview and Goals
In the original implementation, the loss computation is partially batched: the loss functions are batched,
while the bounding box matching/assignment is not.
Here, we leverage the batching_helpers package to extend batching further by computing
the cost matrix for matching in a batched fashion (though we still apply the Hungarian matching procedure
itself on a per-sample basis).
Crucially, we preserve the batch dimension throughout almost the entire pipeline, only flattening data across samples and cameras at the final stage if the loss functions expect a flattened representation. Note that the flattening is only done to enable re-use of the already implemented loss functions, and is not necessary if a loss function is designed to process batched data.
Note that while the RaggedBatch class supports multiple batch dimensions, we
only use a single batch dimension iterating over both samples and cameras as the distinction between the
samples and cameras are not needed for the loss computation and the losses are accumulated over both samples
and cameras to obtain the final loss value.
The late flattening allows us to keep per-entry (sample & camera) data more clearly separated, making
operations like cost matrix computation, heatmap generation, and other per-sample tasks much more
straightforward. As some of the needed operations cannot be easily implemented in a batched manner using
PyTorch build-in functionality, functionality of the RaggedBatch class and
the batching_helpers package in general is used where applicable.
- Compared to the original approach, this batching strategy differs in two key ways:
Data is no longer flattened early across samples and cameras; instead, the batch dimension is maintained, making it possible to apply operations on a per-entry basis (while still processing the batch as a whole). Only if and when the final loss computation implementation necessitates it, we flatten away the batch dimension.
In the original implementation, the ground truth data was aligned with the predictions in the
get_targets()function by creating prediction-sized tensors and filling them with the GT data according to the matching. Instead, we acquire matching indices from the assigner (asRaggedBatchobjects) and perform alignment inside the loss implementation itself (see theloss_batched()function for more details). In this way, we can easily handle either matched-pairs-only alignment or align GT with all predictions for loss functions requiring non-matched items as well (classification loss, for example) in a clearer fashion. This approach yields smaller tensors for matched-only operations and keeps the code more modular and transparent.
For more details about the actual matching/assignment implementation including cost matrix computation,
please also see the HungarianAssigner2DBatched implementation (see
demos/streampetr/projects/mmdet3d_plugin/core/bbox/assigners/hungarian_assigner_2d_batched.py in the
repository).
Note that the approach used here is an adaptation of the approach used in the batching_helpers
Example, adjusted to the specifics of focal head
implementation & the implementation of the used loss functions.
The implementation of the loss computation is done in the loss() function. Please see the notes there and
in the related called functions for more details.
Note
We refer to the reference implementation (in
demo/streampetr/projects/mmdet3d_plugin/models/dense_heads/focal_head.py) in some of the
notes/code comments. Please note that this reference implementation mostly corresponds to the original
implementation. However, we added an option there to use the custom op implementation of the heatmap
generation (for fairer comparison of the speedup achieved by our batching approach).
Below, we walk through the important parts of the implementation which were modified to support batching
using the batching-helpers package.
Top-level Loss Computation Implementation
The top-level loss computation which is called from outside the class is called loss() and is
described here.
Pre-processing
The pre-processing step performs some validation and extracts the necessary predictions from a dictionary.
Compared to the original implementation, there is one important difference: the ground truth (GT) data of the
individual samples and cameras is combined into RaggedBatch objects (see comments in the code).
343 # ===== Pre-processing (`loss()`) =====
344 nvtx_wrp.range_push("FocalHeadBatched.loss")
345 stopwatch.start_meas("FocalHeadBatched.loss")
346
347 assert gt_bboxes_ignore is None, \
348 f'{self.__class__.__name__} only supports ' \
349 f'for gt_bboxes_ignore setting to None.'
350
351 enc_cls_scores = preds_dicts['enc_cls_scores']
352 enc_bbox_preds = preds_dicts['enc_bbox_preds']
353 pred_centers2d = preds_dicts['pred_centers2d']
354 centerness = preds_dicts['centerness']
355
356 #@NOTE
357 # For each type of ground truth, we combine the data from all samples and cameras
358 # into single RaggedBatch objects using the batching-helpers (bhl) package. This differs
359 # from the original implementation, where lists of tensors are used and the individual
360 # elements in the lists are processed individually.
361 #
362 # Note that the `flatten_batch_dims` flag is set to `True` to flatten the batch dimensions
363 # (corresponding to samples and cameras). This is done as the distinction between the samples and
364 # cameras is not needed for the loss computation, as the loss is accumulated over both.
365 gt_labels2d = bhl.combine_data(gt_labels2d_list, flatten_batch_dims=True)
366 gt_bboxes_2d = bhl.combine_data(gt_bboxes2d_list, gt_labels2d, flatten_batch_dims=True)
367 gt_centers2d = bhl.combine_data(centers2d, gt_labels2d, flatten_batch_dims=True)
368 gt_depths = bhl.combine_data(depths, gt_labels2d, flatten_batch_dims=True)
369
Compute Batched Loss (Delegation)
Loss computation is delegated to loss_batched with batched inputs.
370 # ===== Compute batched loss =====
371 #@NOTE
372 # The actual loss computation is done in the `loss_batched()` function, using the combined data.
373 # Please see the notes there for more details on the approach. The main difference to the original
374 # implementation is that the ground truth data is batched. Note that while there, the called function
375 # is called `loss_single()`, it also called once per batch and is provided with the data of all
376 # samples and cameras (with the GT data as lists containing per-sample and per-camera tensors).
377 enc_loss_cls, enc_losses_bbox, enc_losses_iou, centers2d_losses, centerness_losses = \
378 self.loss_batched(enc_cls_scores, enc_bbox_preds, pred_centers2d, centerness,
379 gt_bboxes_2d, gt_labels2d, gt_centers2d, gt_depths, img_metas)
380
Assemble Results
The results are collected into a dictionary and returned.
381 # ===== Assemble results =====
382 #@NOTE: This is similar to the original implementation.
383 loss_dict = dict()
384 loss_dict['enc_loss_cls'] = enc_loss_cls
385 loss_dict['enc_loss_bbox'] = enc_losses_bbox
386 loss_dict['enc_loss_iou'] = enc_losses_iou
387 loss_dict['centers2d_losses'] = centers2d_losses
388 loss_dict['centerness_losses'] = centerness_losses
389
390 nvtx_wrp.range_pop("FocalHeadBatched.loss")
391 stopwatch.end_meas("FocalHeadBatched.loss")
392
393 return loss_dict
Detailed Loss Computation Implementation
The actual loss computation is done in the loss_batched() function. This function is called once per batch
and is provided with the data of all samples and cameras (combined into
RaggedBatch objects for the GT data inside loss()). The function is
called internally from loss().
Pre-processing
Some simple steps are performed first. Note that splitting the predictions into individual samples and cameras (as done in the original implementation) is not needed here (see comment in the code).
447 # ===== Pre-processing (`loss_batched()`) =====
448 assert gt_bboxes_ignore_list is None, (
449 'Using gt_bboxes_ignore_list is not supported. This is consistent with the original '
450 'implementation, where setting ignore lists raises an error in the matcher.')
451
452 num_imgs = cls_scores.size(0)
453 num_preds = bbox_preds.size(1)
454
455 #@NOTE
456 # Also see notes at the beginning of the file for an overview of the batched loss implementation
457 # approach.
458
459 #@NOTE
460 # In the original implementation, the prediction tensors are split into individual samples and
461 # cameras to match the per-entry ground truth and perform the matching on a per-entry basis.
462 # Here, this step is omitted. The ground truth data is (already) batched instead, and we can directly
463 # proceed to the matching step below.
464
Matching
Invoke the batched assigner to get matched indices as RaggedBatch pairs:
465 # ===== Perform the matching =====
466 #@NOTE
467 # In the original implementation, the assigner is called within `get_targets()`, which also performs
468 # additional data processing. Here, we call the assigner directly since we need to return the matching
469 # indices. The batched assigner returns `RaggedBatch` instances containing indices for matched
470 # predictions and the corresponding GT bounding boxes. This is a modular approach, so that the
471 # matching does not need to be concerned with the format that the loss computation expects (beyond the
472 # matching indices). This approach allows us to:
473 # - Maintain a clear separation of concerns between matching and loss computation
474 # - Use `batching-helpers` functionality for any additional processing of the data based on the
475 # matching results later if/as needed depending on the computed loss type (e.g., depending on
476 # whether the loss needs all predictions or only the matched pairs).
477 pred_match_indices, gt_match_indices = self.assigner2d.assign(
478 bbox_preds, cls_scores, pred_centers2d, gt_bboxes, gt_labels, gt_centers2d, img_metas
479 )
480
481 # DETR regress the relative position of boxes (cxcywh) in the image,
482 # thus the learning target is normalized by the image size. So here
483 # we need to re-scale them for calculating IoU loss
484 # construct factors used for rescale bboxes
Normalization Factors
Compute image-size-based factors used for scaling and conversions:
485 # ===== Normalization factors =====
486 img_h, img_w, _ = img_metas[0]['pad_shape'][0]
487
488 #@NOTE
489 # The normalization factors are the same for all bboxes (both for GT and predictions). Therefore,
490 # the first 2 dimensions have the size 1 and are implicitly broadcasted to the correct size
491 # when applied to the bboxes. This is a difference from the original implementation mainly aimed at
492 # improving clarity and conciseness.
493 factors = bbox_preds.new_tensor([img_w, img_h, img_w, img_h]).reshape(1,1,4)
494
Align Data by Matches
In this step, the alignment of the predictions and GT is performed using the matched indices, so that loss computation can be performed on tensors where the elements are aligned according to the matching results.
Note that in contrast to all other losses, the classification loss is computed for all predictions, not only for matched pairs. This leads to a different implementation of the alignment step (see comments in the code).
495 # ===== Align data by matches =====
496 nvtx_wrp.range_push("Indexing [FocalHeadBatched.loss_batched]")
497 #@NOTE
498 # Get the aligned (i.e. indexed by indices established in the matching) versions of the
499 # bounding boxes. Note that in constrast to the targets in the original implementation
500 # (which are returned by the `get_targets()` function), the GT bboxes are still in the xyxy
501 # format and are not normalized. The normalization is done later for the matched targets only.
502 #
503 # Here, aligned means that only the elements which have matches are preserved, and the indices
504 # of the predictions and the mapped GT elements are the same, allowing for direct comparison of the
505 # tensors containing predictions to tensors containing GT.
506 bboxes_preds_normed_matched = bhl.batched_indexing_access(bbox_preds, pred_match_indices)
507 bboxes_gt_nonnormed_matched = bhl.batched_indexing_access(gt_bboxes, gt_match_indices)
508 #@NOTE
509 # Similar to the bounding boxes, get the aligned versions of the centers.
510 centers2d_gt_nonnormed_matched = bhl.batched_indexing_access(gt_centers2d, gt_match_indices)
511 centers2d_pred_normed_matched = bhl.batched_indexing_access(pred_centers2d, pred_match_indices)
512
513 #@NOTE
514 # Align/map the GT labels to the predictions. This is done by creating a tensor corresponding to the
515 # predictions in size and filling it with the GT labels according to the matching results (i.e.
516 # matched indices of both predictions and GT). This is different from the alignment above, as the
517 # classification loss is computed for all predictions, not only for matched predicitons. Therefore,
518 # all predictions need to be preserved, and the GT labels need to be mapped to them.
519 #
520 # Note that this corresponds to the mapping done in the original implementation by the
521 # `get_targets()` function.
522 #
523 # As in the original implementation, the filler values corresponding to non-matched predictions are
524 # set to the number of classes, which is a special label indicating non-existence and is understood as
525 # such by the classification loss function implementation.
526 gt_labels_mapped_to_pred = torch.full(
527 (num_imgs, num_preds), fill_value=self.cls_out_channels, dtype=gt_labels.dtype,
528 device=gt_labels.device
529 )
530 #@NOTE: Insert the GT labels into the positions corresponding to the matched predictions.
531 gt_labels_mapped_to_pred = bhl.batched_index_mapping(
532 gt_labels, gt_match_indices, pred_match_indices, gt_labels_mapped_to_pred
533 )
534 nvtx_wrp.range_pop()
535
Pre-process Tensors
Convert bounding boxes between formats, normalize/denormalize as required, and normalize centers. This step makes
heavy use of the apply() method to modify the data in the
RaggedBatch. Apart from this, it differs from the original implementation,
as the format of the data is different at this point (some conversion is already performed there on the data
before it is returned from the get_targets() function).
536 # ===== Pre-process tensors =====
537 nvtx_wrp.range_push("bboxes preproc. [FocalHeadBatched.loss_batched]")
538 #@NOTE
539 # In constrast to the bboxes in the original implementation (which are returned by the
540 # `get_targets()` function), the GT bboxes are still in the `xyxy` format and are not normalized.
541 # The predictions are in `xywh` and normalized format (same as original implementation). We need both
542 # GT and predictions in both formats. Here, we convert the normalized predictions to non-normalized
543 # format and the non-normalized GT bboxes to normalized format.
544 #
545 # Note the `apply()` method, which is a convenience method for applying a function to
546 # the data in the RaggedBatch object. While here, only the data tensor (i.e. `tensor`) is input to the
547 # lambda function, the passed function may accept more inputs, which allows access to the
548 # sample sizes and related mask (see `RaggedBatch.apply()` documentation for more details).
549 bboxes_preds_nonnormed_matched = bboxes_preds_normed_matched.apply(
550 lambda data: bbox_cxcywh_to_xyxy(data) * factors
551 )
552 bboxes_gt_normed_matched = bboxes_gt_nonnormed_matched.apply(
553 lambda data: bbox_xyxy_to_cxcywh(data) / factors
554 )
555 nvtx_wrp.range_pop()
556
557 nvtx_wrp.range_push("centers preproc [FocalHeadBatched.loss_batched]")
558 #centerness
559 #@NOTE
560 # Compute the normalized versions of the centers. Note that in the original implementation,
561 # normalized centers are returned by the `get_targets()` function, so that no additional
562 # normalization is needed there.
563 centers2d_gt_normed_matched = centers2d_gt_nonnormed_matched.apply(
564 lambda ctr2d: ctr2d / factors[..., 0:2]
565 )
566 nvtx_wrp.range_pop()
567
Count Valid and Invalid Elements
Count the number of the valid and invalid elements, and derive existence weights. This is done using
functionality of the RaggedBatch class. Note that all of these are also
used in the original implementation, but there, they are provided by the get_targets() function.
568 # ===== Existence weights & count valid and invalid elements =====
569 nvtx_wrp.range_push("valid elements preproc [FocalHeadBatched.loss_batched]")
570 #@NOTE
571 # Get weights differentiating between valid (weight: 1.0) and invalid (weight: 0.0) elements.
572 # In the original implementation, this is returned by the `get_targets()` function (in a different
573 # format/shape, corresponding to the batching approach used there).
574 bbox_weights_matched = bboxes_gt_nonnormed_matched.get_existence_weights(torch.float32)
575 #@NOTE
576 # Get the total number of valid (i.e. existent, matched to GT) and invalid (i.e. non-existent,
577 # non-matched) elements. In the original implementation, this information is provided by the
578 # `get_targets()` function.
579 num_total_pos = bboxes_gt_nonnormed_matched.total_num_entries
580 num_total_neg = num_preds * num_imgs - num_total_pos
581 nvtx_wrp.range_pop()
582
Losses: IoU Loss and IoU Score
The IoU loss is a typical example of a loss computation, where only the matched pairs are considered. Apart from the loss, the IoU score is also computed and used as a weight for the classification loss later on.
583 # ===== Losses: IoU & IoU score =====
584 # regression IoU loss, defaultly GIoU loss
585 nvtx_wrp.range_push("loss_iou [FocalHeadBatched.loss_batched]")
586 #@NOTE
587 # Compute the IoU loss between predicted and ground truth bounding boxes. Unlike the original
588 # implementation, we compute this loss only for matched predictions. The original approach computes
589 # loss for all predictions (with GT targets aligned to predictions) and uses zero weights for
590 # unmatched predictions. Our implementation still contains filler values in the data tensors (with
591 # corresponding zero weights), but the number of fillers is minimized, and only as many fillers as
592 # needed to maintain uniform sample sizes (see `RaggedBatch` docs) are used.
593 #
594 # Note that in contrast to the original implementation, the inputs to this loss computation do
595 # include a batch dimension. The loss function handles this automatically and now reduces over both
596 # the bounding boxes and over the entries in the batch dimension.
597 loss_iou = self.loss_iou2d(
598 bboxes_preds_nonnormed_matched.tensor,
599 bboxes_gt_nonnormed_matched.tensor,
600 bbox_weights_matched,
601 avg_factor=num_total_pos,
602 )
603
604 nvtx_wrp.range_pop()
605
606 nvtx_wrp.range_push("iou_score [FocalHeadBatched.loss_batched]")
607 #@NOTE
608 # Compute the IoU score between predicted and ground truth bounding boxes.
609 # The same function is used as in the original implementation. Note that the input tensors
610 # have a batch dimension in this implementation, while there is no batch dimension in the original
611 # implementation (and a sequence of all bounding boxes from all samples and cameras is used instead).
612 # However, this function can handle both cases and does not need to be modified.
613 iou_score = bbox_overlaps(
614 bboxes_gt_nonnormed_matched.tensor, bboxes_preds_nonnormed_matched.tensor, is_aligned=True
615 )
616 nvtx_wrp.range_pop()
617
Losses: Classification (Setup & Weights)
Prepare class scores, average factor, and weights; compute IoU scores and map back to prediction shape for weighting:
618 # ===== Losses: Classification (setup & weights) =====
619 nvtx_wrp.range_push("cls pre-proc [FocalHeadBatched.loss_batched]")
620 # classification loss
621 cls_scores = cls_scores.reshape(-1, self.cls_out_channels)
622 # construct weighted avg_factor to match with the official DETR repo
623 cls_avg_factor = num_total_pos * 1.0 + \
624 num_total_neg * self.bg_cls_weight
625 if self.sync_cls_avg_factor:
626 cls_avg_factor = reduce_mean(
627 cls_scores.new_tensor([cls_avg_factor]))
628 cls_avg_factor = max(cls_avg_factor, 1)
629 nvtx_wrp.range_pop()
630
631 nvtx_wrp.range_push("labels pre-proc [FocalHeadBatched.loss_batched]")
632 #@NOTE: Flatten `gt_labels_mapped_to_pred` as expected by the loss function.
633 gt_labels_mapped_to_pred_flat = gt_labels_mapped_to_pred.view(-1)
634 #@NOTE: Create a tensor of ones as the label weights (consistent with the original implementation).
635 label_weights = torch.ones(
636 gt_labels_mapped_to_pred_flat.shape, dtype=torch.float32, device = gt_labels.device
637 )
638 nvtx_wrp.range_pop()
639
640 nvtx_wrp.range_push("iou score pre-proc [FocalHeadBatched.loss_batched]")
641 #@NOTE
642 # For the classification loss, we need to weight the predictions with the IoU score (for existent
643 # predictions). As the score is computed only for matched predictions, we need to map it back to match
644 # the original prediction tensor. This is done by using `batched_inverse_indexing_access()`. This is
645 # different from the original implementation, where all the data is mapped to the predictions inside
646 # the `get_targets()` function, and the IoU scores for the individual predictions, which are computed
647 # afterwards, are already located at the indices corresponding to the original prediction tensor.
648 #
649 # Note that that for non-existent predictions, the IoU score is set to 0 in
650 # `batched_inverse_indexing_access()`. Again, this is consistent with the original implementation, and
651 # these values are ignored in any case.
652 #
653 # Also note that `batched_inverse_indexing_access()` creates a new tensor with constant values and
654 # then fills the values at the indexed locations with the values from the original tensor.
655 # There is an alternative implementation (`batched_index_write()`) which fills the values
656 # into an already existing tensor (analogous to `__setitem__()`). Please refer to the
657 # documentation of `batching-helpers` for more details and choose the appropriate method based
658 # on the use case.
659 iou_score_mapped_to_pred = bhl.batched_inverse_indexing_access(
660 iou_score, pred_match_indices, num_preds, filler_value=0.0
661 )
662 #@NOTE flatten the mapped IoU scores as needed by the loss function implementation.
663 iou_score_mapped_to_pred_flat = iou_score_mapped_to_pred.view(-1)
664 nvtx_wrp.range_pop()
665
Losses: Classification Loss
Compute the classification loss over all predictions, weighted by IoU for existent predictions:
666 # ===== Losses: Classification =====
667 nvtx_wrp.range_push("loss_cls [FocalHeadBatched.loss_batched]")
668 #@NOTE
669 # Compute the classification loss (for all predictions, and with the IoU scores as weights for
670 # existent predictions). After the inputs were flattened, the inputs to the loss function correspond
671 # to the inputs in the original implementation, and the same loss functions can be used here.
672 # Whether this flattening is needed or not (as in the IoU loss above) depends on the loss function
673 # implementation.
674 loss_cls = self.loss_cls2d(
675 cls_scores,
676 (gt_labels_mapped_to_pred_flat, iou_score_mapped_to_pred_flat.detach()),
677 label_weights,
678 avg_factor=cls_avg_factor,
679 )
680 nvtx_wrp.range_pop()
681
Synchronize Positives
Synchronize positive counts across devices (same as reference):
682 # ===== Synchronize positives =====
683 nvtx_wrp.range_push("num pos preproc. [FocalHeadBatched.loss_batched]")
684 #@NOTE
685 # The following lines are as in the original implementation and are done for cross-device
686 # synchronization.
687 num_total_pos = loss_cls.new_tensor([num_total_pos])
688 num_total_pos = torch.clamp(reduce_mean(num_total_pos), min=1).item()
689 nvtx_wrp.range_pop()
690
Heatmap Generation
Generate target heatmaps in a batched way using draw_heatmap_batched. Note that by using the custom
heatmap generation OP in combination with the RaggedBatch class, the
implementation:
Is optimized by batching compared to the original implementation.
Is simplified compared to our batched implementation of the heatmap generation that was introduces in the reference implementation, as the
RaggedBatchclass makes it clear which bounding box is from which entry in the batch.
691 # ===== Heatmap generation =====
692 nvtx_wrp.range_push("Heatmap generation[FocalHeadBatched.loss_batched]")
693 img_shape = img_metas[0]['pad_shape'][0]
694 img_h, img_w, _ = img_shape
695 #@NOTE: Get the scaled centers and radii of the GT boxes using the `apply()` convenience method.
696 gt_centers_heatmap, gt_radii_heatmap = centers2d_gt_nonnormed_matched.apply(
697 lambda centers: self._get_scaled_centers_and_radii(centers, bboxes_gt_nonnormed_matched.tensor)
698 )
699 #@NOTE: Create the heatmap tensor (to be filled with the individual gaussian heatmaps).
700 heatmaps = torch.zeros(
701 (num_imgs, img_h // self.stride, img_w // self.stride),
702 dtype=torch.float32,
703 device=gt_centers_heatmap.device
704 )
705 nvtx_wrp.range_push("draw_heatmap call [FocalHeadBatched.loss_batched]")
706 #@NOTE
707 # Draw the heatmaps using `draw_heatmap_batched`. This function accepts
708 # the data in the `RaggedBatch` object. Compared to the reference, this simplifies the implementation
709 # as we do not need to manually create additional tensors indicating to which image the individual
710 # bboxes belong.
711 #
712 # Please compare to the heatmap generation implementation in `FocalHead.loss_single()` (and see
713 # the related noted there). There, we also add the option to use the custom op implementation of the
714 # heatmap generation. However, we need to use `draw_heatmap()` and manually construct a tensor
715 # indicating which bounding box is from which entry in the batch (i.e. which image in which sample),
716 # as the batch dimensions is flattened away and so this information is not available from the data
717 # tensors alone.
718 draw_heatmap_batched(heatmaps, gt_centers_heatmap, gt_radii_heatmap)
719 nvtx_wrp.range_pop()
720 nvtx_wrp.range_pop()
721
Losses: Centerness Loss
Centerness is computed for all predictions. Note that this loss is special in the sense that the fixed-sized heatmap is instead of the original, non-uniformly-sized GT.
722 # ===== Losses: Centerness =====
723 nvtx_wrp.range_push("loss_centerness [FocalHeadBatched.loss_batched]")
724 centerness = clip_sigmoid(centerness)
725 #@NOTE
726 # Compute the centerness loss. Note that similar to the classification loss,
727 # the centerness loss is computed for all predictions.
728 # The GT targets are not used directly here, and instead, the previously generated heatmap is used.
729 # The predictions correspond to individual pixels in the heatmap, so that they can be directly
730 # compared to the heatmap.
731 #
732 # As the non-uniformly-sized GT is not directly used for this loss, the loss computation
733 # is done as in the original implementation and no changes are needed to the format of the
734 # input data.
735 loss_centerness = self.loss_centerness(
736 centerness,
737 heatmaps.view(num_imgs, -1, 1),
738 avg_factor=max(num_total_pos, 1))
739 nvtx_wrp.range_pop()
740
Losses: BBox and Centers2D Losses
These losses are computed only for the matched predictions (similar to the IoU loss discussed before).
741 # ===== Losses: BBox and Centers2D =====
742 # regression L1 loss
743 nvtx_wrp.range_push("loss_bbox [FocalHeadBatched.loss_batched]")
744 #@NOTE
745 # Compute the bounding box loss. Similar to `loss_iou2d`, this loss is computed only for the
746 # predictions which have a match.
747 loss_bbox = self.loss_bbox2d(
748 bboxes_preds_normed_matched.tensor,
749 bboxes_gt_normed_matched.tensor,
750 bbox_weights_matched,
751 avg_factor=num_total_pos,
752 )
753 nvtx_wrp.range_pop()
754
755 pred_centers2d = pred_centers2d.view(-1, 2)
756 # centers2d L1 loss
757 nvtx_wrp.range_push("loss_centers2d [FocalHeadBatched.loss_batched]")
758 #@NOTE
759 # Compute the centers2d loss. Note that similar to `loss_iou2d` and `loss_bbox`, this loss is computed
760 # only for the existent/matched predictions.
761 loss_centers2d = self.loss_centers2d(
762 centers2d_pred_normed_matched.tensor,
763 centers2d_gt_normed_matched.tensor,
764 bbox_weights_matched[..., 0:2],
765 avg_factor=num_total_pos,
766 )
767 nvtx_wrp.range_pop()
768
Finalize Loss Computation
In the final step, the individual losses are returned.
769 # ===== Finalize =====
770 nvtx_wrp.range_pop("FocalHeadBatched.loss_batched")
771 return loss_cls, loss_bbox, loss_iou, loss_centers2d, loss_centerness