Skip to content

Allow infinite log odds in categorical_logit and multinomial_logit - #3412

Open
godking123 wants to merge 8 commits into
stan-dev:developfrom
godking123:neg-inf-logit
Open

godking123 wants to merge 8 commits into
stan-dev:developfrom
godking123:neg-inf-logit

Conversation

@godking123

Copy link
Copy Markdown

Closes #3201.

Summary

categorical_logit and multinomial_logit rejected any infinite value in beta, although softmax handles -inf, so categorical_logit(beta) was stricter than categorical(softmax(beta)). For data beta, the _rng and _lpmf functions (and so _lupmf) now accept infinite values, as discussed in the issue:

  • -inf entries have zero probability.
  • +inf entries split the probability evenly between them. A single +inf entry is deterministic.
  • All--inf input and NaN throw a std::domain_error.

Autodiff beta in the _lpmf functions must still be finite, since infinite values would break the gradients.

When there are +inf entries, the probabilities are computed directly, because softmax / log_sum_exp would give NaN. Otherwise the existing code path is used unchanged. categorical_logit_rng's sampling loop now uses >= instead of >, so a uniform draw of exactly 0.0 can't land on a zero-probability first entry.

Tests

  • Updated the prim error tests to allow +/-inf and to throw on all--inf.
  • Added prim tests for the single and multiple +inf entries, for both _rng and _lpmf.
  • Added mix tests showing that autodiff beta with -inf still throws.

Side Effects

None

Release notes

categorical_logit and multinomial_logit now accept infinite log odds when beta
is data: -inf entries get zero probability, and +inf entries split the probability evenly among themselves.

Checklist

  • Copyright holder: Rajit Sareen

    The copyright holder is typically you or your assignee, such as a university or company. By submitting this pull request, the copyright holder is agreeing to the license the submitted work under the following licenses:
    - Code: BSD 3-clause (https://opensource.org/licenses/BSD-3-Clause)
    - Documentation: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)

  • the basic tests are passing

    • unit tests pass (to run, use: ./runTests.py test/unit)
    • header checks pass, (make test-headers)
    • dependencies checks pass, (make test-math-dependencies)
    • docs build, (make doxygen)
    • code passes the built-in coding standards checks (make cpplint)
  • the code is written in idiomatic C++ and changes are documented in the doxygen

  • the new changes are tested

@bob-carpenter

Copy link
Copy Markdown
Member

Hi, @godking123, and thanks for the PR.

Given that this is in our C++ code, we're going to need someone like @SteveBronder to review it. I'll leave some comments, but don't feel confident enough about our C++ style and library to do the final review.

@bob-carpenter bob-carpenter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused in reading the code about why the checks were laid out how they were. The documentation also needs to change for the top-level function to indicate what does and doesn't throw.

Otherwise, I'm going to leave this to @SteveBronder.

// Autodiff args must be finite, data can be +/-inf
if constexpr (is_constant<T_prob>::value) {
check_not_nan(function, "log odds parameter", beta_ref);
check_greater(function, "log odds parameter", beta_ref.maxCoeff(),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this allowing +infinity, but not -infinity? I thought the issue was to have -infinity turn into zero.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is for the all -inf case, it throws when the maximum in the beta vector is -inf. I realize this test isn't very straightforward I can reword it or add a comment

check_greater(function, "log odds parameter", beta_ref.maxCoeff(),
NEGATIVE_INFTY);
} else {
check_finite(function, "log odds parameter", beta_ref);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we checking the log odds parameter is finite? Isn't this allowed to be infinite?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in the autodiff case, when it's a data variable it's allowed to be infinite

if constexpr (is_constant<T_prob>::value) {
int num_infty = (beta_ref.array() == INFTY).count();
if (num_infty > 0) {
return beta_ref.coeff(n - 1) == INFTY ? -std::log(num_infty)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we taking the negative log of the number of infinite coefficients?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need log(1/num_infty) for log probability since the probability is 1/num_infty. -log(num_infty) is an equivalent expression to this

ref_type_t<T_prob> beta_ref = beta;
check_finite(function, "log odds parameter", beta_ref);

// Autodiff args must be finite, data can be +/-inf

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks---this comment is useful---maybe something like this above to explain the range checks?

// Autodiff args must be finite, data can be +/-inf
if constexpr (is_constant<T_prob>::value) {
check_not_nan(function, "log odds parameter", beta_ref);
if (beta_ref.size() > 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is beta_ref.size() == 0 legal at this point or was it short-circuited earlier?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes should be legal it's reachable by an empty beta vector

@godking123

Copy link
Copy Markdown
Author

Hi @bob-carpenter, thanks for the review. I've added comments clarifying the bounds checks and and updated the docstrings to spell out what throws, could you restart the CI checks for the latest commit whenever you get the chance?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

X_logit_{rng, lpdf, lupdf}(theta) should allow -infinite values in theta

3 participants