Mixture Models

Thanks for the detailed response!

I was able to get the mixture to work without the overinflation by changing the code a bit. You’ll see I changed the specification of the repulsive distribution. I’m only adding the pairwise comparisons to lp. That means I have to build the full matrix for S. I’ve let rho vary by each pairwise comparison but it seems to like 0.5 for all the pairs.

But this breaksdown if I run 3 chains. I’m guessing this is the sensitivity to initial values.

Full code and results for 1 chain looks good:

functions {
  real repulsive_lpdf(vector mu, vector rho) {
    int K = num_elements(mu);
    matrix[K, K] S = diag_matrix(rep_vector(1, K));
    matrix[K, K] L;
    int c = 0;

    for (k1 in 1:(K - 1))
      for (k2 in (k1 + 1):K){
        c += 1;
        S[k1, k2] = log(1 - exp(- squared_distance(mu[k1], mu[k2]) / rho[c]));
        S[k2, k1] = S[k1, k2];
      }
    L = cholesky_decompose(S);

    return 2 * sum(log(diagonal(L)));
  }
}

data {
  int<lower=1> K;
  int<lower=1> N;
  real y[N];
}

parameters {
  ordered[K] mu;
  vector<lower=0>[choose(K, 2)] rho;
  real<lower=0> sigma[K];
  simplex[K] lambda;
}

model {
  // Prior model
  mu ~ normal(0, 5);
  sigma ~ std_normal();
  lambda ~ dirichlet(rep_vector(3, K));
  rho ~ gamma(1, 2);
  mu ~ repulsive(rho);
  
  // Observational model
  for (n in 1:N) {
    real comp_lpdf[K];
    for (k in 1:K) {
      comp_lpdf[k] = log(lambda[k]) + normal_lpdf(y[n] | mu[k], sigma[k]);
    }
    target += log_sum_exp(comp_lpdf);  
  }
}

Inference for Stan model: beta_alpha_sean.
1 chains, each with iter=2000; warmup=1000; thin=1; 
post-warmup draws per chain=1000, total post-warmup draws=1000.

              mean se_mean   sd     2.5%      25%      50%      75%    97.5% n_eff Rhat
mu[1]        -5.05    0.00 0.07    -5.17    -5.09    -5.05    -5.00    -4.91   643    1
mu[2]        -0.98    0.00 0.03    -1.04    -1.00    -0.98    -0.97    -0.93  1489    1
mu[3]         4.04    0.00 0.01     4.01     4.03     4.04     4.05     4.06   999    1
mu[4]         6.99    0.00 0.03     6.93     6.97     6.99     7.01     7.05  1197    1
rho[1]        0.49    0.01 0.49     0.01     0.13     0.34     0.71     1.81  1449    1
rho[2]        0.49    0.01 0.46     0.01     0.15     0.35     0.68     1.66  1519    1
rho[3]        0.51    0.01 0.51     0.01     0.14     0.36     0.73     1.94  1437    1
rho[4]        0.48    0.01 0.48     0.01     0.13     0.31     0.67     1.77  1778    1
rho[5]        0.49    0.01 0.49     0.02     0.14     0.33     0.71     1.71  1711    1
rho[6]        0.51    0.01 0.50     0.01     0.15     0.36     0.70     1.94  1387    1
sigma[1]      0.95    0.00 0.05     0.85     0.91     0.94     0.98     1.05  1588    1
sigma[2]      0.55    0.00 0.02     0.51     0.53     0.54     0.56     0.59  1125    1
sigma[3]      0.24    0.00 0.01     0.22     0.24     0.24     0.25     0.26  1380    1
sigma[4]      0.32    0.00 0.02     0.27     0.30     0.32     0.33     0.37  1394    1
lambda[1]     0.18    0.00 0.01     0.16     0.18     0.18     0.19     0.21  1313    1
lambda[2]     0.40    0.00 0.02     0.37     0.38     0.40     0.41     0.43  1524    1
lambda[3]     0.32    0.00 0.01     0.29     0.31     0.32     0.33     0.35  1653    1
lambda[4]     0.10    0.00 0.01     0.08     0.09     0.10     0.11     0.12  1520    1
lp__      -1899.03    0.17 3.04 -1905.97 -1900.81 -1898.75 -1896.88 -1893.98   314    1

Samples were drawn using NUTS(diag_e) at Mon Sep  7 08:00:32 2020.
For each parameter, n_eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor on split chains (at 
convergence, Rhat=1).
singular_fit <- stan(file='beta_alpha_sean.stan', data=input_data,
                     chains = 1, iter = 2000, warmup = 1000, 
                     seed = 483892929, refresh = 500)
singular_fit

Here’s the plot


params1 <- as.data.frame(extract(singular_fit, permuted=FALSE)[,1,])

c_light_trans <- c("#DCBCBCBF")
c_light_highlight_trans <- c("#C79999BF")
c_mid_trans <- c("#B97C7CBF")
c_mid_highlight_trans <- c("#A25050BF")
c_dark_trans <- c("#8F2727BF")
c_dark_highlight_trans <- c("#7C0000BF")

par(mar = c(4, 4, 0.5, 0.5))
plot(params1$"mu[1]", params1$"mu[2]", col=c_dark_highlight_trans, pch=16, cex=0.8,
     xlab="pair_1", xlim=c(-6, 8), ylab="pair_2", ylim=c(-2, 8))
points(params1$"mu[1]", params1$"mu[3]", col="blue", pch=16, cex=0.8)

points(params1$"mu[1]", params1$"mu[4]", col="green", pch=16, cex=0.8)
points(params1$"mu[2]", params1$"mu[3]", col="orange", pch=16, cex=0.8)
points(params1$"mu[2]", params1$"mu[4]", col="purple", pch=16, cex=0.8)
points(params1$"mu[3]", params1$"mu[4]", col=c_light_trans, pch=16, cex=0.8)

Adding 3 chains and you’ll see the separation break down in chains 2 and 3