Custom Theano Op para hacer integración numérica

Estoy intentando escribir un Theano Op personalizado que integra numéricamente una función entre dos valores. El Op es una probabilidad personalizada para PyMC3 que implica la evaluación numérica de algunas integrales. No puedo simplemente usar el decorador @as_op ya que necesito usar HMC para realizar el paso de MCMC. Cualquier ayuda sería muy apreciada, ya que esta pregunta parece haber surgido varias veces pero nunca se ha resuelto (p. Ej., Https://stackoverflow.com/questions/36853015/using-theano-with-numerical-integration , Theano: implementando un función integral ).

Claramente, una solución sería escribir un integrador numérico dentro de Theano, pero esto parece una pérdida de esfuerzo cuando ya hay disponibles muy buenos integradores, por ejemplo, a través de scipy.integrate.

Para mantener esto como un ejemplo mínimo, solo intentemos integrar una función entre 0 y 1 dentro de una Op. Lo siguiente integra una función de Theano fuera de una Op, y produce resultados correctos en lo que respecta a mis pruebas.

import theano import theano.tensor as tt from scipy.integrate import quad x = tt.dscalar('x') y = x**4 # integrand f = theano.function([x], y) print f(0) print f(1) ans = integrate.quad(f, 0, 1)[0] print ans 

Sin embargo, intentar hacer la integración dentro de una Op parece ser mucho más difícil. Mi mejor esfuerzo actual es:

 import numpy as np import theano import theano.tensor as tt from scipy import integrate class IntOp(theano.Op): __props__ = () def make_node(self, x): x = tt.as_tensor_variable(x) return theano.Apply(self, [x], [x.type()]) def perform(self, node, inputs, output_storage): x = inputs[0] z = output_storage[0] f_to_int = theano.function([x], x) z[0] = tt.as_tensor_variable(integrate.quad(f_to_int, 0, 1)[0]) def infer_shape(self, node, i0_shapes): return i0_shapes def grad(self, inputs, output_grads): ans = integrate.quad(output_grads[0], 0, 1)[0] return [ans] intOp = IntOp() x = tt.dmatrix('x') y = intOp(x) f = theano.function([x], y) inp = np.asarray([[2, 4], [6, 8]], dtype=theano.config.floatX) out = f(inp) print inp print out 

Lo que da el siguiente error:

 Traceback (most recent call last): File "stackoverflow.py", line 35, in  out = f(inp) File "/usr/local/lib/python2.7/dist-packages/theano/compile/function_module.py", line 871, in __call__ storage_map=getattr(self.fn, 'storage_map', None)) File "/usr/local/lib/python2.7/dist-packages/theano/gof/link.py", line 314, in raise_with_op reraise(exc_type, exc_value, exc_trace) File "/usr/local/lib/python2.7/dist-packages/theano/compile/function_module.py", line 859, in __call__ outputs = self.fn() File "/usr/local/lib/python2.7/dist-packages/theano/gof/op.py", line 912, in rval r = p(n, [x[0] for x in i], o) File "stackoverflow.py", line 17, in perform f_to_int = theano.function([x], x) File "/usr/local/lib/python2.7/dist-packages/theano/compile/function.py", line 320, in function output_keys=output_keys) File "/usr/local/lib/python2.7/dist-packages/theano/compile/pfunc.py", line 390, in pfunc for p in params] File "/usr/local/lib/python2.7/dist-packages/theano/compile/pfunc.py", line 489, in _pfunc_param_to_in raise TypeError('Unknown parameter type: %s' % type(param)) TypeError: Unknown parameter type:  Apply node that caused the error: IntOp(x) Toposort index: 0 Inputs types: [TensorType(float64, matrix)] Inputs shapes: [(2, 2)] Inputs strides: [(16, 8)] Inputs values: [array([[ 2., 4.], [ 6., 8.]])] Outputs clients: [['output']] Backtrace when the node is created(use Theano flag traceback.limit=N to make it longer): File "stackoverflow.py", line 30, in  y = intOp(x) File "/usr/local/lib/python2.7/dist-packages/theano/gof/op.py", line 611, in __call__ node = self.make_node(*inputs, **kwargs) File "stackoverflow.py", line 11, in make_node return theano.Apply(self, [x], [x.type()]) HINT: Use the Theano flag 'exception_verbosity=high' for a debugprint and storage map footprint of this apply node. 

Me sorprende esto, especialmente el TypeError, ya que pensé que había convertido la variable output_storage en un tensor pero parece creer aquí que todavía es un ndarray.

Encontré tu pregunta porque estoy tratando de construir una variable aleatoria en PyMC3 que represente un proceso general (Hawkes, Cox, Poisson, etc.) y la función de probabilidad tiene una integral. Tengo muchas ganas de poder usar los muestreadores Hamiltonian Monte Carlo o NUTS, por lo que necesitaba esa integral con respecto al tiempo para ser diferenciable.

A partir de su bash, hice una operación integrada de theano que parece funcionar correctamente con el comportamiento que necesito. Lo he probado en algunas entradas diferentes (todavía no en mi modelo de estadísticas, ¡pero parece prometedor!). Soy un theano total n00b, así que perdón por cualquier estupidez. Apreciaría enormemente los comentarios si alguien tiene alguno. No estoy seguro de que sea exactamente lo que está buscando, pero aquí está mi solución (ejemplo en la parte inferior y en las cadenas de documentos). * EDITAR: simplificó algunos remanentes de atornillar con formas de hacer esto.

 import theano import theano.tensor as T from scipy.integrate import quad class integrateOut(theano.Op): """ Integrate out a variable from an expression, computing the definite integral wrt the variable specified !!! Only implemented in this for scalars !!! Parameters ---------- f : scalar input 'function' to integrate t : scalar the variable to integrate out t0: float lower integration limit tf: float upper integration limit Returns ------- scalar a new scalar with the 't' integrated out Notes ----- usage of this looks like: x = T.dscalar('x') y = T.dscalar('y') t = T.dscalar('t') z = (x**2 + y**2)*t # integrate z wrt t as a function of (x,y) intZ = integrateOut(z,t,0.0,5.0)(x,y) gradIntZ = T.grad(intZ,[x,y]) funcIntZ = theano.function([x,y],intZ) funcGradIntZ = theano.function([x,y],gradIntZ) """ def __init__(self,f,t,t0,tf,*args,**kwargs): super(integrateOut,self).__init__() self.f = f self.t = t self.t0 = t0 self.tf = tf def make_node(self,*inputs): self.fvars=list(inputs) # This will fail when taking the gradient... don't be concerned try: self.gradF = T.grad(self.f,self.fvars) except: self.gradF = None return theano.Apply(self,self.fvars,[T.dscalar().type()]) def perform(self,node, inputs, output_storage): # Everything else is an argument to the quad function args = tuple(inputs) # create a function to evaluate the integral f = theano.function([self.t]+self.fvars,self.f) # actually compute the integral output_storage[0][0] = quad(f,self.t0,self.tf,args=args)[0] def grad(self,inputs,grads): return [integrateOut(g,self.t,self.t0,self.tf)(*inputs)*grads[0] \ for g in self.gradF] x = T.dscalar('x') y = T.dscalar('y') t = T.dscalar('t') z = (x**2+y**2)*t intZ = integrateOut(z,t,0,1)(x,y) gradIntZ = T.grad(intZ,[x,y]) funcIntZ = theano.function([x,y],intZ) funcGradIntZ = theano.function([x,y],gradIntZ) print funcIntZ(2,2) print funcGradIntZ(2,2) 

SymPy está demostrando ser más difícil de lo previsto, pero mientras tanto, en caso de que alguien lo encuentre útil, también señalaré cómo modificar esta Op. Para permitir cambiar el punto de tiempo final sin crear una nueva Op. Esto puede ser útil si tiene un proceso puntual, o si tiene incertidumbre en sus mediciones de tiempo.

 class integrateOut2(theano.Op): def __init__(self, f, int_var, *args,**kwargs): super(integrateOut2,self).__init__() self.f = f self.int_var = int_var def make_node(self, *inputs): tmax = inputs[0] self.fvars=list(inputs[1:]) return theano.Apply(self, [tmax]+self.fvars, [T.dscalar().type()]) def perform(self, node, inputs, output_storage): # Everything else is an argument to the quad function tmax = inputs[0] args = tuple(inputs[1:]) # create a function to evaluate the integral f = theano.function([self.int_var]+self.fvars, self.f) # actually compute the integral output_storage[0][0] = quad(f, 0., tmax, args=args)[0] def grad(self, inputs, grads): tmax = inputs[0] param_grads = T.grad(self.f, self.fvars) ## Recall fundamental theorem of calculus ## d/dt \int^{t}_{0}f(x)dx = f(t) ## So sub in t_max to the graph FTC_grad = theano.clone(self.f, {self.int_var: tmax}) grad_list = [FTC_grad*grads[0]] + \ [integrateOut2(grad_fn, self.int_var)(*inputs)*grads[0] \ for grad_fn in param_grads] return grad_list 

Siempre utilizo el siguiente código en el que genero B = 10000 muestras de n = 30 observaciones de una distribución normal con µ = 1 y σ 2 = 2.25. Para cada muestra, los parámetros µ y σ se estiman y almacenan en una matriz. Espero que esto pueda ayudarte.

 loglik <- function(p,z){ sum(dnorm(z,mean=p[1],sd=p[2],log=TRUE)) } set.seed(45) n <- 30 x <- rnorm(n,mean=1,sd=1.5) optim(c(mu=0,sd=1),loglik,control=list(fnscale=-1),z=x) B <- 10000 bootstrap.results <- matrix(NA,nrow=B,ncol=3) colnames(bootstrap.results) <- c("mu","sigma","convergence") for (b in 1:B){ sample.b <- rnorm(n,mean=1,sd=1.5) mb <- optim(c(mu=0,sd=1),loglik,control=list(fnscale=-1),z=sample.b) bootstrap.results[b,] <- c(mb$par,mb$convergence) } 

También se puede obtener la estimación de ML de λ y usar la rutina de carga para estimar el sesgo y el error estándar de la estimación. Primero calcule el MLE de λ. Luego, estimamos el sesgo y el error estándar de λˆ por un bootstrap no paramétrico.

 B <- 9999 lambda.B <- rep(NA,B) n <- length(w.time) for (b in 1:B){ b.sample <- sample(1:n,n,replace=TRUE) lambda.B[b] <- 1/mean(w.time[b.sample]) } bias <- mean(lambda.Bm$estimate) sd(lambda.B) 

En la segunda parte, calculamos un intervalo de confianza del 95% para el tiempo medio entre fallas.

 n <- length(w.time) m <- mean(w.time) se <- sd(w.time)/sqrt(n) interval.1 <- m + se * qnorm(c(0.025,0.975)) interval.1 

Pero también podemos usar el supuesto de que los datos provienen de una distribución exponencial. En ese caso, tenemos varX¯ = 1 / (nλ ^ 2) = θ ^ {2} / n que se puede estimar con X¯ ^ {2} / n.

 sd.m <- sqrt(m^2/n) interval.2 <- m + sd.m * qnorm(c(0.025,0.975)) interval.2 

También podemos estimar el error estándar de ˆθ mediante un procedimiento de refuerzo. Usamos el bootstrap no paramétrico, es decir, tomamos muestras de la muestra original con reemplazo.

 B <- 9999 m.star <- rep(NA,B) for (b in 1:B){ m.star[b] <- mean(sample(w.time,replace=TRUE)) } sd.m.star <- sd(m.star) interval.3 <- m + sd.m.star * qnorm(c(0.025,0.975)) interval.3 An interval not based on the assumption of normality of ˆθ is obtained by the percentile method: interval.4 <- quantile(m.star, probs=c(0.025,0.975)) interval.4