Code
import os
if "KERAS_BACKEND" not in os.environ:
# set this to "torch", "tensorflow", or "jax"
"KERAS_BACKEND"] = "jax"
os.environ[
import matplotlib.pyplot as plt
import numpy as np
import bayesflow as bf
import json
import os
if "KERAS_BACKEND" not in os.environ:
# set this to "torch", "tensorflow", or "jax"
"KERAS_BACKEND"] = "jax"
os.environ[
import matplotlib.pyplot as plt
import numpy as np
import bayesflow as bf
import json
\[\begin{equation} \begin{aligned} \gamma & \sim \text{Uniform}(0.5, 1) \\ y_{iq} & \sim \begin{cases} \text{Bernoulli}(\gamma) & \text{if } t_q = a \\ \text{Bernoulli}(1-\gamma) & \text{if } t_q = b \\ \text{Bernoulli}(0.5) & \text{otherwise}, \\ \end{cases} \end{aligned} \end{equation}\] where \(t_q\) is determined by the take the best decision rule defined below.
with open(os.path.join("data", "StopSearchData.json")) as f:
= json.load(f)
data
data.keys()
dict_keys(['m', 'nc', 'nq', 'ns', 'p', 'v', 'x', 'y'])
First, we define the “take the best” (TTB) decision rule: For each question, we loop over its cues in order of their validity (the questions are already ordered in the data). As soon as a cue prefers decision ‘a’ or ‘b’, the decision is determined.
def ttb(nq: int, nc: int, p: np.ndarray, m: np.ndarray):
= np.zeros(nq) # 0: guessing
output
for q in range(nq):
for c in range(nc):
= m[p[q][0]-1][c]
cue_a = m[p[q][1]-1][c]
cue_b if cue_a > cue_b:
= 1 # decision a
output[q] break
elif cue_a < cue_b:
= 2 # decision b
output[q] break
return output.astype(int)
Applying the decision rule to the data produces the “decision a” for each question according to TTB: This is because the recorded data had been already recoded such that choice “a” always corresponds to the choice according to the TTB rule.
= ttb(data['nq'], data['nc'], data['p'], data['m'])
decisions decisions
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1])
Next, we can define the prior and a likelihood. The only source of alleatoric uncertainty in our simulator comes from the parameter \(\gamma\), which indicates the probability of responding “a” if the TTB decision rule indicates to respond “a”, and vice versa.
The shape of the resulting data is (number of participants, number of questions).
def prior():
return dict(gamma = np.random.uniform(low=0.5, high=1))
def likelihood(
float,
gamma: int = data['ns'],
ns: int = data['nq'],
nq: = decisions
ttb
):
= np.array([0.5, gamma, 1-gamma])
probs = probs[ttb][np.newaxis]
probs = np.tile(probs, reps=[ns, 1])
probs
= np.random.binomial(n=1, p=probs, size=(ns, nq))
y
return dict(y=y)
= bf.make_simulator([prior, likelihood]) simulator
We will approximate the posterior of \(\gamma\), given the observed decisions \(y\).
= (bf.Adapter()
adapter "gamma", lower=0.5, upper=1)
.constrain("gamma", "inference_variables")
.rename("y", "summary_variables")
.rename( )
= bf.BasicWorkflow(
workflow =simulator,
simulator=adapter,
adapter=bf.networks.CouplingFlow(),
inference_network=bf.networks.DeepSet(),
summary_network=1e-3
initial_learning_rate )
= workflow.fit_online(epochs=50, num_batches_per_epoch=500, batch_size=512) history
= simulator.sample(1000) test_data
=workflow.plot_default_diagnostics(test_data=test_data, num_samples=500) figs
Now we can apply to the model to real data (Lee & Wagenmakers, 2013).
= dict(y = np.array(data['y'])[np.newaxis]) inference_data
= workflow.sample(num_samples=1000, conditions=inference_data) samples
=plt.hist(samples['gamma'].flatten(), bins=np.linspace(0.5, 1, 50))
f=plt.xlabel(r"$\gamma$") f