Skip to content

Commit 91fdfef

Browse files
committed
feat: finalized PG and GPG
1 parent 38df3f7 commit 91fdfef

File tree

2 files changed

+75
-29
lines changed

2 files changed

+75
-29
lines changed

pyproximal/optimization/primal.py

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,9 @@ def ProximalPoint(prox, x0, tau, niter=10, callback=None, show=False):
102102
return x
103103

104104

105-
def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
106-
epsg=1., niter=10, niterback=100,
105+
def ProximalGradient(proxf, proxg, x0, epsg=1.,
106+
tau=None, beta=0.5, eta=1.,
107+
niter=10, niterback=100,
107108
acceleration=None,
108109
callback=None, show=False):
109110
r"""Proximal gradient (optionally accelerated)
@@ -127,17 +128,19 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
127128
Proximal operator of g function
128129
x0 : :obj:`numpy.ndarray`
129130
Initial vector
131+
epsg : :obj:`float` or :obj:`np.ndarray`, optional
132+
Scaling factor of g function
130133
tau : :obj:`float` or :obj:`numpy.ndarray`, optional
131134
Positive scalar weight, which should satisfy the following condition
132135
to guarantees convergence: :math:`\tau \in (0, 1/L]` where ``L`` is
133136
the Lipschitz constant of :math:`\nabla f`. When ``tau=None``,
134137
backtracking is used to adaptively estimate the best tau at each
135-
iteration. Finally note that :math:`\tau` can be chosen to be a vector
138+
iteration. Finally, note that :math:`\tau` can be chosen to be a vector
136139
when dealing with problems with multiple right-hand-sides
137140
beta : :obj:`float`, optional
138141
Backtracking parameter (must be between 0 and 1)
139-
epsg : :obj:`float` or :obj:`np.ndarray`, optional
140-
Scaling factor of g function
142+
eta : :obj:`float`, optional
143+
Relaxation parameter (must be between 0 and 1, 0 excluded).
141144
niter : :obj:`int`, optional
142145
Number of iterations of iterative scheme
143146
niterback : :obj:`int`, optional
@@ -161,9 +164,8 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
161164
162165
.. math::
163166
164-
165-
\mathbf{x}^{k+1} = \prox_{\tau^k \epsilon g}(\mathbf{y}^{k+1} -
166-
\tau^k \nabla f(\mathbf{y}^{k+1})) \\
167+
\mathbf{x}^{k+1} = \mathbf{y}^k + \eta (\prox_{\tau^k \epsilon g}(\mathbf{y}^k -
168+
\tau^k \nabla f(\mathbf{y}^k)) - \mathbf{y}^k) \\
167169
\mathbf{y}^{k+1} = \mathbf{x}^k + \omega^k
168170
(\mathbf{x}^k - \mathbf{x}^{k-1})
169171
@@ -187,7 +189,7 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
187189
Different accelerations are provided:
188190
189191
- ``acceleration=None``: :math:`\omega^k = 0`;
190-
- `acceleration=vandenberghe`` [1]_: :math:`\omega^k = k / (k + 3)` for `
192+
- ``acceleration=vandenberghe`` [1]_: :math:`\omega^k = k / (k + 3)` for `
191193
- ``acceleration=fista``: :math:`\omega^k = (t_{k-1}-1)/t_k` for where
192194
:math:`t_k = (1 + \sqrt{1+4t_{k-1}^{2}}) / 2` [2]_
193195
@@ -197,7 +199,7 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
197199
Imaging Sciences, vol. 2, pp. 183-202. 2009.
198200
199201
"""
200-
# check if epgs is a ve
202+
# check if epgs is a vector
201203
if np.asarray(epsg).size == 1.:
202204
epsg_print = str(epsg)
203205
else:
@@ -237,10 +239,15 @@ def ProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
237239

238240
# proximal step
239241
if not backtracking:
240-
x = proxg.prox(y - tau * proxf.grad(y), epsg * tau)
242+
if eta == 1.:
243+
x = proxg.prox(y - tau * proxf.grad(y), epsg * tau)
244+
else:
245+
x = x + eta * (proxg.prox(x - tau * proxf.grad(x), epsg * tau) - x)
241246
else:
242247
x, tau = _backtracking(y, tau, proxf, proxg, epsg,
243248
beta=beta, niterback=niterback)
249+
if eta != 1.:
250+
x = x + eta * (proxg.prox(x - tau * proxf.grad(x), epsg * tau) - x)
244251

245252
# update internal parameters for bilinear operator
246253
if isinstance(proxf, BilinearOperator):
@@ -297,8 +304,9 @@ def AcceleratedProximalGradient(proxf, proxg, x0, tau=None, beta=0.5,
297304
callback=callback, show=show)
298305

299306

300-
def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
301-
epsg=1., niter=10,
307+
def GeneralizedProximalGradient(proxfs, proxgs, x0, tau,
308+
epsg=1., weights=None,
309+
eta=1., niter=10,
302310
acceleration=None,
303311
callback=None, show=False):
304312
r"""Generalized Proximal gradient
@@ -317,24 +325,27 @@ def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
317325
318326
Parameters
319327
----------
320-
proxfs : :obj:`List of pyproximal.ProxOperator`
328+
proxfs : :obj:`list of pyproximal.ProxOperator`
321329
Proximal operators of the :math:`f_i` functions (must have ``grad`` implemented)
322-
proxgs : :obj:`List of pyproximal.ProxOperator`
330+
proxgs : :obj:`list of pyproximal.ProxOperator`
323331
Proximal operators of the :math:`g_j` functions
324332
x0 : :obj:`numpy.ndarray`
325333
Initial vector
326-
tau : :obj:`float` or :obj:`numpy.ndarray`, optional
334+
tau : :obj:`float`
327335
Positive scalar weight, which should satisfy the following condition
328336
to guarantees convergence: :math:`\tau \in (0, 1/L]` where ``L`` is
329-
the Lipschitz constant of :math:`\sum_{i=1}^n \nabla f_i`. When ``tau=None``,
330-
backtracking is used to adaptively estimate the best tau at each
331-
iteration.
337+
the Lipschitz constant of :math:`\sum_{i=1}^n \nabla f_i`.
332338
epsg : :obj:`float` or :obj:`np.ndarray`, optional
333339
Scaling factor(s) of ``g`` function(s)
340+
weights : :obj:`float`, optional
341+
Weighting factors of ``g`` functions. Must sum to 1.
342+
eta : :obj:`float`, optional
343+
Relaxation parameter (must be between 0 and 1, 0 excluded). Note that
344+
this will be only used when ``acceleration=None``.
334345
niter : :obj:`int`, optional
335346
Number of iterations of iterative scheme
336347
acceleration: :obj:`str`, optional
337-
Acceleration (``vandenberghe`` or ``fista``)
348+
Acceleration (``None``, ``vandenberghe`` or ``fista``)
338349
callback : :obj:`callable`, optional
339350
Function with signature (``callback(x)``) to call after each iteration
340351
where ``x`` is the current model vector
@@ -353,16 +364,27 @@ def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
353364
354365
.. math::
355366
\text{for } j=1,\cdots,n, \\
356-
~~~~\mathbf z_j^{k+1} = \mathbf z_j^{k} + \epsilon_j
357-
\left[prox_{\frac{\tau^k}{\omega_j} g_j}\left(2 \mathbf{x}^{k} - \mathbf{z}_j^{k}
367+
~~~~\mathbf z_j^{k+1} = \mathbf z_j^{k} + \eta
368+
\left[prox_{\frac{\tau^k \epsilon_j}{w_j} g_j}\left(2 \mathbf{x}^{k} - \mathbf{z}_j^{k}
358369
- \tau^k \sum_{i=1}^n \nabla f_i(\mathbf{x}^{k})\right) - \mathbf{x}^{k} \right] \\
359-
\mathbf{x}^{k+1} = \sum_{j=1}^n \omega_j f_j \\
360-
361-
where :math:`\sum_{j=1}^n \omega_j=1`. In the current implementation :math:`\omega_j=1/n`.
370+
\mathbf{x}^{k+1} = \sum_{j=1}^n w_j \mathbf z_j^{k+1} \\
371+
372+
where :math:`\sum_{j=1}^n w_j=1`. In the current implementation, :math:`w_j=1/n` when
373+
not provided.
374+
362375
"""
376+
# check if weights sum to 1
377+
if weights is None:
378+
weights = np.ones(len(proxgs)) / len(proxgs)
379+
if len(weights) != len(proxgs) or np.sum(weights) != 1.:
380+
raise ValueError(f'omega={weights} must be an array of size {len(proxgs)} '
381+
f'summing to 1')
382+
print(weights)
383+
363384
# check if epgs is a vector
364385
if np.asarray(epsg).size == 1.:
365386
epsg_print = str(epsg)
387+
epsg = epsg * np.ones(len(proxgs))
366388
else:
367389
epsg_print = 'Multi'
368390

@@ -404,9 +426,9 @@ def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
404426
x = np.zeros_like(x)
405427
for i, proxg in enumerate(proxgs):
406428
ztmp = 2 * y - zs[i] - tau * grad
407-
ztmp = proxg.prox(ztmp, epsg * tau)
408-
zs[i] += (ztmp - y)
409-
x += zs[i] / len(proxgs)
429+
ztmp = proxg.prox(ztmp, tau * epsg[i] / weights[i])
430+
zs[i] += eta * (ztmp - y)
431+
x += weights[i] * zs[i]
410432

411433
# update y
412434
if acceleration == 'vandenberghe':
@@ -417,7 +439,6 @@ def GeneralizedProximalGradient(proxfs, proxgs, x0, tau=None,
417439
omega = ((told - 1.) / t)
418440
else:
419441
omega = 0
420-
421442
y = x + omega * (x - xold)
422443

423444
# run callback

pytests/test_solver.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@
1010
par2 = {'n': 8, 'm': 10, 'dtype': 'float64'} # float32
1111

1212

13+
@pytest.mark.parametrize("par", [(par1), (par2)])
14+
def test_GPG_weights(par):
15+
"""Check GPG raises error if weight is not summing to 1
16+
"""
17+
with pytest.raises(ValueError):
18+
np.random.seed(0)
19+
n, m = par['n'], par['m']
20+
21+
# Random mixing matrix
22+
R = np.random.normal(0., 1., (n, m))
23+
Rop = MatrixMult(R)
24+
25+
# Model and data
26+
x = np.zeros(m)
27+
y = Rop @ x
28+
29+
# Operators
30+
l2 = L2(Op=Rop, b=y, niter=10, warm=True)
31+
l1 = L1(sigma=5e-1)
32+
_ = GeneralizedProximalGradient([l2, ], [l1, ],
33+
x0=np.zeros(m),
34+
tau=1.,
35+
weights=[1., 1.])
36+
37+
1338
@pytest.mark.parametrize("par", [(par1), (par2)])
1439
def test_PG_GPG(par):
1540
"""Check equivalency of ProximalGradient and GeneralizedProximalGradient when using

0 commit comments

Comments
 (0)