|
14 | 14 | check_is_fitted, check_X_y) |
15 | 15 |
|
16 | 16 | from ._base import (ReHLine_solver, _BaseReHLine, |
17 | | - _make_constraint_rehline_param, _make_loss_rehline_param) |
| 17 | + _make_constraint_rehline_param, _make_loss_rehline_param, _cast_sample_weight) |
18 | 18 |
|
19 | 19 |
|
20 | 20 | class ReHLine(_BaseReHLine, BaseEstimator): |
@@ -441,6 +441,231 @@ def decision_function(self, X): |
441 | 441 |
|
442 | 442 | X = check_array(X) |
443 | 443 | return np.dot(X, self.coef_) |
| 444 | + |
| 445 | + |
| 446 | +class plqERM_ElasticNet(_BaseReHLine, BaseEstimator): |
| 447 | + r"""Empirical Risk Minimization (ERM) with a piecewise linear-quadratic (PLQ) objective with a elastic net penalty. |
| 448 | +
|
| 449 | + .. math:: |
| 450 | +
|
| 451 | + \min_{\mathbf{\beta} \in \mathbb{R}^d} C \sum_{i=1}^n \text{PLQ}(y_i, \mathbf{x}_i^T \mathbf{\beta}) + \text{l1_ratio} \| \mathbf{\beta} \|_1 + \frac{1}{2} (1 - \text{l1_ratio}) \| \mathbf{\beta} \|_2^2, \ \text{ s.t. } \ |
| 452 | + \mathbf{A} \mathbf{\beta} + \mathbf{b} \geq \mathbf{0}, |
| 453 | +
|
| 454 | + The function supports various loss functions, including: |
| 455 | + - 'hinge', 'svm' or 'SVM' |
| 456 | + - 'check' or 'quantile' or 'quantile regression' or 'QR' |
| 457 | + - 'sSVM' or 'smooth SVM' or 'smooth hinge' |
| 458 | + - 'TV' |
| 459 | + - 'huber' or 'Huber' |
| 460 | + - 'SVR' or 'svr' |
| 461 | +
|
| 462 | + The following constraint types are supported: |
| 463 | + * 'nonnegative' or '>=0': A non-negativity constraint. |
| 464 | + * 'fair' or 'fairness': A fairness constraint. |
| 465 | + * 'custom': A custom constraint, where the user must provide the constraint matrix 'A' and vector 'b'. |
| 466 | +
|
| 467 | + Parameters |
| 468 | + ---------- |
| 469 | + loss : dict |
| 470 | + A dictionary specifying the loss function parameters. |
| 471 | + |
| 472 | + constraint : list of dict |
| 473 | + A list of dictionaries, where each dictionary represents a constraint. |
| 474 | + Each dictionary must contain a 'name' key, which specifies the type of constraint. |
| 475 | +
|
| 476 | + C : float, default=1.0 |
| 477 | + Regularization parameter. The strength of the regularization is |
| 478 | + inversely proportional to C. Must be strictly positive. |
| 479 | + `C` will be absorbed by the ReHLine parameters when `self.make_ReLHLoss` is conducted. |
| 480 | +
|
| 481 | + l1_ratio : float, default=0.5 |
| 482 | + The ElasticNet mixing parameter, with 0 <= l1_ratio < 1. For l1_ratio = 0 the penalty |
| 483 | + is an L2 penalty. For 0 < l1_ratio < 1, the penalty is a combination of L1 and L2. |
| 484 | +
|
| 485 | + verbose : int, default=0 |
| 486 | + Enable verbose output. Note that this setting takes advantage of a |
| 487 | + per-process runtime setting in liblinear that, if enabled, may not work |
| 488 | + properly in a multithreaded context. |
| 489 | +
|
| 490 | + max_iter : int, default=1000 |
| 491 | + The maximum number of iterations to be run. |
| 492 | +
|
| 493 | + _U, _V: array of shape (L, n_samples), default=np.empty(shape=(0, 0)) |
| 494 | + The parameters pertaining to the ReLU part in the loss function. |
| 495 | +
|
| 496 | + _Tau, _S, _T: array of shape (H, n_samples), default=np.empty(shape=(0, 0)) |
| 497 | + The parameters pertaining to the ReHU part in the loss function. |
| 498 | + |
| 499 | + _A: array of shape (K, n_features), default=np.empty(shape=(0, 0)) |
| 500 | + The coefficient matrix in the linear constraint. |
| 501 | +
|
| 502 | + _b: array of shape (K, ), default=np.empty(shape=0) |
| 503 | + The intercept vector in the linear constraint. |
| 504 | + |
| 505 | + Attributes |
| 506 | + ---------- |
| 507 | + coef\_ : array-like |
| 508 | + The optimized model coefficients. |
| 509 | +
|
| 510 | + n_iter\_ : int |
| 511 | + The number of iterations performed by the ReHLine solver. |
| 512 | +
|
| 513 | + opt_result\_ : object |
| 514 | + The optimization result object. |
| 515 | +
|
| 516 | + dual_obj\_ : array-like |
| 517 | + The dual objective function values. |
| 518 | +
|
| 519 | + primal_obj\_ : array-like |
| 520 | + The primal objective function values. |
| 521 | +
|
| 522 | + Methods |
| 523 | + ------- |
| 524 | + fit(X, y, sample_weight=None) |
| 525 | + Fit the model based on the given training data. |
| 526 | +
|
| 527 | + decision_function(X) |
| 528 | + The decision function evaluated on the given dataset. |
| 529 | +
|
| 530 | + Notes |
| 531 | + ----- |
| 532 | + The `plqERM_ElasticNet` class is a subclass of `_BaseReHLine` and `BaseEstimator`, which suggests that it is part of a larger framework for implementing ReHLine algorithms. |
| 533 | +
|
| 534 | + """ |
| 535 | + |
| 536 | + def __init__(self, loss, |
| 537 | + constraint=[], |
| 538 | + C=1., |
| 539 | + l1_ratio=0.5, |
| 540 | + U=np.empty(shape=(0,0)), V=np.empty(shape=(0,0)), |
| 541 | + Tau=np.empty(shape=(0,0)), |
| 542 | + S=np.empty(shape=(0,0)), T=np.empty(shape=(0,0)), |
| 543 | + A=np.empty(shape=(0,0)), b=np.empty(shape=(0)), |
| 544 | + max_iter=1000, tol=1e-4, shrink=1, warm_start=0, |
| 545 | + verbose=0, trace_freq=100): |
| 546 | + self.loss = loss |
| 547 | + self.constraint = constraint |
| 548 | + self.C = C |
| 549 | + self.l1_ratio = l1_ratio |
| 550 | + self.rho = l1_ratio / (1 - l1_ratio) |
| 551 | + self.C_eff = C / (1 - l1_ratio) |
| 552 | + self._U = U |
| 553 | + self._V = V |
| 554 | + self._S = S |
| 555 | + self._T = T |
| 556 | + self._Tau = Tau |
| 557 | + self._A = A |
| 558 | + self._b = b |
| 559 | + self.L = U.shape[0] |
| 560 | + self.H = S.shape[0] |
| 561 | + self.K = A.shape[0] |
| 562 | + self.max_iter = max_iter |
| 563 | + self.tol = tol |
| 564 | + self.shrink = shrink |
| 565 | + self.warm_start = warm_start |
| 566 | + self.verbose = verbose |
| 567 | + self.trace_freq = trace_freq |
| 568 | + self._Lambda = np.empty(shape=(0, 0)) |
| 569 | + self._Gamma = np.empty(shape=(0, 0)) |
| 570 | + self._xi = np.empty(shape=(0, 0)) |
| 571 | + self._mu = np.empty(shape=(0, 0)) |
| 572 | + self.coef_ = None |
| 573 | + |
| 574 | + def fit(self, X, y, sample_weight=None): |
| 575 | + """Fit the model based on the given training data. |
| 576 | +
|
| 577 | + Parameters |
| 578 | + ---------- |
| 579 | +
|
| 580 | + X: {array-like} of shape (n_samples, n_features) |
| 581 | + Training vector, where `n_samples` is the number of samples and |
| 582 | + `n_features` is the number of features. |
| 583 | +
|
| 584 | + y : array-like of shape (n_samples,) |
| 585 | + The target variable. |
| 586 | +
|
| 587 | + sample_weight : array-like of shape (n_samples,), default=None |
| 588 | + Array of weights that are assigned to individual |
| 589 | + samples. If not provided, then each sample is given unit weight. |
| 590 | +
|
| 591 | + Returns |
| 592 | + ------- |
| 593 | + self : object |
| 594 | + An instance of the estimator. |
| 595 | +
|
| 596 | +
|
| 597 | + """ |
| 598 | + n, d = X.shape |
| 599 | + |
| 600 | + ## loss -> rehline params |
| 601 | + self._U, self._V, self._Tau, self._S, self._T = _make_loss_rehline_param(loss=self.loss, X=X, y=y) |
| 602 | + |
| 603 | + ## constrain -> rehline params |
| 604 | + self._A, self._b = _make_constraint_rehline_param(constraint=self.constraint, X=X, y=y) |
| 605 | + self.auto_shape() |
| 606 | + |
| 607 | + sample_weight = _check_sample_weight(sample_weight, X, dtype=X.dtype) |
| 608 | + |
| 609 | + U_weight, V_weight, Tau_weight, S_weight, T_weight = _cast_sample_weight(self._U, self._V, self._Tau, self._S, self._T, |
| 610 | + C=self.C_eff, sample_weight=sample_weight) |
| 611 | + |
| 612 | + if not self.warm_start: |
| 613 | + ## remove warm_start params |
| 614 | + self._Lambda = np.empty(shape=(0, 0)) |
| 615 | + self._Gamma = np.empty(shape=(0, 0)) |
| 616 | + self._xi = np.empty(shape=(0, 0)) |
| 617 | + self._mu = np.empty(shape=(0, 0)) |
| 618 | + |
| 619 | + result = ReHLine_solver(X=X, |
| 620 | + U=U_weight, V=V_weight, |
| 621 | + Tau=Tau_weight, |
| 622 | + S=S_weight, T=T_weight, |
| 623 | + A=self._A, b=self._b, |
| 624 | + rho=self.rho, |
| 625 | + Lambda=self._Lambda, Gamma=self._Gamma, xi=self._xi, mu=self._mu, |
| 626 | + max_iter=self.max_iter, tol=self.tol, |
| 627 | + shrink=self.shrink, verbose=self.verbose, |
| 628 | + trace_freq=self.trace_freq) |
| 629 | + |
| 630 | + self.opt_result_ = result |
| 631 | + # primal solution |
| 632 | + self.coef_ = result.beta |
| 633 | + # dual solution |
| 634 | + self._Lambda = result.Lambda |
| 635 | + self._Gamma = result.Gamma |
| 636 | + self._xi = result.xi |
| 637 | + self._mu = result.mu |
| 638 | + # algo convergence |
| 639 | + self.n_iter_ = result.niter |
| 640 | + self.dual_obj_ = result.dual_objfns |
| 641 | + self.primal_obj_ = result.primal_objfns |
| 642 | + |
| 643 | + if self.n_iter_ >= self.max_iter: |
| 644 | + warnings.warn( |
| 645 | + "ReHLine failed to converge, increase the number of iterations: `max_iter`.", |
| 646 | + ConvergenceWarning, |
| 647 | + ) |
| 648 | + |
| 649 | + return self |
| 650 | + |
| 651 | + def decision_function(self, X): |
| 652 | + """The decision function evaluated on the given dataset |
| 653 | +
|
| 654 | + Parameters |
| 655 | + ---------- |
| 656 | + X : array-like of shape (n_samples, n_features) |
| 657 | + The data matrix. |
| 658 | +
|
| 659 | + Returns |
| 660 | + ------- |
| 661 | + ndarray of shape (n_samples, ) |
| 662 | + Returns the decision function of the samples. |
| 663 | + """ |
| 664 | + # Check if fit has been called |
| 665 | + check_is_fitted(self) |
| 666 | + |
| 667 | + X = check_array(X) |
| 668 | + return np.dot(X, self.coef_) |
444 | 669 |
|
445 | 670 |
|
446 | 671 | class CQR_Ridge(_BaseReHLine, BaseEstimator): |
|
0 commit comments