1use nuvec::{
4 Array, DType, Error, Result, arange, expand_dims, logsumexp, max, mean, stop_gradient,
5 subtract, sum, where_,
6};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Reduction {
11 None,
13 Mean,
15 Sum,
17}
18
19pub fn cross_entropy(logits: &Array, targets: &Array, reduction: Reduction) -> Result<Array> {
24 let classes = *logits
25 .shape()
26 .last()
27 .ok_or_else(|| Error::InvalidArgument("cross_entropy: logits need a class axis".into()))?;
28 if targets.shape() != &logits.shape()[..logits.ndim() - 1] {
29 return Err(Error::InvalidArgument(format!(
30 "cross_entropy: targets shape {:?} does not match logits shape {:?}",
31 targets.shape(),
32 logits.shape()
33 )));
34 }
35 if !is_index_dtype(targets.dtype()) {
36 return Err(Error::InvalidArgument(format!(
37 "cross_entropy: targets must be integer class indices, got {}",
38 targets.dtype()
39 )));
40 }
41 let loss = if nuvec::default_device().kind == nuvec::DeviceKind::Gpu {
42 nuvec::fast::cross_entropy(logits, targets)?.astype(logits.dtype())?
44 } else {
45 let z = subtract(logits, stop_gradient(&max(logits, &[-1], true)?)?)?;
46 let positions = arange(0.0, classes as f64, 1.0, targets.dtype())?;
47 let hit = nuvec::equal(&positions, expand_dims(targets, -1)?)?;
48 let score = sum(&where_(&hit, &z, 0.0)?, &[-1], false)?;
49 subtract(logsumexp(&z, &[-1], false)?, &score)?
50 };
51 match reduction {
52 Reduction::None => Ok(loss),
53 Reduction::Mean => mean(&loss, &[], false),
54 Reduction::Sum => sum(&loss, &[], false),
55 }
56}
57
58fn is_index_dtype(d: DType) -> bool {
60 !matches!(
61 d,
62 DType::F16 | DType::BF16 | DType::F32 | DType::F64 | DType::Complex64
63 )
64}