Pure-Rust gradient-boosted trees with quantile regression. No BLAS, no C++ bindings, no Python.
The first Rust GBT crate with pinball loss for probabilistic prediction intervals.
- Quantile regression — train P10, P25, P50, P75, P90 models with pinball loss
- Early stopping — monitors validation loss, stops when overfitting
- JSON serialization — save and load models as human-readable JSON
- Quantile ensemble — train multiple quantile models in one call with monotonicity enforcement
- Evaluation metrics — MAE, RMSE, R², MAPE, pinball loss
- High Performance — global histogram thresholding and Rayon multi-threading
- Zero dependencies beyond serde and rayon — no BLAS, LAPACK, or C++ libraries
- Schema versioned — forward-compatible model format
use gbt_quantile::{GBTConfig, GradientBoostedTree, QuantileEnsemble};
use gbt_quantile::trainer;
// Prepare data: x = feature matrix (row-major), y = targets
let x = vec![vec![1.0], vec![2.0], vec![3.0], vec![4.0], vec![5.0]];
let y = vec![2.1, 4.0, 6.1, 7.9, 10.0];
// Train a single model (L2 loss)
let config = GBTConfig::default();
let model = trainer::train(&x, &y, &config, None);
let prediction = model.predict(&[3.5]);
// Train a quantile ensemble (P10-P90)
let ensemble = QuantileEnsemble::train(&x, &y, &[0.1, 0.5, 0.9], &config, None);
let intervals = ensemble.predict(&[3.5]);
println!("P10={:.2}, P50={:.2}, P90={:.2}",
intervals.lower(), intervals.median(), intervals.upper());
// Save and load
let json = model.to_json().unwrap();
let restored = GradientBoostedTree::from_json(&json).unwrap();Train separate models for different quantiles using pinball loss:
let config = GBTConfig {
quantile: Some(0.1), // P10 — conservative lower bound
..Default::default()
};
let p10_model = trainer::train(&x, &y, &config, None);
let config = GBTConfig {
quantile: Some(0.9), // P90 — optimistic upper bound
..Default::default()
};
let p90_model = trainer::train(&x, &y, &config, None);Or use QuantileEnsemble to train all quantiles at once with automatic monotonicity enforcement (P10 <= P25 <= ... <= P90).
Pass validation data to prevent overfitting:
let model = trainer::train_with_validation(
&x_train, &y_train,
&x_val, &y_val,
&config,
None,
);
// Model automatically stops when validation loss plateaus
println!("Trained {} trees (max was {})", model.n_trees(), config.n_trees);use gbt_quantile::metrics;
let preds = model.predict_batch(&x_test);
let m = metrics::evaluate(&y_test, &preds).unwrap();
println!("MAE={:.3}, RMSE={:.3}, R²={:.3}", m.mae, m.rmse, m.r2);
let loss = metrics::pinball_loss(&y_test, &preds, 0.9).unwrap();
println!("Pinball loss (q=0.9): {:.4}", loss);- Split finding: Percentile-based thresholds (configurable bins per feature)
- Gain: Variance reduction
- Quantile loss: Pinball gradient —
if error >= 0 { q } else { q - 1 } - Early stopping: Validation loss checked every 5 rounds
- Base score: mean(y) for L2, quantile(y, q) for pinball
MIT OR Apache-2.0
