Skip to content

binomial_coefficient_log producing wrong results #239

Description

@bob-carpenter

Originally submitted by @akodd as stan-dev/stan#1778

Summary:

Function binomial_coefficient_log is not implemented correctly and causes binomial_log to produce incorrect result.

Description:

Probability mass function binomial_log produced incorrect results for some combinations of successes and trials.

Reproducible Steps:

The following R code illustrates the issue:

library(rstan)

input_grid = 
  expand.grid(
    p = seq(0.01, 0.9, length.out=10),
    N = round(10^seq(1, log10(100000), length.out=100)),
    D = round(10^seq(1, log10(100000), length.out=100))
  )
input_grid = input_grid[input_grid$D<input_grid$N,]
p = input_grid$p
N = input_grid$N
D = input_grid$D
input_grid$D_log_p_NmD_log1m_p = D*log(p)+(N-D)*log(1-p)
input_grid$bin_coef_log_r = lchoose(N, D)
input_grid$bin_log_r = dbinom(D, N, p, log = T)

nrow(input_grid)

mdl = 
  stan_model(model_code = "
  data {
    int<lower=1> K; 
    int<lower=0> D[K];
    int<lower=1> N[K];
    vector<lower=0, upper=1>[K] p;
  }

  parameters {
    real x;
  }
  model {
    x ~ normal(0,1);
  }

  generated quantities {
    vector[K] dbeta;
    vector[K] bin_coeff_log_stan;
    for (i in 1:K) {
      dbeta[i] <- binomial_log(D[i], N[i], p[i]);
      bin_coeff_log_stan[i] <- binomial_coefficient_log(N[i], D[i]);
    }
  }
  ")
mdlfit = sampling(mdl,
  data = list(
    K = length(p),
    D = D,
    N = N,
    p = p
  ), iter=1, chains=1)

mdlpost <- extract(mdlfit, permuted=T)

input_grid$bin_coef_log_stan = mdlpost$bin_coeff_log_stan[1,];
input_grid$bin_log_stan= mdlpost$dbeta[1,]

missmatch = which(abs(input_grid$bin_coef_log_stan-input_grid$bin_coef_log_r)>10)
length(missmatch)

input_grid[missmatch[1],]

sessionInfo()

Current Output:

> nrow(input_grid)
[1] 49500
> length(missmatch)
[1] 35620

Choosing only one example we get:

> input_grid[which(abs(input_grid$bin_coef_log_stan-input_grid$bin_coef_log_r)>10)[1],]
       p    N  D D_log_p_NmD_log1m_p bin_coef_log_r bin_log_r bin_coef_log_stan bin_log_stan
501 0.01 1048 10           -56.48395       54.39891 -2.085044           44.3461    -12.13785

Note that R and Stan versions differ only in binomial coefficient.

I also verified the choose function on Wolfram for this one case http://www.wolframalpha.com/input/?i=log(1048+choose+10) to match R version.

A workaround for binomial_log would be a user defined function (_log suffix) that adds user supplied results of the choose function (can passed as data) to D_log(p)+(N-D)_log1m(p).

Additional Information:

> sessionInfo()
R version 3.2.3 (2015-12-10)
Platform: x86_64-apple-darwin14.5.0 (64-bit)
Running under: OS X 10.11.3 (El Capitan)

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] shinystan_2.0.1  shiny_0.12.2     rstan_2.8.2      pracma_1.8.8     ggplot2_2.0.0    data.table_1.9.6
[7] mvQuad_1.0-4    

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.2       plyr_1.8.3        base64enc_0.1-3   shinyjs_0.3.0     tools_3.2.3       xts_0.9-7        
 [7] digest_0.6.8      gtable_0.1.2      lattice_0.20-33   parallel_3.2.3    gridExtra_2.0.0   stringr_1.0.0    
[13] dygraphs_0.6      htmlwidgets_0.5   gtools_3.5.0      stats4_3.2.3      grid_3.2.3        DT_0.1           
[19] inline_0.3.14     R6_2.1.1          reshape2_1.4.1    magrittr_1.5      codetools_0.2-14  shinythemes_1.0.1
[25] scales_0.3.0      threejs_0.2.1     htmltools_0.3     mime_0.4          xtable_1.8-0      colorspace_1.2-6 
[31] httpuv_1.3.3      stringi_1.0-1     munsell_0.4.2     chron_2.3-47      markdown_0.7.7    zoo_1.7-12   

Current Version:

v2.9.0

Activity

  1. added this to the milestone on Feb 19, 2016
  2. bgoodri commented on Feb 20, 2016

    @bgoodri
    Contributor

    This is a weird bug and possibly actually in rstan. If you add print statements after the end of the loop in generated quantities like

    print("");
    print("N[K] = ", N[K]);
    print("D[K] = ", D[K]);
    print("bcl of N[K] and D[K] = ", binomial_coefficient_log(N[K], D[K]));
    

    you get the wrong answer

    N[K] = 100000
    D[K] = 91116
    bcl of N[K] and D[K] = 27676.8
    

    but if you call expose_stan_functions() on a file that just has

    functions {
      real bcl(real x, real y) return binomial_coefficient_log(x,y);
    }
    model {}
    

    and then call

    bcl(100000, 91116)
    

    you get the right answer, which is 29979.16. So, perhaps it has something to do with repeated calls to binomial_coefficient_log???

  3. bgoodri commented on Feb 20, 2016

    @bgoodri
    Contributor

    More weirdness. It looks as if the second argument is corrupted somehow

    Chain 1, Iteration: 1 / 1 [100%]  (Sampling)
    N[K] = 100000
    D[K] = 91116
    bcl of N[K] and D[K] = 27676.8
    N[K] == 100000 is 1
    D[K] == 91116 is 0
    

    but even if its last digit is off 27676.8 is nowhere close to the right answer.

  4. bgoodri commented on Feb 20, 2016

    @bgoodri
    Contributor

    Okay, the wrong answers occur when it takes the else branch in binomial_coefficient_log but the else branch seems to give the right answer when you type it into R manually. And that does not explain why calling it via expose_stan_functions() yields the correct answer.

  5. syclik commented on Mar 30, 2016

    @syclik
    Member

    I found it. It's integer division:

    return n * log(N - n) + (N + 0.5) * log(N/(N-n))
      + 1/(12*N) - n - 1/(12*(N-n)) - lgamma(n + 1.0);
    

    The problem is that log(N/(N - n)) is evaluating N/(N - n) with integer division. I bet expose_stan_functions() instantiates the code with double instead of int and it's fine.

    I haven't traced through this code, but we could use Stirling's approximation instead and it should be fine:

            return (N + 0.5) * log(N)
              - (n + 0.5) * log(n)
              - (N - n + 0.5) * log(N - n)
              + NEG_LOG_SQRT_TWO_PI;
    

    Any thoughts? I'll implement a fix tomorrow.

  6. bob-carpenter commented on Mar 30, 2016

    @bob-carpenter
    MemberAuthor

    Nice catch!

    No preference as to approximation.

    • Bob

    On Mar 29, 2016, at 11:54 PM, Daniel Lee [email protected] wrote:

    I found it. It's integer division:

    return n * log(N - n) + (N + 0.5) * log(N/(N-n))

    • 1/(12_N) - n - 1/(12_(N-n)) - lgamma(n + 1.0);

    The problem is that log(N/(N - n)) is evaluating N/(N - n) with integer division. I bet expose_stan_functions() instantiates the code with double instead of int and it's fine.

    I haven't traced through this code, but we could use Stirling's approximation instead and it should be fine:

        return (N + 0.5) * log(N)
          - (n + 0.5) * log(n)
          - (N - n + 0.5) * log(N - n)
          + NEG_LOG_SQRT_TWO_PI;
    

    Any thoughts? I'll implement a fix tomorrow.

    —
    You are receiving this because you were assigned.
    Reply to this email directly or view it on GitHub

  7. bgoodri commented on Mar 30, 2016

    @bgoodri
    Contributor

    Can't we just replace n * log(N - n) + (N + 0.5) * log(N / (N - n)) ...
    with
    (n - 1) * log(N - n) + (N + 0.5) * log(N) ...
    ? But all those 1 / terms would to become 1.0 / .

  8. syclik commented on Mar 30, 2016

    @syclik
    Member

    yes, we could. I don't know where that approximation comes from. I can follow the Stirling approx.

    I'm guessing it doesn't matter so much since N >= 1000 or N-n >= 1000. Forward-mode is implemented incorrectly also, so I'll fix that too.

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions