Fitting¶
TSL fits a regression function in stages. Each stage asks: what separable function best predicts the residuals left by the current model?
The answer is built at three levels:
- one grid repeatedly splits, refits, or merges feature intervals;
- one stage combines several randomized grids;
- the full model jointly refits the coefficients of every completed branch.
This page follows those levels in order. The code has the same hierarchy:
GridTensor, StagePredictor,
and TSL.
What the fit targets¶
Let \((X_i,Y_i)\), \(i=1,\ldots,n\), be the training observations, where \(X_i\in\mathbb R^p\) and \(Y_i\in\mathbb R\). Under squared-error loss, the population prediction target is
This is the mean response among observations with features \(x\).
For a model with \(R\) stages and fixed hyperparameters, let \(\mathcal F_{\mathrm{TSL}}\) be the set of prediction functions that TSL can represent. Its members are finite sums
Each \(h_{\ell,s}\) is a non-negative separable branch of an aggregated stage grid. The two branches in a stage share a partition. Their outer coefficients are unconstrained, so either branch may have either sign in the final model.
The best population prediction available in this family is
Fitting is a greedy, randomized estimate of this best-in-family function. The target is the prediction function itself. Different stages and factors can represent the same function, so normalization chooses a stable stored representation rather than a unique scientific decomposition. See Identifiability and stability.
What one stage targets¶
Suppose stages \(1,\ldots,\ell-1\) give the current predictor \(\hat m_{\ell-1}\). Stage \(\ell\) receives the stage residuals
The first stage has \(\hat m_0=0\), so \(R_i^{(1)}=Y_i\). If we hold the current fitted model fixed, the ideal correction for a fresh observation is
One stage greedily approximates this correction. After adding it, TSL jointly refits the coefficients of all completed branches. The next stage uses the residuals after that least-squares refit.
How one grid represents a correction¶
A grid has positive and negative branches
and predicts
The internal scales satisfy \(\lambda_+,\lambda_-\geq0\), and the univariate factors \(a_{\pm,j}\) are positive step functions. For feature \(j\), let
be a partition of its observed range. The factors are constant on these intervals. The number of boundaries in the full partition \(\mathcal P=(\mathcal P_1,\ldots,\mathcal P_p)\) is
A split divides one interval and adds one boundary. Because that feature factor is multiplied by every other feature factor, the split changes every Cartesian cell containing the interval while preserving separability.
The implementation stores each factor pair in backbone and tilt coordinates:
The backbone \(b\) controls common magnitude. The tilt \(d\) controls the imbalance between the two branches.
Starting one grid¶
The fit uses either L2 weights or Huber weights. Define
Every feature starts with one interval, \(b=1\), and \(d=0\), so the initial grid is constant.
If every incoming stage residual is non-negative, grid fitting uses positive-only mode:
Only the positive backbone is updated during fitting. Final normalization gives \(\lambda_-\) a small positive numerical floor, so the returned negative branch is negligible rather than exactly zero.
For mixed-sign stage residuals, let
The initial constant scales are
where \([z]_+=\max(z,0)\). The grid residual after initialization is
These residuals set the scale of the boundary penalty introduced below.
How one grid chooses its next action¶
At step \(t\), let \(g_t\) be the current grid and define
TSL holds these weights fixed while comparing all actions at this step. Every candidate uses the same weighted squared loss:
For Huber fitting, this is a fixed-weight quadratic approximation to Huber loss. After accepting an action, TSL recomputes the residuals and weights before the next comparison.
There are three actions:
| Action | Change | Boundary change \(\Delta K_a\) |
|---|---|---|
| Split | Divide one interval and fit both children | \(+1\) |
| Boundary refit | Keep a boundary fixed and refit the factors on both sides | \(0\) |
| Merge | Remove a boundary and fit one shared backbone/tilt pair on the union | \(-1\) |
Each candidate \(a\) receives the score
Here \(D_a\) is the reduction in fixed-weight loss, \(M_a\) is the factor-update penalty, and \(C_{\partial}\) is the penalty for one boundary. We derive these terms next.
Loss reduction and the factor-update penalty¶
First consider one region affected by an action: either one child of a split or one side of a boundary refit. Let \(S\) be the indices of the training rows in that region. For this derivation, write
The candidate multiplies the current branch predictions in \(S\) by positive factors \(v_+\) and \(v_-\):
Let
Write the prediction change for row \(i\) as
Where \(D_S\) comes from¶
\(D_S\) is the loss before the update minus the loss after the update, restricted to rows in \(S\). The new residual is
Because \(e_i^2-(e_i-\Delta_i)^2=2e_i\Delta_i-\Delta_i^2\), the loss reduction is
Substituting \(\Delta_i=u_+f_{+,i}-u_-f_{-,i}\) and expanding gives
The code computes this expression from five sums:
The minus signs in \(S_{+-}\) and \(t_-\) absorb the minus sign in \(g=f_+-f_-\), which gives the compact form below.
Collecting terms gives
Thus \(D_S>0\) means that the proposed multipliers reduce the fixed-weight loss on \(S\) before penalties.
Write the multipliers as a backbone update \(\beta\) and a tilt update \(\delta\):
Equal multipliers change only the backbone; reciprocal multipliers change only the tilt. The update penalty is
Here \(\alpha\), \(\tau\), and \(\rho\) correspond to alpha, tilt_tau, and tilt_rho.
They shrink common log-magnitude changes, shrink tilt changes, and allow an exactly zero
tilt update, respectively.
For a split, both children are measured from their common parent. For a boundary refit, each new factor is measured from its current value. In both cases,
A merge uses a different baseline, described below.
Why one boundary has a penalty¶
A boundary makes the grid more flexible. For a positive finite complexity_penalty,
TSL assigns every boundary the fixed penalty
where \(\lambda_{\mathrm{cc}}\) is the value of complexity_penalty and
Although \(C_{\partial}\) has several symbols, it is only a product of four parts:
- \(\lambda_{\mathrm{cc}}\) is the user-controlled strength;
- \(s_0^2\) puts the penalty in the same squared-error units as \(D_a\);
- \(\nu_{\partial}\) counts the interval parameters added by one boundary;
- \(\log(\max(n,2))\) increases the penalty with sample size.
The parameter count is
A boundary adds one backbone value in positive-only mode, or one backbone and one tilt value in the full model.
For Gaussian regression, suppose a boundary adds \(\nu_{\partial}\) parameters and reduces the residual sum of squares (\(\mathrm{RSS}\)) by \(\Delta_{\mathrm{RSS}}\). A first-order BIC calculation favors the boundary when
TSL replaces \(\mathrm{RSS}/n\) with the initialized weighted mean squared residual
\(s_0^2\) and multiplies by complexity_penalty. This is a BIC-inspired calibration.
Because the initialized loss can contain learnable signal and the parameter count omits
threshold search, shrinkage, normalization, and greedy selection, tune
complexity_penalty on validation data.
How the three actions use the score¶
The score for each action is
| Action | Score |
|---|---|
| Split | \(D_a-M_a-C_{\partial}\) |
| Boundary refit | \(D_a-M_a\) |
| Merge | \(D_a-M_a+C_{\partial}\) |
The code calls a boundary refit a resplit, although the boundary does not move.
Only the factor values on its two sides are refitted.
The highest-scoring eligible action is accepted only when its score exceeds
min_split_loss and a relative numerical tolerance.
The boundary penalty depends only on the resulting boundary count. Because the update penalty depends on the starting factors, two routes to the same partition can receive different scores.
How a merge is fitted¶
Suppose a boundary on feature \(j\) separates intervals \(L\) and \(R\), with factors
If the intervals contain \(n_L\) and \(n_R\) rows, the merge baseline is their sample-count-weighted geometric mean:
This baseline is symmetric, suits positive factors updated multiplicatively, and equals the common factor when both intervals agree.
TSL evaluates both branch predictions at this baseline and applies the same two-multiplier calculation used for the other actions. If the fitted one-interval grid is \(g_{\mathrm{merged}}\), then
The update penalty \(M_{\mathrm{merge}}\) measures the change from the geometric baseline to the fitted merged factor. The loss reduction accounts for forcing two intervals to share one factor. Removing the boundary supplies the separate \(+C_{\partial}\) score term.
How multiplier proposals are computed¶
The same two-variable solver proposes multipliers for all three actions. Splits and boundary refits use the current branch predictions and residuals. A merge uses the predictions and residuals at its geometric baseline.
The exact update penalty is expressed in \(\log v_+\) and \(\log v_-\) and is not quadratic in \(u_\pm=v_\pm-1\). Since \(v_\pm=1+u_\pm\) and \(\log(1+u)\approx u\) near zero, the solver uses
Using the five regional sums defined above, maximizing the resulting local approximation to \(D_S-M_S\) when \(\rho=0\) gives the linear system
where
When \(\rho>0\), the solver checks positive, negative, and exactly zero tilt updates and keeps the best valid proposal.
The proposed multipliers are limited to
The action score uses the exact log-coordinate update penalty at this limited proposal. When the selected action is applied, the code also enforces
These are absolute safety bounds. If one activates, the applied update can differ from the scored proposal.
Positive-only proposal¶
In positive-only mode, \(f_{-,i}=0\) and the tilt remains zero. The proposal is
After limiting the multiplier, its score uses the exact update penalty \(\alpha\log^2(1+u_+)\).
Search, boundary budget, and stopping¶
For each considered feature, split_try bounds the number of currently allowed split
positions examined by the default Random strategy. Best searches all valid
positions. TopK samples from the highest-scoring positions. Every strategy rejects
thresholds that violate min_interval_samples.
The code calls a grid's boundary count its fineness. Because fitting starts with no
boundaries, it equals \(K(\mathcal P)\): a split adds one, a merge removes one, and a
boundary refit leaves it unchanged. The n_iter parameter is the boundary budget.
A positive finite complexity_penalty enables merges and the boundary penalty. At the
boundary budget, further splits are excluded, but eligible merges and boundary
refits may continue. A merge can free room for a later split.
When complexity_penalty is zero, negative, or non-finite, merges are disabled, the
boundary penalty is zero, and reaching the budget stops the grid fit.
Two guards prevent repeated boundary refits from cycling:
- the boundary touched by the preceding split or refit is skipped;
- at most five boundary refits may occur consecutively.
A split or merge resets this count. A separate limit stops the full action loop after \(3\,\texttt{n_iter}\) steps. Fitting also stops when no candidate clears the acceptance threshold or no valid candidate remains.
Histogram binning changes which split thresholds are examined, not how they are scored.
Setting max_bins restricts candidates to quantile-bin edges. Prefix sums let the code
score each threshold in constant time, regardless of the number of rows in the interval.
See GridTensor: histogram binning.
Combining the randomized grids¶
Once every grid stops, fitting moves from grid level to stage level. Each grid fit
receives its own seed and runs in parallel when Rayon is enabled. Random and TopK
use the seed during partition search; Best searches deterministically. Every grid uses
all training rows, and rows are not resampled.
Aggregation places every grid on the union of their split points and centers the aligned
factor coordinates before comparing grid shapes. When similarity_threshold requests
trimming, it computes pairwise component-shape distances, chooses the grid with the
smallest total distance to the others as the reference, and discards grids farthest from
it. Otherwise, every grid is retained without computing those distances. Aggregation
then takes geometric means of the retained positive factors, negative factors, and
internal scales, and normalizes the aggregated factor coordinates. See
Bagging and aggregation.
Refitting all branch coefficients¶
After aggregation, fitting moves from stage level back to the full model. For stage \(\ell\), define its two branch columns
These columns include the internal scales \(\lambda_{\ell,+}\) and \(\lambda_{\ell,-}\) but exclude the outer OLS branch coefficients. Collect all branch columns through stage \(\ell\):
The full model solves
The coefficients are unconstrained and may have either sign. There is no added intercept column. The SVD drops singular directions below its relative tolerance and returns the minimum-norm solution when branch columns are linearly dependent. This joint least-squares refit updates every completed branch coefficient before the next stage residual is formed.
Where the two scales live
GridTensor.lambda_plus and GridTensor.lambda_minus are positive internal grid
scales included in \(f_{\ell,+}\) and \(f_{\ell,-}\). The unconstrained OLS coefficients
are stored separately as StagePredictor.scaling_plus and
StagePredictor.scaling_minus. Prediction applies these outer scalings exactly once;
the legacy GridTensor.scaling field is ignored in two-tensor mode. See the
architecture invariants.
Computational cost¶
For \(R\) stages, \(B\) randomized grids per stage, at most \(T\) grid actions, \(n\) observations, and \(p\) features, a rough upper bound for grid search is
Randomized grids run in parallel. Cached cumulative sums make split-threshold scores cheap to evaluate, and histogram binning replaces scans over rows with scans over candidate bins. Partition alignment, pairwise grid comparisons during aggregation, and the repeated SVD coefficient refits add separate costs outside this bound.