Source code for tick.prox.prox_nuclear

# License: BSD 3 clause

# -*- coding: utf8 -*-

import numpy as np
from numpy.linalg import svd

from tick.prox.base import Prox

__author__ = 'Stephane Gaiffas'

# TODO: code the incremental strategy, where we try smaller SVDs


[docs]class ProxNuclear(Prox): """Proximal operator of the nuclear norm, aka trace norm Parameters ---------- strength : `float` Level of penalization n_rows : `int` Number of rows in the matrix on which we apply this penalization. The number of columns is then given by (start - end) / n_rows range : `tuple` of two `int`, default=`None` Range on which the prox is applied. If `None` then the prox is applied on the whole vector positive : `bool`, default=`False` If True, apply nuclear-norm penalization followed by a truncation to make all entries non-negative rank_max : `int`, default=`None` Maximum rank to be used in the SVD (not used yet...) Notes ----- The coeffs on which we apply this prox must be flattened (using `np.ravel` for instance), and not two-dimensional. This operator is not usable from a solver with wrapped C++ code. It is based on `scipy.linalg.svd` SVD routine and is not intended for use on large matrices """
[docs] def __init__(self, strength: float, n_rows: int = None, range: tuple = None, positive: bool = False): Prox.__init__(self, range) self.positive = positive self.strength = strength self.n_rows = n_rows self.rank_max = None
def _get_matrix(self, coeffs): if self.n_rows is None: raise ValueError("'n_rows' parameter must be set before, either " "in constructor or manually") range = self.range if range is None: start, end = 0, coeffs.shape[0] else: start, end = range n_rows = self.n_rows if (end - start) % n_rows: raise ValueError("``end``-``start`` must be a multiple of " "``n_rows``") n_cols = int((end - start) / n_rows) x = coeffs[start:end].copy().reshape((n_rows, n_cols)) return x def _call(self, coeffs: np.ndarray, step: float, out: np.ndarray): x = self._get_matrix(coeffs) u, s, v = svd(x, compute_uv=True, full_matrices=False) thresh = step * self.strength s = (s - thresh) * (s > thresh) x_new = u.dot(np.diag(s)).dot(v).ravel() if self.positive: x_new[x_new < 0.] = 0. range = self.range if range is None: start, end = 0, coeffs.shape[0] else: start, end = range out[start:end] = x_new
[docs] def value(self, coeffs: np.ndarray): """ Returns the value of the penalization at ``coeffs`` Parameters ---------- coeffs : `numpy.ndarray`, shape=(n_coeffs,) The value of the penalization is computed at this point Returns ------- output : `float` Value of the penalization at ``coeffs`` """ x = self._get_matrix(coeffs) if x.shape[0] != x.shape[1]: raise ValueError('Prox nuclear must be called on a squared matrix' ', received {} np.ndarray'.format(x.shape)) s = svd(x, compute_uv=False, full_matrices=False) return self.strength * s.sum()