Hi!
One problem with your code is that it’s using the logit function when you actually have three states. Originally, the probabilities add up to 1, but once you add the noise and convert back, they don’t anymore. Look at this:
import pymc as pm
import numpy as np
n_subjects = 100
n_trials = 100
n_states = 3
subject_noise = 0.5
trial_noise = 0.5
state_probabilities = np.array([0.1, 0.4, 0.5])
coords = {
"subject": np.arange(n_subjects),
"trial": np.arange(n_trials),
"state": np.arange(n_states),
}
with pm.Model(coords=coords) as model:
initial_log_p = pm.Deterministic(
"initial_log_p", pm.math.log(state_probabilities / (1 - state_probabilities)), dims="state"
)
subj_noise = pm.Normal("subj_noise", 0, subject_noise, dims=("subject", "state"))
trial_noise = pm.Normal("trial_noise", 0, trial_noise, dims=("subject", "trial", "state"))
total_noise = pm.Deterministic(
"total_noise", subj_noise[:, None, :] + trial_noise, dims=("subject", "trial", "state")
)
#total_noise = np.zeros((n_subjects, n_trials, n_states))
final_log = pm.Deterministic(
"final_log", initial_log_p + total_noise, dims=("subject", "trial", "state")
)
final_p_non_sum = pm.Deterministic(
"final_p_non_sum", pm.math.sigmoid(final_log), dims=("subject", "trial", "state")
)
dirichlet_sample = pm.Dirichlet(
"dirichlet_sample", a=final_p_non_sum * 10, dims=("subject", "trial", "state")
)
sample_state = pm.Multinomial(
"sample_state", n=1, p=dirichlet_sample, dims=("subject", "trial", "state")
)
prior = pm.sample_prior_predictive(samples=1)
prior.prior["final_p_non_sum"].sum("state")
array([[[[1.10843649, 0.86817036, 1.40206735, ..., 1.22028226,
1.01429214, 1.28709837],
[1.03685059, 0.91259717, 0.97796022, ..., 0.81577098,
0.9989243 , 1.06497121],
[0.80130395, 0.85805041, 1.22585814, ..., 0.98804735,
1.35885752, 0.97182725],
...,
[0.75566216, 0.97299343, 1.09703658, ..., 0.89944494,
0.91053022, 0.56049512],
[0.8237814 , 1.00179595, 0.79598627, ..., 0.84665124,
0.80400659, 0.61714707],
[0.75710897, 0.7109351 , 0.62665 , ..., 0.74708109,
0.71287813, 1.13028959]]]])
In your case, you could consider the softmax function, check this: probability - Invert the softmax function - Mathematics Stack Exchange.
And the code would be
import pymc as pm
import numpy as np
n_subjects = 100
n_trials = 100
n_states = 3
subject_noise = 0.3
trial_noise = 0.3
state_probabilities = np.array([0.1, 0.4, 0.5])
coords = {
"subject": np.arange(n_subjects),
"trial": np.arange(n_trials),
"state": np.arange(n_states),
}
with pm.Model(coords=coords) as model:
initial_log_p = pm.Deterministic(
"initial_log_p", pm.math.log(state_probabilities), dims="state"
)
subj_noise = pm.Normal("subj_noise", 0, subject_noise, dims=("subject", "state"))
trial_noise = pm.Normal("trial_noise", 0, trial_noise, dims=("subject", "trial", "state"))
total_noise = pm.Deterministic(
"total_noise", subj_noise[:, None, :] + trial_noise, dims=("subject", "trial", "state")
)
final_log = pm.Deterministic(
"final_log", initial_log_p + total_noise, dims=("subject", "trial", "state")
)
final_p_non_sum = pm.Deterministic(
"final_p_non_sum", pm.math.softmax(final_log, axis=-1), dims=("subject", "trial", "state")
)
dirichlet_sample = pm.Dirichlet(
"dirichlet_sample", a=final_p_non_sum * 1, dims=("subject", "trial", "state")
)
sample_state = pm.Multinomial(
"sample_state", n=1, p=dirichlet_sample, dims=("subject", "trial", "state")
)
prior = pm.sample_prior_predictive(samples=1, random_seed=1)
prior.prior["final_p_non_sum"].sum("state").to_numpy()
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.],
[1., 1., 1., ..., 1., 1., 1.]]]])
prior.prior["final_p_non_sum"].mean(("chain", "draw", "subject", "trial")).to_numpy()
array([0.1061953 , 0.40009692, 0.49370778])
However I’m still a bit unsure about what I’m doing. If I increase the error a bit, the probability seems to be always around ~0.11.
I just realized you’re adding state to the two noises, I’m not sure if that’s right. This is another approach without those:
import pymc as pm
import numpy as np
n_subjects = 100
n_trials = 100
n_states = 3
subject_noise = 0.5
trial_noise = 0.5
state_probabilities = np.array([0.1, 0.4, 0.5])
coords = {
"subject": np.arange(n_subjects),
"trial": np.arange(n_trials),
"state": np.arange(n_states),
}
with pm.Model(coords=coords) as model:
initial_log_p = pm.Deterministic(
"initial_log_p", pm.math.log(state_probabilities), dims="state"
)
subj_noise = pm.Normal("subj_noise", 0, subject_noise, dims=("subject"))
trial_noise = pm.Normal("trial_noise", 0, trial_noise, dims=("subject", "trial"))
total_noise = pm.Deterministic(
"total_noise", subj_noise[:, None] + trial_noise, dims=("subject", "trial")
)
final_log = pm.Deterministic(
"final_log", initial_log_p[None, None, :] + total_noise[..., None], dims=("subject", "trial", "state")
)
final_p_non_sum = pm.Deterministic(
"final_p_non_sum", pm.math.softmax(final_log, axis=-1), dims=("subject", "trial", "state")
)
dirichlet_sample = pm.Dirichlet(
"dirichlet_sample", a=final_p_non_sum * 1, dims=("subject", "trial", "state")
)
sample_state = pm.Multinomial(
"sample_state", n=1, p=dirichlet_sample, dims=("subject", "trial", "state")
)
prior = pm.sample_prior_predictive(samples=1, random_seed=1)
prior.prior["final_p_non_sum"].sum("state").to_numpy()
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.],
[1., 1., 1., ..., 1., 1., 1.]]]])
prior.prior["final_p_non_sum"].mean(("chain", "draw", "subject", "trial")).to_numpy()
array([0.1, 0.4, 0.5])
((prior.prior["sample_state"].data * [1, 2, 3]).sum(axis=-1)[0][0] == 1).mean()
0.0987