use nuvec::{Array, DType, fast, ops};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Reduction {
None,
Mean,
Sum,
}
#[track_caller]
pub fn cross_entropy(logits: &Array, targets: &Array, reduction: Reduction) -> Array {
let valid = logits.ndim() > 0
&& targets.shape() == &logits.shape()[..logits.ndim() - 1]
&& is_index_dtype(targets.dtype());
let loss = if !valid || nuvec::default_device().kind == nuvec::DeviceKind::Gpu {
fast::cross_entropy(logits, targets).astype(logits.dtype())
} else {
let classes = logits.shape()[logits.ndim() - 1];
let z = logits.subtract(logits.max_keepdims(-1).stop_gradient());
let positions = ops::arange(0, classes, 1).astype(targets.dtype());
let hit = positions.equal(targets.expand_dims(-1));
let score = hit.select(&z, 0.0).sum(-1);
z.logsumexp(-1).subtract(&score)
};
match reduction {
Reduction::None => loss,
Reduction::Mean => loss.mean(..),
Reduction::Sum => loss.sum(..),
}
}
fn is_index_dtype(d: DType) -> bool {
!matches!(
d,
DType::F16 | DType::BF16 | DType::F32 | DType::F64 | DType::Complex64
)
}