Source code for mojolearn._solver_impl

# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 Andrew Hendel. Part of mojolearn, https://doi.org/10.5281/zenodo.22068632
"""Coordinate descent on the GPU: Lasso and ElasticNet. Reference: cuML's
`solver='cd'` arm (`cuml/cpp/src/solver/cd.cuh::cdFit`).

The implementation is `solver/` (DEVIATIONS 610-613 and 880); `solver/README.md`,
`solver/NOT_IMPLEMENTED.tsv` are the record. The
reference is cuML pinned at `v26.08.00` = `265b9da`, and every line number
cited in this file was read in that checkout.

These classes are not re-exported from `mojolearn/__init__.py` by this file;
whoever owns that file decides the public namespace.
"""

from . import _mojolearn_solver, _serialize
from ._array import Array
from ._mode import ParamsMixin
from ._buffer import addr, addr_ro, as_f32_c, as_f32_colmajor, empty, zeros, _materialize
from .linear_model import _check_saved_by, _r2_host, _restore_mode, _saved_mode, _shape_of

# cuML's `loss_funct` / DistanceType style codes this surface uses.
_SELECTION_SHUFFLE = {"cyclic": 0, "random": 1}

#: The saved-model format of ElasticNet and Lasso (lane/inference-linear-svm,
#: 2026-09-15): `mojolearn.host_model(path)` predicts from it on a CPU.
_CD_FORMAT = "mojolearn-cd-1"


# THE IMPORT-TIME MODE GUARD THAT STOOD HERE IS DELETED. DEVIATION 1931,
# 2026-08-28, and it is deleted rather than corrected because both halves of
# it were wrong by the time it fired.
#
# It read: "`_mojolearn_solver` is not in `_backend._MODULES` yet, so under
# `identical` the FAST binary would be imported here under the identical
# label", and it ended "Delete it when `_backend._MODULES` lists this
# module." `_MODULES` HAS LISTED IT since DEVIATION 869 (2026-08-24), so the
# reason the message gave a reader was false, and its own instruction said to
# remove it.
#
# What it actually did after that was worse than nothing. `_backend.select()`
# hands out a `_MissingIdentical` stub for any binding whose identical binary
# was not built -- import succeeds, touching it raises BY NAME with the build
# command -- so one unbuilt binding degrades to one broken estimator. A stub
# has no `__file__`, so this guard's `os.path.basename(os.path.dirname(...))`
# check could never pass for it, and a missing solver binary became an
# ImportError at `from . import _mojolearn_solver`: the whole package failed
# to import, and with it tools/e1_traced_fit.py and the entire E2 matrix.
# That is how phases 3 and 4 went dark on every vendor while the bootstrap
# reported two unrelated-looking driver findings.
#
# The protection is not lost. `select()` is the thing that redirects these
# modules and it refuses to fall back to a FAST binary under the identical
# name -- that refusal is the mechanism, and it is the one this guard was
# written to duplicate before the tuple was fixed.



# Maintainer notes (moved from the docstring, 2026-10-12):
# Reference: cuml.linear_model.ElasticNet(solver='cd') and
# cuml/cpp/src/solver/cd.cuh::cdFit; the Mojo implementation is
# solver/impl/cd.mojo and the host surface solver/estimator.mojo.
# THE OBJECTIVE: cd.cuh:84-86 documents an unscaled objective, and the code
# (cd.cuh:201-202, v26.08.00 checkout) scales both penalties by the row count:
#     l2_alpha = (1 - l1_ratio) * alpha * n_rows
#     l1_alpha =      l1_ratio  * alpha * n_rows
# so what is minimized is n_rows times scikit-learn's documented objective
# (sklearn/linear_model/_coordinate_descent.py:499-501; scikit-learn's solver
# applies the same n_samples factor, :781-782). A positive scale does not move
# the minimizer, so alpha and l1_ratio mean the same in sklearn, cuML and here;
# cuML's docstring is off by the factor n.
# Differences from scikit-learn, all matching cuML on purpose:
#   stopping rule  after each epoch, coefMax < tol OR diffMax / coefMax < tol
#                  (cd.cuh:271), a coefficient-change test only; no duality gap,
#                  so dual_gap_ is absent, not zero
#   tol default    1e-3 (cuML's); cuML's CPU-to-GPU converter multiplies a
#                  scikit-learn tol by 10 (elastic_net.py:154)
#   zero-column    `squared > 1e-5 ? r / squared : 0` (cd.cuh:68), an absolute
#   guard          threshold on a sum of squares; on the identity card as
#                  `cd.squared`. scikit-learn skips only an exactly-zero column
# Refusals and their sources:
#   alpha / l1_ratio  non-finite refused by name (DEVIATION 613, cd.mojo): cuML's
#                  guard `alpha < 0` is false for NaN, and a NaN alpha zeroes
#                  every coefficient silently
#   fit_intercept  centers on the device through the implemented preProcessData /
#                  postProcessData (cuML's kernels; on the identity card as
#                  `cd.mu_input`, `cd.mu_labels`), unlike LinearRegression/Ridge
#   selection      'random' refused (cd.mojo): cuML seeds std::mt19937(0) and calls
#                  std::shuffle, whose algorithm the C++ standard does not
#                  specify, so it cannot be gated bitwise; an exact
#                  implementation is DEVIATION 611, reserved and not spent
#   solver 'qn'    refused: cuML's 'auto' picks 'qn' for sparse input
#                  (elastic_net.py:243-244); 'qn' under l1 is OWL-QN on that
#                  surface (glm/NOT_IMPLEMENTED.tsv)
#   sample_weight  the weighted preProcessData and sqrt-weight scaling are not
#                  implemented (cd.mojo raises)
#   positive / warm_start / precompute  cuML raises UnsupportedOnGPU too
#                  (elastic_net.py:143, :146, :149); cdFit reads coef as its
#                  starting point and solver/estimator.mojo zeroes it every fit
#   copy_X=False   refused: the design is copied to the device and the caller's
#                  array is never written (DEVIATION 880; cuML's cdFit centers
#                  and un-centers input in place and does not restore the bits)
#   random_state   accepted and inert (only 'random' reads it); refusing it broke
#                  nested Lasso(random_state=...) in voting/stacking members
#   n_rows <= 1 / n_cols <= 0  refused by cdFit itself (cd.cuh:145, :144), in fit
#                  and in predict
#   sparse X       cdFit is dense only (elastic_net.py:265-269)
# THE DESIGN MATRIX IS COPIED AT MOST ONCE (DEVIATION 2367): cdFit wants cuML's F
# order and `_buffer.as_f32_colmajor` converts dtype and layout in one pass; a
# float32 C-order design crosses as is and the device transposes it
# (lane/gap-nv-classical2). `input_copied_` and `fortran_copied_` report the same
# flag.
# n_iter_: the stopping test branches on reduction bits; under identical it is a
# function of the inputs alone and is the card's `cd.n_iter` stage (bit-identical
# on Apple M4, NVIDIA H100 and AMD MI325X at leg 11).
# IDENTITY TRACE: with MOJOLEARN_IDENTITY_TRACE=<path> set, a fit writes the same
# `cd.*` identity card solver/cd_main.mojo writes (solver/estimator.mojo hands the
# implemented entry a live trace).
[docs] class ElasticNet(ParamsMixin): """Linear regression with a combined l1 and l2 penalty, fitted by coordinate descent. Minimizes scikit-learn's elastic-net objective:: (1 / (2 * n_samples)) * ||y - X w||^2 + alpha * l1_ratio * ||w||_1 + 0.5 * alpha * (1 - l1_ratio) * ||w||^2 so ``alpha`` and ``l1_ratio`` mean the same as in scikit-learn's and cuML's ``ElasticNet``. The solver is cuML's coordinate descent (``solver='cd'``), and it differs from scikit-learn in ways that can change results: - It stops when the largest coefficient, or the largest coefficient change relative to it, falls below ``tol`` after an epoch. There is no duality-gap check, so there is no ``dual_gap_`` attribute. - ``tol`` defaults to 1e-3 (cuML's); scikit-learn's is 1e-4. Set it explicitly for a like-for-like comparison. - A column whose sum of squares is at most 1e-5 gets a zero coefficient. This threshold is absolute, so very small-valued features can be zeroed where the same data in larger units would not be. scikit-learn zeroes only an all-zero column. ``X`` must be dense and is read as float32; the caller's array is never modified. ``y`` must be one-dimensional. At least two rows are required, in ``fit`` and in ``predict``. With ``fit_intercept=True`` the data are centered on the GPU. ``ElasticNet`` has no ``numeric_mode`` parameter. Parameters ---------- alpha : float, default 1.0 Overall penalty strength, >= 0. A negative value raises ``ValueError``; a non-finite value is refused by ``fit``. l1_ratio : float, default 0.5 The l1 share of the penalty, in [0, 1] (``ValueError`` otherwise); 1.0 is the lasso. fit_intercept : bool, default True Whether to fit an (unpenalized) intercept. max_iter : int, default 1000 Maximum number of coordinate-descent epochs. tol : float, default 1e-3 Stopping tolerance (see above). solver : {'auto', 'cd'}, default 'auto' Both mean coordinate descent. ``'qn'`` raises ``NotImplementedError``; any other value raises ``ValueError``. selection : {'cyclic'}, default 'cyclic' Coordinates are updated in order. ``'random'`` raises ``NotImplementedError``; any other value raises ``ValueError``. precompute : False Only False is supported; anything else raises ``NotImplementedError``. copy_X : True Only True is supported; False raises ``NotImplementedError``. warm_start : False Only False is supported (every fit starts from zero); True raises ``NotImplementedError``. positive : False Only False is supported; True raises ``NotImplementedError``. random_state : None, int or NumPy random state, default None Accepted for compatibility and unused, as in scikit-learn with ``selection='cyclic'``. Attributes ---------- coef_ : array of shape (n_features,), float32 The fitted coefficients. intercept_ : float The fitted intercept; exactly 0.0 when ``fit_intercept`` is False. n_iter_ : int The number of epochs run. In ``identical`` mode it is the same on NVIDIA and AMD GPUs; in other modes it can differ between GPUs. n_features_in_ : int The number of features seen in ``fit``. input_copied_ : bool Whether ``fit`` had to copy ``X``. A float32 array in C or Fortran order is used without a copy. fortran_copied_ : bool The same flag as ``input_copied_``, kept for compatibility. Examples -------- >>> import numpy as np, mojolearn >>> rng = np.random.default_rng(0) >>> X = rng.random((1000, 8), dtype=np.float32) >>> y = X @ np.arange(8, dtype=np.float32) + 1.0 >>> model = mojolearn.ElasticNet(alpha=0.01, l1_ratio=0.7).fit(X, y) >>> pred = model.predict(X) """ #: scikit-learn's estimator kind: `cross_val_score` stratifies a #: classifier's default folds, as scikit-learn's does. _estimator_type = "regressor"
[docs] def __init__(self, alpha=1.0, *, l1_ratio=0.5, fit_intercept=True, max_iter=1000, tol=1e-3, solver="auto", selection="cyclic", precompute=False, copy_X=True, warm_start=False, positive=False, random_state=None): # cuML's own guards first, in their order and with their messages # (`elastic_net.py:233-240`), so a script that catches theirs # catches these. if alpha < 0.0: raise ValueError(f"Expected alpha >= 0, got {alpha}") if selection not in _SELECTION_SHUFFLE: raise ValueError(f"selection {selection!r} is not supported") if l1_ratio < 0.0 or l1_ratio > 1.0: raise ValueError(f"Expected 0.0 <= l1_ratio <= 1.0, got {l1_ratio}") if solver not in ("auto", "cd", "qn"): raise ValueError(f"solver={solver!r} is not supported") # Then the refusals that are this implementation's, each by name. if solver == "qn": raise NotImplementedError( "mojolearn ElasticNet: solver='qn' is not implemented. cuML's " "solver='auto' picks 'qn' only for SPARSE input " "(elastic_net.py:243-244), and 'qn' under an l1 penalty is " "OWL-QN (min_owlqn, qn_solvers.cuh), which glm/NOT_IMPLEMENTED.tsv " "lists as not implemented. solver='cd' (cuML's 'auto' for dense " "input) is the implemented arm; see solver/NOT_IMPLEMENTED.tsv" ) if selection == "random": raise NotImplementedError( "mojolearn ElasticNet: selection='random' is REFUSED BY " "NAME. cuML seeds std::mt19937(0) and shuffles the " "coordinate order with std::shuffle each epoch " "(cd.cuh:198-199, :222), and the C++ standard does not specify " "std::shuffle's algorithm, so their permutation is a " "function of the toolchain rather than of the seed and " "cannot be gated bitwise or certified across vendors. An " "exact implementation is DEVIATION 611, reserved and not spent; see " "solver/NOT_IMPLEMENTED.tsv" ) if positive: raise NotImplementedError( "mojolearn ElasticNet: positive=True is not supported " "(cuML raises UnsupportedOnGPU for it too, " "elastic_net.py:143)" ) if warm_start: raise NotImplementedError( "mojolearn ElasticNet: warm_start=True is not supported " "(cuML raises UnsupportedOnGPU for it too, " "elastic_net.py:146). cdFit reads coef as its starting " "point and solver/estimator.mojo zeroes it on every fit" ) if precompute is not False: raise NotImplementedError( "mojolearn ElasticNet: precompute is not supported (cuML " "raises UnsupportedOnGPU for it too, elastic_net.py:149)" ) if not copy_X: raise NotImplementedError( "mojolearn ElasticNet: copy_X=False is refused. The design " "crosses to the device as a copy and the caller's array is " "never written (DEVIATION 880); there is no in-place arm to " "select" ) # random_state is accepted and stored, scikit-learn's semantics: # it seeds the coordinate draw of selection='random' only and is # inert under 'cyclic' (sklearn ElasticNet: "Used when selection == # 'random'"). 'random' is refused by name above (DEVIATION 611), so # no fit reads it and no output bit depends on it. Refusing it broke # every nested Lasso(random_state=...) (the board's voting-reg and # stacking-reg members), so it is now accepted. if random_state is not None and ( isinstance(random_state, bool) or not (isinstance(random_state, int) or hasattr(random_state, "randint") or hasattr(random_state, "integers"))): raise ValueError( f"random_state={random_state!r}: None, an int or a " "numpy random state" ) self.alpha = alpha self.l1_ratio = l1_ratio self.fit_intercept = fit_intercept self.max_iter = max_iter self.tol = tol self.solver = solver self.selection = selection self.precompute = False self.copy_X = True self.warm_start = False self.positive = False self.random_state = random_state
def _as_fortran(self, X, name): """A float32 column-major Array over X, and whether that cost the one copy it may cost (DEVIATION 2367). `cdFit` reads the design in cuML's `F` order (element (i, j) at `j * n_rows + i`). Returned alongside the flag so the caller can keep the Array alive across the Mojo call, which `_buffer.py`'s borrow contract requires. """ f, copied = as_f32_colmajor(X, name=name) # THE ONE THING A SHAPE CHECK CANNOT CATCH LATER. `cd_fit` takes a # bare address plus n_rows and n_cols, and a C-order buffer of the # same size is a VALID transposed design: the fit would run and # return plausible coefficients for the wrong matrix. Two flags of # reading here is the only place that can be caught. if not f.flags["F_CONTIGUOUS"] or f.dtype != "<f4": raise AssertionError( "mojolearn: internal -- the design handed to cd_fit must be " f"float32 and Fortran-ordered, got {f.dtype} " f"F_CONTIGUOUS={f.flags['F_CONTIGUOUS']}" ) return f, copied def _solver(self): """The binding `fit` and `predict` call; the host subclass of `_classical_host.py` answers the CPU binding instead.""" return _mojolearn_solver
[docs] def fit(self, X, y, sample_weight=None): """Fit the model by coordinate descent. Parameters ---------- X : array-like of shape (n_samples, n_features) Dense training data, read as float32. Sparse input raises ``NotImplementedError``. y : array-like of shape (n_samples,) Target values. A 2-D ``y`` or a length mismatch raises ``ValueError``. sample_weight : None Must be None; weights raise ``NotImplementedError``. Returns ------- self : ElasticNet The fitted estimator. """ # The CPU inference boundary: ElasticNet does not inherit # NumericModeMixin's guard, so on a CPU-only install the fit refuses # outside mojolearn._cpu_reference.reference_training(). from ._cpu_reference import require_training require_training(self) if sample_weight is not None: raise NotImplementedError( "mojolearn ElasticNet: sample_weight is not implemented " "(cd.cuh:156-194 and :274-287 -- the weighted " "preProcessData, the sqrt-weight scaling of input and " "labels and its undo; solver/NOT_IMPLEMENTED.tsv). " "solver/impl/cd.mojo refuses it by name" ) if hasattr(X, "toarray") or hasattr(X, "tocsr"): raise NotImplementedError( "mojolearn ElasticNet: sparse X is refused. cdFit is dense " "only; cuML routes sparse input to solver='qn' and raises " "for solver='cd' (elastic_net.py:265-269), and 'qn' is not " "implemented" ) # lane/gap-nv-classical2: a float32 C-order design crosses AS IT IS # and the device transposes it (`cd_fit_host`'s row_major arm), so # no host transpose sits inside the fit. Every other input takes the # one F-order copy as before. a, a_copied = _materialize(X, "X") row_major = bool(a.ndim == 2 and a.dtype == "<f4" and a.size and a.order == "C" and not a._both_orders()) if row_major: work_x, copied = a, a_copied else: work_x, copied = self._as_fortran(a, "X") copied = copied or a_copied self.input_copied_ = copied self.fortran_copied_ = copied n_rows, n_cols = work_x.shape if len(_shape_of(y)) != 1: raise ValueError( "mojolearn ElasticNet currently requires one target") target, _ = as_f32_c(y, ndim=1, name="y") if target.shape[0] != n_rows: raise ValueError("mojolearn ElasticNet X and y lengths differ") self.coef_ = zeros((n_cols,), "<f4") info = zeros((1,), "<f4") n_iter = self._solver().cd_fit( addr_ro(work_x, name="X"), addr_ro(target, name="y"), addr(self.coef_, name="coef_"), addr(info, name="info"), # ORDER MATCHES bindings/_mojolearn_solver.mojo::cd_fit_binding. # n_rows, n_cols, fit_intercept, max_iter, alpha, l1_ratio, tol, # shuffle, has_sample_weight, row_major [ n_rows, n_cols, 1 if self.fit_intercept else 0, int(self.max_iter), float(self.alpha), float(self.l1_ratio), float(self.tol), _SELECTION_SHUFFLE[self.selection], 0, 1 if row_major else 0, ], ) self.intercept_ = float(info[0]) self.n_iter_ = int(n_iter) self.n_features_in_ = n_cols # `work_x` is the object the address above belongs to; it lives in # this frame for the whole call, which is the borrow contract. return self
[docs] def predict(self, X): """Predict with the fitted model. Parameters ---------- X : array-like of shape (n_samples, n_features) Samples, read as float32; at least two rows. Returns ------- y_pred : array of shape (n_samples,), float32 ``X @ coef_ + intercept_``. Raises ------ ValueError If the model is not fitted, or ``X`` has a different number of features than the training data. """ if not hasattr(self, "coef_"): raise ValueError("mojolearn ElasticNet: call fit before predict") work_x, _ = self._as_fortran(X, "X") if work_x.shape[1] != self.n_features_in_: raise ValueError( "mojolearn ElasticNet feature count differs from fit") out = empty((work_x.shape[0],), "<f4") self._solver().cd_predict( addr_ro(work_x, name="X"), addr_ro(self.coef_, name="coef_"), addr(out, name="predictions"), # ORDER MATCHES bindings/_mojolearn_solver.mojo::cd_predict_binding. # n_rows, n_cols, intercept [work_x.shape[0], work_x.shape[1], float(self.intercept_)], ) return out
# The file: `coef_` as fitted, the intercept <f8, `hyper` <f8 [alpha, # l1_ratio, tol], `meta` <i8 [n_features_in_, fit_intercept, max_iter, # n_iter_] and the numeric mode (lane/inference-linear-svm, 2026-09-15).
[docs] def save(self, path): """Save the fitted model to ``path`` as an ``.npz`` file. The file stores ``coef_``, the intercept, ``alpha``, ``l1_ratio``, ``tol``, ``max_iter``, ``fit_intercept``, ``n_iter_`` and the numeric mode. ``mojolearn.host_model(path)`` can predict from the file on a machine without a GPU. Parameters ---------- path : str or path-like Destination file. Raises ------ RuntimeError If the model is not fitted. """ if not hasattr(self, "coef_"): raise RuntimeError("this estimator is not fitted yet") arrays = { "format": _CD_FORMAT, "estimator": type(self).__name__, "numeric_mode": _saved_mode(self), "coef": self.coef_, "intercept": Array.from_list([float(self.intercept_)], "<f8"), "hyper": Array.from_list( [float(self.alpha), float(self.l1_ratio), float(self.tol)], "<f8"), "meta": Array.from_list( [int(self.n_features_in_), 1 if self.fit_intercept else 0, int(self.max_iter), int(self.n_iter_)], "<i8"), } return _serialize.write_npz(path, arrays)
[docs] @classmethod def load(cls, path): """Load a model written by ``save``. The loaded model predicts with the saved coefficients and intercept and carries the saved hyperparameters and ``n_iter_``. Parameters ---------- path : str or path-like A file written by ``save`` of the same class. Returns ------- model : ElasticNet or Lasso Raises ------ ValueError If the file was saved by a different estimator or its fields do not match. """ arrays = _serialize.read_npz(path, _CD_FORMAT) _check_saved_by(arrays, path, cls) meta = _serialize.exact(arrays, "meta", "<i8") if meta.size != 4: raise ValueError(f"mojolearn: {path!r} meta holds {meta.size} fields, 4 are needed") hyper = _serialize.exact(arrays, "hyper", "<f8") if hyper.size != 3: raise ValueError(f"mojolearn: {path!r} hyper holds {hyper.size} fields, 3 are needed") kwargs = dict(alpha=float(hyper[0]), fit_intercept=bool(int(meta[1])), max_iter=int(meta[2]), tol=float(hyper[2])) if not issubclass(cls, Lasso): kwargs["l1_ratio"] = float(hyper[1]) elif float(hyper[1]) != 1.0: raise ValueError(f"mojolearn: {path!r} carries l1_ratio {float(hyper[1])}, a Lasso is 1.0") obj = cls(**kwargs) _restore_mode(obj, arrays) obj.n_features_in_ = int(meta[0]) obj.coef_ = _serialize.exact(arrays, "coef", "<f4") if obj.coef_.ndim != 1 or obj.coef_.size != obj.n_features_in_: raise ValueError(f"mojolearn: {path!r} coef does not match n_features_in_") intercept = _serialize.exact(arrays, "intercept", "<f8") if intercept.size != 1: raise ValueError(f"mojolearn: {path!r} intercept must hold one value") obj.intercept_ = float(intercept[0]) obj.n_iter_ = int(meta[3]) return obj
# `linear_model._r2_host` (formerly a sequential float64 host reduction, # DEVIATION 2365).
[docs] def score(self, X, y): """Return the coefficient of determination R^2 of the prediction. Parameters ---------- X : array-like of shape (n_samples, n_features) Test samples. y : array-like of shape (n_samples,) True target values. Returns ------- score : float ``1 - SS_res / SS_tot``, computed in float64. A constant ``y`` scores 0.0. """ return _r2_host(self.predict(X), y)
# Maintainer note: cuML spells Lasso as ElasticNet(l1_ratio=1.0) # (cuml/linear_model/lasso.py:129-140, "Lasso is just a special case of # ElasticNet"), and so does scikit-learn; the l2 term is absent and # l1_alpha = alpha * n_rows. See ElasticNet's notes.
[docs] class Lasso(ElasticNet): """Linear regression with an l1 penalty, fitted by coordinate descent. ``Lasso(alpha)`` is ``ElasticNet(alpha, l1_ratio=1.0)``: it minimizes ``(1 / (2 * n_samples)) * ||y - X w||^2 + alpha * ||w||_1``, the same objective as scikit-learn's ``Lasso``. Everything in ``ElasticNet``'s description applies, including the stopping rule, the absolute zero-column threshold and the refused options. ``tol`` defaults to 1e-3 (cuML's); scikit-learn's is 1e-4. Parameters ---------- alpha : float, default 1.0 Penalty strength, finite and >= 0. fit_intercept : bool, default True Whether to fit an (unpenalized) intercept. max_iter : int, default 1000 Maximum number of coordinate-descent epochs. tol : float, default 1e-3 Stopping tolerance. solver, selection, precompute, copy_X, warm_start, positive, random_state As on ``ElasticNet``. Attributes ---------- coef_ : array of shape (n_features,), float32 The fitted coefficients; the l1 penalty sets some exactly to zero. intercept_ : float The fitted intercept; exactly 0.0 when ``fit_intercept`` is False. n_iter_ : int The number of epochs run. n_features_in_ : int The number of features seen in ``fit``. Examples -------- >>> import numpy as np, mojolearn >>> rng = np.random.default_rng(0) >>> X = rng.random((1000, 8), dtype=np.float32) >>> y = X @ np.arange(8, dtype=np.float32) + 1.0 >>> model = mojolearn.Lasso(alpha=0.01).fit(X, y) >>> pred = model.predict(X) """
[docs] def __init__(self, alpha=1.0, *, fit_intercept=True, max_iter=1000, tol=1e-3, solver="auto", selection="cyclic", precompute=False, copy_X=True, warm_start=False, positive=False, random_state=None): super().__init__( alpha=alpha, l1_ratio=1.0, fit_intercept=fit_intercept, max_iter=max_iter, tol=tol, solver=solver, selection=selection, precompute=precompute, copy_X=copy_X, warm_start=warm_start, positive=positive, random_state=random_state, )