use super::LossFunction;
#[derive(Debug, Clone, Copy)]
pub struct PseudoHuberLoss {
delta: f32,
delta_sq: f32,
}
impl Default for PseudoHuberLoss {
fn default() -> Self {
Self::new(1.0)
}
}
impl PseudoHuberLoss {
pub fn new(delta: f32) -> Self {
assert!(delta > 0.0, "delta must be positive");
Self {
delta,
delta_sq: delta * delta,
}
}
pub fn delta(&self) -> f32 {
self.delta
}
#[inline]
fn scaled_residual_sq(&self, residual: f32) -> f32 {
(residual * residual) / self.delta_sq
}
}
impl LossFunction for PseudoHuberLoss {
#[inline]
fn loss(&self, target: f32, prediction: f32) -> f32 {
let a = target - prediction;
let scaled_sq = self.scaled_residual_sq(a);
self.delta_sq * ((1.0 + scaled_sq).sqrt() - 1.0)
}
#[inline]
fn gradient(&self, target: f32, prediction: f32) -> f32 {
let a = prediction - target; let scaled_sq = self.scaled_residual_sq(a);
a / (1.0 + scaled_sq).sqrt()
}
#[inline]
fn hessian(&self, target: f32, prediction: f32) -> f32 {
let a = target - prediction;
let scaled_sq = self.scaled_residual_sq(a);
let denom = (1.0 + scaled_sq).sqrt();
1.0 / (denom * denom * denom)
}
#[inline]
fn gradient_hessian(&self, target: f32, prediction: f32) -> (f32, f32) {
let a_pos = prediction - target; let a_neg = target - prediction; let scaled_sq = self.scaled_residual_sq(a_neg);
let sqrt_term = (1.0 + scaled_sq).sqrt();
let gradient = a_pos / sqrt_term;
let hessian = 1.0 / (sqrt_term * sqrt_term * sqrt_term);
(gradient, hessian)
}
fn name(&self) -> &'static str {
"pseudo_huber"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pseudo_huber_loss() {
let loss = PseudoHuberLoss::new(1.0);
assert!((loss.loss(10.0, 10.0) - 0.0).abs() < 1e-6);
let l = loss.loss(10.0, 10.1);
let mse_approx = 0.5 * 0.1 * 0.1;
assert!((l - mse_approx).abs() < 0.01);
}
#[test]
fn test_pseudo_huber_robustness() {
let loss = PseudoHuberLoss::new(1.0);
let mse = super::super::MseLoss::new();
let ph_loss = loss.loss(0.0, 100.0);
let mse_loss = mse.loss(0.0, 100.0);
assert!(ph_loss < mse_loss);
}
#[test]
fn test_pseudo_huber_gradient() {
let loss = PseudoHuberLoss::new(1.0);
let g = loss.gradient(10.0, 10.1);
assert!((g - 0.1).abs() < 0.01);
let g_large = loss.gradient(0.0, 100.0);
assert!(g_large < 100.0); assert!(g_large > 0.9); }
#[test]
fn test_pseudo_huber_hessian() {
let loss = PseudoHuberLoss::new(1.0);
let h = loss.hessian(10.0, 10.0);
assert!((h - 1.0).abs() < 1e-6);
let h_large = loss.hessian(0.0, 100.0);
assert!(h_large < 0.01);
assert!(h_large > 0.0);
}
#[test]
fn test_delta_effect() {
let small_delta = PseudoHuberLoss::new(0.1);
let large_delta = PseudoHuberLoss::new(10.0);
let g_small = small_delta.gradient(0.0, 5.0);
let g_large = large_delta.gradient(0.0, 5.0);
assert!((g_large - 5.0).abs() < (g_small - 5.0).abs());
}
}