use crate::error::RillError;
use crate::traits::{Metric, OnlineBinaryClassifier, OnlineRegressor};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RegressionSample {
pub features: Vec<f64>,
pub target: f64,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BinaryClassificationSample {
pub features: Vec<f64>,
pub target: bool,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ProgressiveStep {
pub index: usize,
pub metric_value: Option<f64>,
}
pub fn evaluate_regression<M, Met>(
model: &mut M,
metric: &mut Met,
samples: impl IntoIterator<Item = RegressionSample>,
) -> Result<Option<f64>, RillError>
where
M: OnlineRegressor,
Met: Metric<Truth = f64, Prediction = f64>,
{
evaluate_regression_with_steps(model, metric, samples).map(|(v, _)| v)
}
pub fn evaluate_regression_with_steps<M, Met>(
model: &mut M,
metric: &mut Met,
samples: impl IntoIterator<Item = RegressionSample>,
) -> Result<(Option<f64>, Vec<ProgressiveStep>), RillError>
where
M: OnlineRegressor,
Met: Metric<Truth = f64, Prediction = f64>,
{
let mut steps = Vec::new();
for (i, sample) in samples.into_iter().enumerate() {
let prediction = model.predict(&sample.features)?;
metric.update(sample.target, prediction)?;
model.learn(&sample.features, sample.target)?;
steps.push(ProgressiveStep {
index: i,
metric_value: metric.value(),
});
}
Ok((metric.value(), steps))
}
pub fn evaluate_binary_classification<M, Met>(
model: &mut M,
metric: &mut Met,
samples: impl IntoIterator<Item = BinaryClassificationSample>,
) -> Result<Option<f64>, RillError>
where
M: OnlineBinaryClassifier,
Met: Metric<Truth = bool, Prediction = bool>,
{
let mut _steps = Vec::new();
for (i, sample) in samples.into_iter().enumerate() {
let prediction = model.predict(&sample.features)?;
metric.update(sample.target, prediction)?;
model.learn(&sample.features, sample.target)?;
_steps.push(ProgressiveStep {
index: i,
metric_value: metric.value(),
});
}
Ok(metric.value())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metrics::Mae;
use crate::models::MeanRegressor;
#[test]
fn progressive_evaluates_before_learning() {
let mut model = MeanRegressor::default();
let mut mae = Mae::default();
let samples = vec![
RegressionSample {
features: vec![],
target: 10.0,
},
RegressionSample {
features: vec![],
target: 20.0,
},
RegressionSample {
features: vec![],
target: 30.0,
},
];
let (final_mae, steps) =
evaluate_regression_with_steps(&mut model, &mut mae, samples).unwrap();
assert!((final_mae.unwrap() - 35.0 / 3.0).abs() < 1e-9);
assert_eq!(steps.len(), 3);
}
}