Skip to content

Commit 3ebc7cf

Browse files
authored
Merge pull request #191 from mrava87/feat-hubercirc
feat: added HuberCircular
2 parents b434388 + 38d8650 commit 3ebc7cf

File tree

5 files changed

+95
-10
lines changed

5 files changed

+95
-10
lines changed

docs/source/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Convex
6666
EuclideanBall
6767
Hankel
6868
Huber
69+
HuberCircular
6970
Intersection
7071
L0
7172
L0Ball

pyproximal/optimization/primal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def AcceleratedProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
345345
r"""Accelerated Proximal gradient
346346
347347
This is a thin wrapper around :func:`pyproximal.optimization.primal.ProximalGradient` with
348-
``vandenberghe`` or ``fista``acceleration. See :func:`pyproximal.optimization.primal.ProximalGradient`
348+
``vandenberghe`` or ``fista`` acceleration. See :func:`pyproximal.optimization.primal.ProximalGradient`
349349
for details.
350350
351351
"""

pyproximal/proximal/Huber.py

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,78 @@
11
import numpy as np
22

33
from pyproximal.ProxOperator import _check_tau
4-
from pyproximal.projection import BoxProj
54
from pyproximal import ProxOperator
5+
from pyproximal.proximal import L2, L1
66

77

88
class Huber(ProxOperator):
99
r"""Huber norm proximal operator.
1010
11-
Proximal operator of the Huber norm defined as:
11+
Proximal operator of the Huber norm defined as
12+
:math:`H_\alpha(\mathbf{x}) = \sum_i H_\alpha(x_i)` where:
13+
14+
.. math::
15+
16+
H_\alpha(x_i) =
17+
\begin{cases}
18+
\frac{|x_i|^2}{2 \alpha}, & |x_i| \leq \alpha \\
19+
|x_i| - \frac{\alpha}{2}, & |x_i| > \alpha
20+
\end{cases}
21+
22+
which behaves like a :math:`\ell_2` norm for :math:`|x_i| \leq \alpha` and a
23+
:math:`\ell_1` norm for :math:`|x_i| > \alpha`.
24+
25+
Parameters
26+
----------
27+
alpha : :obj:`float`
28+
Huber parameter
29+
30+
Notes
31+
-----
32+
The Huber proximal operator is defined as:
33+
34+
.. math::
35+
36+
\prox_{\tau H_\alpha(\cdot)}(\mathbf{x}) =
37+
\begin{cases}
38+
\prox_{\frac{\tau}{2 \alpha} |x_i|^2}(x_i), & |x_i| \leq \alpha \\
39+
\prox_{\tau |x_i|}(x_i), & |x_i| > \alpha
40+
\end{cases}
41+
42+
"""
43+
def __init__(self, alpha):
44+
super().__init__(None, False)
45+
self.alpha = alpha
46+
self.l2 = L2(sigma=1. / self.alpha)
47+
self.l1 = L1()
48+
49+
def __call__(self, x):
50+
h = np.zeros_like(x)
51+
xabs = np.abs(x)
52+
mask = xabs > self.alpha
53+
h[~mask] = xabs[~mask] ** 2 / (2. * self.alpha)
54+
h[mask] = xabs[mask] - self.alpha / 2.
55+
return np.sum(h)
56+
57+
@_check_tau
58+
def prox(self, x, tau):
59+
y = np.zeros_like(x)
60+
xabs = np.abs(x)
61+
mask = xabs > self.alpha
62+
y[~mask] = self.l2.prox(x[~mask], tau)
63+
y[mask] = self.l1.prox(x[mask], tau)
64+
# alternative from https://math.stackexchange.com/questions/1650411/
65+
# proximal-operator-of-the-huber-loss-function... currently commented
66+
# as it does not provide the same result
67+
# y = (1. - tau / np.maximum(np.abs(x), tau + self.alpha)) * x
68+
69+
return y
70+
71+
72+
class HuberCircular(ProxOperator):
73+
r"""Circular Huber norm proximal operator.
74+
75+
Proximal operator of the Circular Huber norm defined as:
1276
1377
.. math::
1478
@@ -18,8 +82,8 @@ class Huber(ProxOperator):
1882
\|\mathbf{x}\|_2 - \frac{\alpha}{2}, & \|\mathbf{x}\|_2 > \alpha \\
1983
\end{cases}
2084
21-
which behaves like a :math:`\ell_2` norm for :math:`|x_i| \leq \alpha` and a
22-
:math:`\ell_1` norm for :math:`|x_i| < \alpha`.
85+
which behaves like a :math:`\ell_2` norm for :math:`\|\mathbf{x}\|_2 \leq \alpha` and a
86+
:math:`\ell_1` norm for :math:`\|\mathbf{x}\|_2 > \alpha`.
2387
2488
Parameters
2589
----------
@@ -28,13 +92,16 @@ class Huber(ProxOperator):
2892
2993
Notes
3094
-----
31-
The Huber proximal operator is defined as:
95+
The Circular Huber proximal operator is defined as [1]_:
3296
3397
.. math::
3498
3599
\prox_{\tau H_\alpha(\cdot)}(\mathbf{x}) =
36100
\left( 1 - \frac{\tau}{\max\{\|\mathbf{x}\|_2, \tau + \alpha \} } \right) \mathbf{x}
37101
102+
.. [1] O’Donoghue, B. and Stathopoulos, G. and Boyd, S. "A Splitting Method for Optimal Control",
103+
In the IEEE Transactions on Control Systems Technology, 2013.
104+
38105
"""
39106
def __init__(self, alpha):
40107
super().__init__(None, False)
@@ -51,4 +118,4 @@ def __call__(self, x):
51118
@_check_tau
52119
def prox(self, x, tau):
53120
x = (1. - tau / max(np.linalg.norm(x), tau + self.alpha)) * x
54-
return x
121+
return x

pyproximal/proximal/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
L21 L2,1 Norm
2323
L21_plus_L1 L2,1 + L1 mixed-norm
2424
Huber Huber Norm
25+
HuberCircular Circular Huber Norm
2526
TV Total Variation Norm
2627
RelaxedMumfordShah Relaxed Mumford Shah Norm
2728
Nuclear Nuclear Norm
@@ -69,7 +70,7 @@
6970

7071
__all__ = ['Box', 'Simplex', 'Intersection', 'AffineSet', 'Quadratic',
7172
'Euclidean', 'EuclideanBall', 'L0', 'L0Ball', 'L01Ball', 'L1', 'L1Ball', 'L2',
72-
'L2Convolve', 'L21', 'L21_plus_L1', 'Huber', 'TV', 'RelaxedMumfordShah',
73+
'L2Convolve', 'L21', 'L21_plus_L1', 'Huber', 'HuberCircular', 'TV', 'RelaxedMumfordShah',
7374
'Nuclear', 'NuclearBall', 'Orthogonal', 'VStack', 'Nonlinear', 'SCAD',
7475
'Log', 'Log1', 'ETP', 'Geman', 'QuadraticEnvelopeCard', 'SingularValuePenalty',
7576
'QuadraticEnvelopeCardIndicator', 'QuadraticEnvelopeRankL2',

pytests/test_norms.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
from pylops.basicoperators import Identity, Diagonal, MatrixMult, FirstDerivative
77
from pyproximal.utils import moreau
8-
from pyproximal.proximal import Box, Euclidean, L2, L1, L21, L21_plus_L1, \
9-
Huber, Nuclear, RelaxedMumfordShah, TV
8+
from pyproximal.proximal import Euclidean, L2, L1, L21, L21_plus_L1, \
9+
Huber, HuberCircular, Nuclear, RelaxedMumfordShah, TV
1010

1111
par1 = {'nx': 10, 'sigma': 1., 'dtype': 'float32'} # even float32
1212
par2 = {'nx': 11, 'sigma': 2., 'dtype': 'float64'} # odd float64
@@ -182,8 +182,24 @@ def test_Huber(par):
182182
"""
183183
hub = Huber(alpha=par['sigma'])
184184

185+
# norm
186+
x = np.random.uniform(0., 0.1 * par['sigma'], par['nx']).astype(par['dtype'])
187+
assert hub(x) == np.sum(x ** 2) / (2 * par['sigma'])
188+
189+
# prox / dualprox
190+
tau = 2.
191+
assert moreau(hub, x, tau)
192+
193+
194+
@pytest.mark.parametrize("par", [(par1), (par2)])
195+
def test_HuberCircular(par):
196+
"""Circular Huber norm and proximal/dual proximal
197+
"""
198+
hub = HuberCircular(alpha=par['sigma'])
199+
185200
# norm
186201
x = np.random.uniform(0., 0.1, par['nx']).astype(par['dtype'])
202+
x = (0.1 * par['sigma']) * x / np.linalg.norm(x) # to ensure that is smaller than sigma
187203
assert hub(x) == np.linalg.norm(x) ** 2 / (2 * par['sigma'])
188204

189205
# prox / dualprox

0 commit comments

Comments
 (0)