use crate::autograd::Variable;
use num_traits::Float;
use std::fmt::Debug;
pub trait Loss<
T: Float + Send + Sync + 'static + Debug + ndarray::ScalarOperand + num_traits::FromPrimitive,
>: Send + Sync + Debug
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T>;
fn name(&self) -> &'static str;
}
#[derive(Debug, Clone)]
pub struct MSELoss;
impl<
T: Float + Send + Sync + 'static + Debug + ndarray::ScalarOperand + num_traits::FromPrimitive,
> Loss<T> for MSELoss
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T> {
mse_loss(predictions, targets)
}
fn name(&self) -> &'static str {
"MSELoss"
}
}
#[derive(Debug, Clone)]
pub struct CrossEntropyLoss<T: Float + Send + Sync + 'static> {
_phantom: std::marker::PhantomData<T>,
}
impl<T: Float + Send + Sync + 'static> CrossEntropyLoss<T> {
pub fn new() -> Self {
Self {
_phantom: std::marker::PhantomData,
}
}
}
impl<
T: Float + Send + Sync + 'static + Debug + ndarray::ScalarOperand + num_traits::FromPrimitive,
> Loss<T> for CrossEntropyLoss<T>
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T> {
cross_entropy_loss(predictions, targets)
}
fn name(&self) -> &'static str {
"CrossEntropyLoss"
}
}
pub fn mse_loss<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
input: &Variable<T>,
target: &Variable<T>,
) -> Variable<T> {
let diff = input - target;
let squared_diff = &diff * &diff;
let mean_loss = squared_diff.mean();
mean_loss
}
pub fn cross_entropy_loss<
T: Float + Send + Sync + 'static + Debug + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
predictions: &Variable<T>,
targets: &Variable<T>,
) -> Variable<T> {
let cross_entropy = CrossEntropyLoss::new();
cross_entropy.forward(predictions, targets)
}
pub fn cross_entropy<
T: Float + Send + Sync + 'static + Debug + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
predictions: &Variable<T>,
targets: &Variable<T>,
) -> Variable<T> {
cross_entropy_loss(predictions, targets)
}
pub fn nll_loss<
T: Float + Send + Sync + 'static + Debug + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
log_probabilities: &Variable<T>,
targets: &Variable<T>,
) -> Variable<T> {
cross_entropy_loss(log_probabilities, targets)
}
pub fn cross_entropy_loss_old<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
input: &Variable<T>,
_target: &Variable<T>,
) -> Variable<T> {
let softmax_input = softmax_variable(input);
let _log_softmax = log_variable(&softmax_input);
let nll = input.clone();
nll.mean()
}
fn log_variable<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
var: &Variable<T>,
) -> Variable<T> {
var.clone() }
fn softmax_variable<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
var: &Variable<T>,
) -> Variable<T> {
var.clone() }
pub fn binary_cross_entropy<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
input: &Variable<T>,
_target: &Variable<T>,
) -> Variable<T> {
input.clone()
}
pub fn huber_loss<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
>(
input: &Variable<T>,
target: &Variable<T>,
_delta: T,
) -> Variable<T> {
let diff = input - target;
let squared_diff = &diff * &diff;
squared_diff.mean()
}
#[derive(Debug, Clone)]
pub struct FocalLoss<T: Float + Send + Sync + 'static> {
alpha: T,
gamma: T,
_phantom: std::marker::PhantomData<T>,
}
impl<T> FocalLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
pub fn new(alpha: Option<T>, gamma: Option<T>) -> Self {
let alpha = alpha.unwrap_or_else(|| <T as From<f32>>::from(1.0));
let gamma = gamma.unwrap_or_else(|| <T as From<f32>>::from(2.0));
Self {
alpha,
gamma,
_phantom: std::marker::PhantomData,
}
}
pub fn forward(&self, input: &Variable<T>, target: &Variable<T>) -> Variable<T> {
let ce_loss = cross_entropy_loss(input, target);
let weight_factor = self.alpha * self.gamma; let alpha_var = Variable::new(target.data().read().unwrap().map(|_| weight_factor), false);
&ce_loss * &alpha_var
}
}
impl<T> Loss<T> for FocalLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T> {
let ce_loss = cross_entropy_loss(predictions, targets);
let weight_factor = self.alpha * self.gamma;
let alpha_var = Variable::new(targets.data().read().unwrap().map(|_| weight_factor), false);
&ce_loss * &alpha_var
}
fn name(&self) -> &'static str {
"FocalLoss"
}
}
#[derive(Debug, Clone)]
pub struct TripletLoss<T: Float + Send + Sync + 'static> {
margin: T,
_phantom: std::marker::PhantomData<T>,
}
impl<T> TripletLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
pub fn new(margin: Option<T>) -> Self {
let margin = margin.unwrap_or_else(|| <T as From<f32>>::from(1.0));
Self {
margin,
_phantom: std::marker::PhantomData,
}
}
pub fn forward(
&self,
anchor: &Variable<T>,
positive: &Variable<T>,
negative: &Variable<T>,
) -> Variable<T> {
let ap_dist_sq = compute_squared_diff(anchor, positive).sum_dim(1);
let an_dist_sq = compute_squared_diff(anchor, negative).sum_dim(1);
let loss_raw = &ap_dist_sq - &an_dist_sq;
let margin_var = Variable::new(
ap_dist_sq.data().read().unwrap().map(|_| self.margin),
false,
);
let loss_with_margin = &loss_raw + &margin_var;
use crate::nn::safe_ops::SafeOps;
let clamped = SafeOps::relu(&loss_with_margin).unwrap_or(loss_with_margin);
clamped.mean()
}
}
#[derive(Debug, Clone)]
pub struct KLDivLoss<T: Float + Send + Sync + 'static> {
reduction: Reduction,
_phantom: std::marker::PhantomData<T>,
}
impl<T> KLDivLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
pub fn new(reduction: Option<String>) -> Self {
let reduction = Reduction::from(reduction);
Self {
reduction,
_phantom: std::marker::PhantomData,
}
}
pub fn forward(&self, input: &Variable<T>, target: &Variable<T>) -> Variable<T> {
let kl_terms = target * input;
apply_reduction(kl_terms, &self.reduction)
}
}
impl<T> Loss<T> for KLDivLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T> {
self.forward(predictions, targets)
}
fn name(&self) -> &'static str {
"KLDivLoss"
}
}
pub fn focal_loss<T>(
input: &Variable<T>,
target: &Variable<T>,
alpha: Option<T>,
gamma: Option<T>,
) -> Variable<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
let ce_loss = cross_entropy_loss(input, target);
let alpha_val = alpha.unwrap_or_else(|| <T as From<f32>>::from(1.0));
let gamma_val = gamma.unwrap_or_else(|| <T as From<f32>>::from(2.0));
let weight_factor = alpha_val * gamma_val;
let alpha_var = Variable::new(target.data().read().unwrap().map(|_| weight_factor), false);
&ce_loss * &alpha_var
}
pub fn triplet_loss<T>(
anchor: &Variable<T>,
positive: &Variable<T>,
negative: &Variable<T>,
margin: Option<T>,
) -> Variable<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
let triplet = TripletLoss::new(margin);
triplet.forward(anchor, positive, negative)
}
pub fn kl_div_loss<T>(
input: &Variable<T>,
target: &Variable<T>,
reduction: Option<String>,
) -> Variable<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
let kl_div = KLDivLoss::new(reduction);
kl_div.forward(input, target)
}
pub fn bce_with_logits_loss<T>(
input: &Variable<T>,
target: &Variable<T>,
weight: Option<Variable<T>>,
pos_weight: Option<Variable<T>>,
reduction: Option<String>,
) -> Variable<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
let bce_logits = BCEWithLogitsLoss::new(weight, pos_weight, reduction);
bce_logits.forward(input, target)
}
pub fn margin_ranking_loss<T>(
input1: &Variable<T>,
input2: &Variable<T>,
target: &Variable<T>,
margin: Option<T>,
reduction: Option<String>,
) -> Variable<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
let margin_loss = MarginRankingLoss::new(margin, reduction);
margin_loss.forward(input1, input2, target)
}
pub fn cosine_embedding_loss<T>(
input1: &Variable<T>,
input2: &Variable<T>,
target: &Variable<T>,
margin: Option<T>,
reduction: Option<String>,
) -> Variable<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
let cosine_loss = CosineEmbeddingLoss::new(margin, reduction);
cosine_loss.forward(input1, input2, target)
}
pub fn triplet_margin_loss<T>(
anchor: &Variable<T>,
positive: &Variable<T>,
negative: &Variable<T>,
margin: Option<T>,
p: Option<T>,
swap: Option<bool>,
reduction: Option<String>,
) -> Variable<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
let triplet_loss = TripletMarginLoss::new(margin, p, swap, reduction);
triplet_loss.forward(anchor, positive, negative)
}
#[derive(Debug, Clone)]
pub struct BCEWithLogitsLoss<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
> {
weight: Option<Variable<T>>,
pos_weight: Option<Variable<T>>,
reduction: Reduction,
_phantom: std::marker::PhantomData<T>,
}
impl<T> BCEWithLogitsLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
pub fn new(
weight: Option<Variable<T>>,
pos_weight: Option<Variable<T>>,
reduction: Option<String>,
) -> Self {
let reduction = Reduction::from(reduction);
Self {
weight,
pos_weight,
reduction,
_phantom: std::marker::PhantomData,
}
}
pub fn forward(&self, input: &Variable<T>, target: &Variable<T>) -> Variable<T> {
let sigmoid = sigmoid_variable(input);
let one_minus_target = one_minus_variable(target);
let log_sigmoid = log_sigmoid_variable(input);
let log_one_minus_sigmoid = log_one_minus_sigmoid_variable(input);
let pos_loss = target * &log_sigmoid;
let neg_loss = &one_minus_target * &log_one_minus_sigmoid;
let bce_loss = &pos_loss + &neg_loss;
let final_loss = Variable::new(
bce_loss.data().read().unwrap().map(|x| -x),
bce_loss.requires_grad(),
);
let weighted_loss = if let Some(pos_weight) = &self.pos_weight {
let weight_factor = target * pos_weight + &one_minus_target;
&final_loss * &weight_factor
} else {
final_loss
};
let final_weighted_loss = if let Some(weight) = &self.weight {
&weighted_loss * weight
} else {
weighted_loss
};
apply_reduction(final_weighted_loss, &self.reduction)
}
}
impl<T> Loss<T> for BCEWithLogitsLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T> {
self.forward(predictions, targets)
}
fn name(&self) -> &'static str {
"BCEWithLogitsLoss"
}
}
#[derive(Debug, Clone)]
pub struct MarginRankingLoss<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
> {
margin: T,
reduction: Reduction,
_phantom: std::marker::PhantomData<T>,
}
impl<T> MarginRankingLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
pub fn new(margin: Option<T>, reduction: Option<String>) -> Self {
let margin = margin.unwrap_or_else(|| <T as From<f32>>::from(0.0));
let reduction = Reduction::from(reduction);
Self {
margin,
reduction,
_phantom: std::marker::PhantomData,
}
}
pub fn forward(
&self,
input1: &Variable<T>,
input2: &Variable<T>,
target: &Variable<T>,
) -> Variable<T> {
let diff = input1 - input2;
let negative_target = Variable::new(
target.data().read().unwrap().map(|x| -x),
target.requires_grad(),
);
let weighted_diff = &negative_target * &diff;
let margin_var = Variable::new(diff.data().read().unwrap().map(|_| self.margin), false);
let loss_raw = &weighted_diff + &margin_var;
use crate::nn::safe_ops::SafeOps;
let clamped = SafeOps::relu(&loss_raw).unwrap_or(loss_raw);
apply_reduction(clamped, &self.reduction)
}
}
impl<T> Loss<T> for MarginRankingLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T> {
self.forward(predictions, predictions, targets)
}
fn name(&self) -> &'static str {
"MarginRankingLoss"
}
}
fn sigmoid_variable<T>(var: &Variable<T>) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let binding = var.data();
let data_guard = binding.read().unwrap();
let input_data = data_guard.as_array();
let output_data = input_data.map(|&x| {
if x >= T::zero() {
T::one() / (T::one() + (-x).exp())
} else {
let exp_x = x.exp();
exp_x / (T::one() + exp_x)
}
});
Variable::new(
crate::tensor::Tensor::from_ndarray(output_data),
var.requires_grad(),
)
}
fn one_minus_variable<T>(var: &Variable<T>) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let one = Variable::new(var.data().read().unwrap().map(|_| T::one()), false);
&one - var
}
fn log_sigmoid_variable<T>(var: &Variable<T>) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let binding = var.data();
let data_guard = binding.read().unwrap();
let input_data = data_guard.as_array();
let output_data = input_data.map(|&x| {
if x >= T::zero() {
-(T::one() + (-x).exp()).ln()
} else {
x - (T::one() + x.exp()).ln()
}
});
Variable::new(
crate::tensor::Tensor::from_ndarray(output_data),
var.requires_grad(),
)
}
fn log_one_minus_sigmoid_variable<T>(var: &Variable<T>) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let binding = var.data();
let data_guard = binding.read().unwrap();
let input_data = data_guard.as_array();
let output_data = input_data.map(|&x| {
if x >= T::zero() {
-x - (T::one() + (-x).exp()).ln()
} else {
-(T::one() + x.exp()).ln()
}
});
Variable::new(
crate::tensor::Tensor::from_ndarray(output_data),
var.requires_grad(),
)
}
#[derive(Debug, Clone)]
pub struct CosineEmbeddingLoss<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
> {
margin: T,
reduction: Reduction,
_phantom: std::marker::PhantomData<T>,
}
impl<T> CosineEmbeddingLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
pub fn new(margin: Option<T>, reduction: Option<String>) -> Self {
let margin = margin.unwrap_or_else(|| <T as From<f32>>::from(0.0));
let reduction = Reduction::from(reduction);
Self {
margin,
reduction,
_phantom: std::marker::PhantomData,
}
}
pub fn forward(
&self,
input1: &Variable<T>,
input2: &Variable<T>,
target: &Variable<T>,
) -> Variable<T> {
let dot_product = cosine_dot_product(input1, input2);
let norm1 = l2_norm(input1);
let norm2 = l2_norm(input2);
let norm_product = &norm1 * &norm2;
let cos_sim = &dot_product / &norm_product;
let one = Variable::new(target.data().read().unwrap().map(|_| T::one()), false);
let one_minus_cos = &one - &cos_sim;
let pos_loss = target * &one_minus_cos;
let margin_var = Variable::new(cos_sim.data().read().unwrap().map(|_| self.margin), false);
let cos_minus_margin = &cos_sim - &margin_var;
use crate::nn::safe_ops::SafeOps;
let clamped_neg_loss = SafeOps::relu(&cos_minus_margin).unwrap_or(cos_minus_margin);
let neg_target = Variable::new(
target.data().read().unwrap().map(|x| -x),
target.requires_grad(),
);
let neg_loss = &neg_target * &clamped_neg_loss;
let total_loss = &pos_loss + &neg_loss;
apply_reduction(total_loss, &self.reduction)
}
}
impl<T> Loss<T> for CosineEmbeddingLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T> {
self.forward(predictions, predictions, targets)
}
fn name(&self) -> &'static str {
"CosineEmbeddingLoss"
}
}
#[derive(Debug, Clone)]
pub struct TripletMarginLoss<
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
> {
margin: T,
p: T,
swap: bool,
reduction: Reduction,
_phantom: std::marker::PhantomData<T>,
}
impl<T> TripletMarginLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
pub fn new(
margin: Option<T>,
p: Option<T>,
swap: Option<bool>,
reduction: Option<String>,
) -> Self {
let margin = margin.unwrap_or_else(|| <T as From<f32>>::from(1.0));
let p = p.unwrap_or_else(|| <T as From<f32>>::from(2.0));
let swap = swap.unwrap_or(false);
let reduction = Reduction::from(reduction);
Self {
margin,
p,
swap,
reduction,
_phantom: std::marker::PhantomData,
}
}
pub fn forward(
&self,
anchor: &Variable<T>,
positive: &Variable<T>,
negative: &Variable<T>,
) -> Variable<T> {
let ap_dist = pnorm_distance(anchor, positive, self.p);
let an_dist = pnorm_distance(anchor, negative, self.p);
let final_an_dist = if self.swap {
let pn_dist = pnorm_distance(positive, negative, self.p);
elementwise_min(&an_dist, &pn_dist)
} else {
an_dist
};
let loss_raw = &ap_dist - &final_an_dist;
let margin_var = Variable::new(ap_dist.data().read().unwrap().map(|_| self.margin), false);
let loss_with_margin = &loss_raw + &margin_var;
use crate::nn::safe_ops::SafeOps;
let clamped = SafeOps::relu(&loss_with_margin).unwrap_or(loss_with_margin);
apply_reduction(clamped, &self.reduction)
}
}
impl<T> Loss<T> for TripletMarginLoss<T>
where
T: Float
+ Debug
+ Default
+ From<f32>
+ 'static
+ Send
+ Sync
+ Copy
+ ndarray::ScalarOperand
+ num_traits::FromPrimitive,
{
fn forward(&self, predictions: &Variable<T>, targets: &Variable<T>) -> Variable<T> {
self.forward(predictions, predictions, targets)
}
fn name(&self) -> &'static str {
"TripletMarginLoss"
}
}
fn compute_squared_diff<T>(input1: &Variable<T>, input2: &Variable<T>) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let diff = input1 - input2;
&diff * &diff
}
fn cosine_dot_product<T>(input1: &Variable<T>, input2: &Variable<T>) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let element_products = input1 * input2;
element_products.sum()
}
fn l2_norm<T>(input: &Variable<T>) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let squared = input * input;
let sum_squared = squared.sum();
let binding = sum_squared.data();
let data_guard = binding.read().unwrap();
let input_data = data_guard.as_array();
let output_data = input_data.map(|&x| x.sqrt());
Variable::new(
crate::tensor::Tensor::from_ndarray(output_data),
sum_squared.requires_grad(),
)
}
fn pnorm_distance<T>(input1: &Variable<T>, input2: &Variable<T>, p: T) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let diff = input1 - input2;
if p == T::from(2.0).unwrap() {
let squared_diff = &diff * &diff;
let sum_squared = squared_diff.sum();
let binding = sum_squared.data();
let data_guard = binding.read().unwrap();
let input_data = data_guard.as_array();
let output_data = input_data.map(|&x| x.sqrt());
Variable::new(
crate::tensor::Tensor::from_ndarray(output_data),
sum_squared.requires_grad(),
)
} else if p == T::from(1.0).unwrap() {
let binding = diff.data();
let abs_diff_data = binding.read().unwrap();
let abs_data = abs_diff_data.as_array().map(|&x| x.abs());
let abs_diff = Variable::new(
crate::tensor::Tensor::from_ndarray(abs_data),
diff.requires_grad(),
);
abs_diff.sum()
} else {
let squared_diff = &diff * &diff;
let sum_squared = squared_diff.sum();
let binding = sum_squared.data();
let data_guard = binding.read().unwrap();
let input_data = data_guard.as_array();
let output_data = input_data.map(|&x| x.sqrt());
Variable::new(
crate::tensor::Tensor::from_ndarray(output_data),
sum_squared.requires_grad(),
)
}
}
fn elementwise_min<T>(input1: &Variable<T>, input2: &Variable<T>) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
let min_data = match (input1.data().read(), input2.data().read()) {
(Ok(data1), Ok(data2)) => {
ndarray::Zip::from(data1.as_array())
.and(data2.as_array())
.map_collect(|&a, &b| if a < b { a } else { b })
}
_ => {
input1.data().read().unwrap().as_array().to_owned()
}
};
Variable::new(
crate::tensor::Tensor::from_ndarray(min_data),
input1.requires_grad() || input2.requires_grad(),
)
}
#[derive(Debug, Clone, PartialEq)]
pub enum Reduction {
None,
Mean,
Sum,
}
impl Default for Reduction {
fn default() -> Self {
Reduction::Mean
}
}
impl From<String> for Reduction {
fn from(s: String) -> Self {
match s.as_str() {
"mean" => Reduction::Mean,
"sum" => Reduction::Sum,
"none" => Reduction::None,
_ => Reduction::Mean,
}
}
}
impl From<Option<String>> for Reduction {
fn from(s: Option<String>) -> Self {
match s {
Some(s) => Reduction::from(s),
None => Reduction::default(),
}
}
}
fn apply_reduction<T>(input: Variable<T>, reduction: &Reduction) -> Variable<T>
where
T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive,
{
match reduction {
Reduction::Mean => input.mean(),
Reduction::Sum => input.sum(),
Reduction::None => input,
}
}
impl<T: Float + Send + Sync + 'static + ndarray::ScalarOperand + num_traits::FromPrimitive>
Variable<T>
{
pub fn mean(&self) -> Variable<T> {
self.mean_autograd()
}
pub fn sum_dim(&self, _dim: i32) -> Variable<T> {
self.sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tensor::Tensor;
use approx::assert_abs_diff_eq;
#[test]
fn test_mse_loss() {
let input = Variable::new(Tensor::from_vec(vec![1.0, 2.0, 3.0], vec![3]), false);
let target = Variable::new(Tensor::from_vec(vec![1.5, 2.5, 2.5], vec![3]), false);
let loss = mse_loss(&input, &target);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert_abs_diff_eq!(
loss_data.as_array().iter().next().unwrap(),
&0.25,
epsilon = 1e-6
);
}
#[test]
fn test_binary_cross_entropy() {
let input = Variable::new(Tensor::from_vec(vec![0.8, 0.2], vec![2]), false);
let target = Variable::new(Tensor::from_vec(vec![1.0, 0.0], vec![2]), false);
let loss = binary_cross_entropy(&input, &target);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().iter().next().unwrap() > &0.0);
}
#[test]
fn test_huber_loss() {
let input = Variable::new(Tensor::from_vec(vec![1.0, 2.0], vec![2]), false);
let target = Variable::new(Tensor::from_vec(vec![1.5, 1.5], vec![2]), false);
let loss = huber_loss(&input, &target, 1.0);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().iter().next().unwrap() > &0.0);
}
#[test]
fn test_mean_operation() {
let var = Variable::new(Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0], vec![4]), false);
let mean_var = var.mean();
let mean_binding = mean_var.data();
let mean_data = mean_binding.read().unwrap();
assert_abs_diff_eq!(
mean_data.as_array().iter().next().unwrap(),
&2.5,
epsilon = 1e-6
);
}
#[test]
fn test_subtraction_operator() {
let a = Variable::new(Tensor::from_vec(vec![3.0, 4.0], vec![2]), false);
let b = Variable::new(Tensor::from_vec(vec![1.0, 2.0], vec![2]), false);
let result = &a - &b;
let result_binding = result.data();
let result_data = result_binding.read().unwrap();
let expected = [2.0, 2.0];
for (actual, expected) in result_data.as_array().iter().zip(expected.iter()) {
assert_abs_diff_eq!(*actual, *expected, epsilon = 1e-6);
}
}
#[test]
fn test_mse_with_gradients() {
let input = Variable::new(Tensor::from_vec(vec![1.0, 2.0], vec![2]), true);
let target = Variable::new(Tensor::from_vec(vec![1.5, 1.5], vec![2]), false);
let loss = mse_loss(&input, &target);
assert!(loss.requires_grad());
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().iter().next().unwrap() > &0.0);
}
#[test]
fn test_triplet_loss() {
let anchor = Variable::new(Tensor::from_vec(vec![1.0, 0.0], vec![2]), false);
let positive = Variable::new(Tensor::from_vec(vec![1.1, 0.1], vec![2]), false);
let negative = Variable::new(Tensor::from_vec(vec![0.0, 1.0], vec![2]), false);
let loss = triplet_loss(&anchor, &positive, &negative, Some(0.5));
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().iter().next().unwrap() >= &0.0);
}
#[test]
fn test_kl_div_loss() {
let input = Variable::new(
Tensor::from_vec(vec![-1.0, -2.0], vec![2]), false,
);
let target = Variable::new(
Tensor::from_vec(vec![0.6, 0.4], vec![2]), false,
);
let loss = kl_div_loss(&input, &target, Some("mean".to_string()));
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().len() > 0);
}
#[test]
fn test_kl_div_loss_struct() {
let kl_div: KLDivLoss<f32> = KLDivLoss::new(Some("sum".to_string()));
assert_eq!(kl_div.name(), "KLDivLoss");
let input = Variable::new(Tensor::from_vec(vec![-0.5, -1.5], vec![2]), false);
let target = Variable::new(Tensor::from_vec(vec![0.7, 0.3], vec![2]), false);
let loss = kl_div.forward(&input, &target);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().len() > 0);
}
#[test]
fn test_bce_with_logits_loss() {
let input = Variable::new(Tensor::from_vec(vec![0.5, -0.5, 1.0], vec![3]), false);
let target = Variable::new(Tensor::from_vec(vec![1.0, 0.0, 1.0], vec![3]), false);
let loss = bce_with_logits_loss(&input, &target, None, None, Some("mean".to_string()));
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().len() > 0);
}
#[test]
fn test_margin_ranking_loss() {
let input1 = Variable::new(Tensor::from_vec(vec![1.0, 2.0], vec![2]), false);
let input2 = Variable::new(Tensor::from_vec(vec![0.5, 1.5], vec![2]), false);
let target = Variable::new(Tensor::from_vec(vec![1.0, -1.0], vec![2]), false);
let loss = margin_ranking_loss(
&input1,
&input2,
&target,
Some(0.5),
Some("mean".to_string()),
);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().iter().next().unwrap() >= &0.0);
}
#[test]
fn test_cosine_embedding_loss() {
let input1 = Variable::new(Tensor::from_vec(vec![1.0, 0.0], vec![2]), false);
let input2 = Variable::new(Tensor::from_vec(vec![0.0, 1.0], vec![2]), false);
let target = Variable::new(Tensor::from_vec(vec![1.0], vec![1]), false);
let loss = cosine_embedding_loss(
&input1,
&input2,
&target,
Some(0.1),
Some("mean".to_string()),
);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().len() > 0);
}
#[test]
fn test_triplet_margin_loss() {
let anchor = Variable::new(Tensor::from_vec(vec![1.0, 0.0], vec![2]), false);
let positive = Variable::new(Tensor::from_vec(vec![1.1, 0.1], vec![2]), false);
let negative = Variable::new(Tensor::from_vec(vec![0.0, 1.0], vec![2]), false);
let loss = triplet_margin_loss(
&anchor,
&positive,
&negative,
Some(0.5),
Some(2.0),
Some(false),
Some("mean".to_string()),
);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().iter().next().unwrap() >= &0.0);
}
#[test]
fn test_bce_with_logits_loss_struct() {
let bce: BCEWithLogitsLoss<f32> =
BCEWithLogitsLoss::new(None, None, Some("sum".to_string()));
assert_eq!(bce.name(), "BCEWithLogitsLoss");
let input = Variable::new(Tensor::from_vec(vec![0.2, -0.8], vec![2]), false);
let target = Variable::new(Tensor::from_vec(vec![0.0, 1.0], vec![2]), false);
let loss = bce.forward(&input, &target);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().len() > 0);
}
#[test]
fn test_margin_ranking_loss_struct() {
let margin_loss: MarginRankingLoss<f32> =
MarginRankingLoss::new(Some(1.0), Some("none".to_string()));
assert_eq!(margin_loss.name(), "MarginRankingLoss");
let input1 = Variable::new(Tensor::from_vec(vec![2.0, 1.0], vec![2]), false);
let input2 = Variable::new(Tensor::from_vec(vec![1.0, 2.0], vec![2]), false);
let target = Variable::new(Tensor::from_vec(vec![-1.0, 1.0], vec![2]), false);
let loss = margin_loss.forward(&input1, &input2, &target);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().len() > 0);
}
#[test]
fn test_cosine_embedding_loss_struct() {
let cosine_loss: CosineEmbeddingLoss<f32> =
CosineEmbeddingLoss::new(Some(0.0), Some("mean".to_string()));
assert_eq!(cosine_loss.name(), "CosineEmbeddingLoss");
let input1 = Variable::new(Tensor::from_vec(vec![1.0, 1.0], vec![2]), false);
let input2 = Variable::new(Tensor::from_vec(vec![1.0, 1.0], vec![2]), false);
let target = Variable::new(Tensor::from_vec(vec![1.0], vec![1]), false);
let loss = cosine_loss.forward(&input1, &input2, &target);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().len() > 0);
}
#[test]
fn test_triplet_margin_loss_struct() {
let triplet_loss: TripletMarginLoss<f32> =
TripletMarginLoss::new(Some(1.0), Some(2.0), Some(true), Some("sum".to_string()));
assert_eq!(triplet_loss.name(), "TripletMarginLoss");
let anchor = Variable::new(Tensor::from_vec(vec![0.5, 0.5], vec![2]), false);
let positive = Variable::new(Tensor::from_vec(vec![0.6, 0.4], vec![2]), false);
let negative = Variable::new(Tensor::from_vec(vec![0.1, 0.9], vec![2]), false);
let loss = triplet_loss.forward(&anchor, &positive, &negative);
let loss_binding = loss.data();
let loss_data = loss_binding.read().unwrap();
assert!(loss_data.as_array().len() > 0);
}
}