Download src/objectmodel_v1/losses.py from bench-labs/objectmodel-v1: direct link, hf CLI and curl.
- Browser
- Download file 9.53 kB
-
https://huggingface.co/bench-labs/objectmodel-v1/resolve/main/src/objectmodel_v1/losses.py
- Command line
-
hf download hf://bench-labs/objectmodel-v1/src/objectmodel_v1/losses.py
-
curl -L -o losses.py https://huggingface.co/bench-labs/objectmodel-v1/resolve/main/src/objectmodel_v1/losses.py
9.53 kB
| from __future__ import annotations | |
| from collections.abc import Sequence | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import Tensor, nn | |
| from .boxes import box_cxcywh_to_xyxy, generalized_box_iou | |
| from .matching import hungarian_match, hungarian_match_layers | |
| def sigmoid_focal_loss( | |
| logits: Tensor, targets: Tensor, alpha: float = 0.25, gamma: float = 2.0 | |
| ) -> Tensor: | |
| probabilities = logits.sigmoid() | |
| ce = F.binary_cross_entropy_with_logits(logits, targets, reduction="none") | |
| p_t = probabilities * targets + (1.0 - probabilities) * (1.0 - targets) | |
| loss = ce * (1.0 - p_t).pow(gamma) | |
| if alpha >= 0: | |
| alpha_t = alpha * targets + (1.0 - alpha) * (1.0 - targets) | |
| loss = alpha_t * loss | |
| return loss | |
| class ObjectModelCriterion(nn.Module): | |
| def __init__(self, config: dict) -> None: | |
| super().__init__() | |
| loss_config = config.get("loss", config) | |
| self.cost_class = float(loss_config.get("cost_class", 2.0)) | |
| self.cost_bbox = float(loss_config.get("cost_bbox", 5.0)) | |
| self.cost_giou = float(loss_config.get("cost_giou", 2.0)) | |
| self.weight_class = float(loss_config.get("weight_class", 2.0)) | |
| self.weight_bbox = float(loss_config.get("weight_bbox", 5.0)) | |
| self.weight_giou = float(loss_config.get("weight_giou", 2.0)) | |
| self.weight_dense = float(loss_config.get("weight_dense", 1.0)) | |
| self.aux_weight = float(loss_config.get("aux_weight", 1.0)) | |
| self.dense_topk = int(loss_config.get("dense_topk", 5)) | |
| self.alpha = float(loss_config.get("focal_alpha", 0.25)) | |
| self.gamma = float(loss_config.get("focal_gamma", 2.0)) | |
| def _dense_targets( | |
| self, | |
| logits: Tensor, | |
| targets: Sequence[dict[str, Tensor]], | |
| level_index: int, | |
| ) -> tuple[Tensor, Tensor, Tensor]: | |
| batch, _, height, width = logits.shape | |
| device = logits.device | |
| target_logits = torch.zeros_like(logits) | |
| target_boxes_hwc = torch.zeros(batch, height, width, 4, dtype=torch.float32, device=device) | |
| positive = torch.zeros(batch, height, width, dtype=torch.float32, device=device) | |
| offsets = torch.tensor( | |
| [ | |
| (-1, -1), | |
| (0, -1), | |
| (1, -1), | |
| (-1, 0), | |
| (0, 0), | |
| (1, 0), | |
| (-1, 1), | |
| (0, 1), | |
| (1, 1), | |
| ], | |
| dtype=torch.int64, | |
| device=device, | |
| ) | |
| distances = offsets.square().sum(dim=1) | |
| candidate_count = min(self.dense_topk, len(offsets)) | |
| nonempty = [(i, t) for i, t in enumerate(targets) if t["labels"].numel() > 0] | |
| if not nonempty: | |
| return target_logits, target_boxes_hwc.permute(0, 3, 1, 2), positive | |
| all_boxes = torch.cat([t["boxes"] for _, t in nonempty]) | |
| all_labels = torch.cat([t["labels"] for _, t in nonempty]) | |
| all_batch = torch.cat( | |
| [ | |
| torch.full((t["labels"].numel(),), i, dtype=torch.int64, device=device) | |
| for i, t in nonempty | |
| ] | |
| ) | |
| areas = all_boxes[:, 2] * all_boxes[:, 3] | |
| target_levels = torch.where(areas < 0.02, 0, torch.where(areas < 0.15, 1, 2)) | |
| level_mask = target_levels == level_index | |
| if not bool(level_mask.any()): | |
| return target_logits, target_boxes_hwc.permute(0, 3, 1, 2), positive | |
| boxes = all_boxes[level_mask] | |
| labels = all_labels[level_mask] | |
| sel_batch = all_batch[level_mask] | |
| grid = (boxes[:, :2] * boxes.new_tensor([width, height])).long() | |
| grid[:, 0].clamp_(0, width - 1) | |
| grid[:, 1].clamp_(0, height - 1) | |
| x = (grid[:, None, 0] + offsets[None, :, 0]).clamp(0, width - 1) | |
| y = (grid[:, None, 1] + offsets[None, :, 1]).clamp(0, height - 1) | |
| sort_key = distances[None] * ((width + 1) * (height + 1)) | |
| sort_key = sort_key + x * (height + 1) + y | |
| order = sort_key.argsort(dim=1, stable=True)[:, :candidate_count] | |
| x = x.gather(1, order) | |
| y = y.gather(1, order) | |
| expanded_labels = labels[:, None].expand_as(x) | |
| expanded_batch = sel_batch[:, None].expand_as(x) | |
| target_logits[expanded_batch, expanded_labels, y, x] = 1.0 | |
| flat_cells = (expanded_batch * (height * width) + y * width + x).reshape(-1) | |
| owners = torch.full((batch * height * width,), -1, dtype=torch.int64, device=device) | |
| source_owners = torch.arange(boxes.shape[0], device=device)[:, None].expand_as(x).reshape(-1) | |
| owners.scatter_reduce_(0, flat_cells, source_owners, reduce="amax", include_self=True) | |
| occupied = owners >= 0 | |
| positive.view(-1)[occupied] = 1.0 | |
| target_boxes_hwc.view(-1, 4)[occupied] = boxes[owners[occupied]] | |
| return target_logits, target_boxes_hwc.permute(0, 3, 1, 2), positive | |
| def _set_loss( | |
| self, outputs: dict[str, Tensor], targets: Sequence[dict[str, Tensor]], matches=None | |
| ) -> dict[str, Tensor]: | |
| logits = outputs["pred_logits"] | |
| boxes = outputs["pred_boxes"] | |
| if matches is None: | |
| matches = hungarian_match( | |
| outputs, | |
| targets, | |
| self.cost_class, | |
| self.cost_bbox, | |
| self.cost_giou, | |
| ) | |
| device = logits.device | |
| target_classes = torch.zeros_like(logits) | |
| normalizer = max(sum(len(target["labels"]) for target in targets), 1) | |
| nonempty = [ | |
| (batch_index, prediction_indices, target_indices) | |
| for batch_index, (prediction_indices, target_indices) in enumerate(matches) | |
| if prediction_indices.numel() > 0 | |
| ] | |
| if nonempty: | |
| batch_ids = torch.cat( | |
| [torch.full_like(pred_idx, batch_index) for batch_index, pred_idx, _ in nonempty] | |
| ) | |
| pred_idx_t = torch.cat([pred_idx for _, pred_idx, _ in nonempty]) | |
| tgt_idx_t = torch.cat([tgt_idx for _, _, tgt_idx in nonempty]) | |
| counts = torch.tensor([len(target["labels"]) for target in targets], device=device) | |
| offsets = torch.cat([counts.new_zeros(1), counts.cumsum(0)[:-1]]) | |
| global_target_idx = tgt_idx_t + offsets[batch_ids] | |
| all_target_boxes = torch.cat([target["boxes"] for target in targets]) | |
| all_target_labels = torch.cat([target["labels"] for target in targets]) | |
| labels = all_target_labels[global_target_idx] | |
| target_classes[batch_ids, pred_idx_t, labels] = 1.0 | |
| predicted = boxes[batch_ids, pred_idx_t] | |
| expected = all_target_boxes[global_target_idx] | |
| else: | |
| predicted = None | |
| expected = None | |
| class_loss = sigmoid_focal_loss(logits, target_classes, self.alpha, self.gamma).sum() | |
| class_loss = class_loss / normalizer | |
| if predicted is not None: | |
| bbox_loss = F.l1_loss(predicted, expected, reduction="sum") / normalizer | |
| giou = generalized_box_iou(box_cxcywh_to_xyxy(predicted), box_cxcywh_to_xyxy(expected)) | |
| giou_loss = (1.0 - giou.diag()).sum() / normalizer | |
| else: | |
| bbox_loss = boxes.sum() * 0.0 | |
| giou_loss = boxes.sum() * 0.0 | |
| return { | |
| "loss_class": class_loss * self.weight_class, | |
| "loss_bbox": bbox_loss * self.weight_bbox, | |
| "loss_giou": giou_loss * self.weight_giou, | |
| } | |
| def _dense_loss( | |
| self, outputs: list[dict[str, Tensor]], targets: Sequence[dict[str, Tensor]] | |
| ) -> Tensor: | |
| total = outputs[0]["logits"].sum() * 0.0 | |
| normalizer = max(sum(len(target["labels"]) for target in targets), 1) | |
| for level_index, level_output in enumerate(outputs): | |
| logits = level_output["logits"] | |
| boxes = level_output["distances"].sigmoid() | |
| target_logits, target_boxes, positive = self._dense_targets( | |
| logits, targets, level_index | |
| ) | |
| cls_loss = sigmoid_focal_loss(logits, target_logits, self.alpha, self.gamma) | |
| cls_loss = cls_loss.sum() / normalizer | |
| positive_mask = positive[:, None].expand_as(boxes) | |
| box_loss = (F.l1_loss(boxes, target_boxes, reduction="none") * positive_mask).sum() | |
| total = total + cls_loss + box_loss / normalizer | |
| return total / len(outputs) | |
| def forward( | |
| self, outputs: dict[str, Tensor], targets: Sequence[dict[str, Tensor]] | |
| ) -> dict[str, Tensor]: | |
| layer_outputs = [outputs, *outputs.get("aux_outputs", [])] | |
| layer_matches = hungarian_match_layers( | |
| layer_outputs, | |
| targets, | |
| self.cost_class, | |
| self.cost_bbox, | |
| self.cost_giou, | |
| ) | |
| primary = self._set_loss(outputs, targets, layer_matches[0]) | |
| total = sum(primary.values()) | |
| for auxiliary, matches in zip( | |
| outputs.get("aux_outputs", []), layer_matches[1:], strict=True | |
| ): | |
| auxiliary_losses = self._set_loss(auxiliary, targets, matches) | |
| total = total + self.aux_weight * sum(auxiliary_losses.values()) / max( | |
| len(outputs["aux_outputs"]), 1 | |
| ) | |
| if "dense_outputs" in outputs: | |
| dense = self._dense_loss(outputs["dense_outputs"], targets) | |
| primary["loss_dense"] = dense * self.weight_dense | |
| total = total + primary["loss_dense"] | |
| primary["loss_total"] = total | |
| return primary | |