diff --git a/DIRECTORY.md b/DIRECTORY.md index 28114a88ae3..8c8f247104b 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -159,6 +159,7 @@ * [Hinge Loss](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/loss_function/hinge_loss.rs) * [Huber Loss](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/loss_function/huber_loss.rs) * [Kl Divergence Loss](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/loss_function/kl_divergence_loss.rs) + * [Marginal Ranking Loss](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/loss_function/average_margin_ranking_loss.rs) * [Mean Absolute Error Loss](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/loss_function/mean_absolute_error_loss.rs) * [Mean Squared Error Loss](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/loss_function/mean_squared_error_loss.rs) * [Negative Log Likelihood](https://github.com/TheAlgorithms/Rust/blob/master/src/machine_learning/loss_function/negative_log_likelihood.rs) diff --git a/src/machine_learning/loss_function/average_margin_ranking_loss.rs b/src/machine_learning/loss_function/average_margin_ranking_loss.rs new file mode 100644 index 00000000000..505bf2a94a7 --- /dev/null +++ b/src/machine_learning/loss_function/average_margin_ranking_loss.rs @@ -0,0 +1,113 @@ +/// Marginal Ranking +/// +/// The 'average_margin_ranking_loss' function calculates the Margin Ranking loss, which is a +/// loss function used for ranking problems in machine learning. +/// +/// ## Formula +/// +/// For a pair of values `x_first` and `x_second`, `margin`, and `y_true`, +/// the Margin Ranking loss is calculated as: +/// +/// - loss = `max(0, -y_true * (x_first - x_second) + margin)`. +/// +/// It returns the average loss by dividing the `total_loss` by total no. of +/// elements. +/// +/// Pytorch implementation: +/// https://pytorch.org/docs/stable/generated/torch.nn.MarginRankingLoss.html +/// https://gombru.github.io/2019/04/03/ranking_loss/ +/// https://vinija.ai/concepts/loss/#pairwise-ranking-loss +/// + +pub fn average_margin_ranking_loss( + x_first: &[f64], + x_second: &[f64], + margin: f64, + y_true: f64, +) -> Result { + check_input(x_first, x_second, margin, y_true)?; + + let total_loss: f64 = x_first + .iter() + .zip(x_second.iter()) + .map(|(f, s)| (margin - y_true * (f - s)).max(0.0)) + .sum(); + Ok(total_loss / (x_first.len() as f64)) +} + +fn check_input( + x_first: &[f64], + x_second: &[f64], + margin: f64, + y_true: f64, +) -> Result<(), MarginalRankingLossError> { + if x_first.len() != x_second.len() { + return Err(MarginalRankingLossError::InputsHaveDifferentLength); + } + if x_first.is_empty() { + return Err(MarginalRankingLossError::EmptyInputs); + } + if margin < 0.0 { + return Err(MarginalRankingLossError::NegativeMargin); + } + if y_true != 1.0 && y_true != -1.0 { + return Err(MarginalRankingLossError::InvalidValues); + } + + Ok(()) +} + +#[derive(Debug, PartialEq, Eq)] +pub enum MarginalRankingLossError { + InputsHaveDifferentLength, + EmptyInputs, + InvalidValues, + NegativeMargin, +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! test_with_wrong_inputs { + ($($name:ident: $inputs:expr,)*) => { + $( + #[test] + fn $name() { + let (vec_a, vec_b, margin, y_true, expected) = $inputs; + assert_eq!(average_margin_ranking_loss(&vec_a, &vec_b, margin, y_true), expected); + assert_eq!(average_margin_ranking_loss(&vec_b, &vec_a, margin, y_true), expected); + } + )* + } + } + + test_with_wrong_inputs! { + invalid_length0: (vec![1.0, 2.0, 3.0], vec![2.0, 3.0], 1.0, 1.0, Err(MarginalRankingLossError::InputsHaveDifferentLength)), + invalid_length1: (vec![1.0, 2.0], vec![2.0, 3.0, 4.0], 1.0, 1.0, Err(MarginalRankingLossError::InputsHaveDifferentLength)), + invalid_length2: (vec![], vec![1.0, 2.0, 3.0], 1.0, 1.0, Err(MarginalRankingLossError::InputsHaveDifferentLength)), + invalid_length3: (vec![1.0, 2.0, 3.0], vec![], 1.0, 1.0, Err(MarginalRankingLossError::InputsHaveDifferentLength)), + invalid_values: (vec![1.0, 2.0, 3.0], vec![2.0, 3.0, 4.0], -1.0, 1.0, Err(MarginalRankingLossError::NegativeMargin)), + invalid_y_true: (vec![1.0, 2.0, 3.0], vec![2.0, 3.0, 4.0], 1.0, 2.0, Err(MarginalRankingLossError::InvalidValues)), + empty_inputs: (vec![], vec![], 1.0, 1.0, Err(MarginalRankingLossError::EmptyInputs)), + } + + macro_rules! test_average_margin_ranking_loss { + ($($name:ident: $inputs:expr,)*) => { + $( + #[test] + fn $name() { + let (x_first, x_second, margin, y_true, expected) = $inputs; + assert_eq!(average_margin_ranking_loss(&x_first, &x_second, margin, y_true), Ok(expected)); + } + )* + } + } + + test_average_margin_ranking_loss! { + set_0: (vec![1.0, 2.0, 3.0], vec![2.0, 3.0, 4.0], 1.0, -1.0, 0.0), + set_1: (vec![1.0, 2.0, 3.0], vec![2.0, 3.0, 4.0], 1.0, 1.0, 2.0), + set_2: (vec![1.0, 2.0, 3.0], vec![1.0, 2.0, 3.0], 0.0, 1.0, 0.0), + set_3: (vec![4.0, 5.0, 6.0], vec![1.0, 2.0, 3.0], 1.0, -1.0, 4.0), + } +} diff --git a/src/machine_learning/loss_function/mod.rs b/src/machine_learning/loss_function/mod.rs index 0637d63e1ef..95686eb8c20 100644 --- a/src/machine_learning/loss_function/mod.rs +++ b/src/machine_learning/loss_function/mod.rs @@ -1,3 +1,4 @@ +mod average_margin_ranking_loss; mod hinge_loss; mod huber_loss; mod kl_divergence_loss; @@ -5,6 +6,7 @@ mod mean_absolute_error_loss; mod mean_squared_error_loss; mod negative_log_likelihood; +pub use self::average_margin_ranking_loss::average_margin_ranking_loss; pub use self::hinge_loss::hng_loss; pub use self::huber_loss::huber_loss; pub use self::kl_divergence_loss::kld_loss; diff --git a/src/machine_learning/mod.rs b/src/machine_learning/mod.rs index c9344a508ef..c77fd65116b 100644 --- a/src/machine_learning/mod.rs +++ b/src/machine_learning/mod.rs @@ -7,6 +7,7 @@ mod optimization; pub use self::cholesky::cholesky; pub use self::k_means::k_means; pub use self::linear_regression::linear_regression; +pub use self::loss_function::average_margin_ranking_loss; pub use self::loss_function::hng_loss; pub use self::loss_function::huber_loss; pub use self::loss_function::kld_loss;