Migrating from sparse-ho

sparho is a clean-break successor to sparse-ho, not a drop-in replacement. There is no compat module — porting code is a manual rewrite. This page is the translation table.

The shape of the rewrite

sparse-ho’s outer call took four objects:

sparse_ho.grad_search(algo, criterion, model, optimizer, X, y, alpha0, monitor)

sparho rolls algo (hypergradient) and optimizer (outer step rule) into the search function itself, and folds model (which estimator) into the Solver adapter you pass:

sparho.hoag_search(
    problem,         # ← Problem(datafit, penalty, X, y)
    hp0=alpha0,      # ← in log α space internally; pass α > 0
    solver=...,      # ← was `model` + adapter choice
    criterion=...,   # ← was `criterion`
    hypergrad=...,   # ← was `algo`, default `implicit_forward`
)

monitor becomes the return value: result.history is an immutable tuple of IterationRecords. Use grad_search instead of hoag_search for the naive fixed-lr outer loop (sparse-ho’s GradientDescent optimizer).

Translation table

Models → Problem + adapter

sparse-ho

sparho

Notes

Lasso(X, y, estimator=None)

Problem(SquaredLoss(), L1(), X, y) + SklearnLasso()

Default. Use CelerLasso() from the [celer] extra for sparse-X.

Lasso(..., estimator=celer.Lasso(...))

Problem(SquaredLoss(), L1(), X, y) + CelerLasso(tol=...)

ElasticNet(X, y, estimator=...)

Problem(SquaredLoss(), ElasticNet(rho), X, y) + SklearnElasticNet()

sparse-ho’s l1_ratio is sparho’s rho.

WeightedLasso(X, y, ...)

Problem(SquaredLoss(), WeightedL1(), X, y) + SklearnWeightedLasso()

Per-feature α.

WeightedElasticNet(X, y, ...)

not yet at v0.1

Open a discussion if you need it.

SparseLogreg(X, y, ...)

Problem(LogisticLoss(), L1(), X, y) + SklearnLogisticRegression()

sparse-ho’s C = 1/α; sparho’s α matches the math directly.

SVM(...), SVR(...), SimplexSVR(...)

not at v0.1

SmoothHinge deferred.

Ridge(...)

not at v0.1

The hypergradient is closed-form; out of scope.

Algorithms → hypergrad=

sparse-ho algo

sparho hypergrad=

Notes

ImplicitForward(...)

implicit_forward (the default)

The only mode at v0.1.

Implicit(...), ImplicitVariational(...)

not at v0.1

Deferred.

Forward(...), Backward(...)

not at v0.1

Unrolled modes deferred.

Criteria

sparse-ho

sparho

Notes

HeldOutMSE(idx_train, idx_val)

HeldOutMSE(idx_train, idx_val)

int32 indices required.

HeldOutLogistic(idx_train, idx_val)

HeldOutLogistic(idx_train, idx_val)

y {−1, +1} (same convention).

CrossVal(cv, criterion=HeldOutMSE)

CrossVal.kfold(n_samples, k=...) or CrossVal(folds=..., base=HeldOutMSE)

sparho’s CrossVal is a frozen dataclass; build it once and reuse. Opt-in warm_start=True reuses per-fold β* across outer iters.

HeldOutSmoothedHinge(...)

not at v0.1

SVM/SVR family deferred.

FiniteDiffMonteCarloSure(...)

Sure(sigma=..., epsilon=..., random_state=...)

Landed in v0.3 §2 (FDMC after Deledalle 2014). Refuses non-SquaredLoss problems.

Optimizers → search function

sparse-ho optimizer

sparho equivalent

Notes

GradientDescent(n_outer, step_size=lr)

grad_search(..., n_iter=n_outer, lr=lr)

Plain GD baseline.

LineSearch(n_outer)

hoag_search(..., n_iter=n_outer)

sparho’s recommended default — Lipschitz-adaptive steps + inner-tolerance scheduling. The original Armijo LineSearch is not a sparho v0.1 deliverable; HOAG subsumes it in practice.

Adam(...), TrustRegion(...), NonMonotoneLineSearch(...)

not at v0.1

Owner-paper features; depend on ImplicitVariational.

Monitor → SearchResult.history

sparse-ho’s Monitor mutated as the loop ran:

monitor = Monitor()
sparse_ho.grad_search(algo, crit, model, opt, X, y, alpha0, monitor)
alphas, mses = monitor.alphas, monitor.objs

sparho returns an immutable SearchResult:

result = sparho.hoag_search(problem, hp0=alpha0, solver=..., criterion=...)
alphas = [r.hyperparam for r in result.history]
mses = [r.value for r in result.history]
best_alpha = result.best_hyperparam
best_coef = result.best_coef        # refit on the full problem

A worked example

sparse-ho:

from celer import Lasso as CelerLasso
from sklearn.datasets import make_regression
from sparse_ho import grad_search
from sparse_ho.algo import ImplicitForward
from sparse_ho.criterion import HeldOutMSE
from sparse_ho.models import Lasso
from sparse_ho.optimizers import LineSearch
from sparse_ho.utils import Monitor

X, y = make_regression(n_samples=300, n_features=100, noise=1.0, random_state=0)
idx_train, idx_val = np.arange(200), np.arange(200, 300)

model = Lasso(X, y, estimator=CelerLasso(fit_intercept=False))
algo = ImplicitForward(criterion="HO")
criterion = HeldOutMSE(idx_train, idx_val)
optimizer = LineSearch(n_outer=30)
monitor = Monitor()

grad_search(algo, criterion, model, optimizer, X, y, alpha0=1e-2, monitor=monitor)
print(monitor.alphas[-1], monitor.objs[-1])

sparho:

import numpy as np
from sklearn.datasets import make_regression

from sparho import HeldOutMSE, L1, Problem, SquaredLoss, hoag_search
from sparho.adapters.celer import CelerLasso

X, y = make_regression(n_samples=300, n_features=100, noise=1.0, random_state=0)
idx_train = np.arange(200, dtype=np.int32)
idx_val = np.arange(200, 300, dtype=np.int32)

result = hoag_search(
    Problem(SquaredLoss(), L1(), X, y),
    hp0=1e-2,
    solver=CelerLasso(tol=1e-8),
    criterion=HeldOutMSE(idx_train, idx_val),
    n_iter=30,
)
print(result.best_hyperparam, result.history[-1].value)

Behavior differences to know about

  • α lives in log space. Both grad_search and hoag_search step in θ = log α; hp0 must be strictly positive. The chain rule dC/dθ = dC/dα · α is applied internally. sparse-ho left this to the user via log_alpha_max.

  • Full-data refit at the end. SearchResult.best_coef is the inner solver run on the full Problem at best_hyperparam. sparse-ho left monitor.alphas[-1] as the only output; a refit was on the user.

  • Sparse-X is CSC, not CSR. Convert before constructing Problem.

  • No Monitor. The history is immutable. If you need streaming observation, wrap the criterion (or the solver) yourself.

  • No sure criterion. Dropped at v0.1; the v0.1 audience tunes validation, not unsupervised SURE. Revisit if asked.

  • No grid-search fallback. sparse-ho had a grid ho.grid_search; sparho doesn’t ship one. Use sklearn.linear_model.LassoCV if you want a grid baseline.

What’s not yet ported

These were sparse-ho features that v0.1 deliberately leaves on the floor. Some are slated for v0.2 (skein adapter, more datafits/penalties); others are out of scope (SVM/SVR, imaging operators). See ROADMAP.md for the full picture.