Releases: pebeto/MichiBoost.jl
Releases · pebeto/MichiBoost.jl
Release list
v0.6.0
MichiBoost v0.6.0
New features
Regression losses
- Six parametrized losses, passed as instances to
loss_function:Huber(δ)— squared error withinδ, linear past it; robust to outliersQuantile(α)— fits theα-quantile for prediction intervals or asymmetric over/under penaltiesExpectile(α)— same asymmetry asQuantilebut with squared error for a smooth fitMAPE()— penalizes relative error; targets must be non-zeroTweedie(p)— non-negative, right-skewed targets with a spike at zero; predicts on a log scaleLogLinQuantile(α)— quantiles of positive targets that span orders of magnitude
TweedieandLogLinQuantilefit on a log scale;predictexponentiates back to the target scale
Continuing training
fit!(model, X, y; init_model=base)resumes from a fitted model, inherits its trees, and addsiterationsmore- Reuses the inherited model's feature borders and categorical encoder;
border_countis ignored wheninit_modelis set - Task and feature counts must match, or
fit!errors before training starts - Works with
early_stopping_rounds: the metric runs over the full ensemble, andbest_iterationcounts from the inherited model's first tree
Training snapshots
snapshot_pathwrites the partial model to disk via JLD2 everysnapshot_intervaliterations and once after the loop exits- Each write overwrites the previous file; the final snapshot matches the model
fit!returns, including early-stopping truncation - Resume a crashed run by loading the snapshot with
load_modeland passing it asinit_model
Other changes
- Tag-valued kwargs (
loss_function,boosting_type,eval_metric,auto_class_weights) now accept aSymbol(:RMSE) alongside tag types (Losses.RMSE) and strings ("RMSE") - Documentation cleanup across the guide and API reference
Internal changes
- Reworked the train loop and histogram code
- Multiclass histograms defer per-class totals to a post-fill summation
No breaking changes.
Closed issues:
v0.5.0
MichiBoost v0.5.0
New features
Model evaluation and selection
score(model, X, y)— coefficient of determination for regressors, accuracy for classifiers- Stratified k-fold cross-validation via `cv(pool; stratified=true, ...)
eval_metrickwarg drives early stopping with a configurable metric (AUC, F1, Accuracy, R^2, Logloss, MultiLogloss, MAE, RMSE)get_best_iteration(model)/get_best_score(model)expose the early-stopping winner- `eval_metrics(model, pool; metrics =[...]) evaluates one or more metrics at every boosting iteration
staged_predict(model, X)/staged_predict_proba(model, X)return per-iteration predictions for convergence diagnostic
Class weighting
class_weights=Dict(label => weight, ...)onMichiBoostClassifierauto_class_weights="Balanced"or"SqrtBalanced"computes weights from label frequencies automatically
Ensemble post-processing
shrink!(model, n)truncates the tree ensemble in place (useful for rolling back toget_best_iteration).
Model introspection and config
isfitted(model)predicateget_params(model)returns a copy of the hyperparameter dict;set_params!(model; kwargs...)updates them
Custom loss functions
- Subtype
MichiBoost.LossFunctionand implementgradient_hessian!,initial_prediction,loss - Optional traits
task-type(:regression,:binary,:multiclass) andlink_inverse(maps raw scores to probabilities forpredict_proba) - Wrapper validates the task matches the model kind; custom losses round-trip through
save_model/load_model
Compile-time-checked enums
- New sobmodules
Metrics.*,Losses.*,BoostingTypes.*,AutoClassWeights.*, andPredictionTypes.*provide singleton tag types (e.g.loss_function=Losses.RMSE,eval_metric=Metrics.AUC). CatBoost-style strings ("RMSE","AUC") are still accepted
Breaking changes
- Model serialization switched to JLD2 — models saved with v0.4.0's
Serializationbackend (.jlsfiles) will not load. Re-save with the new backend;.jld2is now the conventional extension
Internal changes
- Codebase reformatted with JuliaFormatter / BlueStyle
Closed issues:
v0.4.0
MichiBoost v0.4.0
New features
- Documentation site:
Documenter.jlbuilt docs deployed - Benchmark suite implemented:
benchmark/README.mddocuments the four-axis methodology (correctness, speed sweep, scaling on UCI Covertype, per-feature costs) and reports head-to-head numbers againstCatBoost.jl
Performance improvements
- Multiclass histogram axis flip:
HistCacheMCgradient/hessian arrays reshaped to(n_classes, max_leaves, n_bins), so thefor c in 1:n_classesinner loop sweeps stride-1 memory. Per-class accumulators inSplitBuffersMCflipped to match - B-invariant precomputation in
_sweep_gain_mc:total_g^2/(total_h + λ)depends only on(class, leaf)and is now computed once intobuf.total_scoreper call instead of recomputed for every bin index. Savings scale withn_bins * n_leaves * n_classes - Multiclass prefetch path removed:
_fill_num_leaf_mc!and_fill_cat_leaf_mc!now do single-pass direct reads fromgradients/hessiansinto the histograms :dynamicscheduling on multiclass feature loops:_find_best_split_across_leaves_mcnow uses:dynamic, smoothing thread imbalance from variable per-feature bin countsSplitBuffersMCfootprint reduced: dropped fields related to prefetch
Breaking changes
HistCacheMCarray shapes changed:num_hist_g,num_hist_h,cat_hist_g,cat_hist_hare nowArray{Float64,3}of shape(n_classes, max_leaves, n_bins)SplitBuffersMCfield set changed:local_gradients_mc,local_hessians_mc,local_bins,local_cat_valuesremovedtotal_score::Matrix{Float64}of shape(n_classes, max_leaves)added- per-class fields (
total_g,total_h,left_g,left_h,parent_hist_g_scratch,parent_hist_h_scratch) now haven_classesas their first axis
Closed issues:
- Setup Documentation (#3)
v0.3.0
MichiBoost v0.3.0
New features
- Benchmark suite split by topic: the single
benchmark_vs_catboost.jlis replaced by four per-area scripts:correctness.jl(held-out metric gates),speed_sweep.jl(n*p size grid + categorical cardinality + inference batch sweep),scaling.jl(Covertype real dataset +--sweepthreading curve), andfeature_costs.jl(CV, early stopping, SHAP, RSM, sample weights, save/load). A new diagnosticprofile.jlis provided, and a dedicatedbenchmark/README.mdwith methodology and results.- Correctness benchmark uses task metrics instead of agreement: regression is gated on out-of-sample RMSE/MAE/R^2, classification on log-loss/AUC/accuracy, each asserted within a fixed tolerance of CatBoost, replacing the previous correlation/class-agreement gates which could pass while model quality was poor.
Performance improvements
- Histogram subtraction trick: when building children, only the smaller child's histogram is built from scratch; the larger is derived as
parent − smaller. Roughly halves histogram-building work. - Parallelised tree construction:
_apply_split!(sample partitioning across child leaves) and the final leaf-value reduction loop now run one leaf per thread, writing to disjoint slices of the output. - Incremental early-stopping evaluation:
_evaluate_lossno longer walks the full tree ensemble on every ES check — a runningeval_predsbuffer is extended with just the newly-built tree each iteration. Reduces ES cost from O(T^2) to O(T) over a training run. - Row-chunk parallel inference:
_predict_rawrewritten to parallelise over row ranges instead of trees, eliminating the per-threadn * n_classespartials buffer and the serial final reduction. Each thread walks all trees against its own row slice, writing directly into a disjoint view ofpreds. - Parallel
apply_borders: quantisation now parallelises over features with a function-barrier helper, with a small-input fallback so tiny inference batches don't pay fork-join overhead. - Function-barrier helpers eliminate
Core.Boxin threaded loops:g_sum/h_sumaccumulators insideThreads.@threadsbodies were being boxed because the compiler couldn't prove they didn't escape the closure. Moving the per-leaf work into small helpers dropped ~1.1M boxed Float64s per iteration to zero. - In-place gradient + hessian kernels: new
gradient_hessian!(g, h, lf, y, pred, scratch)fused kernels write both outputs in one pass and reuse caller-provided scratch for softmax/sigmoid, eliminating the multiple per-iterationn * n_classesmatrix allocations the old allocatingnegative_gradient/hessianpath produced. - Capped categorical bin count via quantile cuts: ordered target statistics can produce up to
n_samplesdistinct encoded values at low raw cardinality, making per-iteration histogram work O(n_samples).cat_sorted_valsis now quantile-truncated to at mostborder_count + 1entries, so per-feature bin work is bounded regardless of raw cardinality (e.g., k=5 cardinality training dropped from CB 6.26x to CB 1.33x). - Zero-copy
Poolconstruction from matrix: columns are now views into the input matrix instead of fresh per-column copies, saving ~1.6 MB per call on a 10k * 20 matrix and meaningfully improving inference latency. - Index-based loops replace
for idx in group: rewritten asfor k in 1:n; idx = group[k]everywhere aSubArraywas being iterated, eliminating ~480k iterator-state tuple allocations per iteration. - Concrete
LeafGroupViewelement type:_apply_split!now returnsVector{LeafGroupView}instead ofVector{Any}/Vector{SubArray}, so downstreamgroup[k]/length(group)/isempty(group)stay statically typed and don't dispatch dynamically. SplitCandidateis nowisbits:feature_type::Symbolreplaced withis_categorical::Bool, soVector{SplitCandidate}stores elements inline rather than as boxed references.
Bug fixes
- MAE loss leaf values: the Newton step
sum(g)/(sum(h)+λ)is only valid for smooth losses; for MAE it bounded each leaf's contribution to ±1, causing severe underfitting. Fixed with weighted-median-of-residuals leaf refinement (same approach as XGBoost / LightGBM / CatBoost). MAE held-out score now matches or beats CatBoost on benchmark data.
Breaking changes
SplitCandidate.feature_type::Symbolremoved, replaced withis_categorical::Bool. Any caller constructingSplitCandidate(j, :numerical, …)should passSplitCandidate(j, false, …)(ortruefor categorical). Tree-levelsplit_feature_types::Vector{Symbol}stored insideSymmetricTree/SymmetricTreeMultiClassis unchanged, so model serialisation is preserved.build_symmetric_treesignature additions: new keyword argumentsleaf_refine_values,leaf_refine_weights(used by the MAE path; defaultnothing) and explicit per-threadbuffers/hist_cacheobjects. Defaults keep existing callers working.- Categorical split candidates are coarser: at very low raw cardinality, split thresholds are now drawn from a quantile-subsampled set (<=
border_count + 1candidates) instead of every unique encoded value. Can change model output on datasets where the extra candidates carried real signal; correctness gates still pass within tolerance.
v0.2.0
MichiBoost v0.2.0
New features
- Precomputed gradient/hessian scaling by sample weights: gradients and hessians are multiplied by
weightsin the main loop (ones by default), replacing the previous unweighted path. - Public
shap_values(wrapper, data)API:shap_valuesnow works directly onMichiBoostRegressor/MichiBoostClassifierwrappers with optionalcat_features. - Benchmark suite reorganized: benchmarks moved out of
test/into a top-levelbenchmark/directory with its ownProject.toml; added a "real dataset" quick test (test_real_quick.jl), plus CV and early-stopping benchmark scenarios, better CLI handling, and categorical feature names now passed to CatBoost for a fair comparison.
Performance improvements
- Threaded tree-level parallelism in
predict: inference now splits trees across threads with per-thread partial-prediction buffers and pre-allocated leaf-index buffers, for both binary/regression and multiclass. - Threaded
_predict_raw: internal raw-score path parallelized to matchpredict, so SHAP, CV loss eval, and early-stopping checks all benefit from the same tree-level parallelism. - In-place
predict_tree!:predict_tree!now accepts a reusableleaf_indices::Vector{Int}buffer;_evaluate_loss(used during early stopping) now reuses pre-allocatedtree_predsandleaf_indicesinstead of allocating per tree. SplitBuffers/SplitBuffersMCstruct: single pre-allocated, thread-local buffer bundle (histograms, totals, left/right accumulators, index scratch, cache-friendly local gradient/hessian/bin arrays) reused across iterations and trees.- Precomputed sorted categorical values:
cat_sorted_valsis computed once per training run and reused across all iterations, avoiding per-levelSet/sortwork. - Cache-friendly histogram path for large leaves:
_fill_num_hist!/_fill_cat_hist!gather gradients, hessians, and bins into local scratch arrays whenn >= 512, then do the scatter add in a second pass. - Adaptive bin assignment in
apply_borders: linear scan with early exit for <=32 borders, searchsortedfirst beyond that.
Bug fixes
- SHAP formula corrected: path-conditioned means and the correct
/2factor; each row now sums exactly toraw_prediction − E[raw_prediction](uniform over leaves) instead of "approximately." SHAP inference is also now parallelized per-sample. - SHAP summation property test added: new test asserts the exact summation identity.
Breaking changes
src/trees/split intobase/andmulticlass/:build.jl,histograms.jl,predict.jl, andshap.jlnow each have a base and multiclass sibling;shap.jlkept at the top level as the public dispatcher.- Multiple dispatch replaces
_mc/_multiclasssuffixes:build_symmetric_tree_multiclass->build_symmetric_tree,predict_tree_mc!->predict_tree!,predict_tree_multiclass->predict_tree,initial_prediction_multiclass->initial_prediction. The multiclass variants are selected by argument types (Matrixgradients,SymmetricTreeMultiClasstrees) rather than by name. trainsignature additions:build_symmetric_treenow takesbuffersandcat_sorted_valskeyword arguments (defaults provided, so existing callers keep working).
v0.1.0
MichiBoost v0.1.0
Pure Julia gradient boosting with symmetric trees. Supports regression (RMSE, MAE), binary and multiclass classification, ordered target encoding for categorical features, cross-validation, early stopping, RSM feature subsampling, and model serialization.
Note
Training speed on medium/large datasets is currently 2–3× slower than CatBoost's C++ backend. Performance improvements are planned for v0.2.0, targeting histogram buffer pre-allocation and reduced GC pressure