diff --git a/Cargo.toml b/Cargo.toml index da67ab4a5..e12d6fa2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,9 +42,9 @@ rand = { version = "0.8", features = ["small_rng"] } approx = "0.5" getrandom = { version = "0.2", optional = true, features = ["js"] } -ndarray = { version = "0.16", features = ["approx"] } -ndarray-linalg = { version = "0.17", optional = true } -sprs = { version = "=0.11.2", default-features = false } +ndarray = { version = "0.17", features = ["approx"] } +ndarray-linalg = { version = "0.18", optional = true } +sprs = { version = "0.11.4", default-features = false } thiserror = "2.0" @@ -58,7 +58,7 @@ default-features = false features = ["std", "derive"] [dev-dependencies] -ndarray-rand = "0.15" +ndarray-rand = "0.16" linfa-datasets = { path = "datasets", features = [ "winequality", "iris", diff --git a/algorithms/linfa-bayes/Cargo.toml b/algorithms/linfa-bayes/Cargo.toml index 2f21d29e3..f48f54eac 100644 --- a/algorithms/linfa-bayes/Cargo.toml +++ b/algorithms/linfa-bayes/Cargo.toml @@ -21,8 +21,8 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16", features = ["approx"] } -ndarray-stats = "0.6" +ndarray = { version = "0.17", features = ["approx"] } +ndarray-stats = "0.7" thiserror = "2.0" linfa = { version = "0.8.1", path = "../.." } diff --git a/algorithms/linfa-clustering/Cargo.toml b/algorithms/linfa-clustering/Cargo.toml index 4b034789f..6d67308c3 100644 --- a/algorithms/linfa-clustering/Cargo.toml +++ b/algorithms/linfa-clustering/Cargo.toml @@ -35,21 +35,21 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16", features = ["rayon", "approx"] } +ndarray = { version = "0.17", features = ["rayon", "approx"] } linfa-linalg = { version = "0.2", default-features = false } -ndarray-linalg = { version = "0.17", optional = true } -ndarray-rand = "0.15" -ndarray-stats = "0.6" +ndarray-linalg = { version = "0.18", optional = true } +ndarray-rand = "0.16" +ndarray-stats = "0.7" num-traits = "0.2" -rand_xoshiro = "0.6" +rand_xoshiro = "0.8" space = "0.19" thiserror = "2.0" linfa = { version = "0.8.1", path = "../.." } linfa-nn = { version = "0.8.1", path = "../linfa-nn" } -noisy_float = "0.2.0" +noisy_float = "0.2.1" [dev-dependencies] -ndarray-npy = { version = "0.9", default-features = false } +ndarray-npy = { version = "0.10", default-features = false } linfa-datasets = { version = "0.8.1", path = "../../datasets", features = [ "generate", ] } diff --git a/algorithms/linfa-elasticnet/Cargo.toml b/algorithms/linfa-elasticnet/Cargo.toml index dbbf2de68..24e092722 100644 --- a/algorithms/linfa-elasticnet/Cargo.toml +++ b/algorithms/linfa-elasticnet/Cargo.toml @@ -29,9 +29,9 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16", features = ["approx"] } +ndarray = { version = "0.17", features = ["approx"] } linfa-linalg = { version = "0.2", default-features = false } -ndarray-linalg = { version = "0.17", optional = true } +ndarray-linalg = { version = "0.18", optional = true } num-traits = "0.2" approx = "0.5" @@ -44,5 +44,5 @@ linfa-datasets = { version = "0.8.1", path = "../../datasets", features = [ "diabetes", "linnerud", ] } -ndarray-rand = "0.15" +ndarray-rand = "0.16" rand_xoshiro = "0.6" diff --git a/algorithms/linfa-ensemble/Cargo.toml b/algorithms/linfa-ensemble/Cargo.toml index a8e1ffe9c..307918732 100644 --- a/algorithms/linfa-ensemble/Cargo.toml +++ b/algorithms/linfa-ensemble/Cargo.toml @@ -27,8 +27,8 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16", features = ["rayon", "approx"] } -ndarray-rand = "0.15" +ndarray = { version = "0.17", features = ["rayon", "approx"] } +ndarray-rand = "0.16" rand = "0.8.5" linfa = { version = "0.8.1", path = "../.." } diff --git a/algorithms/linfa-ftrl/Cargo.toml b/algorithms/linfa-ftrl/Cargo.toml index 8c2f52abf..711455cf8 100644 --- a/algorithms/linfa-ftrl/Cargo.toml +++ b/algorithms/linfa-ftrl/Cargo.toml @@ -24,8 +24,8 @@ version = "1.0" features = ["derive"] [dependencies] -ndarray = { version = "0.16", features = ["serde"] } -ndarray-rand = "0.15.0" +ndarray = { version = "0.17", features = ["serde"] } +ndarray-rand = "0.16.0" argmin = { version = "0.11.0", default-features = false } argmin-math = { version = "0.5", features = ["ndarray_v0_16-nolinalg"] } thiserror = "2.0" diff --git a/algorithms/linfa-hierarchical/Cargo.toml b/algorithms/linfa-hierarchical/Cargo.toml index a63a2cec6..16c9c0df4 100644 --- a/algorithms/linfa-hierarchical/Cargo.toml +++ b/algorithms/linfa-hierarchical/Cargo.toml @@ -20,7 +20,7 @@ keywords = [ categories = ["algorithms", "mathematics", "science"] [dependencies] -ndarray = { version = "0.16" } +ndarray = { version = "0.17" } kodama = "0.2" thiserror = "2" @@ -29,7 +29,7 @@ linfa-kernel = { version = "0.8.1", path = "../linfa-kernel" } [dev-dependencies] rand = "0.8" -ndarray-rand = "0.15" +ndarray-rand = "0.16" linfa-datasets = { version = "0.8.1", path = "../../datasets", features = [ "iris", ] } diff --git a/algorithms/linfa-ica/Cargo.toml b/algorithms/linfa-ica/Cargo.toml index fe377600b..64d4eae11 100644 --- a/algorithms/linfa-ica/Cargo.toml +++ b/algorithms/linfa-ica/Cargo.toml @@ -25,11 +25,11 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16" } +ndarray = { version = "0.17" } linfa-linalg = { version = "0.2", default-features = false } -ndarray-linalg = { version = "0.17", optional = true } -ndarray-rand = "0.15" -ndarray-stats = "0.6" +ndarray-linalg = { version = "0.18", optional = true } +ndarray-rand = "0.16" +ndarray-stats = "0.7" num-traits = "0.2" rand_xoshiro = "0.6" thiserror = "2.0" @@ -37,7 +37,7 @@ thiserror = "2.0" linfa = { version = "0.8.1", path = "../.." } [dev-dependencies] -ndarray-npy = { version = "0.9", default-features = false } +ndarray-npy = { version = "0.10", default-features = false } paste = "1.0" criterion = "0.5" linfa = { version = "0.8.1", path = "../..", features = ["benchmarks"] } diff --git a/algorithms/linfa-kernel/Cargo.toml b/algorithms/linfa-kernel/Cargo.toml index 7d038b387..21697ec17 100644 --- a/algorithms/linfa-kernel/Cargo.toml +++ b/algorithms/linfa-kernel/Cargo.toml @@ -24,9 +24,9 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = "0.16" +ndarray = "0.17" num-traits = "0.2" -sprs = { version = "=0.11.2", default-features = false } +sprs = { version = "0.11.4", default-features = false } linfa = { version = "0.8.1", path = "../.." } linfa-nn = { version = "0.8.1", path = "../linfa-nn" } diff --git a/algorithms/linfa-lars/Cargo.toml b/algorithms/linfa-lars/Cargo.toml index 0bd0dcbaa..b6b19d7c4 100644 --- a/algorithms/linfa-lars/Cargo.toml +++ b/algorithms/linfa-lars/Cargo.toml @@ -28,9 +28,9 @@ features = ["std", "derive"] linfa = { version = "0.8.1", path = "../.." } linfa-linalg = { version = "0.2", default-features = false } linfa-preprocessing = { version = "0.8.1", path = "../linfa-preprocessing"} -ndarray = { version = "0.16", features = ["approx"] } -ndarray-linalg = { version = "0.17", optional = true } -ndarray-stats = "0.6" +ndarray = { version = "0.17", features = ["approx"] } +ndarray-linalg = { version = "0.18", optional = true } +ndarray-stats = "0.7" thiserror = "2.0" [dev-dependencies] @@ -38,5 +38,5 @@ linfa-datasets = { version = "0.8.1", path = "../../datasets", features = [ "diabetes", ] } approx = "0.5" -ndarray-rand = "0.15" +ndarray-rand = "0.16" rand_xoshiro = "0.6" diff --git a/algorithms/linfa-linear/Cargo.toml b/algorithms/linfa-linear/Cargo.toml index 99eeb5e50..e60fa50b1 100644 --- a/algorithms/linfa-linear/Cargo.toml +++ b/algorithms/linfa-linear/Cargo.toml @@ -29,9 +29,9 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16", features = ["approx"] } +ndarray = { version = "0.17", features = ["approx"] } linfa-linalg = { version = "0.2", default-features = false } -ndarray-linalg = { version = "0.17", optional = true } +ndarray-linalg = { version = "0.18", optional = true } num-traits = "0.2" argmin = { version = "0.11.0", default-features = false } argmin-math = { version = "0.5", features = ["ndarray_v0_16-nolinalg"] } diff --git a/algorithms/linfa-logistic/Cargo.toml b/algorithms/linfa-logistic/Cargo.toml index c771385ac..8efcc9ed7 100644 --- a/algorithms/linfa-logistic/Cargo.toml +++ b/algorithms/linfa-logistic/Cargo.toml @@ -23,8 +23,8 @@ optional = true version = "1.0" [dependencies] -ndarray = { version = "0.16", features = ["approx"] } -ndarray-stats = "0.6.0" +ndarray = { version = "0.17", features = ["approx"] } +ndarray-stats = "0.7.0" num-traits = "0.2" argmin = { version = "0.11.0", default-features = false } argmin-math = { version = "0.5", features = ["ndarray_v0_16-nolinalg"] } diff --git a/algorithms/linfa-nn/Cargo.toml b/algorithms/linfa-nn/Cargo.toml index 76b730a56..b07b1da74 100644 --- a/algorithms/linfa-nn/Cargo.toml +++ b/algorithms/linfa-nn/Cargo.toml @@ -24,9 +24,9 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16", features = ["approx"] } -ndarray-stats = "0.6" -num-traits = "0.2.0" +ndarray = { version = "0.17", features = ["approx"] } +ndarray-stats = "0.7" +num-traits = "0.2.19" noisy_float = "0.2.0" order-stat = "0.1.3" thiserror = "2.0" @@ -39,7 +39,7 @@ linfa = { version = "0.8.1", path = "../.." } approx = "0.5" criterion = "0.5" rand_xoshiro = "0.6" -ndarray-rand = "0.15" +ndarray-rand = "0.16" linfa = { version = "0.8.1", path = "../..", features = ["benchmarks"] } [[bench]] diff --git a/algorithms/linfa-pls/Cargo.toml b/algorithms/linfa-pls/Cargo.toml index 3ec23f59d..c94e95c0b 100644 --- a/algorithms/linfa-pls/Cargo.toml +++ b/algorithms/linfa-pls/Cargo.toml @@ -25,11 +25,11 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16" } +ndarray = { version = "0.17" } linfa-linalg = { version = "0.2", default-features = false } -ndarray-linalg = { version = "0.17", optional = true } -ndarray-stats = "0.6" -ndarray-rand = "0.15" +ndarray-linalg = { version = "0.18", optional = true } +ndarray-stats = "0.7" +ndarray-rand = "0.16" num-traits = "0.2" paste = "1.0" thiserror = "2.0" diff --git a/algorithms/linfa-preprocessing/Cargo.toml b/algorithms/linfa-preprocessing/Cargo.toml index 9161d0b41..e456e3a27 100644 --- a/algorithms/linfa-preprocessing/Cargo.toml +++ b/algorithms/linfa-preprocessing/Cargo.toml @@ -19,17 +19,17 @@ serde = ["serde_crate", "ndarray/serde", "serde_regex"] [dependencies] linfa = { version = "0.8.1", path = "../.." } -ndarray = { version = "0.16", features = ["approx"] } -ndarray-linalg = { version = "0.17", optional = true } +ndarray = { version = "0.17", features = ["approx"] } +ndarray-linalg = { version = "0.18", optional = true } linfa-linalg = { version = "0.2", default-features = false } -ndarray-stats = "0.6" +ndarray-stats = "0.7" thiserror = "2.0" approx = "0.5" -ndarray-rand = "0.15" +ndarray-rand = "0.16" unicode-normalization = "0.1.8" regex = "1.4.5" encoding = "0.2" -sprs = { version = "=0.11.2", default-features = false } +sprs = { version = "0.11.4", default-features = false } serde_regex = { version = "1.1", optional = true } itertools = "0.14.0" diff --git a/algorithms/linfa-reduction/Cargo.toml b/algorithms/linfa-reduction/Cargo.toml index 962c87885..760412b53 100644 --- a/algorithms/linfa-reduction/Cargo.toml +++ b/algorithms/linfa-reduction/Cargo.toml @@ -34,21 +34,21 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16", features = ["approx"] } +ndarray = { version = "0.17", features = ["approx"] } linfa-linalg = { version = "0.2" } -ndarray-linalg = { version = "0.17", optional = true } -ndarray-rand = "0.15" +ndarray-linalg = { version = "0.18", optional = true } +ndarray-rand = "0.16" num-traits = "0.2" thiserror = "2.0" rand = { version = "0.8", features = ["small_rng"] } linfa = { version = "0.8.1", path = "../.." } linfa-kernel = { version = "0.8.1", path = "../linfa-kernel" } -sprs = { version = "=0.11.2" } +sprs = { version = "0.11.4" } rand_xoshiro = "0.6.0" [dev-dependencies] -ndarray-npy = { version = "0.9", default-features = false } +ndarray-npy = { version = "0.10", default-features = false } linfa-datasets = { version = "0.8.1", path = "../../datasets", features = [ "iris", "generate", diff --git a/algorithms/linfa-svm/Cargo.toml b/algorithms/linfa-svm/Cargo.toml index 58ae69616..872c8bd79 100644 --- a/algorithms/linfa-svm/Cargo.toml +++ b/algorithms/linfa-svm/Cargo.toml @@ -24,8 +24,8 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16" } -ndarray-rand = "0.15" +ndarray = { version = "0.17" } +ndarray-rand = "0.16" num-traits = "0.2" thiserror = "2.0" diff --git a/algorithms/linfa-trees/Cargo.toml b/algorithms/linfa-trees/Cargo.toml index c14f0c54f..65cebdac6 100644 --- a/algorithms/linfa-trees/Cargo.toml +++ b/algorithms/linfa-trees/Cargo.toml @@ -24,8 +24,8 @@ default-features = false features = ["std", "derive"] [dependencies] -ndarray = { version = "0.16", features = ["rayon", "approx"] } -ndarray-rand = "0.15" +ndarray = { version = "0.17", features = ["rayon", "approx"] } +ndarray-rand = "0.16" linfa = { version = "0.8.1", path = "../.." } diff --git a/algorithms/linfa-tsne/Cargo.toml b/algorithms/linfa-tsne/Cargo.toml index 1cf57e622..faf1ed80e 100644 --- a/algorithms/linfa-tsne/Cargo.toml +++ b/algorithms/linfa-tsne/Cargo.toml @@ -15,7 +15,7 @@ categories = ["algorithms", "mathematics", "science"] [dependencies] thiserror = "2.0" -ndarray = { version = "0.16" } +ndarray = { version = "0.17" } bhtsne = { version = "0.5.4", default-features = false } linfa = { version = "0.8.1", path = "../.." } @@ -23,7 +23,7 @@ linfa-nn = { version = "0.8.1", path = "../linfa-nn" } [dev-dependencies] rand = "0.8" -ndarray-rand = "0.15" +ndarray-rand = "0.16" approx = "0.5" linfa-datasets = { version = "0.8.1", path = "../../datasets", features = [ diff --git a/datasets/Cargo.toml b/datasets/Cargo.toml index 2cfe0bac0..93d8dd69e 100644 --- a/datasets/Cargo.toml +++ b/datasets/Cargo.toml @@ -9,11 +9,11 @@ repository = "https://github.com/rust-ml/linfa" [dependencies] linfa = { version = "0.8.1", path = ".." } -ndarray = { version = "0.16" } -ndarray-csv = "=0.5.3" +ndarray = { version = "0.17" } +ndarray-csv = "0.5.4" csv = "1.1" flate2 = "1.0" -ndarray-rand = { version = "0.15", optional = true } +ndarray-rand = { version = "0.16", optional = true } [dev-dependencies] approx = "0.5" diff --git a/src/composing/residual_chain.rs b/src/composing/residual_chain.rs index 3713703d1..21122673e 100644 --- a/src/composing/residual_chain.rs +++ b/src/composing/residual_chain.rs @@ -118,7 +118,6 @@ //! ``` use crate::dataset::{AsTargets, DatasetBase, Records}; -use crate::param_guard::ParamGuard; use crate::traits::{Fit, Predict, PredictInplace}; use crate::Float; use ndarray::{Array1, Array2, ArrayBase, Data, Ix1, Ix2, RawDataClone}; @@ -263,45 +262,6 @@ impl Stagewise for B { } } -impl + RawDataClone, T, E1, E2> - Fit, T, ResidualChainError> for ResidualChain -where - Arr2: Records, - F1: Fit, T, E1>, - for<'a> F1::Object: Predict<&'a Arr2, Array1>, - F2: Fit, Array1, E2>, - T: AsTargets, - E1: std::error::Error + From, - E2: std::error::Error + From, -{ - type Object = ResidualChain; - - fn fit( - &self, - dataset: &DatasetBase, T>, - ) -> Result> { - let base = self.base.fit(dataset).map_err(ResidualChainError::Base)?; - - let y_pred = base.predict(dataset.records()); - let residuals = &dataset.targets().as_targets() - &y_pred; - - let residual_dataset = DatasetBase::new(dataset.records().clone(), residuals); - let corrector_model = self - .corrector - .model - .fit(&residual_dataset) - .map_err(ResidualChainError::Corrector)?; - - Ok(ResidualChain { - base, - corrector: Shrunk { - model: corrector_model, - shrinkage: self.corrector.shrinkage, - }, - }) - } -} - impl> PredictInplace, Array1> for ResidualChain where @@ -349,8 +309,8 @@ impl Shrunk { pub fn shrinkage(&self) -> F { self.shrinkage } - /// Set the shrinkage factor. Validation happens when the containing - /// [`ResidualChainParams`] is checked via [`ParamGuard`]. + /// Set the shrinkage factor. Validation (shrinkage ∈ (0, 1]) happens + /// at `.fit()` time on the containing [`ResidualChainParams`]. pub fn with_shrinkage(mut self, shrinkage: F) -> Self { self.shrinkage = shrinkage; self @@ -359,11 +319,10 @@ impl Shrunk { /// Unvalidated [`ResidualChain`] parameters returned by [`Stagewise::chain_shrunk`]. /// -/// Call `.fit()` to validate and fit in one step — the [`ParamGuard`] blanket -/// impl runs `check_ref` first, which verifies that the outermost corrector's -/// shrinkage factor is in (0, 1]. Inner chains validate lazily when their own -/// `.fit()` is called. You can also call `.check()` / `.check_unwrap()` to -/// validate explicitly. +/// Call `.fit()` to validate and fit in one step — `.fit()` validates that the +/// outermost corrector's shrinkage factor is in (0, 1] before fitting. Inner +/// chains validate lazily when their own `.fit()` is called. You can also call +/// `.check()` / `.check_unwrap()` to validate the shrinkage factor explicitly. /// /// To set an explicit shrinkage factor on the corrector use /// [`Shrunk::with_shrinkage`]: @@ -393,11 +352,11 @@ impl Shrunk { #[derive(Debug, Clone, Copy)] pub struct ResidualChainParams(ResidualChain); -impl ParamGuard for ResidualChainParams { - type Checked = ResidualChain; - type Error = crate::Error; - - fn check_ref(&self) -> Result<&ResidualChain, crate::Error> { +impl ResidualChainParams { + /// Validate the shrinkage factor and return a reference to the inner + /// [`ResidualChain`]. Returns an error if the shrinkage factor is not + /// in (0, 1]. + pub fn check_ref(&self) -> Result<&ResidualChain, crate::Error> { let v = self.0.corrector.shrinkage; if v > F::zero() && v <= F::one() { Ok(&self.0) @@ -408,10 +367,65 @@ impl ParamGuard for ResidualChainParams { } } - fn check(self) -> Result, crate::Error> { + /// Validate the shrinkage factor and return the inner [`ResidualChain`]. + /// Returns an error if the shrinkage factor is not in (0, 1]. + pub fn check(self) -> Result, crate::Error> { self.check_ref()?; Ok(self.0) } + + /// Validate the shrinkage factor, panicking on error. + pub fn check_unwrap(self) -> ResidualChain { + self.check().unwrap() + } +} + +impl + RawDataClone, T, E1, E2> + Fit, T, ResidualChainError> for ResidualChainParams +where + Arr2: Records, + F1: Fit, T, E1, Object = M1>, + for<'a> M1: Predict<&'a Arr2, Array1>, + F2: Fit, Array1, E2, Object = M2>, + T: AsTargets, + E1: std::error::Error + From, + E2: std::error::Error + From, +{ + type Object = ResidualChain; + + fn fit( + &self, + dataset: &DatasetBase, T>, + ) -> Result> { + // Validate shrinkage factor before fitting. + let v = self.0.corrector.shrinkage; + if !(v > F::zero() && v <= F::one()) { + return Err(ResidualChainError::BaseCrate(crate::Error::Parameters( + format!("shrinkage must be in (0, 1], got {v}"), + ))); + } + + let base = self.0.base.fit(dataset).map_err(ResidualChainError::Base)?; + + let y_pred = base.predict(dataset.records()); + let residuals = (&dataset.targets().as_targets() - &y_pred).to_owned(); + + let residual_dataset = DatasetBase::new(dataset.records().clone(), residuals); + let corrector_model = self + .0 + .corrector + .model + .fit(&residual_dataset) + .map_err(ResidualChainError::Corrector)?; + + Ok(ResidualChain { + base, + corrector: Shrunk { + model: corrector_model, + shrinkage: self.0.corrector.shrinkage, + }, + }) + } } #[cfg(test)]