如何用theano.op在pymc3中编写自定义的确定性或随机性?

时间:2016-08-18 17:31:17

标签: python theano pymc pymc3

我正在做一些pymc3,我想创建自定义随机指标,但是似乎没有关于它是如何完成的大量文档。我知道如何使用as_op way,但显然这使得无法使用NUTS采样器,在这种情况下,我不会看到pymc3优于pymc的优势。

该教程提到可以通过继承theano.Op来完成。但任何人都可以告诉我这是如何工作的(我仍然开始使用theano)?我有两个我要定义的随机指标。

第一个应该更容易,它是一个只有常量父变量的N维向量F

with myModel:
    F = DensityDist('F', lambda value: pymc.skew_normal_like(value, F_mu_array, F_std_array, F_a_array), shape = N)

我想要一个偏斜的正态分布,这似乎并没有在pymc3中实现,我只是导入了pymc2版本。不幸的是,F_mu_array, F_std_array, F_a_array and F都是N维向量,而lambda似乎不适用于N维列表value

首先,有没有办法让lambda输入成为一个N维数组?如果没有,我想我需要直接定义随机F,这就是我认为我需要theano.Op使其工作的地方。

第二个例子是其他随机指标的更复杂的功能。这里我想如何定义它(目前不正确):

with myModel:
    ln2_var = Uniform('ln2_var', lower=-10, upper=4)
    sigma = Deterministic('sigma', exp(0.5*ln2_var))        
    A = Uniform('A', lower=-10, upper=10, shape=5)
    C = Uniform('C', lower=0.0, upper=2.0, shape=5)
    sw = Normal('sw', mu=5.5, sd=0.5, shape=5)

    # F from before
    F = DensityDist('F', lambda value: skew_normal_like(value, F_mu_array, F_std_array, F_a_array), shape = N)
    M = Normal('M', mu=M_obs_array, sd=M_stdev, shape=N)

    #   Radius forward-model (THIS IS THE STOCHASTIC IN QUESTION)
    R = Normal('R', mu = R_forward(F, M, A, C, sw, N), sd=sigma, shape=N)

函数R_forward(F,M,A,C,sw,N)被天真地定义为:

from theano.tensor import lt, le, eq, gt, ge

def R_forward(Flux, Mass, A, C, sw, num):
    for i in range(num):
        if lt(Mass[i], 0.2):
            if lt(Flux[i], sw[0]):
                muR = C[0]
            else:
                muR = A[0]*log10(Flux[i]) + C[0] - A[0]*log10(sw[0])
        elif (le(0.2, Mass[i]) or le(Mass[i], 0.5)):
            if lt(Flux[i], sw[1]):
                muR = C[1]
            else:
                muR = A[1]*log10(Flux[i]) + C[1] - A[1]*log10(sw[1])
        elif (le(0.5, Mass[i]) or le(Mass[i], 1.5)):
            if lt(Flux[i], sw[2]):
                muR = C[2]
            else:
                muR = A[2]*log10(Flux[i]) + C[2] - A[2]*log10(sw[2])
        elif (le(1.5, Mass[i]) or le(Mass[i], 3.5)):
            if lt(Flux[i], sw[3]):
                muR = C[3]
            else:
                muR = A[3]*log10(Flux[i]) + C[3] - A[3]*log10(sw[3])
        else:
            if lt(Flux[i], sw[4]):
                muR = C[4]
            else:
                muR = A[4]*log10(Flux[i]) + C[4] - A[4]*log10(sw[4])
    return muR

当然,这可能不会成功。我可以看到我将如何使用as_op,但我想保留NUTS采样。

1 个答案:

答案 0 :(得分:4)

我意识到现在有点晚了,但我认为我反正这个问题(相当模糊)。

如果你想定义一个随机函数(例如概率分布),那么你需要做几件事:

首先,定义Discrete(pymc3.distributions.Discrete)或Continuous的子类,它至少包含logp方法,它返回随机指标的对数似然。如果你把它定义为一个简单的符号方程(x + 1),我相信你不需要处理任何渐变(但不要引用我的这个; see the documentation about this)。我将在下面介绍更复杂的案例。在不幸的情况下,你需要做更复杂的事情,就像你的第二个例子(顺便说一下pymc3现在有一个偏差的正态分布),你需要定义它所需的操作(在logp方法中使用)一个Theano Op。如果你不需要衍生工具,那么as_op可以胜任这项工作,但正如你所说,渐变是pymc3的一种想法。

这是变得复杂的地方。如果你想使用NUTS(或者出于任何原因需要渐变),那么你需要将logp中使用的操作实现为theano.gof.Op的子类。你的新操作类(从现在开始称之为Op)至少需要两到三种方法。第一个定义了Op(check the Op documentation)的输入/输出。 perform()方法(或您可能选择的变体)是执行所需操作的方法(例如,您的R_forward函数)。如果您愿意,可以在纯Python中完成。第三种方法grad(),用于定义输入的perform()输出的梯度。 grad()的实际输出有点不同,但并不是什么大问题。

在毕业()中,使用Theano得到回报。如果您在Theano中定义了整个perform(),那么您可以轻松地使用自动区分(theano.tensor.grad或theano.tensor.jacobian)为您完成工作(请参阅下面的示例)。但是,这并不一定容易。

在你的第二个例子中,它意味着在Theano中实现你的R_forward函数,这可能很复杂。

这里我包含了一个我学习做这些事情时创建的Op的最小例子。

def my_th_fun():
    """ Some needed auxiliary functions.
    """
    X = th.tensor.vector('X')
    SCALE = th.tensor.scalar('SCALE')

    X.tag.test_value = np.array([1,2,3,4])
    SCALE.tag.test_value = 5.

    Scale, upd_sm_X = th.scan(lambda x, scale: scale*(scale+ x),
                               sequences=[X],
                               outputs_info=[SCALE])
    fun_Scale = th.function(inputs=[X, SCALE], outputs=Scale)
    D_out_d_scale = th.tensor.grad(Scale[-1], SCALE)
    fun_d_out_d_scale = th.function([X, SCALE], D_out_d_scale)
    return Scale, fun_Scale, D_out_d_scale, fun_d_out_d_scale

class myOp(th.gof.Op):
    """ Op subclass with a somewhat silly computation. It uses
    th.scan and th.tensor.grad is used to calculate the gradient
    automagically in the grad() method.
    """
    __props__ = ()
    itypes = [th.tensor.dscalar]
    otypes = [th.tensor.dvector]
    def __init__(self, *args, **kwargs):
        super(myOp, self).__init__(*args, **kwargs)
        self.base_dist = np.arange(1,5)
        (self.UPD_scale, self.fun_scale, 
         self.D_out_d_scale, self.fun_d_out_d_scale)= my_th_fun()

    def perform(self, node, inputs, outputs):
        scale = inputs[0]
        updated_scale = self.fun_scale(self.base_dist, scale)
        out1 = self.base_dist[0:2].sum()
        out2 = self.base_dist[2:4].sum()
        maxout = np.max([out1, out2])
        exp_out1 = np.exp(updated_scale[-1]*(out1-maxout))
        exp_out2 = np.exp(updated_scale[-1]*(out2-maxout))
        norm_const = exp_out1 + exp_out2
        outputs[0][0] = np.array([exp_out1/norm_const, exp_out2/norm_const])

    def grad(self, inputs, output_gradients): #working!
        """ Calculates the gradient of the output of the Op wrt
        to the input. As a simple example, the input is scalar.

        Notice how the output is actually the gradient multiplied
        by the output_gradients, which is an input provided by
        theano when calculating gradients.
        """
        scale = inputs[0]
        X = th.tensor.as_tensor(self.base_dist)
        # Do I need to recalculate all this or can I assume that perform() has
        # always been called before grad() and thus can take it from there?
        # In any case, this is a small enough example to recalculate quickly:
        all_scale, _ = th.scan(lambda x, scale_1: scale_1*(scale_1+ x),
                               sequences=[X],
                               outputs_info=[scale])
        updated_scale = all_scale[-1]

        out1 = self.base_dist[0:1].sum()
        out2 = self.base_dist[2:3].sum()
        maxout = np.max([out1, out2])

        exp_out1 = th.tensor.exp(updated_scale*(out1 - maxout))
        exp_out2 = th.tensor.exp(updated_scale*(out2 - maxout))
        norm_const = exp_out1 + exp_out2

        d_S_d_scale = th.theano.grad(all_scale[-1], scale)
        Jac1 = (-(out1-out2)*d_S_d_scale*
                th.tensor.exp(updated_scale*(out1+out2 - 2*maxout))/(norm_const**2))
        Jac2 = -Jac1
        return Jac1*output_gradients[0][0]+ Jac2*output_gradients[0][1],

然后可以在pymc3中随机的logp()方法中使用此Op:

import pymc3 as pm

class myDist(pm.distributions.Discrete):
    def __init__(self, invT, *args, **kwargs):
        super(myDist, self).__init__(*args, **kwargs)
        self.invT = invT
        self.myOp = myOp()
    def logp(self, value):
        return self.myOp(self.invT)[value]

我希望它可以帮助任何(绝望的)pymc3 / theano新手。