use aprender::primitives::Vector;
use crate::autograd::BackwardOp;
use crate::Tensor;
use ndarray::Array1;
use std::rc::Rc;
use crate::train::loss::LossFn;
pub struct HuberLoss {
delta: f32,
}
impl HuberLoss {
pub fn new(delta: f32) -> Self {
assert!(delta > 0.0, "delta must be positive");
Self { delta }
}
pub fn default_delta() -> Self {
Self::new(1.0)
}
}
impl Default for HuberLoss {
fn default() -> Self {
Self::new(1.0)
}
}
impl LossFn for HuberLoss {
fn forward(&self, predictions: &Tensor, targets: &Tensor) -> Tensor {
assert_eq!(
predictions.len(),
targets.len(),
"Predictions and targets must have same length"
);
let pred_vec =
Vector::from_slice(predictions.data().as_slice().expect("contiguous tensor data"));
let tgt_vec =
Vector::from_slice(targets.data().as_slice().expect("contiguous tensor data"));
let mean_loss = aprender::loss::huber_loss(&pred_vec, &tgt_vec, self.delta);
let mut loss = Tensor::from_vec(vec![mean_loss], true);
let diff = predictions.data() - targets.data();
let n = predictions.len() as f32;
let delta = self.delta;
let grad: Array1<f32> = diff
.iter()
.map(|&d| {
let abs_d = d.abs();
if abs_d <= delta {
d / n
} else {
delta * d.signum() / n
}
})
.collect();
struct HuberBackward {
pred_grad_cell: Rc<std::cell::RefCell<Option<Array1<f32>>>>,
grad: Array1<f32>,
}
impl BackwardOp for HuberBackward {
fn backward(&self) {
let mut pred_grad = self.pred_grad_cell.borrow_mut();
if let Some(existing) = pred_grad.as_mut() {
*existing = &*existing + &self.grad;
} else {
*pred_grad = Some(self.grad.clone());
}
}
}
if predictions.requires_grad() {
loss.set_backward_op(Rc::new(HuberBackward {
pred_grad_cell: predictions.grad_cell(),
grad,
}));
}
loss
}
fn name(&self) -> &'static str {
"Huber"
}
}
pub type SmoothL1Loss = HuberLoss;