Source code for sporco.pgm.backtrack
# -*- coding: utf-8 -*-
# Copyright (C) 2020 by Cristina Garcia-Cardona <cgarciac@lanl.gov>
# Brendt Wohlberg <brendt@ieee.org>
# All rights reserved. BSD 3-clause License.
# This file is part of the SPORCO package. Details of the copyright
# and user license can be found in the 'LICENSE.txt' file distributed
# with the package.
"""Backtracking methods for PGM algorithms"""
from __future__ import division, print_function
import numpy as np
__author__ = """Cristina Garcia-Cardona <cgarciac@lanl.gov>"""
[docs]
class BacktrackBase(object):
"""Base class for computing step size for
proximal gradient method via backtracking.
This class is intended to be a base class of other classes
that specialise to specific backtracking options.
After termination of the :meth:`update` method the new
state in the proximal gradient method is computed.
This also updates all the supporting variables.
"""
def __init__(self):
super(BacktrackBase, self).__init__()
[docs]
def update(self, solverobj):
"""Update step size via backtracking.
Overriding this method is required.
"""
raise NotImplementedError()
[docs]
class BacktrackStandard(BacktrackBase):
"""Class to estimate step size L by computing a linesearch
that guarantees that F <= Q according to the standard PGM
backtracking strategy in :cite:`beck-2009-fast`.
After termination of the :meth:`update` method the new
state in the proximal gradient method is computed.
This also updates all the supporting variables.
"""
def __init__(self, gamma_u=1.2, maxiter=50):
r"""
Parameters
----------
gamma_u : float
Multiplier applied to increase L when backtracking
in standard PGM (corresponding to :math:`\eta` in
:cite:`beck-2009-fast`).
maxiter : int
Maximum iterations of updating L when backtracking.
"""
super(BacktrackStandard, self).__init__()
# Initialise attributes controlling the backtracking
self.gamma_u = gamma_u
self.maxiter = maxiter
[docs]
def update(self, solverobj):
"""
Parameters
----------
solverobj : PGM object
object containing state and functions
required to adjust the step size
"""
gradY = solverobj.grad_f() # Given Y(f), this update computes gradY(f)
maxiter = self.maxiter
iterBTrack = 0
linesearch = 1
while linesearch and iterBTrack < maxiter:
solverobj.xstep(gradY) # Given gradY(f), L, this updates X(f)
f = solverobj.obfn_f(solverobj.var_x())
Dxy = solverobj.eval_Dxy()
Q = solverobj.obfn_f(solverobj.var_y()) + \
solverobj.eval_linear_approx(Dxy, gradY) + \
(solverobj.L / 2.) * np.linalg.norm(Dxy.flatten(), 2)**2
if f <= Q:
linesearch = 0
else:
solverobj.L *= self.gamma_u
iterBTrack += 1
solverobj.F = f
solverobj.Q = Q
solverobj.iterBTrack = iterBTrack
# Update auxiliary sequence
solverobj.ystep()
[docs]
class BacktrackRobust(BacktrackBase):
"""Class to estimate step size L by computing a linesearch
that guarantees that F <= Q according to the robust PGM
backtracking strategy in :cite:`florea-2017-robust`.
After termination of the :meth:`update` method the new
state in the proximal gradient method is computed.
This also updates all the supporting variables.
"""
def __init__(self, gamma_d=0.9, gamma_u=2.0, maxiter=50):
r"""
Parameters
----------
gamma_d : float
Multiplier applied to decrease L when
backtracking in robust PGM (:math:`\gamma_d` in
:cite:`florea-2017-robust`).
gamma_u : float
Multiplier applied to increase L when
backtracking in robust PGM (corresponding
Total :math:`\gamma_u` in :cite:`florea-2017-robust`).
maxiter : int
Maximum iterations of updating L when backtracking.
"""
super(BacktrackRobust, self).__init__()
# Initialise attributes controlling the backtracking
self.gamma_d = gamma_d
self.gamma_u = gamma_u
self.maxiter = maxiter
self.Tk = 0.
self.Zrb = None
[docs]
def update(self, solverobj):
"""
Parameters
----------
solverobj : PGM object
object containing state and functions
required to adjust the step size
"""
if self.Zrb is None:
self.Zrb = solverobj.var_x().copy()
solverobj.L *= self.gamma_d
maxiter = self.maxiter
iterBTrack = 0
linesearch = 1
while linesearch and iterBTrack < maxiter:
t = float(1. + np.sqrt(1. + 4. * solverobj.L * self.Tk)) / \
(2. * solverobj.L)
T = self.Tk + t
y = (self.Tk * solverobj.var_xprv() + t * self.Zrb) / T
solverobj.var_y(y)
gradY = solverobj.xstep() # Given Y(f), L, this updates X(f)
f = solverobj.obfn_f(solverobj.var_x())
Dxy = solverobj.eval_Dxy()
Q = solverobj.obfn_f(solverobj.var_y()) + \
solverobj.eval_linear_approx(Dxy, gradY) + \
(solverobj.L / 2.) * np.linalg.norm(Dxy.flatten(), 2)**2
if f <= Q:
linesearch = 0
else:
solverobj.L *= self.gamma_u
iterBTrack += 1
self.Tk = T
self.Zrb += (t * solverobj.L * (solverobj.var_x() - solverobj.var_y()))
solverobj.F = f
solverobj.Q = Q
solverobj.iterBTrack = iterBTrack