Thank you so much for the suggestion – it now runs. I’ve set it up as below:
def model(home_id, away_id, score1_obs=None, score2_obs=None):
# priors
mu_att = pyro.sample("mu_att", dist.Normal(0.0, 1.0))
sd_att = pyro.sample("sd_att", dist.StudentT(3.0, 0.0, 2.5))
mu_def = pyro.sample("mu_def", dist.Normal(0.0, 1.0))
sd_def = pyro.sample("sd_def", dist.StudentT(3.0, 0.0, 2.5))
home = pyro.sample("home", dist.Normal(0.0, 1.0)) # home advantage
nt = len(np.unique(home_id))
# team-specific model parameters
with pyro.plate("plate_teams", nt):
attack = pyro.sample("attack", dist.Normal(mu_att, sd_att))
defend = pyro.sample("defend", dist.Normal(mu_def, sd_def))
# likelihood
theta1 = torch.exp(home + attack[home_id] - defend[away_id])
theta2 = torch.exp(attack[away_id] - defend[home_id])
with pyro.plate("data", len(home_id)):
pyro.sample("s1", dist.Poisson(theta1), obs=score1_obs)
pyro.sample("s2", dist.Poisson(theta2), obs=score2_obs)
def guide(home_id, away_id, score1_obs=None, score2_obs=None):
mu_locs = pyro.param("mu_loc", torch.tensor(0.0).expand(5))
mu_scales = pyro.param(
"mu_scale", torch.tensor(0.1).expand(5), constraint=constraints.positive
)
pyro.sample("mu_att", dist.Normal(mu_locs[0], mu_scales[0]))
pyro.sample("mu_def", dist.Normal(mu_locs[1], mu_scales[1]))
pyro.sample("sd_att", dist.LogNormal(mu_locs[2], mu_scales[2]))
pyro.sample("sd_def", dist.LogNormal(mu_locs[3], mu_scales[3]))
pyro.sample("home", dist.Normal(mu_locs[4], mu_scales[4])) # home advantage
nt = len(np.unique(home_id))
mu_team_locs = pyro.param("mu_team_loc", torch.tensor(0.0).expand(2))
mu_team_scales = pyro.param(
"mu_team_scale",
torch.tensor(0.1).expand(2),
constraint=constraints.positive,
)
with pyro.plate("plate_teams", nt):
pyro.sample("attack", dist.Normal(mu_team_locs[0], mu_team_scales[0]))
pyro.sample("defend", dist.Normal(mu_team_locs[1], mu_team_scales[1]))
Two things:
- Here, I’ve used
mu_team_locs = pyro.param("mu_team_loc", torch.tensor(0.0).expand(2))
rather thanmu_team_locs = pyro.param("mu_team_loc", torch.tensor(0.0).expand(2, nt))
. I’m confused why they both ran. In my head, the attack nodes come from the same normal –attack = pyro.sample("attack", dist.Normal(mu_att, sd_att))
– and hence should only need onepyro.param
, notnt
(20). Do you know why they both ran and which is correct? - Here, using a StudentT prior with a LogNormal guide prevents negative values. If possible, I would like to use the HalfStudentT, because that is how I’ve set the model up using other PPLs and I’d like to compare directly. Does Pyro have support for this? I found this post #274 (referencing the stan post where I chose the HalfStudentT prior also) and was wondering whether this has been developed since
Cheers