Learning causal model (Causal Bayesian Networks) in the form of collider

Hi there,
I work on Causal Bayesian Networks and currently implementing common cause (A <—C ---->B) and chain (A —> C ---->B) structures that work perfectly fine. Those randomvariable (A, B, and C) are dependent in general and become independent if C is given and below is my pyro’s code for common cause structure where crime (A) <—economics (C)---->obesity (B).

def model(crime_obs, economics_obs, obesity_obs):
    # declare the beta dist's parameters
    alphaEco, betaEco = torch.tensor(0.5), torch.tensor(0.5)
    alphaCri, betaCri = torch.tensor([0.5, 0.5]), torch.tensor([0.5, 0.5])
    alphaObe, betaObe = torch.tensor([0.5, 0.5]), torch.tensor([0.5, 0.5])

    p_economics  = torch.tensor(0.5)
    p_crime = torch.tensor([0.5, 0.5])
    p_obesity = torch.tensor([0.5, 0.5])

    economics = pyro.sample("economics", dist.Beta(alphaEco, betaEco))
    p_economics = economics
    e = torch.round(economics).long()

    crime = pyro.sample("crime", dist.Beta(alphaCri[e], betaCri[e]))
    c = torch.round(crime).long()
    p_crime[e] = crime

    obesity = pyro.sample("obesity", dist.Beta(alphaObe[e], betaObe[e]))
    o = torch.round(obesity).long()
    p_obesity[e] = obesity

    with pyro.plate('happy_plate_1', len(crime_obs), subsample_size=100) as ind:
        eOb = pyro.sample("economics_obs", dist.Bernoulli(p_economics), obs=economics_obs.index_select(0, ind))
        cOb = pyro.sample("crime_obs", dist.Bernoulli(p_crime[eOb.long()]), obs=crime_obs.index_select(0, ind))
        oOb = pyro.sample("obesity_obs", dist.Bernoulli(p_obesity[eOb.long()]), obs=obesity_obs.index_select(0, ind))


def guide(crime_obs, economics_obs, obesity_obs):
    # print("---guide---")
    alphaEco = pyro.param('alphaEco', torch.tensor(0.5), constraint=constraints.positive)
    betaEco = pyro.param('betaEco', torch.tensor(0.5), constraint=constraints.positive)

    alphaCri = pyro.param('alphaCri', torch.tensor([0.5, 0.5]), constraint=constraints.positive)
    betaCri = pyro.param('betaCri', torch.tensor([0.5, 0.5]), constraint=constraints.positive)

    alphaObe = pyro.param('alphaObe', torch.tensor([0.5, 0.5]), constraint=constraints.positive)
    betaObe = pyro.param('betaObe', torch.tensor([0.5, 0.5]), constraint=constraints.positive)

    p_economics = pyro.param('p_economics', torch.tensor(0.5), constraint=constraints.unit_interval)
    p_crime = pyro.param('p_crime', torch.tensor([0.5, 0.5]), constraint=constraints.simplex)
    p_obesity = pyro.param('p_obesity', torch.tensor([0.5, 0.5]), constraint=constraints.simplex)

    economics = pyro.sample("economics", dist.Beta(alphaEco, betaEco))
    p_economics = economics
    e = torch.round(economics).long()

    crime = pyro.sample("crime", dist.Beta(alphaCri[e], betaCri[e]))
    c = torch.round(crime).long()
    p_crime[e] = crime
    #
    obesity = pyro.sample("obesity", dist.Beta(alphaObe[e], betaObe[e]))
    o = torch.round(obesity).long()
    p_obesity[e] = obesity
    with pyro.plate('happy_plate_1', 100):
        eOb = pyro.sample("economics_obs", dist.Bernoulli(p_economics))
        cOb = pyro.sample("crime_obs", dist.Bernoulli(p_crime[eOb.long()]))
        oOb = pyro.sample("obesity_obs", dist.Bernoulli(p_obesity[eOb.long()]))

We can see that A, B, and C are dependent ( pyro.sample with prior called outside plate), and given observation made them all independent ( pyro.sample with observation called inside plate). The learning model is satisfied because it can explain by following the rule from the discussion.

However, it becomes a problem when I applied this understanding to a collider. I am suffering to design the plate when the structure of the causal model is a collider. I have a simple collider where A causes C and B causes C, RushHour (A) —> Traffic (C) <----Expo (B). A and B are independent in general, but become dependent if C is given. Moreover, conditioning on either A or B is only affected on C. I have codded following the guidline as shown belows:

plate1 = pyro.plate(‘plate_1’, len(RushHour_obs), subsample_size=100)
plate2 = pyro.plate(‘plate_2’, len(Expo_obs), subsample_size=100)
with plate1 as ind:
rOb = pyro.sample(“RushHour_obs”, dist.Bernoulli(p_rushHour), obs=RushHour_obs.index_select(0, ind))
with plate2 as ind:
eOb = pyro.sample(“Expo_obs”, dist.Bernoulli(p_Expo), obs=Expo_obs.index_select(0, ind))
with plate1:
tOb = pyro.sample(“Traffic_obs”, dist.Bernoulli(p_Traffic[RushHour_obs.index_select(0, ind).long(), Expo_obs.index_select(0, ind).long()]), obs=Traffic_obs.index_select(0, ind))

Unfortunately, the plate is still not working and the model parameters are still unreasonable. My question is what is my confusion point that I cannot employ plate with the collider’s definition and how to encode collider structure using Pyro correctly.