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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use ndarray::{Array1, Array2};
use crate::{Error, Result};
/// Neighbor weighting used during k-NN inference.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum KnnWeight {
/// Every selected neighbor receives equal weight.
#[default]
Uniform,
/// Neighbors are weighted by inverse Euclidean distance.
Distance,
}
/// Fitted k-nearest-neighbor regression state.
#[derive(Clone, Debug, PartialEq)]
pub struct KnnRegressor {
/// Training samples shaped `[samples, features]`.
pub samples: Array2<f64>,
/// Target associated with every training sample.
pub targets: Array1<f64>,
/// Number of neighbors.
pub k: usize,
/// Neighbor weighting strategy.
pub weight: KnnWeight,
}
impl KnnRegressor {
/// Creates validated k-NN regression state.
pub fn new(
samples: Array2<f64>,
targets: Array1<f64>,
k: usize,
weight: KnnWeight,
) -> Result<Self> {
validate(&samples, targets.len(), k)?;
if targets.iter().any(|value| !value.is_finite()) {
return Err(Error::InvalidModel("k-NN targets must be finite".into()));
}
Ok(Self {
samples,
targets,
k,
weight,
})
}
}
/// Fitted k-nearest-neighbor classification state.
#[derive(Clone, Debug, PartialEq)]
pub struct KnnClassifier {
/// Training samples shaped `[samples, features]`.
pub samples: Array2<f64>,
/// Zero-based class index associated with every sample.
pub target_indices: Vec<i64>,
/// External integer label for every class index.
pub class_labels: Vec<i64>,
/// Number of neighbors.
pub k: usize,
/// Neighbor weighting strategy.
pub weight: KnnWeight,
}
impl KnnClassifier {
/// Creates validated k-NN classification state.
pub fn new(
samples: Array2<f64>,
target_indices: Vec<i64>,
class_labels: Vec<i64>,
k: usize,
weight: KnnWeight,
) -> Result<Self> {
validate(&samples, target_indices.len(), k)?;
if class_labels.is_empty()
|| target_indices
.iter()
.any(|&index| index < 0 || index as usize >= class_labels.len())
{
return Err(Error::InvalidModel("invalid k-NN class indices".into()));
}
Ok(Self {
samples,
target_indices,
class_labels,
k,
weight,
})
}
}
fn validate(samples: &Array2<f64>, targets: usize, k: usize) -> Result<()> {
if samples.nrows() == 0
|| samples.ncols() == 0
|| targets != samples.nrows()
|| k == 0
|| k > samples.nrows()
|| samples.iter().any(|value| !value.is_finite())
{
return Err(Error::InvalidModel("invalid k-NN fitted state".into()));
}
Ok(())
}