use crate::Float;
pub trait DiffLoss: Clone {
type Output: Clone;
fn mse(&self, truth: &Self) -> Self::Output;
fn mse_input_grads(&self, truth: &Self) -> Self;
fn huber(&self, beta: f32, truth: &Self) -> Self::Output;
fn huber_input_grads(&self, beta: f32, truth: &Self) -> Self;
}
impl<E: Float, const I: usize> DiffLoss for [E; I] {
type Output = E;
fn mse(&self, truth: &Self) -> E {
if I == 0 {
return E::default();
}
self.iter()
.zip(truth)
.fold(E::default(), |a, (test, truth)| {
let err = *test - *truth;
a + (err * err)
})
/ E::from_usize(I).unwrap()
}
fn mse_input_grads(&self, truth: &Self) -> [E; I] {
let c = E::from_usize(I).unwrap();
let mut out = [E::default(); I];
out.iter_mut()
.zip(self)
.zip(truth)
.for_each(|((out, test), truth)| *out = (E::ONE + E::ONE) * (*test - *truth) / c);
out
}
fn huber(&self, beta: f32, truth: &Self) -> E {
if I == 0 {
return E::default();
}
let half = E::from_f32(0.5).unwrap();
let beta = E::from_f32(beta).unwrap();
self.iter()
.zip(truth)
.fold(E::default(), |a, (test, truth)| {
let err = *test - *truth;
let err_abs = err.abs();
let huber_err = if err_abs < beta {
half * err * err
} else {
beta * (err_abs - half * beta * beta)
};
a + huber_err
})
/ E::from_usize(I).unwrap()
}
fn huber_input_grads(&self, beta: f32, truth: &Self) -> [E; I] {
let beta = E::from_f32(beta).unwrap();
let c = E::from_usize(I).unwrap();
let mut out = [E::default(); I];
out.iter_mut()
.zip(self)
.zip(truth)
.for_each(|((out, test), truth)| {
let err = *test - *truth;
let err_abs = err.abs();
*out = if err_abs < beta {
err / c
} else {
let signum = if err > E::default() { E::ONE } else { -E::ONE };
signum * beta / c
};
});
out
}
}
pub trait LogitLoss: Clone {
type Output: Clone;
fn logit_bce(&self, truth: &Self) -> Self::Output;
fn logit_bce_input_grads(&self, truth: &Self) -> Self;
}
impl<E: Float, const I: usize> LogitLoss for [E; I] {
type Output = E;
fn logit_bce(&self, truth: &Self) -> E {
if I == 0 {
return E::default();
}
(self
.iter()
.zip(truth)
.fold(E::default(), |a, (test, truth)| {
let y = *truth;
let y_hat = test.max(E::SMOL).min(E::ONE - E::SMOL);
a + (y * y_hat.ln() + (E::ONE - y) * (E::ONE - y_hat).ln())
})
/ E::from_usize(I).unwrap())
.neg()
.max(E::SMOL)
}
fn logit_bce_input_grads(&self, truth: &Self) -> Self {
let mut out = [E::default(); I];
out.iter_mut()
.zip(self)
.zip(truth)
.for_each(|((out, test), truth)| {
let y = *truth;
let y_hat = *test;
*out = (y_hat - y) / (E::SMOL + y_hat * (E::ONE - y_hat));
});
out
}
}
pub trait CosineLoss<P>: Clone
where
P: Float + std::iter::Sum + std::fmt::Display,
{
fn cosine_similarity(&self, other: &Self) -> Option<P>;
fn cosine_distance(&self, other: &Self) -> Option<P> {
self.cosine_similarity(other).map(|p| P::ONE - p)
}
}
fn cosine_similarity<
P: Float + std::iter::Sum + std::fmt::Display,
E: Float + num_traits::AsPrimitive<P>,
const N: usize,
>(
a: &[E; N],
b: &[E; N],
) -> Option<P> {
let dot_product: P = a.iter().zip(b.iter()).map(|(x, y)| x.as_() * y.as_()).sum();
let magnitude_a: P = a.iter().map(|x| x.as_() * x.as_()).sum::<P>().sqrt();
let magnitude_b: P = b.iter().map(|x| x.as_() * x.as_()).sum::<P>().sqrt();
if magnitude_a == P::default() || magnitude_b == P::default() {
return None;
}
Some(dot_product / (magnitude_a * magnitude_b))
}
impl<P, E: Float + num_traits::AsPrimitive<P>, const I: usize> CosineLoss<P> for [E; I]
where
P: Float + std::iter::Sum + std::fmt::Display,
{
fn cosine_similarity(&self, other: &Self) -> Option<P> {
cosine_similarity(self, other)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mse() {
assert_eq!([0.0f32; 0].mse(&[]), 0.0f32);
assert_eq!([3.5].mse(&[3.5]), 0.0f32);
assert_eq!([2.0, -1.0].mse(&[5.0, 1.0]), 6.5);
assert_eq!([2.0, -1.0].mse_input_grads(&[5.0, 1.0]), [-3.0, -2.0]);
}
#[test]
fn test_logit_bce() {
assert_eq!([0.0f32; 0].logit_bce(&[]), 0.0f32);
assert_eq!([0.0f32; 0].logit_bce_input_grads(&[]), []);
assert_eq!([1.0, 0.0].logit_bce(&[1.0, 0.0]), f32::SMOL);
assert_eq!([1.0, 0.0].logit_bce_input_grads(&[1.0, 0.0]), [0.0, 0.0]);
let [d_under, d_over]: [f32; 2] = [0.99, 0.01].logit_bce_input_grads(&[1.0, 0.0]);
assert!(d_under < 0.0);
assert!(d_over > 0.0);
assert!(d_under.abs() / d_over > 0.99 && d_under.abs() / d_over < 1.01);
let one_wrong_loss = [0.8, 0.2].logit_bce(&[1.0, 0.0]);
assert!(one_wrong_loss >= 0.1);
let two_wrong_loss = [0.0, 1.0].logit_bce(&[1.0, 0.0]);
assert!(two_wrong_loss >= 1.0);
assert!(two_wrong_loss >= 1.999 * one_wrong_loss);
}
#[test]
fn test_cosine_similarity() {
assert_eq!(cosine_similarity::<f64, _, 1>(&[0.0f32], &[1.0f32]), None);
assert_eq!([0.1f32].cosine_similarity(&[99999.0f32]), Some(1.0f32));
assert_eq!(
cosine_similarity(&[1.0f32, 3.0f32], &[1.0f32, 3.0f32]),
Some(1.0f32)
);
}
#[test]
fn test_huber() {
assert_eq!([0.0f32; 0].huber(1.0, &[]), 0.0f32);
assert_eq!([3.5].huber(1.0, &[3.5]), 0.0f32);
assert_eq!([2.0].huber(1.0, &[5.0]), 2.5);
assert_eq!([2.0].huber_input_grads(1.0, &[5.0]), [-1.0]);
assert_eq!([3.0].huber(1.0, &[3.5]), 0.125);
assert_eq!([3.0].huber_input_grads(1.0, &[3.5]), [-0.5]);
assert_eq!([3.5].huber_input_grads(1.0, &[3.0]), [0.5]);
}
}