use burn::tensor::{Int, Tensor, activation::log_softmax, backend::Backend};
pub use crate::train::ppo::loss::scalar_f64;
pub fn compute_bc_loss<B: Backend>(
logits: Tensor<B, 2>,
actions: Tensor<B, 1, Int>,
) -> Tensor<B, 1> {
let [batch, _action_dim] = logits.dims();
let log_probs = log_softmax(logits, 1);
let action_index = actions.reshape([batch, 1]);
let chosen = log_probs.gather(1, action_index);
chosen.reshape([batch]).mean().neg()
}
#[cfg(test)]
mod tests {
use burn::{
backend::{Autodiff, NdArray},
tensor::TensorData,
};
use super::*;
type B = Autodiff<NdArray<f32>>;
fn logits2d(data: &[f32], rows: usize, cols: usize) -> Tensor<B, 2> {
let device = Default::default();
Tensor::<B, 2>::from_data(TensorData::new(data.to_vec(), [rows, cols]), &device)
}
fn actions1d(data: &[i64]) -> Tensor<B, 1, Int> {
let device = Default::default();
Tensor::<B, 1, Int>::from_data(TensorData::new(data.to_vec(), [data.len()]), &device)
}
#[test]
fn bc_loss_is_finite_scalar() {
let logits = logits2d(&[0.1, 0.2, 0.3, -0.5, 0.0, 0.5], 2, 3);
let actions = actions1d(&[2, 0]);
let loss = compute_bc_loss(logits, actions);
assert_eq!(loss.dims(), [1]);
assert!(scalar_f64(loss).is_finite());
}
#[test]
fn bc_loss_is_nonnegative() {
let logits = logits2d(&[1.0, 2.0, -1.0, 0.0, 3.0, 0.5], 2, 3);
let actions = actions1d(&[1, 1]);
let loss_val = scalar_f64(compute_bc_loss(logits, actions));
assert!(loss_val >= 0.0, "cross-entropy loss must be non-negative, got {loss_val}");
}
#[test]
fn bc_loss_decreases_when_logits_match_labels() {
let actions = actions1d(&[0, 0]);
let flat = scalar_f64(compute_bc_loss(logits2d(&[0.0; 6], 2, 3), actions.clone()));
let confident =
scalar_f64(compute_bc_loss(logits2d(&[5.0, 0.0, 0.0, 5.0, 0.0, 0.0], 2, 3), actions));
assert!(
confident < flat,
"logits favoring the expert action should lower loss: {confident} !< {flat}"
);
}
#[test]
fn bc_loss_approaches_zero_when_perfect() {
let logits = logits2d(&[20.0, 0.0, 0.0, 0.0, 20.0, 0.0], 2, 3);
let actions = actions1d(&[0, 1]);
let loss_val = scalar_f64(compute_bc_loss(logits, actions));
assert!(loss_val < 1e-3, "confident-correct logits should give ~0 loss, got {loss_val}");
}
}