Hierarchical model guide with plate

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 than mu_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 one pyro.param, not nt (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