Skip to content

NaN derivative in square(sqrt(x)) in model local variable #21

Closed

Description

@syclik

From @bob-carpenter on September 4, 2013 7:7

Simon Barthelmé reported via the stan-users list. I simplified the reported model to this minimal instance:

parameters {
  real x;
}
model {
  real y;
  y <- sqrt(square(x-x));

  x ~ normal(0,1);
}

As Simon correctly diagnosed, this model causes a problem with autodiff, as can be seen when running from RStan 1.3:

TESTING GRADIENT FOR MODEL 'barthelme2' NOW (CHAIN 4).

TEST GRADIENT MODE

 Log probability=-0.0571533

 param idx           value           model     finite diff           error
         0       -0.338093             nan        0.338093             nan

First step is to move this into a unit test that displays the behavior.

Copied from original issue: stan-dev/stan#203

Activity

  1. syclik commented on Jul 6, 2015

    @syclik
    MemberAuthor

    From @bob-carpenter on September 23, 2013 18:39

    We can't really fix this problem without a huge amount of work moving to expression templates; with expression templates, we could follow Eigen in carrying out symbolic math at compile time, such as reducing sqrt(square(u)) to u.

    I just checked that the boundary conditions in both sqrt() and square() are correct, as is the application of the chain rule.

    The problem is that the chain rule is applied literally with floating-point arithmetic, which given

    sqrt'(u) = 0.5 / sqrt(u)
    
    square'(v) = 2 * v
    

    yields

    d/dx sqrt(square(x-x))
    
      = sqrt'(square(x-x)) * square'(x - x) * d/dx (x - x)
    
      = (+ infinity)  * 0 * 0
    
      = NaN.
    

    I am going to add a discussion of this case to the manual, so I'm linking #

    The problem is that (infinity * 0) produces NaN in floating-point arithmetic. There's no way given just the infinity and 0 that mulitplying them should produce 1.

  2. syclik commented on Jul 6, 2015

    @syclik
    MemberAuthor

    From @bob-carpenter on November 20, 2013 5:20

    Implement as Marcus suggested in e-mail:

    Putting those two things together, maybe we can just rewrite chain() for sqrt() to be:

    void chain() {
      if (val_ > 1e-300)
        avi_->adj_ += adj_ / (2.0 * val_);
      else
        avi_->adj_ += adj_ / 2.0;
      }
    

    I asked: Will this do the right thing for HMC and BFGS? And Marcus responded:

    This is effectively returning a subgradient for points where the gradient isn't defined. HMC won't care so long as same subgradient is always returned (which it is in this case). The regions where this will happen are extremely small, so, except for a few pathological cases, I wouldn't expect it to impact either method significantly.

  3. sakrejda commented on Mar 4, 2016

    @sakrejda
    Contributor

    @bob-carpenter Am I right that this change did not make it into the appropriate file? If it's not on your short list I can go through it for the sake of cleaning up issues. If you have a guess at which additional files the change needs to touch that would help too.

  4. bob-carpenter commented on Mar 4, 2016

    @bob-carpenter
    Member

    Great. This wasn't ever fixed. If you want to do as Marcus suggests, it's just going to be the sqrt.hpp in rev and fwd. I still don't get why Marcus wanted to do it that way but I trust he knows what he's doing.

  5. mbrubake commented on Mar 4, 2016

    @mbrubake
    Member

    What I suggested is something of a kludge. I'm not sure if it's really the best idea but it might resolve the issue in practice. That said, I really don't remember too much of the discussion around this. To put it another way, I think I might mistrust my past self.

  6. sakrejda commented on Mar 5, 2016

    @sakrejda
    Contributor

    Ok, I'll figure out what to do here, thanks for the pointers. It sounds like it needs a test and some demonstration that whatever we do doesn't screw up results as far as sampling is concerned. Seems like a gentle enough introduction to the autodiff code base...

  7. bob-carpenter commented on Aug 17, 2016

    @bob-carpenter
    Member

    I'm closing this because I don't think having square(sqrt(0)) produce NaN is a bug.

  8. modified the milestone: on Sep 7, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions