|
4 | 4 |
|
5 | 5 | from skglm.penalties.base import BasePenalty |
6 | 6 | from skglm.utils.prox_funcs import ( |
7 | | - ST, box_proj, prox_05, prox_2_3, prox_SCAD, value_SCAD, prox_MCP, |
8 | | - value_MCP, value_weighted_MCP) |
| 7 | + ST, box_proj, prox_05, prox_2_3, prox_SCAD, value_SCAD, prox_MCP, value_MCP, |
| 8 | + value_weighted_MCP, prox_log_sum) |
9 | 9 |
|
10 | 10 |
|
11 | 11 | class L1(BasePenalty): |
@@ -607,6 +607,66 @@ def generalized_support(self, w): |
607 | 607 | return w != 0 |
608 | 608 |
|
609 | 609 |
|
| 610 | +class LogSumPenalty(BasePenalty): |
| 611 | + """Log sum penalty. |
| 612 | +
|
| 613 | + The penalty value reads |
| 614 | +
|
| 615 | + .. math:: |
| 616 | +
|
| 617 | + "value"(w) = sum_(j=1)^(n_"features") log(1 + abs(w_j) / epsilon) |
| 618 | + """ |
| 619 | + |
| 620 | + def __init__(self, alpha, eps): |
| 621 | + self.alpha = alpha |
| 622 | + self.eps = eps |
| 623 | + |
| 624 | + def get_spec(self): |
| 625 | + spec = ( |
| 626 | + ('alpha', float64), |
| 627 | + ('eps', float64), |
| 628 | + ) |
| 629 | + return spec |
| 630 | + |
| 631 | + def params_to_dict(self): |
| 632 | + return dict(alpha=self.alpha, eps=self.eps) |
| 633 | + |
| 634 | + def value(self, w): |
| 635 | + """Compute the value of the log-sum penalty at ``w``.""" |
| 636 | + return self.alpha * np.sum(np.log(1 + np.abs(w) / self.eps)) |
| 637 | + |
| 638 | + def derivative(self, w): |
| 639 | + """Compute the element-wise derivative.""" |
| 640 | + return np.sign(w) / (np.abs(w) + self.eps) |
| 641 | + |
| 642 | + def prox_1d(self, value, stepsize, j): |
| 643 | + """Compute the proximal operator of the log-sum penalty.""" |
| 644 | + return prox_log_sum(value, self.alpha * stepsize, self.eps) |
| 645 | + |
| 646 | + def subdiff_distance(self, w, grad, ws): |
| 647 | + """Compute distance of negative gradient to the subdifferential at w.""" |
| 648 | + subdiff_dist = np.zeros_like(grad) |
| 649 | + alpha = self.alpha |
| 650 | + eps = self.eps |
| 651 | + |
| 652 | + for idx, j in enumerate(ws): |
| 653 | + if w[j] == 0: |
| 654 | + # distance of -grad_j to [-alpha/eps, alpha/eps] |
| 655 | + subdiff_dist[idx] = max(0, np.abs(grad[idx]) - alpha / eps) |
| 656 | + else: |
| 657 | + # distance of -grad_j to {alpha * sign(w[j]) / (eps + |w[j]|)} |
| 658 | + subdiff_dist[idx] = np.abs( |
| 659 | + grad[idx] + np.sign(w[j]) * alpha / (eps + np.abs(w[j]))) |
| 660 | + return subdiff_dist |
| 661 | + |
| 662 | + def is_penalized(self, n_features): |
| 663 | + """Return a binary mask with the penalized features.""" |
| 664 | + return np.ones(n_features, bool_) |
| 665 | + |
| 666 | + def generalized_support(self, w): |
| 667 | + return w != 0 |
| 668 | + |
| 669 | + |
610 | 670 | class PositiveConstraint(BasePenalty): |
611 | 671 | """Positivity constraint penalty.""" |
612 | 672 |
|
|
0 commit comments