feat: initial HSAP platform
Huaxu Sentinel Active Safety Platform with embedded algorithm code, Docker Compose setup, and vendored dataset scaffolds for clone-and-run. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import mmcv
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
@mmcv.jit(coderize=True)
|
||||
def accuracy(pred, target, topk=1, thresh=None):
|
||||
"""Calculate accuracy according to the prediction and target.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): The model prediction, shape (N, num_class)
|
||||
target (torch.Tensor): The target of each prediction, shape (N, )
|
||||
topk (int | tuple[int], optional): If the predictions in ``topk``
|
||||
matches the target, the predictions will be regarded as
|
||||
correct ones. Defaults to 1.
|
||||
thresh (float, optional): If not None, predictions with scores under
|
||||
this threshold are considered incorrect. Default to None.
|
||||
|
||||
Returns:
|
||||
float | tuple[float]: If the input ``topk`` is a single integer,
|
||||
the function will return a single float as accuracy. If
|
||||
``topk`` is a tuple containing multiple integers, the
|
||||
function will return a tuple containing accuracies of
|
||||
each ``topk`` number.
|
||||
"""
|
||||
assert isinstance(topk, (int, tuple))
|
||||
if isinstance(topk, int):
|
||||
topk = (topk, )
|
||||
return_single = True
|
||||
else:
|
||||
return_single = False
|
||||
|
||||
maxk = max(topk)
|
||||
if pred.size(0) == 0:
|
||||
accu = [pred.new_tensor(0.) for i in range(len(topk))]
|
||||
return accu[0] if return_single else accu
|
||||
assert pred.ndim == 2 and target.ndim == 1
|
||||
assert pred.size(0) == target.size(0)
|
||||
assert maxk <= pred.size(1), \
|
||||
f'maxk {maxk} exceeds pred dimension {pred.size(1)}'
|
||||
pred_value, pred_label = pred.topk(maxk, dim=1)
|
||||
pred_label = pred_label.t() # transpose to shape (maxk, N)
|
||||
correct = pred_label.eq(target.view(1, -1).expand_as(pred_label))
|
||||
if thresh is not None:
|
||||
# Only prediction values larger than thresh are counted as correct
|
||||
correct = correct & (pred_value > thresh).t()
|
||||
res = []
|
||||
for k in topk:
|
||||
correct_k = correct[:k].reshape(-1).float().sum(0, keepdim=True)
|
||||
res.append(correct_k.mul_(100.0 / pred.size(0)))
|
||||
return res[0] if return_single else res
|
||||
|
||||
|
||||
class Accuracy(nn.Module):
|
||||
def __init__(self, topk=(1, ), thresh=None):
|
||||
"""Module to calculate the accuracy.
|
||||
|
||||
Args:
|
||||
topk (tuple, optional): The criterion used to calculate the
|
||||
accuracy. Defaults to (1,).
|
||||
thresh (float, optional): If not None, predictions with scores
|
||||
under this threshold are considered incorrect. Default to None.
|
||||
"""
|
||||
super().__init__()
|
||||
self.topk = topk
|
||||
self.thresh = thresh
|
||||
|
||||
def forward(self, pred, target):
|
||||
"""Forward function to calculate accuracy.
|
||||
|
||||
Args:
|
||||
pred (torch.Tensor): Prediction of models.
|
||||
target (torch.Tensor): Target for each prediction.
|
||||
|
||||
Returns:
|
||||
tuple[float]: The accuracies under different topk criterions.
|
||||
"""
|
||||
return accuracy(pred, target, self.topk, self.thresh)
|
||||
@@ -0,0 +1,191 @@
|
||||
# pylint: disable-all
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
# Source: https://github.com/kornia/kornia/blob/f4f70fefb63287f72bc80cd96df9c061b1cb60dd/kornia/losses/focal.py
|
||||
|
||||
|
||||
class SoftmaxFocalLoss(nn.Module):
|
||||
def __init__(self, gamma, ignore_lb=255, *args, **kwargs):
|
||||
super(SoftmaxFocalLoss, self).__init__()
|
||||
self.gamma = gamma
|
||||
self.nll = nn.NLLLoss(ignore_index=ignore_lb)
|
||||
|
||||
def forward(self, logits, labels):
|
||||
scores = F.softmax(logits, dim=1)
|
||||
factor = torch.pow(1. - scores, self.gamma)
|
||||
log_score = F.log_softmax(logits, dim=1)
|
||||
log_score = factor * log_score
|
||||
loss = self.nll(log_score, labels)
|
||||
return loss
|
||||
|
||||
|
||||
def one_hot(labels: torch.Tensor,
|
||||
num_classes: int,
|
||||
device: Optional[torch.device] = None,
|
||||
dtype: Optional[torch.dtype] = None,
|
||||
eps: Optional[float] = 1e-6) -> torch.Tensor:
|
||||
r"""Converts an integer label x-D tensor to a one-hot (x+1)-D tensor.
|
||||
|
||||
Args:
|
||||
labels (torch.Tensor) : tensor with labels of shape :math:`(N, *)`,
|
||||
where N is batch size. Each value is an integer
|
||||
representing correct classification.
|
||||
num_classes (int): number of classes in labels.
|
||||
device (Optional[torch.device]): the desired device of returned tensor.
|
||||
Default: if None, uses the current device for the default tensor type
|
||||
(see torch.set_default_tensor_type()). device will be the CPU for CPU
|
||||
tensor types and the current CUDA device for CUDA tensor types.
|
||||
dtype (Optional[torch.dtype]): the desired data type of returned
|
||||
tensor. Default: if None, infers data type from values.
|
||||
|
||||
Returns:
|
||||
torch.Tensor: the labels in one hot tensor of shape :math:`(N, C, *)`,
|
||||
|
||||
Examples::
|
||||
>>> labels = torch.LongTensor([[[0, 1], [2, 0]]])
|
||||
>>> kornia.losses.one_hot(labels, num_classes=3)
|
||||
tensor([[[[1., 0.],
|
||||
[0., 1.]],
|
||||
[[0., 1.],
|
||||
[0., 0.]],
|
||||
[[0., 0.],
|
||||
[1., 0.]]]]
|
||||
"""
|
||||
if not torch.is_tensor(labels):
|
||||
raise TypeError(
|
||||
"Input labels type is not a torch.Tensor. Got {}".format(
|
||||
type(labels)))
|
||||
if not labels.dtype == torch.int64:
|
||||
raise ValueError(
|
||||
"labels must be of the same dtype torch.int64. Got: {}".format(
|
||||
labels.dtype))
|
||||
if num_classes < 1:
|
||||
raise ValueError("The number of classes must be bigger than one."
|
||||
" Got: {}".format(num_classes))
|
||||
shape = labels.shape
|
||||
one_hot = torch.zeros(shape[0],
|
||||
num_classes,
|
||||
*shape[1:],
|
||||
device=device,
|
||||
dtype=dtype)
|
||||
return one_hot.scatter_(1, labels.unsqueeze(1), 1.0) + eps
|
||||
|
||||
|
||||
def focal_loss(input: torch.Tensor,
|
||||
target: torch.Tensor,
|
||||
alpha: float,
|
||||
gamma: float = 2.0,
|
||||
reduction: str = 'none',
|
||||
eps: float = 1e-8) -> torch.Tensor:
|
||||
r"""Function that computes Focal loss.
|
||||
|
||||
See :class:`~kornia.losses.FocalLoss` for details.
|
||||
"""
|
||||
if not torch.is_tensor(input):
|
||||
raise TypeError("Input type is not a torch.Tensor. Got {}".format(
|
||||
type(input)))
|
||||
|
||||
if not len(input.shape) >= 2:
|
||||
raise ValueError(
|
||||
"Invalid input shape, we expect BxCx*. Got: {}".format(
|
||||
input.shape))
|
||||
|
||||
if input.size(0) != target.size(0):
|
||||
raise ValueError(
|
||||
'Expected input batch_size ({}) to match target batch_size ({}).'.
|
||||
format(input.size(0), target.size(0)))
|
||||
|
||||
n = input.size(0)
|
||||
out_size = (n, ) + input.size()[2:]
|
||||
if target.size()[1:] != input.size()[2:]:
|
||||
raise ValueError('Expected target size {}, got {}'.format(
|
||||
out_size, target.size()))
|
||||
|
||||
if not input.device == target.device:
|
||||
raise ValueError(
|
||||
"input and target must be in the same device. Got: {} and {}".
|
||||
format(input.device, target.device))
|
||||
|
||||
# compute softmax over the classes axis
|
||||
input_soft: torch.Tensor = F.softmax(input, dim=1) + eps
|
||||
|
||||
# create the labels one hot tensor
|
||||
target_one_hot: torch.Tensor = one_hot(target,
|
||||
num_classes=input.shape[1],
|
||||
device=input.device,
|
||||
dtype=input.dtype)
|
||||
|
||||
# compute the actual focal loss
|
||||
weight = torch.pow(-input_soft + 1., gamma)
|
||||
|
||||
focal = -alpha * weight * torch.log(input_soft)
|
||||
loss_tmp = torch.sum(target_one_hot * focal, dim=1)
|
||||
|
||||
if reduction == 'none':
|
||||
loss = loss_tmp
|
||||
elif reduction == 'mean':
|
||||
loss = torch.mean(loss_tmp)
|
||||
elif reduction == 'sum':
|
||||
loss = torch.sum(loss_tmp)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Invalid reduction mode: {}".format(reduction))
|
||||
return loss
|
||||
|
||||
|
||||
class FocalLoss(nn.Module):
|
||||
r"""Criterion that computes Focal loss.
|
||||
|
||||
According to [1], the Focal loss is computed as follows:
|
||||
|
||||
.. math::
|
||||
|
||||
\text{FL}(p_t) = -\alpha_t (1 - p_t)^{\gamma} \, \text{log}(p_t)
|
||||
|
||||
where:
|
||||
- :math:`p_t` is the model's estimated probability for each class.
|
||||
|
||||
|
||||
Arguments:
|
||||
alpha (float): Weighting factor :math:`\alpha \in [0, 1]`.
|
||||
gamma (float): Focusing parameter :math:`\gamma >= 0`.
|
||||
reduction (str, optional): Specifies the reduction to apply to the
|
||||
output: ‘none’ | ‘mean’ | ‘sum’. ‘none’: no reduction will be applied,
|
||||
‘mean’: the sum of the output will be divided by the number of elements
|
||||
in the output, ‘sum’: the output will be summed. Default: ‘none’.
|
||||
|
||||
Shape:
|
||||
- Input: :math:`(N, C, *)` where C = number of classes.
|
||||
- Target: :math:`(N, *)` where each value is
|
||||
:math:`0 ≤ targets[i] ≤ C−1`.
|
||||
|
||||
Examples:
|
||||
>>> N = 5 # num_classes
|
||||
>>> kwargs = {"alpha": 0.5, "gamma": 2.0, "reduction": 'mean'}
|
||||
>>> loss = kornia.losses.FocalLoss(**kwargs)
|
||||
>>> input = torch.randn(1, N, 3, 5, requires_grad=True)
|
||||
>>> target = torch.empty(1, 3, 5, dtype=torch.long).random_(N)
|
||||
>>> output = loss(input, target)
|
||||
>>> output.backward()
|
||||
|
||||
References:
|
||||
[1] https://arxiv.org/abs/1708.02002
|
||||
"""
|
||||
def __init__(self,
|
||||
alpha: float,
|
||||
gamma: float = 2.0,
|
||||
reduction: str = 'none') -> None:
|
||||
super(FocalLoss, self).__init__()
|
||||
self.alpha: float = alpha
|
||||
self.gamma: float = gamma
|
||||
self.reduction: str = reduction
|
||||
self.eps: float = 1e-6
|
||||
|
||||
def forward( # type: ignore
|
||||
self, input: torch.Tensor, target: torch.Tensor) -> torch.Tensor:
|
||||
return focal_loss(input, target, self.alpha, self.gamma,
|
||||
self.reduction, self.eps)
|
||||
@@ -0,0 +1,38 @@
|
||||
import torch
|
||||
|
||||
|
||||
def line_iou(pred, target, img_w, length=15, aligned=True):
|
||||
'''
|
||||
Calculate the line iou value between predictions and targets
|
||||
Args:
|
||||
pred: lane predictions, shape: (num_pred, 72)
|
||||
target: ground truth, shape: (num_target, 72)
|
||||
img_w: image width
|
||||
length: extended radius
|
||||
aligned: True for iou loss calculation, False for pair-wise ious in assign
|
||||
'''
|
||||
px1 = pred - length
|
||||
px2 = pred + length
|
||||
tx1 = target - length
|
||||
tx2 = target + length
|
||||
if aligned:
|
||||
invalid_mask = target
|
||||
ovr = torch.min(px2, tx2) - torch.max(px1, tx1)
|
||||
union = torch.max(px2, tx2) - torch.min(px1, tx1)
|
||||
else:
|
||||
num_pred = pred.shape[0]
|
||||
invalid_mask = target.repeat(num_pred, 1, 1)
|
||||
ovr = (torch.min(px2[:, None, :], tx2[None, ...]) -
|
||||
torch.max(px1[:, None, :], tx1[None, ...]))
|
||||
union = (torch.max(px2[:, None, :], tx2[None, ...]) -
|
||||
torch.min(px1[:, None, :], tx1[None, ...]))
|
||||
|
||||
invalid_masks = (invalid_mask < 0) | (invalid_mask >= img_w)
|
||||
ovr[invalid_masks] = 0.
|
||||
union[invalid_masks] = 0.
|
||||
iou = ovr.sum(dim=-1) / (union.sum(dim=-1) + 1e-9)
|
||||
return iou
|
||||
|
||||
|
||||
def liou_loss(pred, target, img_w, length=15):
|
||||
return (1 - line_iou(pred, target, img_w, length)).mean()
|
||||
Reference in New Issue
Block a user