Quick Start: A Mixture Model Example
Let’s start with a simple model of a coin toss experiment so that you can become familiar with some of Hakaru’s data types and functionality. We will assume that a single coin flip can be represented using a Bernoulli distribution. After we have created the Bernoulli model, we will use it to create a mixture model and condition the model to estimate what the original coin toss experiment looked like based on the resulting mixture model samples.
Modeling a Bernoulli Experiment
We will use the categorical
Hakaru Random Primitive to write a Bernoulli distribution1 for our model. The categorical
primitive requires an
array representing the probability of achieving each category in the experiement. Let’s start with a fair experiment and state that each side of the
coin has an equal chance of being picked. The result of the coin toss is stored in the variable b
using Hakaru’s notation for bind:
b <~ categorical([0.5, 0.5])
For data type simplicity, we will map Heads to true
and Tails to false
. By putting the values of true
and false
into an array, we can use the value in b
to
select which of them to return as the result of the coin toss:
return [true, false][b]
A characteristic of the Bernoulli distribution is that it assumes that only one experiment is conducted. To collect samples, we need to run this experiment multiple times.
To aid in this task, we can rewrite the Bernoulli model as a function. We will call our function bern
:
def bern ():
b <~ categorical([0.5, 0.5])
return [true, false][b]
Now that we are using functions, we can generalize our model so that we can run experiments on both fair and trick coins. To do this, we should pass in a probability p
as
a function argument, which is then used to populate the categorical
primitive. Hakaru has a specialized data type for probabilities called prob
, which we will use as the
data type for our function input:
def bern (p prob):
b <~ categorical([p, (1 - p)])
return [true, false][b]
If you we to run this function, we will get a Type Mismatch
error. This is because the value (1 - p)
is converted to type real
as a result of the subtraction operation
and categorical
expects all of the values in its array to be of type prob
. One solution would be to manually pass in the value of (1 - p)
as a function argument, which
would artificially complicate our function. Instead, we can use Hakaru’s coercions to recast (1 - p)
to type prob
:
def bern (p prob):
b <~ categorical([p, real2prob(1 - p)])
return [true, false][b]
We can now use our model to run a series of Bernoullli experiments. Let’s set up our program to use a fair coin and save it as bernoulli.hk
:
def bern (p prob):
b <~ categorical([p, real2prob(1 - p)])
return [true, false][b]
bern(0.5)
Running this program using hakaru bernoulli.hk
should result in an infinite stream of coin toss trials:
false
true
false
true
true
true
...
Now that we have set up our Bernoulli experiment, let’s use it to create a mixture model.
Creating a Mixture Model
Let’s use our coin flip experiment to create a mixture model by drawing a sample from a normal distribution when the coin is Heads (true
) and from a uniform distribution
when it is Tails (false
). This is called a mixture model2 because we are selecting samples from different distributions. Let’s start by saving a copy of your
Bernoulli function into a new program so that we can use it in our new model. For this example, we will call it twomixture.hk
.
Let’s start by binding the return value of our bern
function to a variable called coin
to represent the outcome of an experiment:
coin <~ bern(0.5)
Now that we have stored the result of our experiment, let’s use it to generate a sample. Our model has a selection condition where Heads causes a sample to be drawn from achieving normal distribution and Tails draws from a uniform distribution. There are two ways of handling this in Hakaru – conditionals and pattern matching. Since we are working with Booleans, let’s use patern matching so that we can see what it looks like in Hakaru.
Hakaru pattern matching requires a sequence and a set of possible patterns to compare the sequence to. In our model, our sequence would be coin
because that is what we are
using to select a distribution. Our possible patterns are the possible values that coin
could have – true
and false
. When the pattern is true
, we call the normal
distribution and when it is false
we call the uniform distribution. Both the normal
and uniform
functions are included in Hakaru’s Random Primitives,
so we do not need to define our own functions for them. The outcome of the pattern match will not be saved unless we bind it to a variable, so let’s bind it to a variable
called sample
:
sample <~ match coin:
true: normal(0,1)
false: uniform(0,1)
Now that we have both the result of our coin toss experiment (coin
) and our mixture model (sample
), we can return the values:
return(coin, sample)
We have completed the mixture model program and can run it using the command hakaru twomixture.hk
to collect samples indefinitely:
(true, -0.37622272051934547)
(false, 4.666320977960081e-2)
(true, 1.3351978120820147)
(true, 0.4657111228024136)
(false, 0.6528078075939211)
(false, 0.2410145787295287)
(false, 0.624335005419879)
(true, -1.5127939371882644)
(false, 0.15925713370352967)
(true, 2.2762774663914114e-2)
...
Of course, Hakaru would not be very interesting if it only provided the means for you to define your model. Let’s try conditioning our model so that we can experiment with
different values for sample
to estimate what values of coin
were used.
Conditioning a Hakaru Program
Suppose for our twomixture.hk
program, we know the value of sample
and want to see what the original values for coin
were. We can symbolically produce the unnormalized
conditional distribution from which coin
samples are taken by using Hakaru’s disintegration transform. Before we use disintegrate
, we
must change the line return (coin, sample)
to return (sample, coin)
. This tells disintegrate
that we want to create a posterior distribution for coin
using known
values for sample
.
Once we have setup our model for the disintegrate
transform, we can transform our model by calling disintegrate twomixture.hk
. The disintegrate
transform creates a new
model written as an anonymous function so that it is easier for you to use in other applications. In the model generated by the disintegrate
transform, our variable
sample
has been renamed to x5
:
fn x5 real:
bern = fn p prob:
b <~ categorical([p, real2prob((1 - prob2real(p)))])
return [true, false][b]
coin <~ bern(1/2)
(match coin:
true:
x12 <~ weight((exp((negate(((x5 + 0) ^ 2)) / 2))
/
1
/
sqrt((2 * pi))),
return ())
return coin
_: reject. measure(bool)) <|>
bern = fn p prob:
b <~ categorical([p, real2prob((1 - prob2real(p)))])
return [true, false][b]
coin <~ bern(1/2)
(match coin:
false:
(match (not((x5 < 0)) && not((1 < x5))):
true:
x12 <~ return ()
return coin
_: reject. measure(bool))
_: reject. measure(bool))
Note: The output for disintegrate
will be printed in the console. You can easily save this program to a file by redirecting the output to a file by calling
hakaru disintegrate model1.hk > modelDis.hk
. For this example, we will call our new program twomixture_D.hk
.
We can use this program to experiment with different values of sample
(x5
) to see what the original coin toss experiment looked like. To avoid altering the function
generated by disintegrate
, let’s assign it to a variable coinToss
so that we can reference it at the end of our program. For our first experiment, let’s try a value of
0.3
. This means that we are conditioning our model to be more likely to pick samples from the uniform distribution:
coinToss = fn x5 real:
bern = fn p prob:
b <~ categorical([p, real2prob((1 - prob2real(p)))])
return [true, false][b]
coin <~ bern(1/2)
(match coin:
true:
x12 <~ weight((exp((negate(((x5 + 0) ^ 2)) / 2))
/
1
/
sqrt((2 * pi))),
return ())
return coin
_: reject. measure(bool)) <|>
bern = fn p prob:
b <~ categorical([p, real2prob((1 - prob2real(p)))])
return [true, false][b]
coin <~ bern(1/2)
(match coin:
false:
(match (not((x5 < 0)) && not((1 < x5))):
true:
x12 <~ return ()
return coin
_: reject. measure(bool))
_: reject. measure(bool))
coinToss(0.3)
We can now run the program to estimate what values for coin
. Let’s use some Unix commands to run the program 1000 times and gather the results into counts:
hakaru -w twomixture_D.hk | head -n 1000 | sort | uniq -c
526 false
474 true
As we can see, when x5 = 0.3
, our coin tosses were more likely to be Tails (false
) than Heads (true
). Let’s change our argument to coinToss
to 3.0
so that we are
conditioned to pick values from the normal distribution much more frequently. Running this program shows that our coin tosses must have all been Heads for this value to be
possible:
hakaru -w twomixture_D.hk | head -n 1000 | sort | uniq -c
1000 true
You have written a model to represent a Bernoulli experiement and used it to create a mixture model using a normal and uniform distribution. You have also used the
disintegrate
transform to generate a new model that can be conditioned with different mixture model results to infer what the original distribution of coin toss
experiements might have been. For more Hakaru examples, see the Examples.