Skip to content

Commit 6088ff5

Browse files
authored
Merge pull request #86 from reivilo3/main
add TV operator, merge ProximalGradient and AcceleratedProximalGradient, add GeneralizedProximalGradient
2 parents fc85097 + 82fd7e3 commit 6088ff5

File tree

11 files changed

+1037
-96
lines changed

11 files changed

+1037
-96
lines changed

docs/source/adding.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ Once the operator has been created, we can add it to the documentation of PyProx
156156
the operator within the ``index.rst`` file in ``docs/source/api`` directory.
157157

158158
Moreover, in order to facilitate the user of your operator by other users, a simple example should be provided as part of the
159-
Sphinx-gallery of the documentation of the PyProximal library. The directory ``examples`` containes several scripts that
159+
Sphinx-gallery of the documentation of the PyProximal library. The directory ``examples`` contains several scripts that
160160
can be used as template.
161161

162162

docs/source/api/index.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ Convex
7575
Orthogonal
7676
Quadratic
7777
Simplex
78+
TV
7879

7980

8081
Non-Convex
@@ -140,6 +141,8 @@ Primal
140141
HQS
141142
LinearizedADMM
142143
ProximalGradient
144+
AcceleratedProximalGradient
145+
GeneralizedProximalGradient
143146
ProximalPoint
144147
TwIST
145148

examples/plot_norms.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,22 @@
123123
plt.xlabel('x')
124124
plt.title(r'$||x||_1$')
125125
plt.legend()
126+
plt.tight_layout()
127+
128+
###############################################################################
129+
# We consider now the TV norm.
130+
TV = pyproximal.TV(dim=1, sigma=1.)
131+
132+
x = np.arange(-1, 1, 0.1)
133+
print('||x||_{TV}: ', l1(x))
134+
135+
tau = 0.5
136+
xp = TV.prox(x, tau)
137+
138+
plt.figure(figsize=(7, 2))
139+
plt.plot(x, x, 'k', lw=2, label='x')
140+
plt.plot(x, xp, 'r', lw=2, label='prox(x)')
141+
plt.xlabel('x')
142+
plt.title(r'$||x||_{TV}$')
143+
plt.legend()
126144
plt.tight_layout()

pyproximal/optimization/primal.py

Lines changed: 116 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import time
2+
import warnings
23
import numpy as np
34

45
from math import sqrt
@@ -101,11 +102,12 @@ def ProximalPoint(prox, x0, tau, niter=10, callback=None, show=False):
101102

102103
def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
103104
epsg=1., niter=10, niterback=100,
105+
acceleration=None,
104106
callback=None, show=False):
105-
r"""Proximal gradient
107+
r"""Proximal gradient (optionnally accelerated)
106108
107-
Solves the following minimization problem using Proximal gradient
108-
algorithm:
109+
Solves the following minimization problem using (Accelerated) Proximal
110+
gradient algorithm:
109111
110112
.. math::
111113
@@ -130,14 +132,16 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
130132
backtracking is used to adaptively estimate the best tau at each
131133
iteration. Finally note that :math:`\tau` can be chosen to be a vector
132134
when dealing with problems with multiple right-hand-sides
133-
beta : obj:`float`, optional
135+
beta : :obj:`float`, optional
134136
Backtracking parameter (must be between 0 and 1)
135137
epsg : :obj:`float` or :obj:`np.ndarray`, optional
136138
Scaling factor of g function
137139
niter : :obj:`int`, optional
138140
Number of iterations of iterative scheme
139141
niterback : :obj:`int`, optional
140142
Max number of iterations of backtracking
143+
acceleration: :obj:`str`, optional
144+
Acceleration (``None``, ``vandenberghe`` or ``fista``)
141145
callback : :obj:`callable`, optional
142146
Function with signature (``callback(x)``) to call after each iteration
143147
where ``x`` is the current model vector
@@ -151,12 +155,15 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
151155
152156
Notes
153157
-----
154-
The Proximal point algorithm can be expressed by the following recursion:
158+
The (Accelerated) Proximal point algorithm can be expressed by the
159+
following recursion:
155160
156161
.. math::
157162
158-
\mathbf{x}^{k+1} = \prox_{\tau^k \epsilon g}(\mathbf{x}^k -
159-
\tau^k \nabla f(\mathbf{x}^k))
163+
\mathbf{y}^{k+1} = \mathbf{x}^k + \omega^k
164+
(\mathbf{x}^k - \mathbf{x}^{k-1})
165+
\mathbf{x}^{k+1} = \prox_{\tau^k \epsilon g}(\mathbf{y}^{k+1} -
166+
\tau^k \nabla f(\mathbf{y}^{k+1})) \\
160167
161168
where at each iteration :math:`\tau^k` can be estimated by back-tracking
162169
as follows:
@@ -173,7 +180,17 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
173180
174181
where :math:`\tilde{f}_\tau(\mathbf{x}, \mathbf{y}) = f(\mathbf{y}) +
175182
\nabla f(\mathbf{y})^T (\mathbf{x} - \mathbf{y}) +
176-
1/(2\tau)||\mathbf{x} - \mathbf{y}||_2^2`.
183+
1/(2\tau)||\mathbf{x} - \mathbf{y}||_2^2`,
184+
and
185+
:math:`\omega^k = 0` for ``acceleration=None``,
186+
:math:`\omega^k = k / (k + 3)` for ``acceleration=vandenberghe`` [1]_
187+
or :math:`\omega^k = (t_{k-1}-1)/t_k` for ``acceleration=fista`` where
188+
:math:`t_k = (1 + \sqrt{1+4t_{k-1}^{2}}) / 2` [2]_
189+
190+
.. [1] Vandenberghe, L., "Fast proximal gradient methods", 2010.
191+
.. [2] Beck, A., and Teboulle, M. "A Fast Iterative Shrinkage-Thresholding
192+
Algorithm for Linear Inverse Problems", SIAM Journal on
193+
Imaging Sciences, vol. 2, pp. 183-202. 2009.
177194
178195
"""
179196
# check if epgs is a ve
@@ -182,9 +199,12 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
182199
else:
183200
epsg_print = 'Multi'
184201

202+
if acceleration not in [None, 'None', 'vandenberghe', 'fista']:
203+
raise NotImplementedError('Acceleration should be None, vandenberghe '
204+
'or fista')
185205
if show:
186206
tstart = time.time()
187-
print('Proximal Gradient\n'
207+
print('Accelerated Proximal Gradient\n'
188208
'---------------------------------------------------------\n'
189209
'Proximal operator (f): %s\n'
190210
'Proximal operator (g): %s\n'
@@ -201,13 +221,32 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
201221
backtracking = True
202222
tau = 1.
203223

224+
# initialize model
225+
t = 1.
204226
x = x0.copy()
227+
y = x.copy()
228+
229+
# iterate
205230
for iiter in range(niter):
231+
xold = x.copy()
232+
233+
# proximal step
206234
if not backtracking:
207-
x = proxg.prox(x - tau * proxf.grad(x), epsg * tau)
235+
x = proxg.prox(y - tau * proxf.grad(y), epsg * tau)
208236
else:
209-
x, tau = _backtracking(x, tau, proxf, proxg, epsg,
237+
x, tau = _backtracking(y, tau, proxf, proxg, epsg,
210238
beta=beta, niterback=niterback)
239+
240+
# update y
241+
if acceleration == 'vandenberghe':
242+
omega = iiter / (iiter + 3)
243+
elif acceleration == 'fista':
244+
told = t
245+
t = (1. + np.sqrt(1. + 4. * t ** 2)) / 2.
246+
omega = ((told - 1.) / t)
247+
else:
248+
omega = 0
249+
y = x + omega * (x - xold)
211250

212251
# run callback
213252
if callback is not None:
@@ -233,40 +272,57 @@ def AcceleratedProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
233272
callback=None, show=False):
234273
r"""Accelerated Proximal gradient
235274
236-
Solves the following minimization problem using Accelerated Proximal
275+
This is a thin wrapper around :func:`pyproximal.optimization.primal.ProximalGradient` with
276+
``vandenberghe`` or ``fista``acceleration. See :func:`pyproximal.optimization.primal.ProximalGradient`
277+
for details.
278+
279+
"""
280+
warnings.warn('AcceleratedProximalGradient has been integrated directly into ProximalGradient '
281+
'from v0.5.0. It is recommended to start using ProximalGradient by selecting the '
282+
'appropriate acceleration parameter as this behaviour will become default in '
283+
'version v1.0.0 and AcceleratedProximalGradient will be removed.', FutureWarning)
284+
return ProximalGradient(proxf, proxg, x0, tau=tau, beta=beta,
285+
epsg=epsg, niter=niter, niterback=niterback,
286+
acceleration=acceleration,
287+
callback=callback, show=show)
288+
289+
290+
def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
291+
epsg=1., niter=10,
292+
acceleration=None,
293+
callback=None, show=False):
294+
r"""Generalized Proximal gradient
295+
296+
Solves the following minimization problem using Generalized Proximal
237297
gradient algorithm:
238298
239299
.. math::
240300
241-
\mathbf{x} = \argmin_\mathbf{x} f(\mathbf{x}) + \epsilon g(\mathbf{x})
301+
\mathbf{x} = \argmin_\mathbf{x} \sum_{i=1}^n f_i(\mathbf{x})
302+
+ \sum_{j=1}^m \tau_j g_j(\mathbf{x}),~~n,m \in \mathbb{N}^+
242303
243-
where :math:`f(\mathbf{x})` is a smooth convex function with a uniquely
244-
defined gradient and :math:`g(\mathbf{x})` is any convex function that
245-
has a known proximal operator.
304+
where the :math:`f_i(\mathbf{x})` are smooth convex functions with a uniquely
305+
defined gradient and the :math:`g_j(\mathbf{x})` are any convex function that
306+
have a known proximal operator.
246307
247308
Parameters
248309
----------
249-
proxf : :obj:`pyproximal.ProxOperator`
250-
Proximal operator of f function (must have ``grad`` implemented)
251-
proxg : :obj:`pyproximal.ProxOperator`
252-
Proximal operator of g function
310+
proxfs : :obj:`List of pyproximal.ProxOperator`
311+
Proximal operators of the :math:`f_i` functions (must have ``grad`` implemented)
312+
proxgs : :obj:`List of pyproximal.ProxOperator`
313+
Proximal operators of the :math:`g_j` functions
253314
x0 : :obj:`numpy.ndarray`
254315
Initial vector
255316
tau : :obj:`float` or :obj:`numpy.ndarray`, optional
256317
Positive scalar weight, which should satisfy the following condition
257318
to guarantees convergence: :math:`\tau \in (0, 1/L]` where ``L`` is
258-
the Lipschitz constant of :math:`\nabla f`. When ``tau=None``,
319+
the Lipschitz constant of :math:`\sum_{i=1}^n \nabla f_i`. When ``tau=None``,
259320
backtracking is used to adaptively estimate the best tau at each
260-
iteration. Finally note that :math:`\tau` can be chosen to be a vector
261-
when dealing with problems with multiple right-hand-sides
262-
beta : obj:`float`, optional
263-
Backtracking parameter (must be between 0 and 1)
321+
iteration.
264322
epsg : :obj:`float` or :obj:`np.ndarray`, optional
265323
Scaling factor of g function
266324
niter : :obj:`int`, optional
267325
Number of iterations of iterative scheme
268-
niterback : :obj:`int`, optional
269-
Max number of iterations of backtracking
270326
acceleration: :obj:`str`, optional
271327
Acceleration (``vandenberghe`` or ``fista``)
272328
callback : :obj:`callable`, optional
@@ -282,76 +338,75 @@ def AcceleratedProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
282338
283339
Notes
284340
-----
285-
The Accelerated Proximal point algorithm can be expressed by the
341+
The Generalized Proximal point algorithm can be expressed by the
286342
following recursion:
287343
288344
.. math::
289-
290-
\mathbf{x}^{k+1} = \prox_{\tau^k \epsilon g}(\mathbf{y}^{k+1} -
291-
\tau^k \nabla f(\mathbf{y}^{k+1})) \\
292-
\mathbf{y}^{k+1} = \mathbf{x}^k + \omega^k
293-
(\mathbf{x}^k - \mathbf{x}^{k-1})
294-
295-
where :math:`\omega^k = k / (k + 3)` for ``acceleration=vandenberghe`` [1]_
296-
or :math:`\omega^k = (t_{k-1}-1)/t_k` for ``acceleration=fista`` where
297-
:math:`t_k = (1 + \sqrt{1+4t_{k-1}^{2}}) / 2` [2]_
298-
299-
.. [1] Vandenberghe, L., "Fast proximal gradient methods", 2010.
300-
.. [2] Beck, A., and Teboulle, M. "A Fast Iterative Shrinkage-Thresholding
301-
Algorithm for Linear Inverse Problems", SIAM Journal on
302-
Imaging Sciences, vol. 2, pp. 183-202. 2009.
303-
345+
\text{for } j=1,\cdots,n, \\
346+
~~~~\mathbf z_j^{k+1} = \mathbf z_j^{k} + \eta_k (prox_{\frac{\tau^k}{\omega_j} g_j}(2 \mathbf{x}^{k} - z_j^{k})
347+
- \tau^k \sum_{i=1}^n \nabla f_i(\mathbf{x}^{k})) - \mathbf{x}^{k} \\
348+
\mathbf{x}^{k+1} = \sum_{j=1}^n \omega_j f_j \\
349+
350+
where :math:`\sum_{j=1}^n \omega_j=1`.
304351
"""
305-
# check if epgs is a ve
352+
# check if epgs is a vector
306353
if np.asarray(epsg).size == 1.:
307354
epsg_print = str(epsg)
308355
else:
309356
epsg_print = 'Multi'
310357

311-
if acceleration not in ['vandenberghe', 'fista']:
312-
raise NotImplementedError('Acceleration should be vandenberghe '
358+
if acceleration not in [None, 'None', 'vandenberghe', 'fista']:
359+
raise NotImplementedError('Acceleration should be None, vandenberghe '
313360
'or fista')
314361
if show:
315362
tstart = time.time()
316-
print('Accelerated Proximal Gradient\n'
363+
print('Generalized Proximal Gradient\n'
317364
'---------------------------------------------------------\n'
318-
'Proximal operator (f): %s\n'
319-
'Proximal operator (g): %s\n'
320-
'tau = %10e\tepsg = %s\tniter = %d\n' % (type(proxf),
321-
type(proxg),
322-
0 if tau is None else tau,
365+
'Proximal operators (f): %s\n'
366+
'Proximal operators (g): %s\n'
367+
'tau = %10e\nepsg = %s\tniter = %d\n' % ([type(proxf) for proxf in proxfs],
368+
[type(proxg) for proxg in proxgs],
369+
0 if tau is None else tau,
323370
epsg_print, niter))
324371
head = ' Itn x[0] f g J=f+eps*g'
325372
print(head)
326373

327-
backtracking = False
328374
if tau is None:
329-
backtracking = True
330375
tau = 1.
331376

332377
# initialize model
333378
t = 1.
334379
x = x0.copy()
335380
y = x.copy()
381+
zs = [x.copy() for _ in range(len(proxgs))]
336382

337383
# iterate
338384
for iiter in range(niter):
339385
xold = x.copy()
340386

341387
# proximal step
342-
if not backtracking:
343-
x = proxg.prox(y - tau * proxf.grad(y), epsg * tau)
344-
else:
345-
x, tau = _backtracking(y, tau, proxf, proxg, epsg,
346-
beta=beta, niterback=niterback)
388+
grad = np.zeros_like(x)
389+
for i, proxf in enumerate(proxfs):
390+
grad += proxf.grad(x)
391+
392+
sol = np.zeros_like(x)
393+
for i, proxg in enumerate(proxgs):
394+
tmp = 2 * y - zs[i] - tau * grad
395+
tmp[:] = proxg.prox(tmp, tau *len(proxgs) )
396+
zs[i] += epsg * (tmp - y)
397+
sol += zs[i] / len(proxgs)
398+
x[:] = sol.copy()
347399

348400
# update y
349401
if acceleration == 'vandenberghe':
350402
omega = iiter / (iiter + 3)
351-
else:
403+
elif acceleration== 'fista':
352404
told = t
353405
t = (1. + np.sqrt(1. + 4. * t ** 2)) / 2.
354406
omega = ((told - 1.) / t)
407+
else:
408+
omega = 0
409+
355410
y = x + omega * (x - xold)
356411

357412
# run callback
@@ -360,7 +415,7 @@ def AcceleratedProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
360415

361416
if show:
362417
if iiter < 10 or niter - iiter < 10 or iiter % (niter // 10) == 0:
363-
pf, pg = proxf(x), proxg(x)
418+
pf, pg = np.sum([proxf(x) for proxf in proxfs]), np.sum([proxg(x) for proxg in proxgs])
364419
msg = '%6g %12.5e %10.3e %10.3e %10.3e' % \
365420
(iiter + 1, x[0] if x.ndim == 1 else x[0, 0],
366421
pf, pg[0] if epsg_print == 'Multi' else pg,

pyproximal/proximal/L21.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class L21(ProxOperator):
1111
1212
Parameters
1313
----------
14-
ndims : :obj:`int`
14+
ndim : :obj:`int`
1515
Number of dimensions :math:`N_{dim}`. Used to reshape the input array
1616
in a matrix of size :math:`N_{dim} \times N'_{x}` where
1717
:math:`N'_x = \frac{N_x}{N_{dim}}`. Note that the input

0 commit comments

Comments
 (0)