1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
use ndarray::{Array1, Array2};
use crate::{Error, Result};
/// A classifier selecting the largest affine class score.
///
/// This captures multinomial and Bernoulli Naive Bayes after their fitted
/// log-probabilities are algebraically reduced to `X * coefficients + bias`.
#[derive(Clone, Debug, PartialEq)]
pub struct LinearScoreClassifier {
/// Score coefficients shaped `[features, classes]`.
pub coefficients: Array2<f64>,
/// One score offset per class.
pub bias: Array1<f64>,
/// Integer label corresponding to each score column.
pub class_labels: Vec<i64>,
/// Optional strict threshold used to binarize every input feature.
pub binarize: Option<f64>,
}
impl LinearScoreClassifier {
/// Creates a validated score classifier.
///
/// # Errors
///
/// Returns an error for inconsistent, empty, duplicate-label, or
/// non-finite parameters.
pub fn new(
coefficients: Array2<f64>,
bias: Array1<f64>,
class_labels: Vec<i64>,
binarize: Option<f64>,
) -> Result<Self> {
let classes = coefficients.ncols();
let mut unique = class_labels.clone();
unique.sort_unstable();
unique.dedup();
if coefficients.nrows() == 0
|| classes == 0
|| bias.len() != classes
|| class_labels.len() != classes
|| unique.len() != classes
|| coefficients
.iter()
.chain(bias.iter())
.any(|value| !value.is_finite())
|| binarize.is_some_and(|value| !value.is_finite())
{
return Err(Error::InvalidModel(
"invalid linear score classifier".into(),
));
}
Ok(Self {
coefficients,
bias,
class_labels,
binarize,
})
}
}