odl icon indicating copy to clipboard operation
odl copied to clipboard

Proximal of LInfty wrong?

Open mehrhardt opened this issue 3 years ago • 7 comments

https://github.com/odlgroup/odl/blob/a269486387f51559fece569704097dda07b3ce86/odl/solvers/nonsmooth/proximal_operators.py#L1497

As it is implemented, the proximal of the Linfty-norm does not depend on its scaling factor in front of it (see line above). Not sure what the correct formulation is but the one implemented has to be wrong. This is related to the missing tests as in #1385.

mehrhardt avatar Oct 20 '21 16:10 mehrhardt

My current guess is that it should be

        def _call(self, x, out):
            """Return ``self(x)``."""

            if x is out:
                x = x.copy()

            proj_l1(x, self.sigma, out)

but I didn't have time to verify it.

Anyone knows the answer?

mehrhardt avatar Oct 20 '21 16:10 mehrhardt

As it is implemented, the proximal of the Linfty-norm does not depend on its scaling factor in front of it (see line above). Not sure what the correct formulation is but the one implemented has to be wrong.

One way to see this is for sigma = 0 where one expects to get the identity as the prox.

mehrhardt avatar Oct 20 '21 16:10 mehrhardt

@JevgenijaAksjonova or @sbanert, do you have any input regarding the mathematical question that @mehrhardt posted?

ozanoktem avatar Oct 21 '21 04:10 ozanoktem

I think I figured what the prox should be:

        def _call(self, x, out):
            """Return ``self(x)``."""

            if x is out:
                x = x.copy()

            proj_l1(x, self.sigma, out)

            out.lincomb(-1, out, 1, x)

but is there a good way to test whether this is correct?

mehrhardt avatar Oct 21 '21 09:10 mehrhardt

It turns out that the issue is even deeper than I thought. The following example reveals that the projection onto L1 balls is incorrect.

import odl

X = odl.rn(2)

in0 = X.one().copy()

print(f(in0))
for sigma in [0, 1, 10]:

    f = odl.solvers.L1Norm(X)
    out = odl.solvers.nonsmooth.proj_l1(in0, sigma)
        
    print(sigma, f(out))

2.0 0 0.0 1 1.0 10 10.0

mehrhardt avatar Oct 21 '21 10:10 mehrhardt

OK, I believe I figured it out :D

The issue is the projection onto the L1-ball which should have been

    if out is None:
        out = x.space.element()

    u = x.ufuncs.absolute()
    
    if u.ufuncs.sum() <= radius:
        out[:] = x
    else:
        v = x.ufuncs.sign()
        proj_simplex(u, radius, out)
        out *= v

    return out

where in the original ODL code the "if" was missing. I'll make a PR.

mehrhardt avatar Oct 21 '21 16:10 mehrhardt

PR is now open #1614

mehrhardt avatar Oct 21 '21 16:10 mehrhardt