Documentation
¶
Overview ¶
Package xgoboost trains gradient-boosted decision trees with per-feature monotone constraints, in pure Go with no dependencies outside the standard library.
Training is second order: every round computes each row's gradient and hessian under a pluggable Loss, grows one regression tree whose leaves take the Newton value -G/(H+λ), and adds it, scaled by the learning rate, to the ensemble. Splits are scored by the usual second-order gain and found either by exact greedy enumeration or on quantile-binned feature histograms with histogram subtraction.
Monotone constraints use the XGBoost envelope construction: a split on a constrained feature is kept only when its children's values are ordered the right way, and each child's subtree is then bounded at the midpoint between them. Every tree, and therefore the ensemble, is monotone in each constrained feature; see the README for the proof.
Missing values are NaN. Each split learns a default direction for them from the training data.
A minimal regression:
p := xgoboost.DefaultParams()
p.Monotone = []int{+1, 0, -1}
m, err := xgoboost.Train(X, y, nil, p)
if err != nil { ... }
yhat := m.Predict(x)
Example ¶
Price rises with quality and falls with wear, whatever the noise says locally; the constraints make the model promise the same.
package main
import (
"fmt"
"math"
"math/rand/v2"
xgoboost "github.com/natemsz/xgoboost"
)
func main() {
r := rand.New(rand.NewPCG(1, 2))
var X [][]float64
var y []float64
for i := 0; i < 2000; i++ {
quality, wear, colour := r.Float64(), r.Float64(), r.Float64()
if i%10 == 0 {
wear = math.NaN() // unknown
}
price := 10*quality - 4*wear + math.Sin(6*colour) + r.NormFloat64()
if math.IsNaN(wear) {
price = 10*quality - 2 + math.Sin(6*colour) + r.NormFloat64()
}
X = append(X, []float64{quality, wear, colour})
y = append(y, price)
}
p := xgoboost.DefaultParams()
p.Monotone = []int{+1, -1, 0} // quality up, wear down, colour free
p.Rounds = 200
m, err := xgoboost.Train(X, y, nil, p)
if err != nil {
panic(err)
}
better := m.Predict([]float64{0.9, 0.2, 0.5})
worse := m.Predict([]float64{0.8, 0.2, 0.5})
fmt.Println("better quality never prices lower:", better >= worse)
fmt.Printf("unknown wear: %.0f\n", m.Predict([]float64{0.5, math.NaN(), 0.5}))
}
Output: better quality never prices lower: true unknown wear: 3
Index ¶
- Constants
- func RegisterLoss(name string, dec LossDecoder)
- type Huber
- func (l Huber) GradHess(y, raw float64) (g, h float64)
- func (l Huber) InitScore(y, w []float64) float64
- func (l Huber) LossParams() map[string]float64
- func (l Huber) Metric(y, raw, w []float64) float64
- func (l Huber) Name() string
- func (l Huber) Transform(raw float64) float64
- func (l Huber) Validate([]float64) error
- type ImportanceType
- type LeafRenewer
- type Logistic
- type Loss
- type LossDecoder
- type LossValidator
- type Model
- func (m *Model) FeatureImportance(kind ImportanceType) []float64
- func (m *Model) MarshalJSON() ([]byte, error)
- func (m *Model) Predict(x []float64) float64
- func (m *Model) PredictBatch(X [][]float64) []float64
- func (m *Model) PredictRaw(x []float64) float64
- func (m *Model) Save(w io.Writer) error
- func (m *Model) UnmarshalJSON(b []byte) error
- type Node
- type ParamLoss
- type Params
- type Quantile
- func (l Quantile) GradHess(y, raw float64) (g, h float64)
- func (l Quantile) InitScore(y, w []float64) float64
- func (l Quantile) LossParams() map[string]float64
- func (l Quantile) Metric(y, raw, w []float64) float64
- func (l Quantile) Name() string
- func (l Quantile) RenewLeaf(residual, w []float64) float64
- func (l Quantile) Transform(raw float64) float64
- func (l Quantile) Validate([]float64) error
- type SquaredError
- type Tree
- type TreeMethod
- type ValidationSet
Examples ¶
Constants ¶
const AutoExactMaxRows = 2000
AutoExactMaxRows is the largest training set Auto fits with Exact.
Variables ¶
This section is empty.
Functions ¶
func RegisterLoss ¶
func RegisterLoss(name string, dec LossDecoder)
RegisterLoss makes a custom loss loadable from serialised models. It panics if name is empty, dec is nil, or name is already registered.
Types ¶
type Huber ¶
type Huber struct {
Delta float64
}
Huber is squared error within Delta of the label and absolute error beyond it, so outliers pull the fit with bounded force. Its gradient is the residual clipped to ±Delta and its hessian is taken as 1 (the true curvature, 0 outside the band, would make Newton steps unbounded). The metric is the mean Huber loss.
func (Huber) LossParams ¶
type ImportanceType ¶
type ImportanceType int
ImportanceType selects what FeatureImportance sums.
const ( // TotalGain sums the loss reduction of every split on a feature. TotalGain ImportanceType = iota // SplitCount counts the splits on a feature. SplitCount )
type LeafRenewer ¶
LeafRenewer is implemented by losses whose Newton step is a poor leaf value (the quantile loss has zero curvature). After a tree is grown, each leaf's value is replaced by RenewLeaf over the residuals y - raw of the training rows in it, then clamped to the leaf's monotone envelope, so constraints still hold.
type Logistic ¶
type Logistic struct{}
Logistic is binary cross-entropy on labels in [0, 1]; raw predictions are log-odds and Transform is the logistic function. The metric is the mean log loss.
type Loss ¶
type Loss interface {
// Name identifies the loss in serialised models. A custom loss must
// be registered under this name with RegisterLoss before a model
// using it can be loaded.
Name() string
// InitScore is the constant raw prediction boosting starts from.
InitScore(y, w []float64) float64
// GradHess returns the first and second derivative of one row's
// loss with respect to its raw prediction. hess must be >= 0.
GradHess(y, raw float64) (grad, hess float64)
// Transform maps a raw prediction to the output scale. It must be
// non-decreasing, so that monotone constraints carry over to
// Model.Predict.
Transform(raw float64) float64
// Metric scores raw predictions against labels; lower is better.
// It drives early stopping.
Metric(y, raw, w []float64) float64
}
Loss is a training objective. Boosting works on raw predictions (the margin); Transform maps a raw prediction to the output scale.
Train always passes non-nil weight slices. Implementations used elsewhere should treat a nil w as unit weights.
type LossDecoder ¶
LossDecoder rebuilds a loss from its serialised parameters.
type LossValidator ¶
LossValidator is implemented by losses that restrict their labels or their own parameters. Train calls Validate before fitting.
type Model ¶
type Model struct {
NumFeatures int
BaseScore float64 // the raw prediction before any tree
LearningRate float64 // informational: leaf values are pre-scaled
Loss Loss
Monotone []int // nil when unconstrained
Trees []Tree
// BestIteration is the 0-based round with the lowest validation
// metric, and BestScore that metric. BestIteration is -1 when
// training had no validation set.
BestIteration int
BestScore float64
}
Model is a trained ensemble. It is safe for concurrent use by multiple goroutines once trained or loaded.
func Train ¶
Train fits a boosted ensemble to rows X (n rows of equal length; NaN marks a missing value, ±Inf is allowed), labels y and row weights w (nil means unit weights; weights must be finite and >= 0 with a positive sum).
func (*Model) FeatureImportance ¶
func (m *Model) FeatureImportance(kind ImportanceType) []float64
FeatureImportance returns one unnormalised score per feature.
func (*Model) MarshalJSON ¶
MarshalJSON encodes the model. Floats round-trip exactly, so a reloaded model predicts bit-for-bit the same.
func (*Model) Predict ¶
Predict returns the prediction for x on the output scale: the raw prediction passed through Loss.Transform (a probability for Logistic).
func (*Model) PredictBatch ¶
PredictBatch returns Predict for every row of X.
func (*Model) PredictRaw ¶
PredictRaw returns the raw prediction (the margin) for x: the base score plus every tree's leaf value. NaN, or a feature index beyond len(x), counts as missing.
func (*Model) UnmarshalJSON ¶
UnmarshalJSON decodes and validates a model. Losses other than the built-ins must be registered with RegisterLoss first.
type Node ¶
type Node struct {
Leaf bool
// Value is a leaf's contribution to the raw prediction, already
// scaled by the learning rate.
Value float64
Feature int
Threshold float64
DefaultLeft bool
Left, Right int
// Gain is the split's loss reduction (training only; used for
// feature importance).
Gain float64
// Cover is the hessian sum of the training rows that reached the
// node.
Cover float64
}
Node is one node of a Tree. A split node sends x left when x[Feature] <= Threshold, right when greater, and by DefaultLeft when x[Feature] is NaN (or x is shorter than Feature+1). Children always have larger indices than their parent; node 0 is the root.
type ParamLoss ¶
ParamLoss is implemented by losses with parameters that must survive serialisation. The map is passed back to the loss's LossDecoder.
type Params ¶
type Params struct {
// Loss is the objective. nil means SquaredError.
Loss Loss
// Rounds is the number of trees (at most, with early stopping).
// 0 means 100.
Rounds int
// LearningRate scales every tree. 0 means 0.1.
LearningRate float64
// MaxDepth limits tree depth; a depth-d tree has at most 2^d
// leaves. 0 means 6.
MaxDepth int
// Lambda is the L2 penalty on leaf values: leaves are -G/(H+λ).
Lambda float64
// MinChildWeight is the least hessian sum a child may hold. For
// squared error with unit weights it is a row count.
MinChildWeight float64
// MinSplitGain is the least loss reduction a split must achieve
// (XGBoost's gamma).
MinSplitGain float64
// Monotone holds one constraint per feature: +1 non-decreasing,
// -1 non-increasing, 0 free. nil leaves every feature free.
Monotone []int
// Subsample is the share of rows drawn, without replacement, for
// each tree. 0 means 1.
Subsample float64
// ColSample is the share of features drawn for each tree. 0 means 1.
ColSample float64
// Seed drives row and column sampling. Training is deterministic
// for a given Seed, whatever Workers is.
Seed uint64
// Method picks the split finder. The zero value is Auto.
Method TreeMethod
// MaxBins bounds the histogram bins per feature, between 2 and
// 65535. 0 means 256.
MaxBins int
// Validation, if set, is scored with Loss.Metric after each round;
// the model records the best round.
Validation *ValidationSet
// EarlyStoppingRounds > 0 stops training once the validation
// metric has not improved for that many rounds, and keeps only the
// trees up to the best round. It requires Validation.
EarlyStoppingRounds int
// Workers bounds the goroutines used to build histograms and
// search splits. 0 means GOMAXPROCS.
Workers int
}
Params configures Train. Fields marked "0 means default" take the default when left zero; the others are used as given, so the zero Params is valid but unregularised. DefaultParams returns the recommended starting point.
func DefaultParams ¶
func DefaultParams() Params
DefaultParams returns sensible defaults: squared error, 100 rounds of depth-6 trees at learning rate 0.1, λ = 1, min child weight 1.
type Quantile ¶
type Quantile struct {
Alpha float64
}
Quantile is the pinball loss for the Alpha quantile of y given x. Its curvature is zero, so trees are grown on gradients with unit hessians and each leaf is then renewed to the Alpha quantile of its rows' residuals. The metric is the mean pinball loss.
func (Quantile) LossParams ¶
type SquaredError ¶
type SquaredError struct{}
SquaredError is ½(raw - y)², the default. Its metric is RMSE.
func (SquaredError) GradHess ¶
func (SquaredError) GradHess(y, raw float64) (g, h float64)
func (SquaredError) InitScore ¶
func (SquaredError) InitScore(y, w []float64) float64
func (SquaredError) Metric ¶
func (SquaredError) Metric(y, raw, w []float64) float64
func (SquaredError) Name ¶
func (SquaredError) Name() string
func (SquaredError) Transform ¶
func (SquaredError) Transform(raw float64) float64
type Tree ¶
type Tree struct {
Nodes []Node
}
Tree is one regression tree, stored as a flat node slice.
type TreeMethod ¶
type TreeMethod int
TreeMethod selects how split candidates are found.
const ( // Auto uses Exact for training sets of at most AutoExactMaxRows // rows and Hist above that. Auto TreeMethod = iota // Hist bins each feature into at most MaxBins quantile bins once, // then finds splits on per-node gradient/hessian histograms. Only // the smaller child's histogram is built; its sibling's is the // parent's minus it. Hist // Exact enumerates every threshold between distinct values present // at a node. It is O(n log n) per node and feature. Exact )
type ValidationSet ¶
ValidationSet is held-out data scored after every round.