Skip to content

Commit e949fde

Browse files
committed
Minmax prox method
1 parent fb1941f commit e949fde

3 files changed

Lines changed: 278 additions & 1 deletion

File tree

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
from PEPit import PEP
2+
from PEPit.functions import BlockConvexConcaveFunction
3+
from PEPit.point import Point
4+
from PEPit.expression import Expression
5+
6+
7+
def wc_proximal_point(gamma, n, wrapper="cvxpy", solver=None, verbose=1):
8+
"""
9+
Consider the minimization problem
10+
11+
.. math:: f_\\star \\triangleq \\min_x f(x),
12+
13+
where :math:`f` is closed, proper, and convex (and potentially non-smooth).
14+
15+
This code computes a worst-case guarantee for the **proximal point method** with step-size :math:`\\gamma`.
16+
That is, it computes the smallest possible :math:`\\tau(n,\\gamma)` such that the guarantee
17+
18+
.. math:: f(x_n) - f_\\star \\leqslant \\tau(n, \\gamma) \\|x_0 - x_\\star\\|^2
19+
20+
is valid, where :math:`x_n` is the output of the proximal point method, and where :math:`x_\\star` is a
21+
minimizer of :math:`f`.
22+
23+
In short, for given values of :math:`n` and :math:`\\gamma`,
24+
:math:`\\tau(n,\\gamma)` is computed as the worst-case value of :math:`f(x_n)-f_\\star`
25+
when :math:`\\|x_0 - x_\\star\\|^2 \\leqslant 1`.
26+
27+
**Algorithm**:
28+
29+
The proximal point method is described by
30+
31+
.. math:: x_{t+1} = \\arg\\min_x \\left\\{f(x)+\\frac{1}{2\gamma}\\|x-x_t\\|^2 \\right\\},
32+
33+
where :math:`\\gamma` is a step-size.
34+
35+
**Theoretical guarantee**:
36+
37+
The **tight** theoretical guarantee can be found in [1, Theorem 4.1]:
38+
39+
.. math:: f(x_n)-f_\\star \\leqslant \\frac{\\|x_0-x_\\star\\|^2}{4\\gamma n},
40+
41+
where tightness is obtained on, e.g., one-dimensional linear problems on the positive orthant.
42+
43+
**References**:
44+
45+
`[1] A. Taylor, J. Hendrickx, F. Glineur (2017).
46+
Exact worst-case performance of first-order methods for composite convex optimization.
47+
SIAM Journal on Optimization, 27(3):1283–1313.
48+
<https://arxiv.org/pdf/1512.07516.pdf>`_
49+
50+
Args:
51+
gamma (float): step-size.
52+
n (int): number of iterations.
53+
wrapper (str): the name of the wrapper to be used.
54+
solver (str): the name of the solver the wrapper should use.
55+
verbose (int): level of information details to print.
56+
57+
- -1: No verbose at all.
58+
- 0: This example's output.
59+
- 1: This example's output + PEPit information.
60+
- 2: This example's output + PEPit information + solver details.
61+
62+
Returns:
63+
pepit_tau (float): worst-case value
64+
theoretical_tau (float): theoretical value
65+
66+
Example:
67+
>>> pepit_tau, theoretical_tau = wc_proximal_point(gamma=3, n=4, wrapper="cvxpy", solver=None, verbose=1)
68+
(PEPit) Setting up the problem: size of the Gram matrix: 6x6
69+
(PEPit) Setting up the problem: performance measure is the minimum of 1 element(s)
70+
(PEPit) Setting up the problem: Adding initial conditions and general constraints ...
71+
(PEPit) Setting up the problem: initial conditions and general constraints (1 constraint(s) added)
72+
(PEPit) Setting up the problem: interpolation conditions for 1 function(s)
73+
Function 1 : Adding 20 scalar constraint(s) ...
74+
Function 1 : 20 scalar constraint(s) added
75+
(PEPit) Setting up the problem: additional constraints for 0 function(s)
76+
(PEPit) Compiling SDP
77+
(PEPit) Calling SDP solver
78+
(PEPit) Solver status: optimal (wrapper:cvxpy, solver: MOSEK); optimal value: 0.020833335685730252
79+
(PEPit) Primal feasibility check:
80+
The solver found a Gram matrix that is positive semi-definite up to an error of 3.626659005644299e-09
81+
All the primal scalar constraints are verified up to an error of 1.1386158081487519e-08
82+
(PEPit) Dual feasibility check:
83+
The solver found a residual matrix that is positive semi-definite
84+
All the dual scalar values associated with inequality constraints are nonnegative
85+
(PEPit) The worst-case guarantee proof is perfectly reconstituted up to an error of 3.0464297827437203e-08
86+
(PEPit) Final upper bound (dual): 0.020833337068527292 and lower bound (primal example): 0.020833335685730252
87+
(PEPit) Duality gap: absolute: 1.382797040067052e-09 and relative: 6.637425042856655e-08
88+
*** Example file: worst-case performance of proximal point method ***
89+
PEPit guarantee: f(x_n)-f_* <= 0.0208333 ||x_0 - x_*||^2
90+
Theoretical guarantee: f(x_n)-f_* <= 0.0208333 ||x_0 - x_*||^2
91+
92+
"""
93+
94+
# Instantiate PEP
95+
problem = PEP()
96+
97+
# Declare a convex function
98+
99+
block_partition = problem.declare_block_partition(d=2)
100+
func = problem.declare_function(function_class=BlockConvexConcaveFunction, partition=block_partition)
101+
# Start by defining its unique optimal point xs = x_* and corresponding function value fs = f_*
102+
zs = func.stationary_point()
103+
fs = func(zs)
104+
105+
# Then define the starting point x0 of the algorithm
106+
z0 = problem.set_initial_point()
107+
108+
# Set the initial constraint that is the distance between x0 and x^*
109+
problem.set_initial_condition((z0 - zs) ** 2 <= 1)
110+
111+
# Run n steps of the proximal point method
112+
z = z0
113+
z_avg = z
114+
count = 1
115+
for _ in range(n):
116+
117+
118+
# Compute x from the docstring equation.
119+
gz = Point()
120+
fz = Expression()
121+
z = z - gamma * block_partition.get_block(gz, 0)
122+
z = z + gamma * block_partition.get_block(gz, 1)
123+
z_avg += z
124+
count+=1
125+
# Add point to Function f.
126+
func.add_point((z, gz, fz))
127+
128+
z_avg /= count
129+
#func.add_point((z_avg, func.gradient(z_avg), func(z_avg)))
130+
# Set the performance metric to the final distance to optimum in function values
131+
problem.set_performance_metric(func(z_avg)-fs)
132+
133+
# Solve the PEP
134+
pepit_verbose = max(verbose, 0)
135+
pepit_tau = problem.solve(wrapper=wrapper, solver=solver, verbose=pepit_verbose)
136+
137+
# Compute theoretical guarantee (for comparison)
138+
theoretical_tau = 1 / (gamma * n)
139+
140+
# Print conclusion if required
141+
if verbose != -1:
142+
print('*** Example file: worst-case performance of proximal point method ***')
143+
print('\tPEPit guarantee:\t f(z_avg)-f_* <= {:.6} ||z_0 - z_*||^2'.format(pepit_tau))
144+
print('\tTheoretical guarantee:\t f(z_avg)-f_* <= {:.6} ||z_0 - z_*||^2'.format(theoretical_tau))
145+
146+
# Return the worst-case guarantee of the evaluated method (and the reference theoretical value)
147+
return pepit_tau, theoretical_tau
148+
149+
150+
if __name__ == "__main__":
151+
pepit_tau, theoretical_tau = wc_proximal_point(gamma=0.2, n=10, wrapper="cvxpy", solver=None, verbose=1)

PEPit/functions/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from .block_convex_concave_function import BlockConvexConcaveFunction
12
from .block_smooth_convex_function_cheap import BlockSmoothConvexFunctionCheap
23
from .block_smooth_convex_function_expensive import BlockSmoothConvexFunctionExpensive
34
from .convex_function import ConvexFunction
@@ -15,7 +16,8 @@
1516
from .smooth_strongly_convex_quadratic_function import SmoothStronglyConvexQuadraticFunction
1617
from .strongly_convex_function import StronglyConvexFunction
1718

18-
__all__ = ['block_smooth_convex_function_cheap', 'BlockSmoothConvexFunctionCheap',
19+
__all__ = ['block_convex_concave_function', 'BlockConvexConcaveFunction',
20+
'block_smooth_convex_function_cheap', 'BlockSmoothConvexFunctionCheap',
1921
'block_smooth_convex_function_expensive', 'BlockSmoothConvexFunctionExpensive',
2022
'convex_function', 'ConvexFunction',
2123
'convex_indicator', 'ConvexIndicatorFunction',
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import numpy as np
2+
3+
from PEPit.function import Function
4+
from PEPit.block_partition import BlockPartition
5+
6+
7+
class BlockConvexConcaveFunction(Function):
8+
"""
9+
The :class:`BlockSmoothConvexFunction` class overwrites the `add_class_constraints` method of :class:`Function`,
10+
by implementing necessary constraints for interpolation of the class of smooth convex functions by blocks.
11+
12+
Attributes:
13+
partition (BlockPartition): partitioning of the variables (in blocks).
14+
L (list): smoothness parameters (one per block).
15+
16+
Smooth convex functions by blocks are characterized by a list of parameters :math:`L_i` (one per block),
17+
hence can be instantiated as
18+
19+
Example:
20+
>>> from PEPit import PEP
21+
>>> from PEPit.functions import BlockSmoothConvexFunction
22+
>>> problem = PEP()
23+
>>> partition = problem.declare_block_partition(d=3)
24+
>>> L = [1, 4, 10]
25+
>>> func = problem.declare_function(function_class=BlockSmoothConvexFunction, partition=partition, L=L)
26+
27+
References:
28+
29+
`[1] Z. Shi, R. Liu (2016).
30+
Better worst-case complexity analysis of the block coordinate descent method for large scale machine learning.
31+
In 2017 16th IEEE International Conference on Machine Learning and Applications (ICMLA).
32+
<https://arxiv.org/pdf/1608.04826.pdf>`_
33+
34+
"""
35+
36+
def __init__(self,
37+
partition,
38+
is_leaf=True,
39+
decomposition_dict=None,
40+
reuse_gradient=True,
41+
name=None):
42+
"""
43+
44+
Args:
45+
partition (BlockPartition): a :class:`BlockPartition`.
46+
is_leaf (bool): True if self is defined from scratch.
47+
False if self is defined as linear combination of leaf.
48+
decomposition_dict (dict): Decomposition of self as linear combination of leaf :class:`Function` objects.
49+
Keys are :class:`Function` objects and values are their associated coefficients.
50+
reuse_gradient (bool): If True, the same subgradient is returned
51+
when one requires it several times on the same :class:`Point`.
52+
If False, a new subgradient is computed each time one is required.
53+
name (str): name of the object. None by default. Can be updated later through the method `set_name`.
54+
55+
Note:
56+
Smooth convex functions by blocks are necessarily differentiable, hence `reuse_gradient` is set to True.
57+
58+
"""
59+
super().__init__(is_leaf=is_leaf,
60+
decomposition_dict=decomposition_dict,
61+
reuse_gradient=True,
62+
name=name,
63+
)
64+
65+
# Store partition and L
66+
assert isinstance(partition, BlockPartition)
67+
assert partition.get_nb_blocks() == 2
68+
self.partition = partition
69+
70+
71+
def add_class_constraints(self):
72+
"""
73+
Formulates the list of necessary constraints for interpolation for self (block smooth convex function);
74+
see [1, Lemma 1.1].
75+
76+
"""
77+
# Set function ID
78+
function_id = self.get_name()
79+
if function_id is None:
80+
function_id = "Function_{}".format(self.counter)
81+
82+
# Set tables_of_constraints attributes
83+
self.tables_of_constraints["convexity_block_{}".format(0)] = [[]]*len(self.list_of_points)
84+
self.tables_of_constraints["concavity_block_{}".format(1)] = [[]]*len(self.list_of_points)
85+
86+
# Browse list of points and create interpolation constraints
87+
for i, point_i in enumerate(self.list_of_points):
88+
89+
xi, gi, fi = point_i
90+
xi_id = xi.get_name()
91+
if xi_id is None:
92+
xi_id = "Point_{}".format(i)
93+
94+
for j, point_j in enumerate(self.list_of_points):
95+
96+
xj, gj, fj = point_j
97+
xj_id = xj.get_name()
98+
if xj_id is None:
99+
xj_id = "Point_{}".format(j)
100+
101+
if point_i == point_j:
102+
self.tables_of_constraints["convexity_block_{}".format(0)][i].append(0)
103+
self.tables_of_constraints["concavity_block_{}".format(1)][i].append(0)
104+
105+
else:
106+
gj_cvx = self.partition.get_block(gj, 0)
107+
xi_cvx = self.partition.get_block(xi, 0)
108+
xj_cvx = self.partition.get_block(xj, 0)
109+
constraint = fi - fj >= gj_cvx * (xi_cvx - xj_cvx)
110+
constraint.set_name("IC_{}_convexity_block_{}({}, {})".format(function_id, 0,
111+
xi_id, xj_id))
112+
self.tables_of_constraints["convexity_block_{}".format(0)][i].append(constraint)
113+
self.list_of_class_constraints.append(constraint)
114+
115+
gj_ccv = self.partition.get_block(gj, 1)
116+
xi_ccv = self.partition.get_block(xi, 1)
117+
xj_ccv = self.partition.get_block(xj, 1)
118+
constraint = fi - fj <= gj_ccv * (xi_ccv - xj_ccv)
119+
constraint.set_name("IC_{}_concavity_block_{}({}, {})".format(function_id, 1,
120+
xi_id, xj_id))
121+
self.tables_of_constraints["concavity_block_{}".format(1)][i].append(constraint)
122+
self.list_of_class_constraints.append(constraint)
123+
124+

0 commit comments

Comments
 (0)