Source code for pyproximal.proximal.SCAD
import numpy as np
from pyproximal.ProxOperator import _check_tau
from pyproximal import ProxOperator
[docs]class SCAD(ProxOperator):
r"""Smoothly clipped absolute deviation (SCAD) penalty.
The SCAD penalty is a concave function and is defined as
.. math::
\mathrm{SCAD}_{\sigma, a}(\mathbf{x}) =
\begin{cases}
\sigma x_i, & |x_i| \leq \sigma \\
\frac{-x_i^2 + 2 a \sigma - \sigma^2}{2 (a - 1)}, & \sigma < |x_i| \leq a\sigma \\
\frac{(a + 1)\sigma^2}{2}, & |x_i| > a\sigma
\end{cases}
Parameters
----------
sigma : :obj:`float`
First threshold parameter (named :math:`\lambda` in the original paper [1]_)
a : :obj:`float`, optional
Second threshold parameter (must be larger than 2). Default is 3.7, see [1]_ for more information.
Notes
-----
The SCAD penalty is continuous and differentiable and does not suffer from
biasedness as the :math:`\ell_1`-norm, nor is discontinuous as the hard
thresholding penalty. Thus, it fuses the most favourable properties of both
penalties.
The proximal operator is given by
.. math::
\prox_{\tau \mathrm{SCAD}_{\sigma, a}(\cdot)}(\mathbf{x}) =
\begin{cases}
\sgn(x_i)\max(0, |x_i| - \sigma), & |x_i| \leq \frac{\sigma(a - 1 - \tau + a\tau)}{a - 1} \\
\frac{(a-1)x_i - \sgn(x_i)a\tau\sigma}{a-1-\tau}, & \frac{\sigma(a - 1 - \tau + a\tau)}{a - 1} < |x_i| \leq a\sigma \\
x_i, & |x_i| > a\sigma
\end{cases}
.. [1] Fan, J. and Li, R. "Variable selection via nonconcave penalized likelihood and its oracle
properties" Journal of the American Statistical Association, 96(456):1348–1360, 2001
"""
def __init__(self, sigma, a=3.7):
super().__init__(None, False)
self.sigma = sigma
if sigma <= 0:
raise ValueError('Variable "sigma" must be positive.')
if a <= 2:
raise ValueError('Variable "a" must be larger than two.')
self.a = a
def __call__(self, x):
return np.sum(self.elementwise(x))
def elementwise(self, x):
f = np.zeros_like(x)
absx = np.abs(x)
ind = absx <= self.sigma
f[ind] = self.sigma * absx[ind]
ind = np.logical_and(self.sigma < absx, absx <= self.a * self.sigma)
f[ind] = (-x[ind] ** 2 + 2 * self.a * self.sigma * absx[ind] - self.sigma ** 2) / (2 * (self.a - 1))
ind = absx > self.a * self.sigma
f[ind] = (self.a + 1) * self.sigma ** 2 / 2
return f
@_check_tau
def prox(self, x, tau):
theta = x.copy()
absx = np.abs(x)
first_threshold = self.sigma * (self.a - 1 - tau + tau * self.a) / (self.a - 1)
ind = absx <= first_threshold
theta[ind] = np.sign(x[ind]) * np.maximum(0, absx[ind] - self.sigma * tau)
ind = np.logical_and(first_threshold < absx, absx <= self.a * self.sigma)
theta[ind] = ((self.a - 1) * x[ind] - np.sign(x[ind]) * self.a * self.sigma * tau) / (self.a - 1 - tau)
return theta