Implement Focal Loss for Class-Imbalanced Classification
DatadogMachine Learning Engineer · Technical ScreenLast updated
Tests the ability to implement binary focal loss with raw logits from first principles, emphasizing numerical stability, custom weighting, and tensor reduction strategies.
Problem Statement
In high-throughput observability platforms like Datadog, anomaly detection and alerting models frequently deal with extreme class imbalance where anomalies represent less than 0.1% of all ingested metrics. Standard binary cross-entropy (BCE) loss tends to be overwhelmed by easy negative examples.
Focal Loss addresses this by adding a modulating factor (1 - p_t)^gamma to standard cross-entropy, down-weighting well-classified examples and focusing training on hard negatives and false positives.
Implement binary_focal_loss_with_logits using PyTorch or NumPy operations from scratch. You must not call high-level library focal loss functions (such as torchvision.ops.sigmoid_focal_loss). Your implementation must compute the loss directly from unnormalized log-odds (logits) to avoid numerical instability (e.g., NaN or -inf values caused by log(0) or extreme exponentiation).
Mathematical Formulation
For a binary target y in {0, 1} and predicted logit z:
- Let
p = sigmoid(z) = 1 / (1 + exp(-z)) p_t = pify == 1else1 - palpha_t = alphaify == 1else(1 - alpha)Loss = -alpha_t * (1 - p_t)^gamma * log(p_t)
Your implementation must be numerically stable even when z takes extreme values such as z = -100 or z = 100.
Function Signature
import torch
def binary_focal_loss_with_logits(
logits: torch.Tensor,
targets: torch.Tensor,
alpha: float = 0.25,
gamma: float = 2.0,
reduction: str = "mean"
) -> torch.Tensor:
pass
Input & Output Format
logits: Tensor of arbitrary shape(...)containing unnormalized prediction scores (float32/float64).targets: Tensor of the same shape aslogitswith values in{0.0, 1.0}.alpha: Weighting factor for the positive class (0.0 = 0.0). Whengamma = 0, focal loss is equivalent to standard binary cross-entropy.reduction: Specifies reduction to apply:'none','mean', or'sum'.- Output: Scalar tensor if
reductionis'mean'or'sum'; otherwise, a tensor with the same shape aslogits.
Examples
Example 1
logits = torch.tensor([0.0, 2.0, -2.0])
targets = torch.tensor([1.0, 1.0, 0.0])
loss = binary_focal_loss_with_logits(logits, targets, alpha=0.25, gamma=2.0, reduction="mean")
# Output: approx 0.0401
Explanation: For logits[1] = 2.0 and target = 1.0, p ≈ 0.8808. The modulating factor is (1 - 0.8808)^2 ≈ 0.0142, heavily discounting this easy positive example.
Example 2 (Numerical Stability)
logits = torch.tensor([-1000.0, 1000.0])
targets = torch.tensor([0.0, 1.0])
loss = binary_focal_loss_with_logits(logits, targets, alpha=0.5, gamma=2.0, reduction="none")
# Output: tensor([0.0, 0.0]) without NaN or Inf values
Constraints & Edge Cases
logitsandtargetsmatch in shape and data type.gamma = 0andalpha = -1should strictly matchtorch.nn.functional.binary_cross_entropy_with_logits.- Must gracefully handle large positive and negative logits without returning
NaNgradients or outputs.
Hints
Hint 1
Express log(p_t) and (1 - p_t) directly in terms of logits z using log-sigmoid and softplus identities rather than calling sigmoid(z) first.
Hint 2
Notice that 1 - sigmoid(z) = sigmoid(-z), so (1 - p_t) equals sigmoid(-z) when y = 1 and sigmoid(z) when y = 0.
Hint 3
For log(p_t), recall that log(sigmoid(z)) = -softplus(-z) = -log(1 + exp(-z)), which PyTorch can compute stably via -torch.nn.functional.softplus(-z).
Follow-up questions
- How would you extend this implementation to handle multi-class classification where targets are one-hot encoded or class indices?
- How do the gradients of Focal Loss with respect to logits compare to standard cross-entropy for confident correct versus confident incorrect predictions?
- In highly imbalanced datasets, how does tuning alpha interact with tuning gamma, and why is alpha often set lower for the rare positive class when gamma > 0?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.