Huber.prox uses the wrong branch threshold (|x| > alpha instead of |x| > alpha + tau)

Aperta
#275 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@raashish1601 ci sta già lavorando.

Dal 7/10/2026.

  • #276 di @raashish1601 — aperta

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
42/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Ferma
Stack tecnologico
numpy, python

Direzione di ricerca

Read pyproximal/proximal/Huber.py at the Huber.prox implementation and inspect the existing tests for that operator. Reproduce the reported values, especially where alpha < |x| <= alpha + tau, then check the prox against the stated formula or a numerical minimizer. Done means the prox is monotone and matches the minimizer across those inputs; issue #276 already has an open pull request.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

pyproximal.Huber.prox decides which branch to apply from |x| > alpha, but for the proximal operator the switch happens at |x| = alpha + tau. Inputs with alpha < |x| <= alpha + tau are soft-thresholded when they should be scaled. The result is not the minimizer, and the prox becomes discontinuous and non-monotone in x. This is in pyproximal/proximal/Huber.py (line 63 on main, also in 0.12.0). Related: #189, which split Huber and HuberCircular but left this threshold as it was.

Reproducer

import numpy as np
import pyproximal

alpha, tau = 1.0, 1.0
h = pyproximal.Huber(alpha=alpha)
x = np.array([-2.5, -1.5, -0.5, 0.5, 1.0, 1.2, 1.5, 1.9, 2.0, 2.5, 4.0])

# brute-force argmin_y H(y) + (y - x)^2 / (2 tau)
ys = np.linspace(-5, 5, 1000001)
H = lambda t: np.where(np.abs(t) <= alpha, t**2 / (2 * alpha), np.abs(t) - alpha / 2)
brute = np.array([ys[np.argmin(H(ys) + (ys - xi) ** 2 / (2 * tau))] for xi in x])

print(np.round(h.prox(x, tau), 4))  # [-1.5 -0.5  -0.25 0.25 0.5 0.2 0.5  0.9  1. 1.5 3.]
print(np.round(brute, 4))           # [-1.5 -0.75 -0.25 0.25 0.5 0.6 0.75 0.95 1. 1.5 3.]

For example, x = 1.2 returns 0.2, which is smaller than the 0.5 returned for x = 1.0.

Fix

The prox of tau * H_alpha is

prox(x) = x * alpha / (alpha + tau)    if |x| <= alpha + tau
          x - tau * sign(x)            otherwise

which can also be written in closed form as (1 - tau / max(|x|, alpha + tau)) * x. That is the alternative already in the commented-out code; the comment says it "does not provide the same result", and that is because the current code is the one that is wrong. A minimal change to prox:

mask = xabs > self.alpha + tau

Equivalently, replace the body with y = (1.0 - tau / np.maximum(np.abs(x), tau + self.alpha)) * x. With either change the output matches the brute-force minimizer at all the points above. A test against a brute-force or scipy.optimize minimizer on a grid of x values that includes alpha < |x| < alpha + tau would catch a regression.

Related suggestion: HuberCircular applies the Huber function to the norm of the whole vector. For isotropic Huber-TV you need it per group, the way L21 works, e.g. a HuberL21(ndim, sigma, alpha) whose prox is v_i * (1 - tau*sigma / max(||v_i||, tau*sigma + alpha)) for each pixel.

Lingua principale
Python
Stelle
85
Fork
22
Merge medio
2h 11m
PR unite (30g)
7

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di PyLops/pyproximal

Tutte le issue di PyLops/pyproximal

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.