"""
Regression loss functions.
"""
import numpy as np
from fit.core.tensor import Tensor
[docs]
class MSELoss:
"""
Mean Squared Error loss for regression.
L(y, ŷ) = (1/n) * Σ(y - ŷ)²
"""
[docs]
def __init__(self, reduction="mean"):
"""
Initialize MSELoss.
Args:
reduction: Reduction method ('mean', 'sum', or 'none')
"""
self.reduction = reduction
[docs]
def __call__(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""
Compute mean squared error loss.
Args:
predictions: Predicted values
targets: Target values
Returns:
Loss tensor
"""
return self.forward(predictions, targets)
[docs]
def forward(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""Forward pass of MSE loss."""
# Compute squared differences
diff = predictions - targets
squared_diff = diff * diff
# Apply reduction
if self.reduction == "mean":
return squared_diff.mean()
elif self.reduction == "sum":
return squared_diff.sum()
else: # 'none'
return squared_diff
[docs]
class MAELoss:
"""
Mean Absolute Error loss for regression.
L(y, ŷ) = (1/n) * Σ|y - ŷ|
"""
[docs]
def __init__(self, reduction="mean"):
"""
Initialize MAELoss.
Args:
reduction: Reduction method ('mean', 'sum', or 'none')
"""
self.reduction = reduction
[docs]
def __call__(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""
Compute mean absolute error loss.
Args:
predictions: Predicted values
targets: Target values
Returns:
Loss tensor
"""
return self.forward(predictions, targets)
[docs]
def forward(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""Forward pass of MAE loss."""
# Compute absolute differences
diff = predictions - targets
abs_diff = Tensor(np.abs(diff.data), requires_grad=diff.requires_grad)
def _backward():
if diff.requires_grad and abs_diff.grad is not None:
# Gradient of abs(x) is sign(x)
grad = abs_diff.grad * np.sign(diff.data)
diff.grad = grad if diff.grad is None else diff.grad + grad
abs_diff._backward = _backward
abs_diff._prev = {diff}
# Apply reduction
if self.reduction == "mean":
return abs_diff.mean()
elif self.reduction == "sum":
return abs_diff.sum()
else: # 'none'
return abs_diff
[docs]
class SmoothL1Loss:
"""
Smooth L1 Loss (Huber Loss) for regression.
Less sensitive to outliers than MSE.
L(x) = 0.5*x² if |x| < β
β*|x| - 0.5*β² otherwise
"""
[docs]
def __init__(self, beta=1.0, reduction="mean"):
"""
Initialize SmoothL1Loss.
Args:
beta: Threshold for switching between L2 and L1 loss
reduction: Reduction method ('mean', 'sum', or 'none')
"""
self.beta = beta
self.reduction = reduction
[docs]
def __call__(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""
Compute smooth L1 loss.
Args:
predictions: Predicted values
targets: Target values
Returns:
Loss tensor
"""
return self.forward(predictions, targets)
[docs]
def forward(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""Forward pass of Smooth L1 loss."""
diff = predictions - targets
abs_diff = np.abs(diff.data)
# Smooth L1 loss
loss_data = np.where(
abs_diff < self.beta,
0.5 * diff.data * diff.data / self.beta,
abs_diff - 0.5 * self.beta,
)
loss_tensor = Tensor(loss_data, requires_grad=diff.requires_grad)
def _backward():
if diff.requires_grad and loss_tensor.grad is not None:
# Gradient computation
grad = np.where(
abs_diff < self.beta, diff.data / self.beta, np.sign(diff.data)
)
grad = grad * loss_tensor.grad
diff.grad = grad if diff.grad is None else diff.grad + grad
loss_tensor._backward = _backward
loss_tensor._prev = {diff}
# Apply reduction
if self.reduction == "mean":
return loss_tensor.mean()
elif self.reduction == "sum":
return loss_tensor.sum()
else: # 'none'
return loss_tensor
[docs]
class HuberLoss:
"""
Huber Loss for regression.
Combines MSE and MAE - quadratic for small errors, linear for large errors.
"""
[docs]
def __init__(self, delta=1.0, reduction="mean"):
"""
Initialize Huber Loss.
Args:
delta: Threshold for switching between quadratic and linear
reduction: Reduction method ('mean', 'sum', or 'none')
"""
self.delta = delta
self.reduction = reduction
[docs]
def __call__(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""
Compute Huber loss.
Args:
predictions: Predicted values
targets: Target values
Returns:
Loss tensor
"""
return self.forward(predictions, targets)
[docs]
def forward(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""Forward pass of Huber loss."""
diff = predictions - targets
abs_diff = np.abs(diff.data)
# Huber loss
loss_data = np.where(
abs_diff <= self.delta,
0.5 * diff.data * diff.data,
self.delta * abs_diff - 0.5 * self.delta * self.delta,
)
loss_tensor = Tensor(loss_data, requires_grad=diff.requires_grad)
def _backward():
if diff.requires_grad and loss_tensor.grad is not None:
# Gradient computation
grad = np.where(
abs_diff <= self.delta, diff.data, self.delta * np.sign(diff.data)
)
grad = grad * loss_tensor.grad
diff.grad = grad if diff.grad is None else diff.grad + grad
loss_tensor._backward = _backward
loss_tensor._prev = {diff}
# Apply reduction
if self.reduction == "mean":
return loss_tensor.mean()
elif self.reduction == "sum":
return loss_tensor.sum()
else: # 'none'
return loss_tensor
[docs]
class LogCoshLoss:
"""
Logarithm of the hyperbolic cosine loss.
Smooth approximation to MAE that is less sensitive to outliers than MSE.
"""
[docs]
def __init__(self, reduction="mean"):
"""
Initialize LogCosh Loss.
Args:
reduction: Reduction method ('mean', 'sum', or 'none')
"""
self.reduction = reduction
[docs]
def __call__(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""
Compute log-cosh loss.
Args:
predictions: Predicted values
targets: Target values
Returns:
Loss tensor
"""
return self.forward(predictions, targets)
[docs]
def forward(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""Forward pass of log-cosh loss."""
diff = predictions - targets
# log(cosh(x)) = log((e^x + e^-x)/2) = log(e^x + e^-x) - log(2)
# For numerical stability, use: log(cosh(x)) = |x| + log(1 + exp(-2|x|)) - log(2)
abs_diff = np.abs(diff.data)
loss_data = abs_diff + np.log(1 + np.exp(-2 * abs_diff)) - np.log(2)
loss_tensor = Tensor(loss_data, requires_grad=diff.requires_grad)
def _backward():
if diff.requires_grad and loss_tensor.grad is not None:
# Gradient of log(cosh(x)) is tanh(x)
grad = np.tanh(diff.data) * loss_tensor.grad
diff.grad = grad if diff.grad is None else diff.grad + grad
loss_tensor._backward = _backward
loss_tensor._prev = {diff}
# Apply reduction
if self.reduction == "mean":
return loss_tensor.mean()
elif self.reduction == "sum":
return loss_tensor.sum()
else: # 'none'
return loss_tensor
[docs]
class QuantileLoss:
"""
Quantile Loss for quantile regression.
Allows predicting specific quantiles of the target distribution.
"""
[docs]
def __init__(self, quantile=0.5, reduction="mean"):
"""
Initialize Quantile Loss.
Args:
quantile: Quantile to predict (0 < quantile < 1)
reduction: Reduction method ('mean', 'sum', or 'none')
"""
if not 0 < quantile < 1:
raise ValueError("Quantile must be between 0 and 1")
self.quantile = quantile
self.reduction = reduction
[docs]
def __call__(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""
Compute quantile loss.
Args:
predictions: Predicted quantile values
targets: Target values
Returns:
Loss tensor
"""
return self.forward(predictions, targets)
[docs]
def forward(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""Forward pass of quantile loss."""
diff = targets - predictions
# Quantile loss: max(q*diff, (q-1)*diff)
loss_data = np.maximum(
self.quantile * diff.data, (self.quantile - 1) * diff.data
)
loss_tensor = Tensor(loss_data, requires_grad=diff.requires_grad)
def _backward():
if diff.requires_grad and loss_tensor.grad is not None:
# Gradient is q if diff > 0, else (q-1)
grad = np.where(diff.data > 0, self.quantile, self.quantile - 1)
# Note: gradient w.r.t. predictions is -grad
pred_grad = -grad * loss_tensor.grad
predictions.grad = (
pred_grad
if predictions.grad is None
else predictions.grad + pred_grad
)
loss_tensor._backward = _backward
loss_tensor._prev = {predictions}
# Apply reduction
if self.reduction == "mean":
return loss_tensor.mean()
elif self.reduction == "sum":
return loss_tensor.sum()
else: # 'none'
return loss_tensor
[docs]
class CosineSimilarityLoss:
"""
Cosine Similarity Loss for regression.
Measures the cosine of the angle between prediction and target vectors.
"""
[docs]
def __init__(self, reduction="mean"):
"""
Initialize Cosine Similarity Loss.
Args:
reduction: Reduction method ('mean', 'sum', or 'none')
"""
self.reduction = reduction
[docs]
def __call__(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""
Compute cosine similarity loss.
Args:
predictions: Predicted vectors
targets: Target vectors
Returns:
Loss tensor (1 - cosine_similarity)
"""
return self.forward(predictions, targets)
[docs]
def forward(self, predictions: Tensor, targets: Tensor) -> Tensor:
"""Forward pass of cosine similarity loss."""
# Cosine similarity = (A·B) / (||A|| * ||B||)
dot_product = (predictions * targets).sum(axis=-1, keepdims=True)
pred_norm = (predictions * predictions).sum(axis=-1, keepdims=True).sqrt()
target_norm = (targets * targets).sum(axis=-1, keepdims=True).sqrt()
# Add small epsilon for numerical stability
eps = 1e-8
pred_norm = pred_norm + Tensor(eps)
target_norm = target_norm + Tensor(eps)
cosine_sim = dot_product / (pred_norm * target_norm)
# Loss is 1 - cosine_similarity
loss = Tensor(1.0) - cosine_sim
# Apply reduction
if self.reduction == "mean":
return loss.mean()
elif self.reduction == "sum":
return loss.sum()
else: # 'none'
return loss