pub fn shapelet_classifier_fit(
data: &FdMatrix,
labels: &[usize],
config: &ShapeletClassifierConfig,
) -> Result<ShapeletClassifierFit, FdarError>Expand description
Fit a bundled shapelet-transform classifier: discover shapelets, transform the
training curves to an n×K distance-feature matrix, and train an inner fdars
classifier (k-NN default, LDA optional) on those features.
The returned ShapeletClassifierFit stores the discovered shapelets and the
fitted inner model; reuse them on new curves via
ShapeletClassifierFit::predict.
data is a column-major FdMatrix with rows = curves, columns = evaluation
points; labels[i] is the integer class of curve i.
The inner classifier’s FPCA ncomp is resolved from config.ncomp as
ncomp.unwrap_or(K).min(K).min(n-1).max(1) where K is the number of
discovered shapelets — full rank by default, so the inner FPCA is an
information-preserving rotation of the raw shapelet-distance features. See the
module docs for the divergence note (sktime uses RotationForest; fdars reuses
k-NN / LDA).
§Errors
- Any error from shapelet discovery/transform (e.g. label/row mismatch, fewer than 2 classes, a series shorter than a discovered shapelet).
- Any error from the inner classifier fit.
§Examples
use fdars_core::matrix::FdMatrix;
use fdars_core::{shapelet_classifier_fit, ShapeletClassifierConfig, ShapeletDiscoveryConfig};
// Build a 2-class dataset: class 1 carries a triangular motif class 0 lacks.
fn make(n: usize, m: usize) -> (FdMatrix, Vec<usize>) {
let mut flat = vec![0.0f64; n * m];
let mut labels = vec![0usize; n];
let (start, len) = (m / 2, (m / 4).max(1));
for i in 0..n {
let class1 = i % 2 == 1;
labels[i] = usize::from(class1);
for j in 0..m {
let hash = (i.wrapping_mul(2654435761) ^ j.wrapping_mul(40503)) % 211;
let mut v = 0.01 * (i as f64) + (j as f64) * 0.001 + 0.05 * (hash as f64 / 211.0 - 0.5);
if class1 && j >= start && j < start + len {
let k = j - start;
let half = len / 2;
v += if k <= half { k as f64 } else { (len - k) as f64 };
}
flat[i + j * n] = v;
}
}
(FdMatrix::from_column_major(flat, n, m).unwrap(), labels)
}
// TRAIN/TEST discipline: discover on train only, evaluate on held-out test.
let (train, train_y) = make(24, 24);
let (test, test_y) = make(12, 24);
let cfg = ShapeletClassifierConfig {
discovery: ShapeletDiscoveryConfig { min_length: 3, max_length: 6, max_shapelets: 4, ..Default::default() },
..Default::default()
};
let fit = shapelet_classifier_fit(&train, &train_y, &cfg).unwrap();
let preds = fit.predict(&test).unwrap();
let correct = preds.iter().zip(&test_y).filter(|(p, t)| p == t).count();
let acc = correct as f64 / test_y.len() as f64;
assert!(acc > 0.5, "held-out accuracy {acc} should beat chance");