use crate::error::Result;
use crate::losses::Loss;
use scirs2_core::ndarray::{Array, IxDyn};
use scirs2_core::numeric::{Float, NumAssign};
use std::fmt::Debug;
#[derive(Debug, Clone, Copy, Default)]
pub struct MeanAbsoluteError;
impl MeanAbsoluteError {
pub fn new() -> Self {
Self
}
}
impl<F: Float + Debug + NumAssign> Loss<F> for MeanAbsoluteError {
fn forward(&self, predictions: &Array<F, IxDyn>, targets: &Array<F, IxDyn>) -> Result<F> {
let n = F::from(predictions.len()).unwrap_or(F::one());
let abs_diff_sum = predictions
.iter()
.zip(targets.iter())
.map(|(&p, &t)| (p - t).abs())
.fold(F::zero(), |acc, x| acc + x);
Ok(abs_diff_sum / n)
}
fn backward(
&self,
predictions: &Array<F, IxDyn>,
targets: &Array<F, IxDyn>,
) -> Result<Array<F, IxDyn>> {
let n = F::from(predictions.len()).unwrap_or(F::one());
let gradients: Vec<F> = predictions
.iter()
.zip(targets.iter())
.map(|(&p, &t)| {
let diff = p - t;
if diff > F::zero() {
F::one() / n
} else if diff < F::zero() {
-F::one() / n
} else {
F::zero() }
})
.collect();
Array::from_shape_vec(predictions.dim(), gradients).map_err(|e| {
crate::error::NeuralError::InferenceError(format!(
"Failed to create gradient array: {}",
e
))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array;
#[test]
fn test_mae_forward_basic() {
let mae = MeanAbsoluteError::new();
let predictions = Array::from_vec(vec![1.0, 2.0, 3.0]).into_dyn();
let targets = Array::from_vec(vec![1.5, 1.5, 3.5]).into_dyn();
let loss = mae
.forward(&predictions, &targets)
.expect("Operation failed");
assert!((loss - 0.5).abs() < 1e-6);
}
#[test]
fn test_mae_forward_perfect() {
let mae = MeanAbsoluteError::new();
let predictions = Array::from_vec(vec![1.0, 2.0, 3.0]).into_dyn();
let targets = Array::from_vec(vec![1.0, 2.0, 3.0]).into_dyn();
let loss = mae
.forward(&predictions, &targets)
.expect("Operation failed");
assert!(loss.abs() < 1e-10);
}
#[test]
fn test_mae_backward() {
let mae = MeanAbsoluteError::new();
let predictions = Array::from_vec(vec![1.0, 3.0, 2.0]).into_dyn();
let targets = Array::from_vec(vec![2.0, 2.0, 2.0]).into_dyn();
let gradients = mae
.backward(&predictions, &targets)
.expect("Operation failed");
let expected = [-1.0 / 3.0, 1.0 / 3.0, 0.0];
for (g, e) in gradients.iter().zip(expected.iter()) {
assert!((*g - *e).abs() < 1e-6);
}
}
#[test]
fn test_mae_with_negative_values() {
let mae = MeanAbsoluteError::new();
let predictions = Array::from_vec(vec![-1.0, -2.0, 0.0]).into_dyn();
let targets = Array::from_vec(vec![1.0, -1.0, -1.0]).into_dyn();
let loss = mae
.forward(&predictions, &targets)
.expect("Operation failed");
assert!((loss - 4.0 / 3.0).abs() < 1e-6);
}
#[test]
fn test_mae_2d() {
let mae = MeanAbsoluteError::new();
let predictions = Array::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
.expect("Operation failed")
.into_dyn();
let targets = Array::from_shape_vec((2, 2), vec![1.0, 3.0, 2.0, 5.0])
.expect("Operation failed")
.into_dyn();
let loss = mae
.forward(&predictions, &targets)
.expect("Operation failed");
assert!((loss - 0.75).abs() < 1e-6);
}
}