我正在做一些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采样。
答案 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新手。