Question

I have three question regarding decorators which I am not able to find answer to :

Q1)What do the arguments to decorators in PyMC (@Deterministic, @Stochastic) denote ?

Q2)

@pymc.stochastic(dtype=int)
def switchpoint(value=10, t_l=0, t_h=110):
    def logp(value, t_l, t_h):
        if value > t_h or value < t_l:
            return -np.inf
        else:
            return -np.log(t_h - t_l + 1)
    def random(t_l, t_h):
        from numpy.random import random
        return np.round( (t_l - t_h) * random() ) + t_l

1)print switchpoint.logp #prints log-probability as expected

2)print switchpoint.random #does not generate the random number

3)print switchpoint.random() # generates a random number

4)print switchpoint.logp() #error

If 2 did not work and 3 worked then 1 should not have worked and instaed 4 should have worked (which is opposite of what I observed). Can someone explain what is going on ?

Q3)

@pymc.stochastic(dtype=int)
def switchpoint(value=1900, t_l=1851, t_h=1962):
    if value > t_h or value < t_l:
        # Invalid values
        return -np.inf
    else:
        # Uniform log-likelihood
        return -np.log(t_h - t_l + 1)

Here it is not specified that it is logp still if I type switchpoint.logp, this piece of code is executed ?

Was it helpful?

Solution

Q1) The meaning of all the arguments to stochastic is documented here. The arguments to deterministic are the same, plus the additional ones documented here.

Q2) The difference in behavior is that there is some magic inside PyMC that actually executes the switchpoint.logp function and turns it into a Python property, while switchpoint.random doesn't get this treatment, and is kept as a function.

If you're curious about what's actually going on, here's some of the relevant the source:

def get_logp(self):
    if self.verbose > 1:
        print '\t' + self.__name__ + ': log-probability accessed.'
    logp = self._logp.get()
    if self.verbose > 1:
        print '\t' + self.__name__ + ': Returning log-probability ', logp

    try:
        logp = float(logp)
    except:
        raise TypeError, self.__name__ + ': computed log-probability ' + str(logp) + ' cannot be cast to float'

    if logp != logp:
        raise ValueError, self.__name__ + ': computed log-probability is NaN'

    # Check if the value is smaller than a double precision infinity:
    if logp <= d_neg_inf:
        if self.verbose > 0:
            raise ZeroProbability, self.errmsg + ": %s" %self._parents.value
        else:
            raise ZeroProbability, self.errmsg

    return logp

def set_logp(self,value):
    raise AttributeError, 'Potential '+self.__name__+'\'s log-probability cannot be set.'

logp = property(fget = get_logp, fset=set_logp, doc="Self's log-probability value conditional on parents.")

There's some other stuff going on there, like during the logp function into something called a LazyFunction, but that's the basic idea.

Q3) The stochastic decorator has some (more) magic in it that uses code introspection to determine if random and logp sub functions are defined inside switchpoint. If they are, it uses the logp sub-function to compute logp, if not, it just uses switchpoint itself. That source code for that is here:

# This gets used by stochastic to check for long-format logp and random:
if probe:
    # Define global tracing function (I assume this is for debugging??)
    # No, it's to get out the logp and random functions, if they're in there.
    def probeFunc(frame, event, arg):
        if event == 'return':
            locals = frame.f_locals
            kwds.update(dict((k,locals.get(k)) for k in keys))
            sys.settrace(None)
        return probeFunc

    sys.settrace(probeFunc)

    # Get the functions logp and random (complete interface).
    # Disable special methods to prevent the formation of a hurricane of Deterministics
    cur_status = check_special_methods()
    disable_special_methods()
    try:
        __func__()
    except:
        if 'logp' in keys:
            kwds['logp']=__func__
        else:
            kwds['eval'] =__func__
    # Reenable special methods.
    if cur_status:
        enable_special_methods()

for key in keys:
    if not kwds.has_key(key):
        kwds[key] = None

for key in ['logp', 'eval']:
    if key in keys:
        if kwds[key] is None:
            kwds[key] = __func__

Again, there's some more stuff going on, and it's fairly complicated, but that's the basic idea.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top