Tobias-Kohn / PyFOPPL

A FOPPL-Implementation in Python (Lisp-based Probabilistic Programming)
MIT License
4 stars 1 forks source link

If_1d model has something wrong. #5

Closed haohaiziround closed 6 years ago

haohaiziround commented 6 years ago

The model is as following:

(let [x (sample (normal 0 1))]
  (if (> x 0)
    (observe (normal 1 1) 1)
    (observe (normal -1 1) 1))
  x)
  1. The graph generate from

    the compiler (the one in pyfo) spits out is

    """
    Vertices V:
      c20002, cond_20003, f20004, x20001, y20005, y20006
    Arcs A:
      (x20001, f20004), (cond_20003, c20002), (f20004, cond_20003), (y20006, c20002), (x20001, c20002), (y20005, c20002), (f20004, c20002)
    Conditional densities C:
      x20001 -> dist.Normal(mu=0, sigma=1.0)
      f20004 -> -x20001
      cond_20003 -> (f20004 >= 0).data[0]
      y20005 -> dist.Normal(mu=1, sigma=1.0)
      y20006 -> dist.Normal(mu=-1, sigma=1.0)
      c20002 -> y20005 if not cond_20003 else y20006
    Observed values O:
      y20005 -> 1
      y20006 -> 1
    """

    where the >= in cond_20003 -> (f20004 >= 0).data[0] and the - in f20004 -> -x20001 is a little bit weird.

  2. In gen_pdf

    @classmethod
    def gen_pdf(self, state):
        dist_x20001 = dist.Normal(mu=0, sigma=1.0)
        x20001 = state['x20001']
        p10000 = dist_x20001.log_pdf(x20001)
        dist_y20005 = dist.Normal(mu=1, sigma=1.0)
        p10001 = dist_y20005.log_pdf(1) if not cond_20003 else 0
        dist_y20006 = dist.Normal(mu=-1, sigma=1.0)
        p10002 = dist_y20006.log_pdf(1) if not not cond_20003 else 0
        logp = p10000 + p10001 + p10002
        return logp

    variable cond_20003 is not output. And one not seems to be inserted redundantly.

The entire python src is as following:

#
# Generated: 2018-01-11 18:27:49.379808
#
import math
import numpy as np
import torch
from torch.autograd import Variable
import pyfo.distributions as dist
from pyfo.utils.interface import interface

class model(interface):
    """
    Vertices V:
      c20002, cond_20003, f20004, x20001, y20005, y20006
    Arcs A:
      (x20001, f20004), (cond_20003, c20002), (f20004, cond_20003), (y20006, c20002), (x20001, c20002), (y20005, c20002), (f20004, c20002)
    Conditional densities C:
      x20001 -> dist.Normal(mu=0, sigma=1.0)
      f20004 -> -x20001
      cond_20003 -> (f20004 >= 0).data[0]
      y20005 -> dist.Normal(mu=1, sigma=1.0)
      y20006 -> dist.Normal(mu=-1, sigma=1.0)
      c20002 -> y20005 if not cond_20003 else y20006
    Observed values O:
      y20005 -> 1
      y20006 -> 1
    """
    vertices = {'y20006', 'y20005', 'x20001', 'f20004', 'cond_20003', 'c20002'}
    arcs = {('x20001', 'f20004'), ('cond_20003', 'c20002'), ('f20004', 'cond_20003'), ('y20006', 'c20002'), ('x20001', 'c20002'), ('y20005', 'c20002'), ('f20004', 'c20002')}
    names = {'x20001': 'x'}
    cond_functions = {
      'cond_20003': 'f20004',
      'f20004': lambda state: -state['x20001']
    }

    @classmethod
    def get_vertices(self):
        return list(self.vertices)

    @classmethod
    def get_arcs(self):
        return list(self.arcs)

    @classmethod
    def gen_cond_vars(self):
        return ['cond_20003']

    @classmethod
    def gen_cont_vars(self):
        return ['x20001']

    @classmethod
    def gen_disc_vars(self):
        return []

    @classmethod
    def gen_if_vars(self):
        return ['f20004', 'x20001']

    @classmethod
    def gen_pdf(self, state):
        dist_x20001 = dist.Normal(mu=0, sigma=1.0)
        x20001 = state['x20001']
        p10000 = dist_x20001.log_pdf(x20001)
        dist_y20005 = dist.Normal(mu=1, sigma=1.0)
        p10001 = dist_y20005.log_pdf(1) if not cond_20003 else 0
        dist_y20006 = dist.Normal(mu=-1, sigma=1.0)
        p10002 = dist_y20006.log_pdf(1) if not not cond_20003 else 0
        logp = p10000 + p10001 + p10002
        return logp

    @classmethod
    def gen_prior_samples(self):
        dist_x20001 = dist.Normal(mu=0, sigma=1.0)
        x20001 = dist_x20001.sample()
        dist_y20005 = dist.Normal(mu=1, sigma=1.0)
        y20005 = 1
        dist_y20006 = dist.Normal(mu=-1, sigma=1.0)
        y20006 = 1
        f20004 = -x20001
        cond_20003 = (f20004 >= 0).data[0]
        c20002 = y20005 if not cond_20003 else y20006
        state = {}
        for _gv in self.gen_vars():
            state[_gv] = locals()[_gv]
        return state  # dictionary

    @classmethod
    def gen_vars(self):
        return ['c20002', 'cond_20003', 'f20004', 'x20001']
Tobias-Kohn commented 6 years ago

The transformation of (x > 0) to not (-x >= 0) is deliberate and not an accident. The idea behind it is that all conditions (except for tests of equality) are transformed into the format f(x, y, z) >= 0. You can then use the value of f20004 to estimate how far you are from the boundary. In other words: This design gives you some additional information. However, there is a flag Options.uniform_conditionals, which can be set to False if you want to avoid these transformations. I. e.:

import foppl.imports
from foppl import Options
Options.uniform_conditionals = False

import my_model
...
Tobias-Kohn commented 6 years ago

The missing cond_20003 in gen_pdf is a bug, though. I will look into it.

haohaiziround commented 6 years ago

For if 1d models, the following should be a correct interface source code for the inference.

#
# Generated: 2018-01-12 13:46:38.373557
#
import math
import numpy as np
import torch
from torch.autograd import Variable
import pyfo.distributions as dist
from pyfo.utils.interface import interface

class model(interface):
    """
    Vertices V:
      c20002, cond_20003, f20004, x20001, y20005, y20006
    Arcs A:
      (cond_20003, y20005), (f20004, c20002), (x20001, c20002), (y20005, c20002), (cond_20003, c20002), (y20006, c20002), (x20001, f20004), (cond_20003, y20006), (f20004, cond_20003)
    Conditional densities C:
      x20001 -> dist.Normal(mu=0, sigma=1.0)
      f20004 -> -x20001
      cond_20003 -> (f20004 >= 0).data[0]
      y20005 -> dist.Normal(mu=1, sigma=1.0)
      y20006 -> dist.Normal(mu=-1, sigma=1.0)
      c20002 -> y20005 if not cond_20003 else y20006
    Observed values O:
      y20005 -> 1
      y20006 -> 1
    """
    vertices = {'x20001', 'c20002', 'f20004', 'cond_20003', 'y20005', 'y20006'}
    arcs = {('cond_20003', 'y20005'), ('f20004', 'c20002'), ('x20001', 'c20002'), ('y20005', 'c20002'), ('cond_20003', 'c20002'), ('y20006', 'c20002'), ('x20001', 'f20004'), ('cond_20003', 'y20006'), ('f20004', 'cond_20003')}
    names = {'x20001': 'x'}
    cond_functions = {
      'cond_20003': 'f20004',
      'f20004': lambda state: -state['x20001']
    }

    @classmethod
    def get_vertices(self):
        return list(self.vertices)

    @classmethod
    def get_arcs(self):
        return list(self.arcs)

    @classmethod
    def gen_cond_vars(self):
        return ['cond_20003']

    @classmethod
    def gen_cont_vars(self):
        return []

    @classmethod
    def gen_disc_vars(self):
        return []

    @classmethod
    def gen_if_vars(self):
        # return ['x20001', 'f20004']  # ZY probabably just want the RVs, not the function
        return ['x20001']  #ZY, 2018-01-12

    @classmethod
    def gen_pdf(self, state):
        dist_x20001 = dist.Normal(mu=0, sigma=1.0)
        x20001 = state['x20001']
        p10000 = dist_x20001.log_pdf(x20001)
        f20004 = -x20001
        cond_20003 = state['cond_20003']
        dist_y20005 = dist.Normal(mu=1, sigma=1.0)
        p10001 = dist_y20005.log_pdf(1) if not cond_20003 else 0
        dist_y20006 = dist.Normal(mu=-1, sigma=1.0)
        p10002 = dist_y20006.log_pdf(1) if cond_20003 else 0
        logp = p10000 + p10001 + p10002
        return logp

    @classmethod
    def gen_prior_samples(self):
        dist_x20001 = dist.Normal(mu=0, sigma=1.0)
        x20001 = dist_x20001.sample()
        f20004 = -x20001
        cond_20003 = (f20004 >= 0).data[0]
        dist_y20005 = dist.Normal(mu=1, sigma=1.0)
        y20005 = 1
        dist_y20006 = dist.Normal(mu=-1, sigma=1.0)
        y20006 = 1
        c20002 = y20005 if not cond_20003 else y20006
        state = {} # need to have all the vertices (not the same as RVs), gen_pdf need
        for _gv in self.gen_all_keys():
            state[_gv] = locals()[_gv]
        return state  # dictionary

    @classmethod
    def gen_vars(self):
        return ['x20001'] 

    @classmethod
    def gen_all_keys(self):
        return ['c20002', 'cond_20003', 'f20004', 'x20001']

What I have chanaged is the gen_if_vars, gen_varsand gen_all_keys.

The key point here is, for gen_cont_vars, gen_disc_vars, gen_if_vars and gen_vars, we only want the random variables (RVs). The keys might contain other variables (eg. condition variables and function variables).

bayesianbrad commented 6 years ago

Issue resolved.