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.
- lr : α
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.
Starting at initialPrediction, visit trees in index order and update
acc := acc + lr * tree_i(x). Shrinkage is applied before each addition; rounded
arithmetic does not in general permit factoring it out of the sum.
Instances For
Instances For
Apply a gradient-boosted ensemble independently at every index of a leading shape.
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
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
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
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
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.