Skip to content
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

Improved numerical stability of binomial_coefficient_log #1614

Merged

Conversation

martinmodrak
Copy link
Contributor

@martinmodrak martinmodrak commented Jan 14, 2020

Summary

Reimplemented binomial_coefficient_log. Previously, this was done via calls to lgamma or normal approximation to the binomial coefficient. The current version delegates to lbeta for most cases, using the fact that binomial_coefficient_log(N,n) == -lbeta(N - n + 1, n + 1) - log(N + 1) . This in turn relies on the stability improvements made to lbeta in #1612 (this PR depends on #1612 - the branch has been merged here as otherwise the tests don't past).

Additionally, better handling of edge cases has been improved. The function is now valid to call whenever calling lgamma(N + 1) - lgamma(n + 1) - lgamma(N + 1 - n) would be, i.e. for n >= -1, N >= -1, N + 1 >= n. With the cases when equality holds return 0 or infinity with correct sign.

Tests

Side Effects

I don't think so, although the code might slightly change performance in either direction.

Checklist

martinmodrak and others added 19 commits January 14, 2020 13:37
…s' into bugfix/1592-binomial_coefficient_log
…s' into bugfix/1592-binomial_coefficient_log
@martinmodrak
Copy link
Contributor Author

@bbbales2 Can I ask for a review? This is a prerequisite for the neg. binomial PR (#1497).

@bbbales2
Copy link
Member

bbbales2 commented Mar 9, 2020

Sure yeah. Did we do a pull related to this for the last release? I went to the closed pull reqs. and didn't find it.

@bbbales2
Copy link
Member

bbbales2 commented Mar 9, 2020

Found it: #1612

@bbbales2 bbbales2 self-assigned this Mar 9, 2020
@stan-buildbot
Copy link
Contributor


Name Old Result New Result Ratio Performance change( 1 - new / old )
gp_pois_regr/gp_pois_regr.stan 4.85 4.85 1.0 0.06% faster
low_dim_corr_gauss/low_dim_corr_gauss.stan 0.02 0.02 1.02 2.03% faster
eight_schools/eight_schools.stan 0.09 0.09 1.03 2.45% faster
gp_regr/gp_regr.stan 0.22 0.22 1.01 0.52% faster
irt_2pl/irt_2pl.stan 6.47 6.49 1.0 -0.39% slower
performance.compilation 88.71 86.23 1.03 2.8% faster
low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 7.53 7.52 1.0 0.07% faster
pkpd/one_comp_mm_elim_abs.stan 20.6 21.24 0.97 -3.12% slower
sir/sir.stan 95.43 95.89 1.0 -0.48% slower
gp_regr/gen_gp_data.stan 0.05 0.05 0.99 -0.61% slower
low_dim_gauss_mix/low_dim_gauss_mix.stan 2.96 2.96 1.0 0.04% faster
pkpd/sim_one_comp_mm_elim_abs.stan 0.32 0.31 1.03 2.61% faster
arK/arK.stan 1.75 1.74 1.0 0.22% faster
arma/arma.stan 0.66 0.66 1.0 -0.1% slower
garch/garch.stan 0.51 0.52 1.0 -0.26% slower
Mean result: 1.00413980921

Jenkins Console Log
Blue Ocean
Commit hash: 47c7962


Machine information ProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G22010

CPU:
Intel(R) Xeon(R) CPU E5-1680 v2 @ 3.00GHz

G++:
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

Clang:
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

Copy link
Member

@bbbales2 bbbales2 left a comment

Choose a reason for hiding this comment

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

Looks good, just a couple explanations so I can double check yah on the last couple bits.

test/unit/math/rev/fun/lbeta_test.cpp Show resolved Hide resolved
}
}
if (!is_constant_all<T_k>::value) {
if (k_ == 0 && n_ == -1.0) {
Copy link
Member

Choose a reason for hiding this comment

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

Could you explain the gradient logic for k here?

I think I kinda followed the ones for n but these ones confused me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what to add. The only fact I use is that lim x-> 0 digamma(x) from above is negative infinity. Mentioned that in the comments, if that is still confusing, let me know (it is also possible I am missing some of the edge cases).

Copy link
Member

Choose a reason for hiding this comment

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

Ah yeah, that makes sense.

@bbbales2
Copy link
Member

Just for clarity just explain here on Github then we can throw them in the code if that seems worth the 8 hours of testing or just staple it on the next pull.

Copy link
Contributor

@mcol mcol left a comment

Choose a reason for hiding this comment

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

I left some style comments. As @bbbales2 is actually reviewing this, consider my comments optional unless Ben says otherwise.

stan/math/prim/fun/binomial_coefficient_log.hpp Outdated Show resolved Hide resolved
stan/math/prim/fun/binomial_coefficient_log.hpp Outdated Show resolved Hide resolved
stan/math/prim/fun/binomial_coefficient_log.hpp Outdated Show resolved Hide resolved
stan/math/prim/fun/binomial_coefficient_log.hpp Outdated Show resolved Hide resolved
Comment on lines 122 to 125
if (k_ == 0) {
ops_partials.edge1_.partials_[0] = 0;
} else {
ops_partials.edge1_.partials_[0] = stan::math::NEGATIVE_INFTY;
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be ops_partials.edge1_.partials_[0] = k_dbl == 0 ? 0 : NEGATIVE_INFTY.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I just thought the ternary operator is discouraged. But if that is the Stan style, I can happily use it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's discouraged, I've been suggested to use it in similar cases. It's a matter of preference, if you find your version clearer, don't change it. :) For me, given that there are a series of nested ifs, it helped reducing the number of things I had to keep in mind while reading.

@martinmodrak
Copy link
Contributor Author

Thanks @mcol and @bbbales2 for the suggestions I implemented most of them (the others I commented and left as unresolved).

@stan-buildbot
Copy link
Contributor


Name Old Result New Result Ratio Performance change( 1 - new / old )
gp_pois_regr/gp_pois_regr.stan 4.84 4.93 0.98 -1.95% slower
low_dim_corr_gauss/low_dim_corr_gauss.stan 0.02 0.02 0.98 -1.96% slower
eight_schools/eight_schools.stan 0.09 0.09 0.94 -5.94% slower
gp_regr/gp_regr.stan 0.22 0.22 0.98 -1.66% slower
irt_2pl/irt_2pl.stan 6.44 6.45 1.0 -0.11% slower
performance.compilation 87.51 88.01 0.99 -0.57% slower
low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 7.53 7.53 1.0 0.07% faster
pkpd/one_comp_mm_elim_abs.stan 21.55 20.83 1.03 3.35% faster
sir/sir.stan 95.48 94.15 1.01 1.39% faster
gp_regr/gen_gp_data.stan 0.05 0.05 1.01 0.59% faster
low_dim_gauss_mix/low_dim_gauss_mix.stan 2.96 2.95 1.0 0.11% faster
pkpd/sim_one_comp_mm_elim_abs.stan 0.31 0.31 1.0 -0.19% slower
arK/arK.stan 1.75 1.74 1.0 0.28% faster
arma/arma.stan 0.66 0.66 1.0 0.32% faster
garch/garch.stan 0.51 0.52 0.99 -0.92% slower
Mean result: 0.995619320051

Jenkins Console Log
Blue Ocean
Commit hash: 6aed316


Machine information ProductName: Mac OS X ProductVersion: 10.11.6 BuildVersion: 15G22010

CPU:
Intel(R) Xeon(R) CPU E5-1680 v2 @ 3.00GHz

G++:
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

Clang:
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.6.0
Thread model: posix

Copy link
Member

@bbbales2 bbbales2 left a comment

Choose a reason for hiding this comment

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

Awesome!

}
}
if (!is_constant_all<T_k>::value) {
if (k_ == 0 && n_ == -1.0) {
Copy link
Member

Choose a reason for hiding this comment

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

Ah yeah, that makes sense.

@bbbales2 bbbales2 merged commit fe3a41c into stan-dev:develop Mar 13, 2020
@mcol mcol linked an issue Mar 13, 2020 that may be closed by this pull request
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.

binomial_coefficient_log produces wrong results when N >> n
5 participants