use std::io::Cursor;
use ndarray::{Array1, Array2};
use tract_onnx::prelude::*;
use crate::{Error, Result};
#[derive(Clone, Debug, PartialEq)]
pub struct ValidationReport {
pub max_absolute_difference: f64,
pub mean_absolute_difference: f64,
pub tolerance: f64,
pub passed: bool,
}
#[must_use]
pub fn compare_predictions(original: &[f64], exported: &[f32], tolerance: f64) -> ValidationReport {
if original.len() != exported.len() || original.is_empty() || tolerance < 0.0 {
return ValidationReport {
max_absolute_difference: f64::INFINITY,
mean_absolute_difference: f64::INFINITY,
tolerance,
passed: false,
};
}
let differences: Vec<_> = original
.iter()
.zip(exported)
.map(|(&left, &right)| (left - f64::from(right)).abs())
.collect();
let finite = differences.iter().all(|value| value.is_finite());
let max = differences.iter().copied().fold(0.0_f64, f64::max);
let mean = differences.iter().sum::<f64>() / differences.len() as f64;
ValidationReport {
max_absolute_difference: max,
mean_absolute_difference: mean,
tolerance,
passed: finite && max <= tolerance,
}
}
pub fn validate_export(
onnx_bytes: &[u8],
test_inputs: &Array2<f64>,
original_predict_fn: impl Fn(&Array2<f64>) -> Array1<f64>,
tolerance: f64,
) -> Result<ValidationReport> {
let mut reader = Cursor::new(onnx_bytes);
let runnable = tract_onnx::onnx()
.model_for_read(&mut reader)
.and_then(|model| model.into_optimized())
.and_then(|model| model.into_runnable())
.map_err(|error| Error::Validation(error.to_string()))?;
let input_values: Vec<f32> = test_inputs.iter().map(|&value| value as f32).collect();
let input = Tensor::from_shape(test_inputs.shape(), &input_values)
.map_err(|error| Error::Validation(error.to_string()))?;
let outputs = runnable
.run(tvec!(input.into()))
.map_err(|error| Error::Validation(error.to_string()))?;
let exported = outputs[0]
.to_plain_array_view::<f32>()
.map_err(|error| Error::Validation(error.to_string()))?;
let exported: Vec<f32> = exported.iter().copied().collect();
let original = original_predict_fn(test_inputs);
Ok(compare_predictions(
original.as_slice().unwrap_or(&[]),
&exported,
tolerance,
))
}