use candle_core::{Result, Tensor};
use crate::candle::sgvb::BlackBoxLikelihood;
pub struct WeightedGaussianLikelihood {
y: Tensor,
inv_2var: Tensor,
half_log_2pi_var: Tensor,
}
impl WeightedGaussianLikelihood {
pub fn new(y: Tensor, variance: &Tensor) -> Result<Self> {
let ln_2pi = (2.0 * std::f64::consts::PI).ln();
let inv_2var = (variance * 2.0)?.recip()?.unsqueeze(0)?; let half_log_2pi_var = ((variance.log()? + ln_2pi)? * 0.5)?.unsqueeze(0)?; Ok(Self {
y,
inv_2var,
half_log_2pi_var,
})
}
}
impl BlackBoxLikelihood for WeightedGaussianLikelihood {
fn log_likelihood(&self, etas: &[&Tensor]) -> Result<Tensor> {
let eta = etas[0]; let diff_sq = eta.broadcast_sub(&self.y)?.sqr()?; let scaled = diff_sq.broadcast_mul(&self.inv_2var)?; let log_prob = scaled.neg()?.broadcast_sub(&self.half_log_2pi_var)?; log_prob.sum(2)?.sum(1) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::candle::sgvb::FixedGaussianLikelihood;
use candle_core::Device;
#[test]
fn test_weighted_matches_fixed_uniform_variance() -> Result<()> {
let device = Device::Cpu;
let y = Tensor::from_vec(vec![0.0f32, 1.0, 2.0], (3, 1), &device)?;
let eta = Tensor::from_vec(vec![0.0f32, 1.0, 2.0], (1, 3, 1), &device)?;
let variance = Tensor::ones((3, 1), candle_core::DType::F32, &device)?;
let weighted = WeightedGaussianLikelihood::new(y.clone(), &variance)?;
let fixed = FixedGaussianLikelihood::new(y, 1.0);
let ll_weighted = weighted.log_likelihood(&[&eta])?;
let ll_fixed = fixed.log_likelihood(&[&eta])?;
let w_val: f32 = ll_weighted.get(0)?.to_scalar()?;
let f_val: f32 = ll_fixed.get(0)?.to_scalar()?;
assert!(
(w_val - f_val).abs() < 1e-4,
"Weighted and fixed should match with uniform variance: weighted={}, fixed={}",
w_val,
f_val,
);
Ok(())
}
#[test]
fn test_weighted_high_variance_downweights() -> Result<()> {
let device = Device::Cpu;
let y = Tensor::from_vec(vec![1.0f32, 1.0], (2, 1), &device)?;
let eta = Tensor::from_vec(vec![5.0f32, 5.0], (1, 2, 1), &device)?;
let var_low = Tensor::from_vec(vec![0.1f32, 0.1], (2, 1), &device)?;
let var_mixed = Tensor::from_vec(vec![0.1f32, 1e6], (2, 1), &device)?;
let ll_low = WeightedGaussianLikelihood::new(y.clone(), &var_low)?;
let ll_mixed = WeightedGaussianLikelihood::new(y, &var_mixed)?;
let val_low: f32 = ll_low.log_likelihood(&[&eta])?.get(0)?.to_scalar()?;
let val_mixed: f32 = ll_mixed.log_likelihood(&[&eta])?.get(0)?.to_scalar()?;
assert!(
val_mixed > val_low,
"High variance obs should be less penalized: mixed={}, low={}",
val_mixed,
val_low,
);
Ok(())
}
}