Bob Carpenter
unread,Oct 26, 2012, 3:17:53 PM10/26/12Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to stan...@googlegroups.com
Time to do discrete parameters (and another Bob mail-novel).
First an example that's more indicative of coding
Markov blankets than LDA, which is relatively easy.
Dawid and Skene (1979) introduced a model of
data coding by multiple coders where the truth
wasn't known. Here's what it would look like in
Stan if Stan did discrete sampling:
---------------------------------
data {
int<lower=2> K; // number of categories
int<lower=1> I; // number of items
int<lower=1> J; // number of coders
int<lower=1> N; // number of observations
int<lower=1,upper=J> jj[N]; // coder for observation n
int<lower=1,upper=I> ii[N]; // item for observation n
int<lower=1,upper=K> y[N]; // label for observation n
}
parameters {
simplex[K] pi; // prevalence of categories
simplex[K] theta[J,K]; // response of anotator j to category k
int<lower=1,upper=K> z[I]; // true category of item i
}
model {
for (i in 1:I)
z[i] ~ categorical(pi);
for (n in 1:N)
y[n] ~ categorical(theta[jj[n],z[ii[n]]]);
}
-------------------------------------
It uses the standard DB-like encoding of the data with y[N],
ii[N] and jj[N]. In general, every item is only coded by
a subset of the coders.
The relevant lines of the model for sampling a given discrete
z[j] for j in 1:I are
* i s.t. (i == j) in the first loop, and
* n s.t. (ii[n] == j) in the second loop.
It's the second of these that's particularly problematic
because it's data dependent --- there's no way to annotate the
relevant loop indexes at compile time.
What I did to run the model(* see P.S.) in RStan 1.0.2 (I'm a very happy
RStan user) was sum out the z[i]:
---------------------------------
data {
int<lower=2> K; // number of categories
int<lower=1> I; // number of items
int<lower=1> J; // number of coders
int<lower=1> N; // number of observations
int<lower=1,upper=J> jj[N]; // coder for observation n
int<lower=1,upper=I> ii[N]; // item for observation n
int<lower=1,upper=K> y[N]; // label for observation n
}
parameters {
simplex[K] pi; // prevalence of categories
simplex[K] theta[J,K]; // response of anotator j to category k
}
model {
real cat_log[I,K]; // vector of log probs (up to const) for item i
for (k in 1:K)
for (i in 1:I)
cat_log[i,k] <- log(pi[k]);
for (n in 1:N)
for (k in 1:K)
cat_log[ii[n],k] <- cat_log[ii[n],k] + log(theta[jj[n],k,y[n]]);
for (i in 1:I)
lp__ <- lp__ + log_sum_exp(cat_log[i]);
}
------------------------------------
The log_sum_exp just helps with arithmetic stability.
This is the same kind of code as we'd need for Gibbs.
(This isn't actually how I coded the first loop -- it can
be optimized by computing log(pi[k]) once for i=1 and
reusing it, but the above is simpler to understand.)
We could instead of this summing out as in the current Stan,
declare the z[I] parameter and have a Gibbs block like this:
------------------------------
gibbs {
real cat_log[I,K]; // vector of log probs (up to const) for item i
for (k in 1:K)
for (i in 1:I)
cat_log[i,k] <- log(pi[k]);
for (n in 1:N)
for (k in 1:K)
cat_log[ii[n],k] <- cat_log[ii[n],k] + log(theta[jj[n],k,y[n]]);
for (i in 1:I)
z[i] ~ categorical_log(cat_log[i]);
}
--------------------------------
where the "_log" in "categorical_log" here just indicates the params
are on the (unnormalized) log scale rather than a simplex:
categorical_log(z|alpha) = categorical(z|softmax(exp(alpha)))
Implementing discrete sampling this way would be:
* very very easy to implement, and
* optimally efficient to sample (given we're doing Gibbs/slice).
Basically, we'd just do it the same way we'd do generated
quantities -- the ~ just indicates a randomly generated variate.
And with the Markov blanket in hand, we can't do much better
computation-wise. Users can even control shared subcomputations.
The big downside is that it's an enormous burden on the users compared
to the "natural" specification of the model in the first example.
One upside is that by the time they've figured out Gibbs,
they can probably marginalize out the discrete params!
You could add the same calcs to generated quantities and either
save the category distributions for z[i] or actually sample.
A serious problem with the suggested model (not the approach,
just the particular coding above) is that it computes all the probs
first, then samples them all. Usually Gibbs is implemented in a
rotated style, where you sample, e.g.,
x[1], y[1], z[1] init
x[2] | y[1], z[1]
y[2] | x[2], z[1]
z[2] | x[2], y[2]
x[3] | y[2], z[2]
...
What the above model codes is this:
x[2] | y[1], z[1]
y[2] | x[1], z[1]
z[2] | x[1], y[1]
Nice for parallelism, but not so nice for mixing.
We can solve that problem with the same kind of gibbs block.
To make the models efficient in these non-full data designs, we'd
need to compute DB-like indexes. All we need is to be able to find,
given j in 1:I, the n such that (ii[n] == j). It would even be
possible to compute these indexes in the transformed data block.
More on this later.
BUGS and JAGS compute the Markov blanket for each variable
by expanding out the plates of the graphical model
explicitly. If we restrict the expressiveness of Stan a bit,
we could do the same kind of thing.
- Bob
P.S. This model rocks in Stan, by the way -- it's what I
showed you the fits for. We've got about 30 datasets
for different words, with Mechanical Turk coders identifying
the dictionary meaning for a word in context. We have I=1000, K=7,
N=25,000 and it converges in fewer than 10 steps and mixes
very very well (n_eff is between 1/3 and 2/3 the number of draws!).
It takes about 2 minutes for 500 iterations of a chain, and
even runs on Becky's ancient 2GB 32-bit Windows machine. The discrete
form of the model is a bit of a strain on JAGS -- Becky couldn't
get it in memory and it took hours to run without good
mixing on my machine -- I haven't done the fair comparison
of using the marginalized out model in JAGS. The obvious
next step is to do something hierarchical over the 30
data sets -- I already fit hierarchical models for theta within
datasets. We also want to fit item effects, so I'm going to
move it all over to a more multilevel-like logistic structure
as Andrew and Jennifer first suggested about four years ago!
(I've done this in JAGS already, but didn't have enough data to
pin down the difficulty per item -- that's why we collected so
much data, 25 labels per word instance.)