-
-
Notifications
You must be signed in to change notification settings - Fork 371
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sampling is able to initialize when there is an exception in transformed parameters #2570
Sampling is able to initialize when there is an exception in transformed parameters #2570
Conversation
… exception behavior in new generated code.
Travis was broken by something downstream in Math - submitted #2573 to fix it, sorry about that. |
…2562-transformed-parameters
@@ -56,3 +56,5 @@ stan.kdev4 | |||
|
|||
# local make include | |||
/make/local | |||
|
|||
*.gch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[optional]
Looks like this file is missing a newline. I don't know if that's a big deal or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
* | ||
* When at least some of the initialization is random, it will | ||
* randomly initialize until it finds a set of unconstrained | ||
* parameters that are valid or it hits <code>MAX_INIT_TRIES = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is MAX_INIT_TRIES
? Is it something the user can set? If not, then I don't think it should be given a name like this that makes it look like a property that a user could set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a holdout from old code. No, it's not something the user can set. I think the original intention was that it could be specified.
Any other suggestions for names? I'm reluctant to replace that with 100
just because it'll be harder to track down later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the fact that there's what looks like a variable name there that makes it look like someone should be able to control it. Anyway, this is just doc, so no big deal.
* 100</code> (hard-coded). | ||
* | ||
* Valid initialization is defined as a finite, non-NaN value | ||
* for the evaluation of the log probability and all its |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"log probability" -> "log density function" (or "log probability desnity function")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
for (; num_init_tries < MAX_INIT_TRIES; num_init_tries++) { | ||
stan::io::random_var_context | ||
random_context(model, rng, init_radius, init_zero); | ||
bool init_zero = init_radius <= std::numeric_limits<double>::min(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[required]
This needs documentation. The name init_zero
also needs to change as it's not testing for zero, it's testing for smaller than smallest normalized floating point number. That means there are values other than 0 that this passes for.
[optional]
This could just change to == 0
and the only thing you'd miss is unnormalized small numbers and it wouldn't need any doc or name changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll leave an inline comment. There's also a function-level comment:
- When the
var_context
provides all variables or- the init_radius is 0, this function will only evaluate the
- log probability of the model with the unconstrained
- parameters once to see if it's valid.
...- @param[in] init_radius the radius for generating random values.
- A value of 0 indicates that the unconstrained parameters (not
- provided by init) should be initialized with 0.
I'm sure I could doc it better; what documentation do I need to leave behind so this is clear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the name to is_initialized_with_zero
and I think there isn't much I can do with an inline comment now. Just let me know if there's anything else that I can add to the doc.
unconstrained, | ||
&msg); | ||
} | ||
} catch (std::domain_error& e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did someone take a pass to make sure we only throw domain errors when we want to reject and keeping going? I thought we still had Boost errors of various types and other kinds of std::exception
like range errors and things that shouldn't stop sampling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a while ago. After this catch, we catch all std::exception
, which include Boost errors.
I haven't seen any behavior stop sampling where it shouldn't. Have you? Or have you seen it reported anywhere? We can take a pass over every throw if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it looks like only std::domain_error
gets caught.
That should probably be a const
, too (missed that the first time around, but not an issue if it works like this).
It's not taking passes over the throws I'm worried about in our library so much as the ones coming out of Boost or Eigen or CVODES. Or did we take complete control over errors so that we only get error codes and control all of our own throwing? It's so long ago that I was hoping you could remind me.
logger.info(""); | ||
std::stringstream msg1; | ||
msg1 << "Gradient evaluation took " << deltaT << " seconds"; | ||
logger.info(msg1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[future request]
Our loggers should just take these, as in:
logger.info() << "Gradient evaluation took " << ...;
Or we should have a polyadic info function that could look like:
logger.info("Gradient evaluation took ", deltaT, " seconds");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just created a new issue for this.
The first implementation was a compromise and was only meant to be there as a stepping stone. Now that it's in, it's a lot easier to change safely.
init_writer(unconstrained); | ||
return unconstrained; | ||
} | ||
logger.info("Adjust your expectations accordingly!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[optional]
I strongly prefer logger.info("Adjust ... accordingly!\n\n");
without two extra empty lines. I really think we should get rid of empty logger messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind getting rid of empty lines, but we need the empty logger message. This is the behavior that allows RStan to print what chain messages came from. We assume each message is a single line and we don't have the loggers scan for \n
to split on that. We could, but currently, we don't.
logger.info(""); | ||
logger.info(""); | ||
} | ||
if (gradient_ok) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should return from here---there's no more work to do, then you won't need a confusing test in the next block.
if (gradient_ok) {
init_writer(unconstrained);
return unconstrained;
}
Then the rest of the logic simplifies.
|
||
if (num_init_tries == MAX_INIT_TRIES) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now you don't need to test any more. We've returned if we found a decent solution with finite gradient, right?
I think there should be a separate print for the init zero condition and the same tips should be given. So this all reduces to something like:
if (init_zero) {
logger.info("initialization at zero fialed after 100 attempts");
} else {
std::stringstream msg;
msg << "Initialization between (-" << init_radius ...
logger.info(msg);
}
msg << " Try specifying initial values,"
" reducing ranges of constrained values,"
" or reparameterizing the model.";
logger.info(msg);
throw std::domain_error("Initialization failed");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. That is much simpler.
} | ||
|
||
init_writer(unconstrained); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does init_writer
modify unconstrained
? I have no idea what this is doing here, so some doc may help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it does not. It just outputs the initialization. This is used for ADVI and it really is necessary if we want to think about restarting.
@bob-carpenter, thanks for leaving the detailed comments! I fixed most of it. Some of it I deferred by creating new issues. I think some of it was legitimately outside the scope here. |
Looks like the tests are passing too. Once we get this in, I think I can release 2.18. |
Submission Checklist
./runTests.py src/test/unit
make cpplint
Summary
This pull request allows initialization of a Stan program to continue when there's a violation of constraints in the transformed parameters block (or a
reject()
statement). This fixes #2562, reverting behavior to pre-v2.17.Intended Effect
This is meant to make running Stan easier. When there's an exception thrown, the initialization procedure treats that as a bad starting point and retries.
How to Verify
There are updated tests in
src/test/unit/services/util/initialize_test.cpp
. In the new tests, there is a mock model that throws in thewrite_array()
method of the model, which is where the transformed parameters are validated. The new tests make sure that the initialization is retried 100 times.Side Effects
Some additional things in the pull request:
random_var_context_test.cpp
to verify thatrandom_var_context
will now be constructed in a bad state.Documentation
None needed.
Copyright and Licensing
Please list the copyright holder for the work you are submitting (this will be you or your assignee, such as a university or company):
Generable
By submitting this pull request, the copyright holder is agreeing to license the submitted work under the following licenses: