Gradient boosted trees (spec model) #
This is a math/reference specification of gradient boosting using decision trees.
Important caveat:
- Many computations here are written in a straightforward, proof-friendly style rather than as a tuned implementation.
References (classical):
- CART: Breiman, Friedman, Olshen, Stone, "Classification and Regression Trees", 1984.
- Gradient boosting: Friedman, "Greedy Function Approximation: A Gradient Boosting Machine", 2001.
- XGBoost: Chen and Guestrin, "XGBoost: A Scalable Tree Boosting System", 2016.
- LightGBM: Ke et al., "LightGBM: A Highly Efficient Gradient Boosting Decision Tree", 2017.
Squared-error boosting fits trees to target - prediction. The shared mean-squared-error gradient
is mseLossGradSpec in NN/Spec/Models/LinearRegression.lean.
Implementation status #
No API builder implements this model. NN/Spec/Models/RandomForest.lean reuses its decision
trees; no theorem is proved about it.
Tree representation #
We represent a decision tree as a small inductive datatype:
leaf valuestores the prediction for that leafsplit feature threshold left rightbranches on a single feature
This is kept compact: it is easy to interpret (forward pass) and easy to fit with a simple greedy CART-style algorithm (implemented below).
Note on comparisons:
The spec layer’s scalar interface (Context α) gives us a decidable > (via
Context.decidableGT)
but does not promise a decidable < for every backend. To stay portable, the tree uses the rule:
goRight := (x_feature > threshold)
and goes left otherwise (equivalently, $x_{\mathrm{feature}}\leq\mathrm{threshold}$ for the usual numeric orders).
A regression-tree node for the typed GBDT specification.
leaf valuestores the prediction for that leaf.split feature threshold left rightbranches on a single feature using the rulegoRight := (x_feature > threshold).
- leaf {α : Type} {nFeatures depth : ℕ} (value : α) : TreeNode α nFeatures depth
- split {α : Type} {nFeatures depth : ℕ} (feature : Fin nFeatures) (threshold : α) (left right : TreeNode α nFeatures depth) : TreeNode α nFeatures (depth + 1)
Instances For
Decision-tree specification whose type records its feature count and maximum split depth.
Each split consumes one unit of the depth index, so a value of this type cannot contain a path
deeper than maxDepth. Its feature index is a Fin nFeatures, ruling out invalid feature access.
- root : TreeNode α nFeatures maxDepth
Root node with at most
maxDepthsplits on any path.
Instances For
Gradient boosted tree ensemble (regression-style) specification.
The model stores an explicit tensor of trees, a shrinkage parameter, and an initial prediction.
- trees : TorchLean.Tensor (DecisionTreeSpec α nFeatures maxDepth) [nTrees]
The boosted regression trees, applied in order and summed.
- learningRate : α
The shrinkage factor multiplying each tree's contribution.
- initialPrediction : α
The constant base prediction that the trees correct.
Instances For
Forward pass #
All forward passes in this file are explicit about the feature dimension nFeatures. Tree depth
(maxDepth) limits how many splits a tree may contain; it has nothing to do with how many input
features exist.
Forward pass for a single decision tree on an input vector of nFeatures features.
Instances For
Instances For
Apply a decision tree independently at every index of a leading shape.
Instances For
Forward pass for a gradient boosted ensemble on a single input.
This computes initialPrediction + learningRate * sum(tree_i(x)).
Instances For
Instances For
Apply a gradient-boosted ensemble independently at every index of a leading shape.
Instances For
"Gradient" w.r.t. a tree's prediction.
Decision trees are piecewise-constant in the inputs, so we do not attempt to define meaningful derivatives through their internal decisions here. For boosting, this convention makes the intended dataflow explicit: gradient information is used to fit subsequent trees, not to differentiate through split predicates.
Instances For
Approximate gradient w.r.t. input features for a tree.
In this spec we return 0 gradients (trees are treated as non-differentiable).
Instances For
Zero input-gradient convention for the ensemble.
This file treats boosted trees as a classical model: we do not backpropagate through tree
structure. Instead, residuals/gradients are used to fit new trees. This helper is intentionally
not wired into an OpSpec; callers should not mistake it for a differentiable surrogate.
Instances For
Classical training: CART-style regression trees (MSE) #
Tree-based models here are intended as baselines and reference points. For neural models we implement reverse-mode explicitly; for trees we instead provide a classical (non-gradient) training routine.
The code below implements a small greedy CART-like procedure for regression:
- choose the split
(feature, threshold)that minimizesSSE(left) + SSE(right)(sum of squared errors around each side’s mean) - recurse until depth runs out or the split becomes degenerate
This is deterministic by construction:
- thresholds are chosen from the observed feature values
- ties are broken by the “first best” encountered during folding
This implementation prioritizes clarity and determinism over performance.
A single regression training example: feature vector x and scalar target y.
- x : TorchLean.Tensor α [nFeatures]
The feature vector.
- y : α
The scalar regression target.
Instances For
Sum all elements of an array.
Instances For
Mean of an array, with 0 as a convenient default for an empty sample.
Instances For
Sum of squared deviations from the mean (SSE).
Instances For
Decide whether a sample goes to the right branch for a (feature, threshold) split.
Instances For
Partition samples into (left, right) for a given split.
Instances For
Extract the regression targets from an array of examples.
Instances For
Score a candidate split by sum of squared errors (SSE).
Returns none for degenerate splits (all samples go to one side).
Instances For
Find the best split (feature, threshold) by exhaustive search over observed thresholds.
Instances For
Leaf prediction value for regression: the mean target.
Instances For
Fit a regression tree by greedy CART-style splitting (MSE/SSE), with a depth budget.
depthLeft counts how many splits we are still allowed to make.
Instances For
Fit a regression decision tree from a batched dataset.
Instances For
Classical training: CART-style classification trees (Gini impurity) #
For classification we often want the leaf prediction to be a label (e.g. String or Nat),
while split thresholds remain numeric. To avoid forcing labels into the numeric scalar type α,
we define a separate classifier tree type parameterized by the label type β.
The training algorithm mirrors the regression case:
- enumerate candidate thresholds from observed feature values
- pick the split that minimizes weighted Gini impurity
- recurse until depth runs out or no improvement is possible
- leaf prediction is the majority class (deterministic tie-breaking via first occurrence)
Why β is separate from α:
- Splits compare numeric features (
α), so they need ordering/decidable comparison. - Leaf predictions are usually discrete (
β), and we do not want to pretend that labels form a numeric scalar domain.
PyTorch / sklearn analogies:
- This is closest in spirit to
sklearn.tree.DecisionTreeClassifierwithcriterion="gini", expressed as a small pure spec. - The boosting semantics (adding many trees sequentially) matches the high-level idea of
sklearn.ensemble.GradientBoostingClassifier, but TorchLean does not try to reproduce all of sklearn’s engineering details (regularization knobs, histogram binning, etc.).
A classifier tree node: numeric splits, label-valued leaves.
- leaf {α β : Type} {nFeatures depth : ℕ} (label : β) : ClassifierTreeNode α β nFeatures depth
- split {α β : Type} {nFeatures depth : ℕ} (feature : Fin nFeatures) (threshold : α) (left right : ClassifierTreeNode α β nFeatures depth) : ClassifierTreeNode α β nFeatures (depth + 1)
Instances For
Specification wrapper for a classification decision tree (numeric splits, label-valued leaves).
- root : ClassifierTreeNode α β nFeatures maxDepth
Root node with bounded depth and valid feature indices.
Instances For
Forward pass for a classifier decision tree on an input vector of nFeatures features.
Branching convention:
- go right iff
(x[feature] > threshold), - otherwise go left.
This mirrors the common convention
$x_{\mathrm{feature}}\leq\mathrm{threshold}$ goes left and
$x_{\mathrm{feature}}>\mathrm{threshold}$ goes right, but avoids needing a decidable < for every
Context α backend.
Instances For
Instances For
A single classification training example: feature vector x and label y.
- x : TorchLean.Tensor α [nFeatures]
The feature vector.
- y : β
The class label.
Instances For
Count how many times lbl appears in ys.
Instances For
Remove repeated labels while preserving their first-occurrence order.
Instances For
Majority label with deterministic tie-breaking.
If there is a tie, we keep the earlier winner from the fold. This is intentional: it avoids non-determinism and keeps the spec stable across backends.
Instances For
Gini impurity of a multiset of labels.
gini(ys) = 1 - Σ_c p(c)^2 where p(c) is the empirical class frequency.
This is the standard CART impurity used by many tree classifiers.
Instances For
Weighted Gini impurity: |ys| * gini(ys).
Instances For
Extract the labels from an array of classification examples.
Instances For
Decide whether a classification sample goes right for a (feature, threshold) split.
Instances For
Partition classification samples into (left, right) for a candidate split.
Instances For
Score a candidate classification split (feature, threshold) by weighted Gini impurity.
Returns none when the split is degenerate (one side is empty); otherwise returns the score and
the (left, right) partitions.
Instances For
Find the best classification split (feature, threshold) by exhaustive search.
Thresholds are drawn from the observed feature values in the dataset.
Instances For
Fit a classification tree node by greedy CART-style splitting (Gini impurity).
depthLeft counts how many more splits we are allowed to make.
Instances For
Fit a classification decision tree (CART-style) using Gini impurity.
The label vector has exactly one element per input row. Its length is part of the type, so fitting cannot silently discard labels or invent missing ones.
Instances For
Mean squared error (MSE) loss for regression, reduced to a scalar by averaging over the batch.
Instances For
Mean binary cross-entropy of the ensemble's logits.
For logit z and target y, evaluate
max z 0 - z * y + log (1 + exp (-abs z)). This avoids taking the logarithm of a sigmoid rounded
to zero or one. The logarithmic term compensates for rounding in 1 + tail; if that addition
rounds to one, it retains tail instead of returning zero.
Instances For
Per-example sigmoid BCE derivative sigmoid(logit) - target, without batch reduction.
Divide by batch to obtain the derivative of gbtBinaryCrossentropyLossSpec.
When the target compares equal to one, use (1 - target) - sigmoid(-logit) to retain
the positive-logit tail and any target tangent carried by the scalar.
Instances For
Residual computation for gradient boosting.
For squared-error regression, the residual is target - prediction.
Instances For
One gradient-boosting "add a tree" step, given a pre-fit newTree.
This returns the loss before the update and the model with newTree appended.
gradientBoostedTreesTrainStepFitSpec also fits the new tree to the current residuals.
Instances For
Gradient boosting: a "fit-one-more-tree" step #
The original gradientBoostedTreesTrainStepSpec expects a pre-fit newTree. For a more
complete baseline, we also provide a deterministic step that fits that tree to the residuals.
Fit a new tree to residuals and append it to the ensemble.
Instances For
Increment a single feature counter by 1 inside a length-nFeatures vector.
This is used by the split-count feature-importance computation below.
Instances For
Count how many times each feature index appears in split nodes of a tree.
This mirrors a very common "split count" importance heuristic.
Instances For
Simple split-count feature importance for an ensemble.
This mirrors the common "how often was a feature used in a split?" heuristic. It is not the same as gain-based importance in XGBoost/LightGBM, but it is deterministic and easy to interpret.
Instances For
Instances For
Coefficient of determination (R^2) for regression.
This uses the standard formula 1 - ss_res / ss_tot, written as (ss_tot - ss_res) / ss_tot
to avoid an explicit 1 - ... when working in an abstract scalar context.
Instances For
Mean absolute error (MAE) for regression.
Instances For
Root mean squared error (RMSE) for regression.
Instances For
Adjust the ensemble learning rate (shrinkage) while keeping the same trees.
Instances For
Select the first newBatch paired input rows and targets.
The requested row count is explicit; no random sampling or ratio-based rounding is performed.