From bde6f97cbf11d27a8a904eb1801b81fb2474a95b Mon Sep 17 00:00:00 2001 From: Leona <129308028+Leona-LYT@users.noreply.github.com> Date: Thu, 2 Apr 2026 23:56:44 +0800 Subject: [PATCH 1/3] add classifier and regressor for plq_ElasticNet --- rehline/__init__.py | 4 +- rehline/_sklearn_mixin.py | 545 +++++++++++++++++++++++++++++++++++++- 2 files changed, 547 insertions(+), 2 deletions(-) diff --git a/rehline/__init__.py b/rehline/__init__.py index 270a8a3..c60b729 100644 --- a/rehline/__init__.py +++ b/rehline/__init__.py @@ -13,7 +13,7 @@ from ._loss import ReHLoss from ._mf_class import plqMF_Ridge from ._path_sol import plqERM_Ridge_path_sol -from ._sklearn_mixin import plq_Ridge_Classifier, plq_Ridge_Regressor +from ._sklearn_mixin import plq_Ridge_Classifier, plq_Ridge_Regressor, plq_ElasticNet_Classifier, plq_ElasticNet_Regressor __all__ = ( "_BaseReHLine", @@ -26,6 +26,8 @@ "plqERM_Ridge_path_sol", "plq_Ridge_Classifier", "plq_Ridge_Regressor", + "plq_ElasticNet_Classifier", + "plq_ElasticNet_Regressor", "_make_loss_rehline_param", "_make_constraint_rehline_param", "make_mf_dataset", diff --git a/rehline/_sklearn_mixin.py b/rehline/_sklearn_mixin.py index 0b2fffa..68ba90b 100644 --- a/rehline/_sklearn_mixin.py +++ b/rehline/_sklearn_mixin.py @@ -9,7 +9,7 @@ from sklearn.utils.multiclass import check_classification_targets from sklearn.utils.validation import check_array, check_is_fitted, check_X_y -from ._class import plqERM_Ridge +from ._class import plqERM_Ridge, plqERM_ElasticNet class plq_Ridge_Classifier(plqERM_Ridge, ClassifierMixin): @@ -677,3 +677,546 @@ def __sklearn_tags__(self): tags.input_tags.sparse = False tags.target_tags.required = True return tags + + +class plq_ElasticNet_Classifier(plqERM_ElasticNet, ClassifierMixin): + """ + Empirical Risk Minimization (ERM) Classifier with a Piecewise Linear-Quadratic (PLQ) loss + and elastic net penalty, compatible with the scikit-learn API. + + This wrapper makes ``plqERM_ElasticNet`` behave as a classifier: + - Accepts arbitrary binary labels in the original label space. + - Computes class weights on original labels (if ``class_weight`` is set). + - Encodes labels with ``LabelEncoder`` into {0,1}, then maps to {-1,+1} for training. + - Supports optional intercept fitting (via an augmented constant feature). + - Provides standard methods ``fit``, ``predict``, and ``decision_function``. + - Integrates with scikit-learn ecosystem (e.g., GridSearchCV, Pipeline). + - Supports multiclass classification via OvR or OvO method. + + Parameters + ---------- + loss : dict + Dictionary specifying the loss function parameters. Examples include: + - {'name': 'svm'} + - {'name': 'sSVM'} + - {'name': 'huber'} + and other PLQ losses supported by ``plqERM_ElasticNet``. + + constraint : list of dict, default=[] + Optional constraints. Each dictionary must include a ``'name'`` key. + + C : float, default=1.0 + Inverse regularization strength (scales the loss term). + + l1_ratio : float, default=0.5 + The ElasticNet mixing parameter, 0 <= l1_ratio < 1. + - l1_ratio = 0 → pure Ridge (equivalent to plq_Ridge_Classifier) + - 0 < l1_ratio < 1 → combined L1 + L2 penalty + Must be strictly less than 1.0 to avoid division by zero in rho/C_eff. + + fit_intercept : bool, default=True + Whether to fit an intercept term via an augmented constant feature column. + + intercept_scaling : float, default=1.0 + Value of the constant feature column when ``fit_intercept=True``. + + class_weight : dict, 'balanced', or None, default=None + Class weights applied like in LinearSVC. + + multi_class : str or list, default=[] + Method for multiclass classification: + - 'ovr': One-vs-Rest + - 'ovo': One-vs-One + - [] or ignored when only 2 classes are present. + + n_jobs : int or None, default=None + Number of parallel jobs for multiclass fitting. + + max_iter : int, default=1000 + tol : float, default=1e-4 + shrink : int, default=1 + warm_start : int, default=0 + verbose : int, default=0 + trace_freq : int, default=100 + + Attributes + ---------- + coef_ : ndarray of shape (n_features,) for binary, (n_estimators, n_features) for multiclass + intercept_ : float for binary, ndarray of shape (n_estimators,) for multiclass + classes_ : ndarray of shape (n_classes,) + estimators_ : list, only present for multiclass + _label_encoder : LabelEncoder + """ + + def __init__( + self, + loss, + constraint=[], + C=1.0, + l1_ratio=0.5, + U=np.empty((0, 0)), + V=np.empty((0, 0)), + Tau=np.empty((0, 0)), + S=np.empty((0, 0)), + T=np.empty((0, 0)), + A=np.empty((0, 0)), + b=np.empty((0,)), + max_iter=1000, + tol=1e-4, + shrink=1, + warm_start=0, + verbose=0, + trace_freq=100, + fit_intercept=True, + intercept_scaling=1.0, + class_weight=None, + multi_class=[], + n_jobs=None, + ): + if not (0.0 <= l1_ratio < 1.0): + raise ValueError( + f"l1_ratio must be in [0, 1), got {l1_ratio}. " + f"Use l1_ratio=0 for pure Ridge, or plq_Ridge_Classifier directly." + ) + + super().__init__( + loss=loss, + constraint=constraint, + C=C, + l1_ratio=l1_ratio, + U=U, V=V, Tau=Tau, S=S, T=T, + A=A, b=b, + max_iter=max_iter, + tol=tol, + shrink=shrink, + warm_start=warm_start, + verbose=verbose, + trace_freq=trace_freq, + ) + self.fit_intercept = fit_intercept + self.intercept_scaling = float(intercept_scaling) + self.class_weight = class_weight + self._label_encoder = None + self.classes_ = None + self.multi_class = multi_class + self.n_jobs = n_jobs + + @staticmethod + def _fit_subproblem(estimator, X_aug, y_pm, sample_weight, fit_intercept): + """ + Train a plqERM_ElasticNet instance on a single multiclass subproblem. + + Directly constructs plqERM_ElasticNet from estimator's hyperparameters, + bypassing plq_ElasticNet_Classifier.fit() preprocessing (LabelEncoder, + intercept augmentation) since X_aug and y_pm are already preprocessed. + + Parameters + ---------- + estimator : plq_ElasticNet_Classifier + Source estimator from which hyperparameters are extracted. + + X_aug : ndarray of shape (n_samples, n_features[+1]) + Already preprocessed feature matrix (intercept column included if needed). + + y_pm : ndarray of shape (n_samples,) + Binary labels already in {-1, +1}. + + sample_weight : ndarray or None + + fit_intercept : bool + + Returns + ------- + coef : ndarray of shape (n_features,) + intercept : float + """ + clf = plqERM_ElasticNet( + loss=estimator.loss, + constraint=estimator.constraint, + C=estimator.C, + l1_ratio=estimator.l1_ratio, + max_iter=estimator.max_iter, + tol=estimator.tol, + shrink=estimator.shrink, + warm_start=estimator.warm_start, + verbose=estimator.verbose, + trace_freq=estimator.trace_freq, + ) + clf.fit(X_aug, y_pm, sample_weight=sample_weight) + if fit_intercept: + coef = clf.coef_[:-1].copy() + intercept = float(clf.coef_[-1]) + else: + coef = clf.coef_.copy() + intercept = 0.0 + return coef, intercept + + def fit(self, X, y, sample_weight=None): + """ + Fit the classifier to training data. + + Parameters + ---------- + X : array-like of shape (n_samples, n_features) + y : array-like of shape (n_samples,) + sample_weight : array-like of shape (n_samples,), default=None + + Returns + ------- + self + """ + X, y = check_X_y(X, y, accept_sparse=False, dtype=np.float64, order="C") + self.n_features_in_ = X.shape[1] + + check_classification_targets(y) + + self.classes_ = np.unique(y) + if self.classes_.size < 2: + raise ValueError( + f"plq_ElasticNet_Classifier requires at least 2 classes, " + f"but received {self.classes_.size} class(es): {self.classes_}." + ) + + # Compute class weights on original labels + if self.class_weight is not None: + cw_vec = compute_class_weight( + class_weight=self.class_weight, + classes=self.classes_, + y=y, + ) + cw_map = {c: w for c, w in zip(self.classes_, cw_vec)} + sw_cw = np.asarray([cw_map[yi] for yi in y], dtype=np.float64) + sample_weight = ( + sw_cw if sample_weight is None else (np.asarray(sample_weight) * sw_cw) + ) + + le = LabelEncoder().fit(self.classes_) + self._label_encoder = le + + # Intercept augmentation + X_aug = X + if self.fit_intercept: + col = np.full((X.shape[0], 1), self.intercept_scaling, dtype=X.dtype) + X_aug = np.hstack([X, col]) + + if self.classes_.size == 2: + y01 = le.transform(y) + y_pm = 2 * y01 - 1 + + # super() resolves to plqERM_ElasticNet.fit() + super().fit(X_aug, y_pm, sample_weight=sample_weight) + + if self.fit_intercept: + self.intercept_ = float(self.coef_[-1]) + self.coef_ = self.coef_[:-1].copy() + else: + self.intercept_ = 0.0 + + else: + if self.multi_class not in ('ovr', 'ovo'): + raise ValueError( + f"multi_class must be 'ovr' or 'ovo' for multiclass problems, " + f"got '{self.multi_class}'." + ) + self._fit_multiclass(X_aug, y, sample_weight) + + return self + + def _fit_multiclass(self, X_aug, y, sample_weight=None): + """ + Fit multiple binary classifiers for multiclass classification. + Identical logic to plq_Ridge_Classifier._fit_multiclass; dispatches + to self._fit_subproblem which uses plqERM_ElasticNet internally. + """ + if self.multi_class == 'ovr': + tasks = [ + (X_aug, np.where(y == cls, 1, -1).astype(np.float64), sample_weight) + for cls in self.classes_ + ] + class_pairs = None + + elif self.multi_class == 'ovo': + tasks = [] + class_pairs = [] + for cls_i, cls_j in combinations(self.classes_, 2): + mask = np.isin(y, [cls_i, cls_j]) + y_pm = np.where(y[mask] == cls_j, 1, -1).astype(np.float64) + sw_sub = sample_weight[mask] if sample_weight is not None else None + tasks.append((X_aug[mask], y_pm, sw_sub)) + class_pairs.append((cls_i, cls_j)) + + results = Parallel(n_jobs=self.n_jobs, prefer="threads")( + delayed(self._fit_subproblem)(self, X_sub, y_pm, sw, self.fit_intercept) + for X_sub, y_pm, sw in tasks + ) + + if self.multi_class == 'ovr': + self.estimators_ = [(coef, intercept) for coef, intercept in results] + elif self.multi_class == 'ovo': + self.estimators_ = [ + (coef, intercept, cls_i, cls_j) + for (coef, intercept), (cls_i, cls_j) in zip(results, class_pairs) + ] + + self.coef_ = np.array([e[0] for e in self.estimators_]) + self.intercept_ = np.array([e[1] for e in self.estimators_]) + + def decision_function(self, X): + """ + Compute the decision function for samples in X. + + For binary: 1D array of shape (n_samples,). + For OvR/OvO multiclass: 2D array of shape (n_samples, n_estimators). + """ + check_is_fitted( + self, attributes=["coef_", "intercept_", "_label_encoder", "classes_"] + ) + X = check_array(X, accept_sparse=False, dtype=np.float64, order="C") + return X @ self.coef_.T + self.intercept_ + + def predict(self, X): + """ + Predict class labels for samples in X. + + Binary: threshold at 0. + OvR: argmax across K classifiers. + OvO: majority vote + normalized confidence tie-breaking. + """ + scores = self.decision_function(X) + + if self.classes_.size == 2: + pred01 = (scores >= 0).astype(int) + return self._label_encoder.inverse_transform(pred01) + + elif self.multi_class == 'ovr': + idx = np.argmax(scores, axis=1) + return self.classes_[idx] + + elif self.multi_class == 'ovo': + n_samples = X.shape[0] + n_classes = len(self.classes_) + votes = np.zeros((n_samples, n_classes), dtype=np.float64) + sum_of_confidences = np.zeros((n_samples, n_classes), dtype=np.float64) + + for k, (_, _, cls_i, cls_j) in enumerate(self.estimators_): + i = np.where(self.classes_ == cls_i)[0][0] + j = np.where(self.classes_ == cls_j)[0][0] + + pred = (scores[:, k] > 0).astype(int) + votes[:, j] += pred + votes[:, i] += 1 - pred + + sum_of_confidences[:, j] += scores[:, k] + sum_of_confidences[:, i] -= scores[:, k] + + transformed_confidences = sum_of_confidences / ( + 3 * (np.abs(sum_of_confidences) + 1) + ) + return self.classes_[np.argmax(votes + transformed_confidences, axis=1)] + + def __sklearn_tags__(self): + tags = super().__sklearn_tags__() + tags.estimator_type = "classifier" + tags.classifier_tags = ClassifierTags() + tags.target_tags.required = True + tags.input_tags.sparse = False + return tags + +class plq_ElasticNet_Regressor(plqERM_ElasticNet, RegressorMixin): + """ + Empirical Risk Minimization (ERM) regressor with a Piecewise Linear-Quadratic (PLQ) loss + and an elastic net penalty, implemented as a scikit-learn compatible estimator. + + This wrapper makes ``plqERM_ElasticNet`` behave as a regressor: + - Supports optional intercept fitting via an augmented constant feature column. + - Provides standard methods ``fit``, ``predict``, and ``decision_function``. + - Integrates with the scikit-learn ecosystem (e.g., GridSearchCV, Pipeline). + + Notes + ----- + - **Intercept handling**: if ``fit_intercept=True``, a constant column + (value = ``intercept_scaling``) is appended to the right of the design + matrix before calling the base solver. The last learned coefficient is + then split out as ``intercept_``. + Original feature indices are therefore unaffected; ``sen_idx`` in a + ``'fair'`` constraint continues to reference the original columns. + - **Sparse input**: not supported. Convert to dense before fitting. + + Parameters + ---------- + loss : dict, default={'name': 'QR', 'qt': 0.5} + PLQ loss configuration. Examples: + ``{'name': 'QR', 'qt': 0.5}``, ``{'name': 'huber', 'tau': 1.0}``, + ``{'name': 'SVR', 'epsilon': 0.1}``. + + constraint : list of dict, default=[] + Constraint specifications: + - ``{'name': 'nonnegative'}`` or ``{'name': '>=0'}`` + - ``{'name': 'fair', 'sen_idx': list[int], 'tol_sen': list[float]}`` + - ``{'name': 'custom', 'A': ndarray[K, d], 'b': ndarray[K]}`` + + C : float, default=1.0 + Regularization parameter (scales the loss term). + + l1_ratio : float, default=0.5 + The ElasticNet mixing parameter, 0 <= l1_ratio < 1. + - l1_ratio = 0 → pure Ridge (equivalent to plq_Ridge_Regressor) + - 0 < l1_ratio < 1 → combined L1 + L2 penalty + Must be strictly less than 1.0 to avoid division by zero in rho/C_eff. + + fit_intercept : bool, default=True + If True, append a constant column (value = ``intercept_scaling``) to + the design matrix before solving. The last learned coefficient is then + extracted as ``intercept_``. + + intercept_scaling : float, default=1.0 + Scaling applied to the appended constant column when + ``fit_intercept=True``. + + max_iter : int, default=1000 + tol : float, default=1e-4 + shrink : int, default=1 + warm_start : int, default=0 + verbose : int, default=0 + trace_freq : int, default=100 + + Attributes + ---------- + coef_ : ndarray of shape (n_features,) + Learned linear coefficients (excluding the intercept term). + intercept_ : float + Intercept term. 0.0 if ``fit_intercept=False``. + n_features_in_ : int + Number of input features seen during :meth:`fit` (before intercept + augmentation). + """ + + def __init__( + self, + loss={"name": "QR", "qt": 0.5}, + constraint=[], + C=1.0, + l1_ratio=0.5, + U=np.empty((0, 0)), + V=np.empty((0, 0)), + Tau=np.empty((0, 0)), + S=np.empty((0, 0)), + T=np.empty((0, 0)), + A=np.empty((0, 0)), + b=np.empty((0,)), + max_iter=1000, + tol=1e-4, + shrink=1, + warm_start=0, + verbose=0, + trace_freq=100, + fit_intercept=True, + intercept_scaling=1.0, + ): + if not (0.0 <= l1_ratio < 1.0): + raise ValueError( + f"l1_ratio must be in [0, 1), got {l1_ratio}. " + f"Use l1_ratio=0 for pure Ridge, or plq_Ridge_Regressor directly." + ) + + super().__init__( + loss=loss, + constraint=constraint, + C=C, + l1_ratio=l1_ratio, + U=U, V=V, Tau=Tau, S=S, T=T, + A=A, b=b, + max_iter=max_iter, + tol=tol, + shrink=shrink, + warm_start=warm_start, + verbose=verbose, + trace_freq=trace_freq, + ) + self.fit_intercept = fit_intercept + self.intercept_scaling = float(intercept_scaling) + + def fit(self, X, y, sample_weight=None): + """ + Fit the regressor to training data. + + If ``fit_intercept=True``, a constant column (value = + ``intercept_scaling``) is appended to the right of ``X`` before + calling the base solver (``plqERM_ElasticNet.fit``). After solving, + the last coefficient is split as ``intercept_`` and removed from + ``coef_``. + + Parameters + ---------- + X : ndarray of shape (n_samples, n_features) + Training design matrix (dense). Sparse inputs are not supported. + y : ndarray of shape (n_samples,) + Target values. + sample_weight : ndarray of shape (n_samples,), default=None + Optional per-sample weights; forwarded to the underlying solver. + + Returns + ------- + self : object + Fitted estimator. + """ + X, y = check_X_y(X, y, accept_sparse=False, dtype=np.float64, order="C") + self.n_features_in_ = X.shape[1] + + X_aug = X + if self.fit_intercept: + col = np.full((X.shape[0], 1), self.intercept_scaling, dtype=X.dtype) + X_aug = np.hstack([X, col]) + + # MRO resolves super() to plqERM_ElasticNet.fit() + super().fit(X_aug, y, sample_weight=sample_weight) + + if self.fit_intercept: + self.intercept_ = float(self.coef_[-1]) + self.coef_ = self.coef_[:-1].copy() + else: + self.intercept_ = 0.0 + + return self + + def decision_function(self, X): + """ + Compute f(X) = X @ coef_ + intercept_. + + Parameters + ---------- + X : ndarray of shape (n_samples, n_features) + Input data (dense). + + Returns + ------- + scores : ndarray of shape (n_samples,) + Predicted real-valued scores. + """ + check_is_fitted(self, attributes=["coef_", "intercept_"]) + X = check_array(X, accept_sparse=False, dtype=np.float64, order="C") + return X @ self.coef_ + self.intercept_ + + def predict(self, X): + """ + Predict target values as the linear decision function. + + Parameters + ---------- + X : ndarray of shape (n_samples, n_features) + Input data (dense). + + Returns + ------- + y_pred : ndarray of shape (n_samples,) + Predicted target values (real-valued). + """ + return self.decision_function(X) + + def __sklearn_tags__(self): + tags = super().__sklearn_tags__() + tags.estimator_type = "regressor" + tags.regressor_tags = RegressorTags() + tags.input_tags.sparse = False + tags.target_tags.required = True + return tags \ No newline at end of file From 0f9118f4c7c2f7cbe0a9f9ceff32fa6bd0b33b9f Mon Sep 17 00:00:00 2001 From: Leona <129308028+Leona-LYT@users.noreply.github.com> Date: Tue, 7 Apr 2026 20:05:24 +0800 Subject: [PATCH 2/3] add tutorial for ElasticNet --- doc/source/example.rst | 2 + doc/source/examples/ElasticNet.ipynb | 1466 ++++++++++++++++++++++ doc/source/tutorials/ReHLine_sklearn.rst | 1 + 3 files changed, 1469 insertions(+) create mode 100644 doc/source/examples/ElasticNet.ipynb diff --git a/doc/source/example.rst b/doc/source/example.rst index 33d145f..bba8ebc 100644 --- a/doc/source/example.rst +++ b/doc/source/example.rst @@ -20,6 +20,7 @@ Example Gallery examples/Sklearn_Mixin.ipynb examples/Multiclass_Classification.ipynb examples/NMF.ipynb + examples/ElasticNet.ipynb List of Examples ---------------- @@ -39,3 +40,4 @@ List of Examples examples/Sklearn_Mixin.ipynb examples/Multiclass_Classification.ipynb examples/NMF.ipynb + examples/ElasticNet.ipynb diff --git a/doc/source/examples/ElasticNet.ipynb b/doc/source/examples/ElasticNet.ipynb new file mode 100644 index 0000000..b9f1090 --- /dev/null +++ b/doc/source/examples/ElasticNet.ipynb @@ -0,0 +1,1466 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "xSWYNZ1zvzCA" + }, + "source": [ + "# ElasticNet Compatible Estimators\n", + "\n", + "[](https://rehline-python.readthedocs.io/en/latest/)\n", + "\n", + "The core class `plqERM_ElasticNet` serves as a base implementation for both classification and regression tasks. Its subclasses, `plq_ElasticNet_Classifier` and `plq_ElasticNet_Regressor`, extend the Ridge-based variants by introducing an additional `l1_ratio` parameter that controls the mix between L1 and L2 regularization. These estimators integrate seamlessly with scikit-learn utilities such as `Pipeline`, `cross_val_score`, and `GridSearchCV`." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HDGBmNUmxZtn" + }, + "source": [ + "ElasticNet regularization solves the following optimization problem:\n", + "\n", + "$$\n", + "\\min_{\\beta \\in \\mathbb{R}^d} \\; C \\sum_{i=1}^{n} \\text{PLQ}(y_i, \\mathbf{x}_i^T \\beta) + \\ell_1\\text{ratio} \\|\\beta\\|_1 + \\frac{1}{2}(1 - \\ell_1\\text{ratio})\\|\\beta\\|_2^2, \\quad \\text{s.t.} \\quad \\mathbf{A}\\beta + \\mathbf{b} \\geq \\mathbf{0},\n", + "$$\n", + "\n", + "where\n", + "\n", + "- $\\text{PLQ}(\\cdot)$ is a piecewise linear-quadratic loss function (e.g., SVM hinge, quantile, Huber),\n", + "- $\\mathbf{x}_i \\in \\mathbb{R}^d$ is a feature vector,\n", + "- $y_i$ is the response variable (class label or continuous value),\n", + "- $C > 0$ is the regularization strength (larger $C$ = less regularization),\n", + "- $\\ell_1\\text{ratio} \\in [0, 1]$ is the mixing parameter: $\\ell_1\\text{ratio} = 1$ gives Lasso, $\\ell_1\\text{ratio} = 0$ gives Ridge,\n", + "- $\\mathbf{A}\\beta + \\mathbf{b} \\geq \\mathbf{0}$ represents optional linear constraints on $\\beta$." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "L_j1q7cFEBxy" + }, + "source": [ + "#### Classification Example with GridSearchCV and Pipeline\n", + "\n", + "Here we show a classification example using `Pipeline`, `cross_val_score`, and `GridSearchCV`. Compared to the Ridge classifier, the key difference is the additional `l1_ratio` parameter in `param_grid`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "id": "39IeObaaHBDz" + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "from sklearn.datasets import make_classification\n", + "from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score\n", + "from sklearn.pipeline import Pipeline\n", + "from sklearn.preprocessing import StandardScaler\n", + "from sklearn.metrics import accuracy_score, classification_report, confusion_matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "id": "Rc33Ym8ZHB6a" + }, + "outputs": [], + "source": [ + "# generate the dataset\n", + "X, y = make_classification(\n", + " n_samples=2000,\n", + " n_features=20,\n", + " n_informative=8,\n", + " n_redundant=4,\n", + " n_repeated=0,\n", + " n_classes=2,\n", + " weights=[0.7, 0.3],\n", + " class_sep=1.2,\n", + " flip_y=0.01,\n", + " random_state=42,\n", + ")\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(\n", + " X, y, test_size=0.25, stratify=y, random_state=42\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "id": "Q54w-eLSHDlq" + }, + "outputs": [], + "source": [ + "from rehline import plq_ElasticNet_Classifier\n", + "\n", + "# set the pipeline\n", + "pipe = Pipeline([\n", + " (\"scaler\", StandardScaler()),\n", + " (\"clf\", plq_ElasticNet_Classifier(loss={\"name\": \"svm\"})),\n", + "])" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "id": "c8hG_-p5HFRk" + }, + "outputs": [], + "source": [ + "# set the parameter grid\n", + "param_grid = {\n", + " \"clf__loss\": [{\"name\": \"svm\"}, {\"name\": \"sSVM\"}],\n", + " \"clf__C\": [0.1, 1.0, 3.0],\n", + " \"clf__l1_ratio\": [0.0, 0.3, 0.5, 0.8],\n", + " \"clf__fit_intercept\": [True, False],\n", + " \"clf__intercept_scaling\": [0.5, 1.0, 2.0],\n", + " \"clf__max_iter\": [5000, 10000],\n", + " \"clf__class_weight\": [None, \"balanced\", {0: 1.0, 1: 2.0}],\n", + " \"clf__constraint\": [\n", + " [],\n", + " [{\"name\": \"nonnegative\"}],\n", + " [{\"name\": \"fair\", \"sen_idx\": [0], \"tol_sen\": 0.1}],\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "TrRcyQP8HILw", + "outputId": "3d4e7e02-2a0a-4f36-b71e-5283f59d8f2f" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CV scores: [0.79666667 0.82 0.82666667 0.81 0.81 ]\n" + ] + } + ], + "source": [ + "# cross_val_score\n", + "cv_scores = cross_val_score(\n", + " pipe,\n", + " X_train, y_train,\n", + " cv=5,\n", + " scoring=\"accuracy\",\n", + " n_jobs=-1,\n", + ")\n", + "print(\"CV scores:\", cv_scores)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 207 + }, + "id": "hMvSW0ifHJnZ", + "outputId": "eaeb9c6f-e206-401c-fd61-9e6de3f41499" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fitting 5 folds for each of 2592 candidates, totalling 12960 fits\n" + ] + }, + { + "data": { + "text/html": [ + "
GridSearchCV(cv=5,\n",
+ " estimator=Pipeline(steps=[('scaler', StandardScaler()),\n",
+ " ('clf',\n",
+ " plq_ElasticNet_Classifier(loss={'name': 'svm'}))]),\n",
+ " n_jobs=-1,\n",
+ " param_grid={'clf__C': [0.1, 1.0, 3.0],\n",
+ " 'clf__class_weight': [None, 'balanced',\n",
+ " {0: 1.0, 1: 2.0}],\n",
+ " 'clf__constraint': [[], [{'name': 'nonnegative'}],\n",
+ " [{'name': 'fair', 'sen_idx': [0],\n",
+ " 'tol_sen': 0.1}]],\n",
+ " 'clf__fit_intercept': [True, False],\n",
+ " 'clf__intercept_scaling': [0.5, 1.0, 2.0],\n",
+ " 'clf__l1_ratio': [0.0, 0.3, 0.5, 0.8],\n",
+ " 'clf__loss': [{'name': 'svm'}, {'name': 'sSVM'}],\n",
+ " 'clf__max_iter': [5000, 10000]},\n",
+ " scoring='accuracy', verbose=1)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. GridSearchCV(cv=5,\n",
+ " estimator=Pipeline(steps=[('scaler', StandardScaler()),\n",
+ " ('clf',\n",
+ " plq_ElasticNet_Classifier(loss={'name': 'svm'}))]),\n",
+ " n_jobs=-1,\n",
+ " param_grid={'clf__C': [0.1, 1.0, 3.0],\n",
+ " 'clf__class_weight': [None, 'balanced',\n",
+ " {0: 1.0, 1: 2.0}],\n",
+ " 'clf__constraint': [[], [{'name': 'nonnegative'}],\n",
+ " [{'name': 'fair', 'sen_idx': [0],\n",
+ " 'tol_sen': 0.1}]],\n",
+ " 'clf__fit_intercept': [True, False],\n",
+ " 'clf__intercept_scaling': [0.5, 1.0, 2.0],\n",
+ " 'clf__l1_ratio': [0.0, 0.3, 0.5, 0.8],\n",
+ " 'clf__loss': [{'name': 'svm'}, {'name': 'sSVM'}],\n",
+ " 'clf__max_iter': [5000, 10000]},\n",
+ " scoring='accuracy', verbose=1)Pipeline(steps=[('scaler', StandardScaler()),\n",
+ " ('clf',\n",
+ " plq_ElasticNet_Classifier(C=0.1,\n",
+ " constraint=[{'name': 'fair',\n",
+ " 'sen_idx': [0],\n",
+ " 'tol_sen': 0.1}],\n",
+ " l1_ratio=0.0, loss={'name': 'sSVM'},\n",
+ " max_iter=5000))])StandardScaler()
plq_ElasticNet_Classifier(C=0.1,\n",
+ " constraint=[{'name': 'fair', 'sen_idx': [0],\n",
+ " 'tol_sen': 0.1}],\n",
+ " l1_ratio=0.0, loss={'name': 'sSVM'}, max_iter=5000)GridSearchCV(cv=5,\n",
+ " estimator=Pipeline(steps=[('scaler', StandardScaler()),\n",
+ " ('reg', plq_ElasticNet_Regressor())]),\n",
+ " n_jobs=-1,\n",
+ " param_grid={'reg__C': [0.1, 1.0, 10.0],\n",
+ " 'reg__constraint': [[], [{'name': 'nonnegative'}],\n",
+ " [{'name': 'fair', 'sen_idx': [0],\n",
+ " 'tol_sen': 0.1}]],\n",
+ " 'reg__fit_intercept': [True, False],\n",
+ " 'reg__intercept_scaling': [0.5, 1.0],\n",
+ " 'reg__l1_ratio': [0.0, 0.3, 0.5, 0.8],\n",
+ " 'reg__loss': [{'name': 'QR', 'qt': 0.5},\n",
+ " {'name': 'huber', 'tau': 1.0},\n",
+ " {'epsilon': 0.1, 'name': 'SVR'}],\n",
+ " 'reg__max_iter': [5000, 8000]},\n",
+ " scoring='r2', verbose=1)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. GridSearchCV(cv=5,\n",
+ " estimator=Pipeline(steps=[('scaler', StandardScaler()),\n",
+ " ('reg', plq_ElasticNet_Regressor())]),\n",
+ " n_jobs=-1,\n",
+ " param_grid={'reg__C': [0.1, 1.0, 10.0],\n",
+ " 'reg__constraint': [[], [{'name': 'nonnegative'}],\n",
+ " [{'name': 'fair', 'sen_idx': [0],\n",
+ " 'tol_sen': 0.1}]],\n",
+ " 'reg__fit_intercept': [True, False],\n",
+ " 'reg__intercept_scaling': [0.5, 1.0],\n",
+ " 'reg__l1_ratio': [0.0, 0.3, 0.5, 0.8],\n",
+ " 'reg__loss': [{'name': 'QR', 'qt': 0.5},\n",
+ " {'name': 'huber', 'tau': 1.0},\n",
+ " {'epsilon': 0.1, 'name': 'SVR'}],\n",
+ " 'reg__max_iter': [5000, 8000]},\n",
+ " scoring='r2', verbose=1)Pipeline(steps=[('scaler', StandardScaler()),\n",
+ " ('reg',\n",
+ " plq_ElasticNet_Regressor(C=0.1,\n",
+ " constraint=[{'name': 'nonnegative'}],\n",
+ " l1_ratio=0.0,\n",
+ " loss={'name': 'huber', 'tau': 1.0},\n",
+ " max_iter=5000))])StandardScaler()
plq_ElasticNet_Regressor(C=0.1, constraint=[{'name': 'nonnegative'}],\n",
+ " l1_ratio=0.0, loss={'name': 'huber', 'tau': 1.0},\n",
+ " max_iter=5000)