hessboost 0.2.2

Fast, deterministic gradient boosting (GBDT) in Rust: conformal intervals, explainable boosting machines, distributional boosting, tree-based diffusion, and XGBoost model interchange
Documentation
//! Bring-your-own loss and metric: the custom-objective and custom-metric hooks.
//! Run: `cargo run --release --example custom_objective`.

use hessboost::metric::{CustomMetric, EvalMetric};
use hessboost::objective::{CustomLoss, GradPair};
use hessboost::prelude::*;
use std::num::NonZeroUsize;

mod common;
use common::{fill_random, lcg};

fn main() -> Result<()> {
    let (n, f) = (800usize, 3usize);
    let mut rng = lcg(5);
    let mut x = vec![0f32; n * f];
    let mut y = vec![0f32; n];
    fill_random(&mut rng, &mut x);
    for i in 0..n {
        y[i] = 1.5 * x[i * f] - x[i * f + 1];
    }
    let d = DMatrix::from_dense(&x, n, f)?.with_labels(&y)?;

    // --- Custom objective: squared error via first/second-order gradients. ---
    // Signature: (raw_margins, labels, optional_weights, out_gradients).
    let obj = CustomLoss::new("my:squarederror", 1, |preds, labels, w, out| {
        for i in 0..preds.len() {
            let wi = w.map_or(1.0, |ws| ws[i]);
            out[i] = GradPair::new((preds[i] - labels[i]) * wi, wi); // grad, hess
        }
    });
    let params = TrainingParams::builder()
        .objective(Objective::custom(obj))
        .max_depth(3)
        .eta(0.2)
        .build()?;
    let model = Trainer::new(&params, &d, 60).train()?.model;
    let preds = model.predict(&d, Iterations::Best)?;
    let rmse = EvalMetric::Rmse.build(1)?.eval(preds.as_slice(), &y, None);
    println!("custom-objective RMSE: {rmse:.4}");

    // --- Custom metric: mean absolute error, used for early stopping. ---
    // Signature: (predictions, labels, optional_weights) -> f64; `maximize=false`.
    let mae = CustomMetric::new("my:mae", false, |p, l, _w| {
        p.iter()
            .zip(l)
            .map(|(a, b)| (f64::from(*a) - f64::from(*b)).abs())
            .sum::<f64>()
            / p.len() as f64
    });
    let builtin = TrainingParams::builder()
        .objective(Objective::SquaredError(RegLoss::default()))
        .max_depth(3)
        .eta(0.2)
        .build()?;
    let out = Trainer::new(&builtin, &d, 100)
        .eval(&d, "train")
        .early_stopping_rounds(NonZeroUsize::new(10).unwrap())
        .custom_metric(Box::new(mae))
        .train()?;
    println!(
        "custom-metric run: {} trees, last MAE = {:.4}",
        out.model.num_trees(),
        out.history.last().unwrap().values().last().unwrap()
    );
    Ok(())
}