|
| 1 | +import numpy as np |
| 2 | +from numba import float64 |
| 3 | +from skglm.datafits import BaseDatafit |
| 4 | +from skglm.utils.prox_funcs import ST_vec |
| 5 | + |
| 6 | + |
| 7 | +class Pinball(BaseDatafit): |
| 8 | + r"""Pinball datafit. |
| 9 | +
|
| 10 | + The datafit reads:: |
| 11 | +
|
| 12 | + sum_i quantile_level * max(y_i - Xw_i, 0) + |
| 13 | + (1 - quantile_level) * max(Xw_i - y_i, 0) |
| 14 | +
|
| 15 | + with ``quantile_level`` in [0, 1]. |
| 16 | +
|
| 17 | + Parameters |
| 18 | + ---------- |
| 19 | + quantile_level : float |
| 20 | + Quantile level must be in [0, 1]. When ``quantile_level=0.5``, |
| 21 | + the datafit becomes a Least Absolute Deviation (LAD) datafit. |
| 22 | + """ |
| 23 | + |
| 24 | + def __init__(self, quantile_level): |
| 25 | + self.quantile_level = quantile_level |
| 26 | + |
| 27 | + def value(self, y, w, Xw): |
| 28 | + # implementation taken from |
| 29 | + # github.com/benchopt/benchmark_quantile_regression/blob/main/objective.py |
| 30 | + quantile_level = self.quantile_level |
| 31 | + |
| 32 | + residual = y - Xw |
| 33 | + sign = residual >= 0 |
| 34 | + |
| 35 | + loss = (quantile_level * sign * residual - |
| 36 | + (1 - quantile_level) * (1 - sign) * residual) |
| 37 | + return np.sum(loss) |
| 38 | + |
| 39 | + def prox(self, w, step, y): |
| 40 | + """Prox of ``step * pinball``.""" |
| 41 | + shift_cst = (self.quantile_level - 1/2) * step |
| 42 | + return y - ST_vec(y - w - shift_cst, step / 2) |
| 43 | + |
| 44 | + def prox_conjugate(self, z, step, y): |
| 45 | + """Prox of ``step * pinball^*``.""" |
| 46 | + # using Moreau decomposition |
| 47 | + inv_step = 1 / step |
| 48 | + return z - step * self.prox(inv_step * z, inv_step, y) |
| 49 | + |
| 50 | + def subdiff_distance(self, Xw, z, y): |
| 51 | + """Distance of ``z`` to subdiff of pinball at ``Xw``.""" |
| 52 | + # computation note: \partial ||y - . ||_1(Xw) = -\partial || . ||_1(y - Xw) |
| 53 | + y_minus_Xw = y - Xw |
| 54 | + shift_cst = self.quantile_level - 1/2 |
| 55 | + |
| 56 | + max_distance = 0. |
| 57 | + for i in range(len(y)): |
| 58 | + |
| 59 | + if y_minus_Xw[i] == 0.: |
| 60 | + distance_i = max(0, abs(z[i] - shift_cst) - 1) |
| 61 | + else: |
| 62 | + distance_i = abs(z[i] + shift_cst + np.sign(y_minus_Xw[i])) |
| 63 | + |
| 64 | + max_distance = max(max_distance, distance_i) |
| 65 | + |
| 66 | + return max_distance |
| 67 | + |
| 68 | + def get_spec(self): |
| 69 | + spec = ( |
| 70 | + ('quantile_level', float64), |
| 71 | + ) |
| 72 | + return spec |
| 73 | + |
| 74 | + def params_to_dict(self): |
| 75 | + return dict(quantile_level=self.quantile_level) |
0 commit comments