Scikit-learn integration ======================== When to use the estimator ------------------------- Use ``BezierRegressor`` when a Bézier surrogate belongs in a scikit-learn workflow: cloning estimators, cross-validation, a parameter grid, or a larger pipeline. It delegates fitting to the same numerical core as ``fit`` and exposes the complete numerical result. It does not introduce a different meaning of degree, penalty or sample weighting. Install ``pytorch-bsf[sklearn]``. The estimator handles one simplex or product domain with NumPy-compatible arrays. Use the Python ``Points`` and ``Samples`` API for assembled mixed-cell observations, because a feature matrix alone does not carry the required per-row cell IDs. Prepare barycentric features ---------------------------- A segment input uses two columns ``[1-x, x]``, where ``0 <= x <= 1``. These are dependent barycentric coordinates, not two arbitrary tabular features. Do not pass a single ``x`` column expecting an ordinary polynomial regressor. By default one input column denotes a constant domain and its numeric values are ignored; the column is only a scikit-learn-compatible dummy feature. .. testcode:: sklearn :skipif: __import__("importlib.util").util.find_spec("sklearn") is None import numpy as np from sklearn.model_selection import GridSearchCV, KFold from torch_bsf import QuadraticPenalty from torch_bsf.sklearn import BezierRegressor x = np.linspace(0, 1, 30) X = np.column_stack([1 - x, x]) y = np.sin(np.pi * x) + .2 * x estimator = BezierRegressor(degree=3) estimator.fit(X, y) prediction = estimator.predict(X) assert prediction.shape == y.shape assert estimator.result_.success assert estimator.diagnostics_.compute_dtype == "float64" ``dimensions=None`` infers one simplex from the column count. A triangle uses three columns. A square represented as a product uses four columns, ``[1-u, u, 1-v, v]``, and requires ``dimensions=(1, 1)``. A prism requires ``dimensions=(2, 1)`` and five columns. Each factor block must independently sum to one; the entire product row generally does not. Choose degree and penalty through validation -------------------------------------------- .. testcode:: sklearn :skipif: __import__("importlib.util").util.find_spec("sklearn") is None search = GridSearchCV( BezierRegressor(), {"degree": [1, 2, 3, 5], "penalty": [QuadraticPenalty(), QuadraticPenalty(ridge=1e-4)]}, scoring="neg_mean_squared_error", cv=KFold(3, shuffle=True, random_state=7), ) search.fit(X, y) best = search.best_estimator_ assert best.result_.success assert best.predict(X).shape == (30,) Scikit-learn maximizes its score, so negative MSE closer to zero is better. ``GridSearchCV`` refits its chosen estimator by default, unlike the core ``select_degree`` operation, which requires an explicit full-data refit. Inspect ``cv_results_`` rather than reporting only the winning degree. Increasing degree adds controls and can overfit noise. Increasing ridge shrinks controls toward the output origin; increasing smoothness draws adjacent controls together. Pass complete typed ``QuadraticPenalty`` objects in the grid, as above; the penalty is an immutable numerical configuration, not a nested scikit-learn estimator. See :doc:`regularization` for units and :doc:`auto_degree` for an actual before/after degree comparison. .. figure:: ../_static/guides/degree.png :width: 100% :alt: Candidate curve fits and their cross-validation errors. The numerical core's degree comparison illustrates the same capacity trade-off used by the estimator. The figure is generated by the core three-fold search; it is not the output of the separate GridSearchCV example above, whose scoring and refit policy should be recorded separately. Weights, outputs and evidence ----------------------------- ``estimator.fit(X, y, sample_weight=w)`` supplies row weights. Larger relative weights emphasize particular observations. Uniformly scaling all weights leaves the normalized regression problem unchanged. A one-dimensional ``y`` returns a one-dimensional prediction; multioutput ``y`` returns a two-dimensional array with the same output count. Predictions are NumPy arrays from the CPU float64 model, even if the original input arrays were float32. ``result_`` holds metrics, diagnostics, model and termination status; ``model_`` is the shared ``BezierModel``; ``diagnostics_`` exposes solver evidence directly. The estimator requires numerical success during fitting, so a failed solve raises through ``require_success``. A successful fit can still be unidentifiable or statistically poor; inspect data rank and held-out error. A high default R-squared score is not an application-specific error guarantee, especially for low-variance outputs. Avoid using a generic feature standardizer on barycentric columns inside a pipeline: it generally breaks their domain constraints. If ordinary scalar features need conversion into barycentric blocks, perform that explicit conversion first. Output transforms must be inverted when reporting original units and fitted independently within each validation fold. See :doc:`normalization` for that distinction. For product degrees use a tuple matching ``dimensions``. Fixed controls and typed solvers use the same constructor arguments as the core API. If a search changes degree, fixed global row numbers cannot be reused blindly: resolve constraints for each space in a manual core validation loop. Save ``best.model_`` with ``io.save_model`` when only the common numerical model is needed; store preprocessing and feature schemas alongside it in the application. Fit independent input blocks as a product ----------------------------------------- .. testcode:: sklearn :skipif: __import__("importlib.util").util.find_spec("sklearn") is None u, v = np.meshgrid(np.linspace(0, 1, 8), np.linspace(0, 1, 7), indexing="ij") u, v = u.ravel(), v.ravel() product_X = np.column_stack([1 - u, u, 1 - v, v]) product_y = np.square(u) + v product = BezierRegressor(dimensions=(1, 1), degree=(2, 1)) product.fit(product_X, product_y) test_u = np.array([.13, .47, .81]) test_v = np.array([.22, .56, .73]) test_X = np.column_stack([1 - test_u, test_u, 1 - test_v, test_v]) assert np.allclose(product.predict(test_X), np.square(test_u) + test_v) The domain is the square pictured in the :ref:`simploid overview `. Its two factors have different degrees because the target is quadratic in ``u`` and linear in ``v``. Without explicit ``dimensions``, four columns would imply one three-dimensional simplex, whose sum-to-one constraint these rows violate. Random row folds assess interpolation among the sampled combinations. If the intended task is prediction at entirely unseen operating conditions, hold out complete ``v`` slices or use an appropriate grouped split instead. Choose a validation design that matches the use of the fitted model rather than relying on a default splitter to define that use implicitly.