TorchLean API

NN.Spec.Models.GradientBoostedTrees

Gradient boosted trees (spec model) #

This is a math/reference specification of gradient boosting using decision trees.

Important caveat:

References (classical):

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:

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).

inductive Spec.TreeNode (α : Type) (nFeatures : ℕ) :
ℕ → Type

A regression-tree node for the typed GBDT specification.

  • leaf value stores the prediction for that leaf.
  • split feature threshold left right branches on a single feature using the rule goRight := (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
    @[instance_reducible]
    instance Spec.instInhabitedTreeNode {a✝ : Type} [Inhabited a✝] {a✝¹ a✝² : ℕ} :
    Inhabited (TreeNode a✝ a✝¹ a✝²)
    structure Spec.DecisionTreeSpec (α : Type) (nFeatures maxDepth : ℕ) :

    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 maxDepth splits on any path.

    Instances For
      @[instance_reducible]
      instance Spec.instInhabitedDecisionTreeSpec {a✝ : Type} [Inhabited a✝] {a✝¹ a✝² : ℕ} :
      Inhabited (DecisionTreeSpec a✝ a✝¹ a✝²)
      structure Spec.GradientBoostedTreesSpec (α : Type) (nFeatures nTrees maxDepth : ℕ) :

      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.

        def Spec.decisionTreeForwardSpec {α : Type} [TorchLean.Storage α] [Context α] {maxDepth nFeatures : ℕ} (tree : DecisionTreeSpec α nFeatures maxDepth) (input : TorchLean.Tensor α [nFeatures]) :
        α

        Forward pass for a single decision tree on an input vector of nFeatures features.

        Instances For
          def Spec.decisionTreeForwardSpec.traverse {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} (input : TorchLean.Tensor α [nFeatures]) {depth : ℕ} (node : TreeNode α nFeatures depth) :
          α
          Instances For
            def Spec.decisionTreeForwardLeadingSpec {α : Type} [TorchLean.Storage α] [Context α] (leading : Shape) {maxDepth nFeatures : ℕ} (tree : DecisionTreeSpec α nFeatures maxDepth) (input : TorchLean.Tensor α (leading.concat [nFeatures])) :

            Apply a decision tree independently at every index of a leading shape.

            Instances For
              def Spec.gradientBoostedTreesForwardSpec {α : Type} [TorchLean.Storage α] [Context α] {nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) (input : TorchLean.Tensor α [nFeatures]) :

              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
                @[irreducible]
                def Spec.gradientBoostedTreesForwardSpec.accumulate_trees {α : Type} [TorchLean.Storage α] [Context α] {nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) (input : TorchLean.Tensor α [nFeatures]) (i : ℕ) (acc : α) :
                α
                Instances For
                  def Spec.gradientBoostedTreesForwardLeadingSpec {α : Type} [TorchLean.Storage α] [Context α] (leading : Shape) {nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) (input : TorchLean.Tensor α (leading.concat [nFeatures])) :

                  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:

                    This is deterministic by construction:

                    This implementation prioritizes clarity and determinism over performance.

                    structure Spec.RegressionExample {α : Type} [TorchLean.Storage α] (nFeatures : ℕ) :

                    A single regression training example: feature vector x and scalar target y.

                    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
                          def Spec.GradientBoostedTrees.Internal.partitionBySplit {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} (feature : Fin nFeatures) (threshold : α) (xs : Array (RegressionExample nFeatures)) :

                          Partition samples into (left, right) for a given split.

                          Instances For

                            Extract the regression targets from an array of examples.

                            Instances For
                              def Spec.GradientBoostedTrees.Internal.splitScore {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} (feature : Fin nFeatures) (threshold : α) (xs : Array (RegressionExample nFeatures)) :

                              Score a candidate split by sum of squared errors (SSE).

                              Returns none for degenerate splits (all samples go to one side).

                              Instances For
                                def Spec.bestSplit {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} (xs : Array (RegressionExample nFeatures)) :
                                Option (Fin nFeatures × α × Array (RegressionExample nFeatures) × Array (RegressionExample nFeatures) × α)

                                Find the best split (feature, threshold) by exhaustive search over observed thresholds.

                                Instances For
                                  def Spec.leafValue {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} (xs : Array (RegressionExample nFeatures)) :
                                  α

                                  Leaf prediction value for regression: the mean target.

                                  Instances For
                                    def Spec.fitRegressionNode {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} (depth : ℕ) :
                                    Array (RegressionExample nFeatures) → TreeNode α nFeatures depth

                                    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
                                      def Spec.decisionTreeFitRegressionMseSpec {α : Type} [TorchLean.Storage α] [Context α] {batch maxDepth nFeatures : ℕ} (x : TorchLean.Tensor α [batch, nFeatures]) (y : TorchLean.Tensor α [batch]) :
                                      DecisionTreeSpec α nFeatures maxDepth

                                      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:

                                        Why β is separate from α:

                                        PyTorch / sklearn analogies:

                                        inductive Spec.ClassifierTreeNode (α β : Type) (nFeatures : ℕ) :
                                        ℕ → Type

                                        A classifier tree node: numeric splits, label-valued leaves.

                                        Instances For
                                          @[instance_reducible]
                                          instance Spec.instInhabitedClassifierTreeNode {a✝ a✝¹ : Type} [Inhabited a✝¹] {a✝² a✝³ : ℕ} :
                                          Inhabited (ClassifierTreeNode a✝ a✝¹ a✝² a✝³)
                                          structure Spec.DecisionTreeClassifierSpec (α β : Type) (nFeatures maxDepth : ℕ) :

                                          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
                                            @[instance_reducible]
                                            instance Spec.instInhabitedDecisionTreeClassifierSpec {a✝ a✝¹ : Type} [Inhabited a✝¹] {a✝² a✝³ : ℕ} :
                                            Inhabited (DecisionTreeClassifierSpec a✝ a✝¹ a✝² a✝³)
                                            def Spec.decisionTreeClassifyForwardSpec {α : Type} [TorchLean.Storage α] [Context α] {β : Type} {maxDepth nFeatures : ℕ} (tree : DecisionTreeClassifierSpec α β nFeatures maxDepth) (input : TorchLean.Tensor α [nFeatures]) :
                                            β

                                            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
                                              def Spec.decisionTreeClassifyForwardSpec.traverse {α : Type} [TorchLean.Storage α] [Context α] {β : Type} {nFeatures : ℕ} (input : TorchLean.Tensor α [nFeatures]) {depth : ℕ} (node : ClassifierTreeNode α β nFeatures depth) :
                                              β
                                              Instances For
                                                structure Spec.ClassificationExample {α : Type} [TorchLean.Storage α] (nFeatures : ℕ) (β : Type) :

                                                A single classification training example: feature vector x and label y.

                                                Instances For

                                                  Count how many times lbl appears in ys.

                                                  Instances For

                                                    Remove repeated labels while preserving their first-occurrence order.

                                                    Instances For
                                                      def Spec.majorityLabel {β : Type} [DecidableEq β] [Inhabited β] (ys : Array β) :
                                                      β

                                                      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
                                                        def Spec.GradientBoostedTrees.Internal.gini {α : Type} [Context α] {β : Type} [DecidableEq β] (ys : Array β) :
                                                        α

                                                        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
                                                          def Spec.giniWeighted {α : Type} [Context α] {β : Type} [DecidableEq β] (ys : Array β) :
                                                          α

                                                          Weighted Gini impurity: |ys| * gini(ys).

                                                          Instances For
                                                            def Spec.classTargets {α : Type} [TorchLean.Storage α] {nFeatures : ℕ} {β : Type} (xs : Array (ClassificationExample nFeatures β)) :

                                                            Extract the labels from an array of classification examples.

                                                            Instances For
                                                              def Spec.partitionBySplitC {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} {β : Type} (feature : Fin nFeatures) (threshold : α) (xs : Array (ClassificationExample nFeatures β)) :

                                                              Partition classification samples into (left, right) for a candidate split.

                                                              Instances For
                                                                def Spec.GradientBoostedTrees.Internal.splitScoreC {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} {β : Type} [DecidableEq β] (feature : Fin nFeatures) (threshold : α) (xs : Array (ClassificationExample nFeatures β)) :

                                                                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
                                                                  def Spec.bestSplitC {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} {β : Type} [DecidableEq β] (xs : Array (ClassificationExample nFeatures β)) :
                                                                  Option (Fin nFeatures × α × Array (ClassificationExample nFeatures β) × Array (ClassificationExample nFeatures β) × α)

                                                                  Find the best classification split (feature, threshold) by exhaustive search.

                                                                  Thresholds are drawn from the observed feature values in the dataset.

                                                                  Instances For
                                                                    def Spec.fitClassificationNode {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} {β : Type} [DecidableEq β] [Inhabited β] (depth : ℕ) :
                                                                    Array (ClassificationExample nFeatures β) → ClassifierTreeNode α β nFeatures depth

                                                                    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
                                                                      def Spec.decisionTreeFitClassificationGiniSpec {α : Type} [TorchLean.Storage α] [Context α] {β : Type} [TorchLean.Storage β] [DecidableEq β] [Inhabited β] {batch maxDepth nFeatures : ℕ} (x : TorchLean.Tensor α [batch, nFeatures]) (y : TorchLean.Tensor β [batch]) :
                                                                      DecisionTreeClassifierSpec α β nFeatures maxDepth

                                                                      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
                                                                        def Spec.gbtMseLossSpec {α : Type} [TorchLean.Storage α] [Context α] {batch nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) (input : TorchLean.Tensor α [batch, nFeatures]) (target : TorchLean.Tensor α [batch]) (h : batch ≠ 0) :

                                                                        Mean squared error (MSE) loss for regression, reduced to a scalar by averaging over the batch.

                                                                        Instances For
                                                                          def Spec.computeResidualsSpec {α : Type} [TorchLean.Storage α] [Context α] {batch nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) (input : TorchLean.Tensor α [batch, nFeatures]) (target : TorchLean.Tensor α [batch]) :

                                                                          Residual computation for gradient boosting.

                                                                          For squared-error regression, the residual is target - prediction.

                                                                          Instances For
                                                                            def Spec.gradientBoostedTreesTrainStepSpec {α : Type} [TorchLean.Storage α] [Context α] {batch nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) (input : TorchLean.Tensor α [batch, nFeatures]) (target : TorchLean.Tensor α [batch]) (newTree : DecisionTreeSpec α nFeatures maxDepth) (h : batch ≠ 0) :
                                                                            TorchLean.Tensor α Shape.scalar × GradientBoostedTreesSpec α nFeatures (nTrees + 1) maxDepth

                                                                            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.

                                                                              def Spec.gradientBoostedTreesTrainStepFitSpec {α : Type} [TorchLean.Storage α] [Context α] {batch nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) (input : TorchLean.Tensor α [batch, nFeatures]) (target : TorchLean.Tensor α [batch]) (h : batch ≠ 0) :
                                                                              TorchLean.Tensor α Shape.scalar × GradientBoostedTreesSpec α nFeatures (nTrees + 1) maxDepth

                                                                              Fit a new tree to residuals and append it to the ensemble.

                                                                              Instances For
                                                                                def Spec.GradientBoostedTrees.Internal.incrFeature {α : Type} [TorchLean.Storage α] [Context α] {nFeatures : ℕ} (acc : TorchLean.Tensor α [nFeatures]) (feature : Fin nFeatures) :
                                                                                TorchLean.Tensor α [nFeatures]

                                                                                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
                                                                                  def Spec.treeFeatureCounts {α : Type} [TorchLean.Storage α] [Context α] {nFeatures depth : ℕ} :
                                                                                  TreeNode α nFeatures depth → TorchLean.Tensor α [nFeatures] → TorchLean.Tensor α [nFeatures]

                                                                                  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
                                                                                    def Spec.computeFeatureImportanceSpec {α : Type} [TorchLean.Storage α] [Context α] {nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) :
                                                                                    TorchLean.Tensor α [nFeatures]

                                                                                    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
                                                                                      @[irreducible]
                                                                                      def Spec.computeFeatureImportanceSpec.accumulate_importance {α : Type} [TorchLean.Storage α] [Context α] {nTrees maxDepth nFeatures : ℕ} (model : GradientBoostedTreesSpec α nFeatures nTrees maxDepth) (i : ℕ) (acc : TorchLean.Tensor α [nFeatures]) :
                                                                                      TorchLean.Tensor α [nFeatures]
                                                                                      Instances For