use candle_core::{Result, Tensor};
use candle_nn::VarBuilder;
use crate::candle::loss::approx_lgamma;
pub trait BipartiteLikelihood {
fn compute_llik(&self, scores: &Tensor, a_dn: &Tensor) -> Result<Tensor>;
}
pub struct BlockModelMultinomial;
impl BipartiteLikelihood for BlockModelMultinomial {
fn compute_llik(&self, scores: &Tensor, a_dn: &Tensor) -> Result<Tensor> {
let flat = scores.flatten_all()?;
let log_p = candle_nn::ops::log_softmax(&flat, 0)?;
let flat_a = a_dn.flatten_all()?;
(flat_a * log_p)?.sum_all()
}
}
pub struct SymmetricMultinomial;
impl BipartiteLikelihood for SymmetricMultinomial {
fn compute_llik(&self, scores: &Tensor, a_dn: &Tensor) -> Result<Tensor> {
let log_p_col = candle_nn::ops::log_softmax(scores, 0)?;
let llik_col = (a_dn * &log_p_col)?.sum_all()?;
let log_p_row = candle_nn::ops::log_softmax(scores, 1)?;
let llik_row = (a_dn * &log_p_row)?.sum_all()?;
&llik_col + &llik_row
}
}
pub struct PoissonLikelihood;
impl BipartiteLikelihood for PoissonLikelihood {
fn compute_llik(&self, scores: &Tensor, a_dn: &Tensor) -> Result<Tensor> {
let s = scores.clamp(-20.0, 20.0)?;
(a_dn * &s - s.exp()?)?.sum_all()
}
}
pub struct GaussianLikelihood;
impl BipartiteLikelihood for GaussianLikelihood {
fn compute_llik(&self, scores: &Tensor, a_dn: &Tensor) -> Result<Tensor> {
(a_dn - scores)?.powf(2.0)?.sum_all()? * (-0.5)
}
}
pub struct NbLikelihood {
pub log_phi: Tensor,
}
impl BipartiteLikelihood for NbLikelihood {
fn compute_llik(&self, scores: &Tensor, a_dn: &Tensor) -> Result<Tensor> {
let log_mu = scores.clamp(-20.0, 20.0)?;
let log_phi = self.log_phi.clamp(-10.0, 10.0)?;
let mu = log_mu.exp()?;
let phi = log_phi.exp()?;
let phi_plus_mu = phi.broadcast_add(&mu)?;
let log_phi_plus_mu = phi_plus_mu.log()?;
let term_phi = phi.broadcast_mul(&log_phi.broadcast_sub(&log_phi_plus_mu)?)?;
let term_x = a_dn.mul(&log_mu.broadcast_sub(&log_phi_plus_mu)?)?;
let x_plus_phi = a_dn.broadcast_add(&phi)?;
let lgamma_term = approx_lgamma(&x_plus_phi)?
.broadcast_sub(&approx_lgamma(&phi)?)?
.sub(&approx_lgamma(&(a_dn + 1.0)?)?)?;
(lgamma_term + term_phi + term_x)?.sum_all()
}
}
pub struct BipartiteDecoder {
relation: Tensor,
diagonal: bool,
feature_bias: Tensor,
cell_bias: Tensor,
}
impl BipartiteDecoder {
fn make_biases(n_features: usize, n_cells: usize, vb: &VarBuilder) -> Result<(Tensor, Tensor)> {
let feature_bias =
vb.get_with_hints((1, n_features), "feature_bias", candle_nn::Init::Const(0.0))?;
let cell_bias =
vb.get_with_hints((1, n_cells), "cell_bias", candle_nn::Init::Const(0.0))?;
Ok((feature_bias, cell_bias))
}
pub fn new_diagonal(
n_topics: usize,
n_features: usize,
n_cells: usize,
vb: VarBuilder,
) -> Result<Self> {
let relation = vb.get_with_hints(n_topics, "relation", candle_nn::Init::Const(1.0))?;
let (feature_bias, cell_bias) = Self::make_biases(n_features, n_cells, &vb)?;
Ok(Self {
relation,
diagonal: true,
feature_bias,
cell_bias,
})
}
pub fn new_full(
n_topics: usize,
n_features: usize,
n_cells: usize,
vb: VarBuilder,
) -> Result<Self> {
let relation = vb.get_with_hints(
(n_topics, n_topics),
"relation",
candle_nn::Init::Const(0.0),
)?;
let (feature_bias, cell_bias) = Self::make_biases(n_features, n_cells, &vb)?;
Ok(Self {
relation,
diagonal: false,
feature_bias,
cell_bias,
})
}
pub fn forward_scores(&self, log_z_f: &Tensor, log_z_c: &Tensor) -> Result<Tensor> {
let z_f = log_z_f.exp()?; let z_c = log_z_c.exp()?;
let scores = if self.diagonal {
let z_f_scaled = z_f.broadcast_mul(&self.relation)?; z_f_scaled.matmul(&z_c.t()?)? } else {
let z_f_r = z_f.matmul(&self.relation)?; z_f_r.matmul(&z_c.t()?)? };
let scores = scores
.broadcast_add(&self.feature_bias.t()?)? .broadcast_add(&self.cell_bias)?;
Ok(scores)
}
pub fn forward_llik<L: BipartiteLikelihood + ?Sized>(
&self,
log_z_f: &Tensor,
log_z_c: &Tensor,
a_dn: &Tensor,
likelihood: &L,
) -> Result<Tensor> {
let scores = self.forward_scores(log_z_f, log_z_c)?;
likelihood.compute_llik(&scores, a_dn)
}
pub fn relation(&self) -> &Tensor {
&self.relation
}
pub fn is_diagonal(&self) -> bool {
self.diagonal
}
}