Source code for pyproximal.proximal.L21

import numpy as np

from pyproximal.ProxOperator import _check_tau
from pyproximal import ProxOperator

[docs]class L21(ProxOperator):
r""":math:L_{2,1} proximal operator.

Proximal operator for :math:L_{2,1} matrix norm.

Parameters
----------
ndim : :obj:int
Number of dimensions :math:N_{dim}. Used to reshape the input array
in a matrix of size :math:N_{dim} \times N'_{x} where
:math:N'_x = \frac{N_x}{N_{dim}}. Note that the input
vector x should be created by stacking vectors from different
dimensions.
sigma : :obj:float, optional
Multiplicative coefficient of :math:L_{2,1} norm

Notes
-----
Given the :math:L_{2,1} norm of a matrix of size
:math:N_{dim} \times N'_x defined as:

.. math::

\sigma \|\mathbf{X}\|_{2,1} = \sigma \sum_{j=0}^{N'_x} \|\mathbf{x}_j\|_2 =
\sigma \sum_{j=0}^{N'_x} \sqrt{\sum_{i=0}^{N_{dim}}} |x_{ij}|^2

the proximal operator is:

.. math::

\prox_{\tau \sigma \|\cdot\|_{2,1}}(\mathbf{x}_j) =
\left(1 - \frac{\sigma \tau}{max\{||\mathbf{x}_j||_2,
\sigma \tau \}}\right) \mathbf{x}_j \quad \forall j

Similar to the Euclidean norm, the dual operator is defined as:

.. math::

\prox^*_{\tau \sigma \||\cdot\|_{2,1}}(\mathbf{x}_j) =
\frac{\sigma \mathbf{x}_j}{\max\{||\mathbf{x}_j||_2, \sigma\}}

Finally, we note that the :math:L_{2,1} norm is a separable function
on each column on the matrix :math:\mathbf{X}. Taking advantage of the
property of proximal operator of separable function [1]_, its proximal and
dual proximal operators can be interpreted as a series of
:class:pyproximal.proximal.Euclidean operators on each column
of the matrix :math:\mathbf{X}.

.. [1] N., Parikh, "Proximal Algorithms", Foundations and Trends
in Optimization. 2013.

"""
def __init__(self, ndim, sigma=1.):
super().__init__(None, False)
self.ndim = ndim
self.sigma = sigma

def __call__(self, x):
x = x.reshape(self.ndim, len(x) // self.ndim)
f = self.sigma * np.sum(np.sqrt(np.sum(x ** 2, axis=0)))
return f

@_check_tau
def prox(self, x, tau):
x = x.reshape(self.ndim, len(x) // self.ndim)
aux = np.sqrt(np.sum(x ** 2, axis=0))
aux = np.vstack([aux] * self.ndim).ravel()
x = (1 - (tau * self.sigma) / np.maximum(aux, tau * self.sigma)) * x.ravel()
return x

@_check_tau
def proxdual(self, x, tau):
x = x.reshape(self.ndim, len(x) // self.ndim)
aux = np.sqrt(np.sum(x ** 2, axis=0))
aux = np.vstack([aux] * self.ndim).ravel()
x = self.sigma * x.ravel() / np.maximum(aux, self.sigma)
return x