0

I have a model in which rates of a random (Poisson) process are distributed according to a function p_nu(gamma,delta,nu_max). With pymc3/theano I want to obtain the posterior distribution of gamma,delta,nu_max when observing N_AP events (N_AP being a vector, each entry being a measurement of one unit, whose rate is drawn from p_nu). I'm running into issues when evaluating the jacobian of the scan function, required to iterate over multiple measurements given in N_AP - details are described below.

This is my first post on stackoverflow, as well as part of my first dive into theano and I've been trying to break the model down to a minimum example, providing explanations where needed and including all code snippets necessary to run the example. Thus, I'm also happy to receive feedback on how the issue was posted and if things should be more concise / detailed, etc, or if my way of using theano could be improved.


Used imports for this example are

import numpy as np

from scipy.integrate import quad

import theano
import theano.tensor as T
from theano.graph.op import Op
from theano.graph.basic import Apply

For this, I have built a custom theano Op (or rather slightly adapted the one found in Custom Theano Op to do numerical integration), as it involves integration over (complete code below)

p_nu(nu,gamma,delta,nu_max)*poisson_spikes(nu,N,T_total)

with function definitions

def p_nu(NU,gamma,delta,nu_max):

    return gamma / ( nu_max * np.sqrt( -np.pi * np.log( NU / nu_max ) ) ) * \
        np.exp( - delta**2/2.) * ( NU / nu_max )**(gamma**2 - 1) * \
        np.cosh( gamma * delta * np.sqrt( -2 * np.log( NU / nu_max) ) )

def poisson_spikes(nu,N,T_total):
    return (nu*T_total)**N / T.gamma(N+1) * T.exp(-nu*T_total)

and variables

T_total = 600.      # measurement time

N_AP = T.lvector('N_AP')           # number of events (for vector values)
N = T.dscalar('N')                 # number of events (for single values)

nu = T.dscalar('nu')               # event rate; is integrated out in the process

gamma = T.dscalar('gamma')         # model parameter
delta = T.dscalar('delta')         # model parameter
nu_max = T.dscalar('nu_max')       # model parameter

With the integration Op defined as (using a couple of prints for debugging purposes)

class integrateOut(Op):
    """
    Integrate out a variable from an expression, computing
    the definite integral w.r.t. the variable specified
    !!! Only implemented in this for scalars !!!
    """
    def __init__(self,f,t,*args,**kwargs):
        super(integrateOut,self).__init__()
        self.f = f
        self.t = t

    def make_node(self,*inputs):
        self.fvars=list(inputs)
        #print(f'[make node]: inputs: {inputs}, fvars:{self.fvars}')
        # This will fail when taking the gradient... don't be concerned
        try:
            self.gradF = T.grad(self.f,self.fvars)
            print(f'[make node (fun)]: inputs: {inputs}, fvars:{self.fvars}')
        except:
            self.gradF = None
            print(f'[make node (grad)]: inputs: {inputs}, fvars:{self.fvars}')

        return Apply(self,self.fvars,[T.dscalar().type()])

    def perform(self,node, inputs, output_storage):       
        if self.gradF is None:
            print(f'[perform (grad)]: inputs: {inputs}, fvars:{self.fvars}')
        else:
            print(f'[perform (fun)]: inputs: {inputs}, fvars:{self.fvars}')

        # create a function to evaluate the integral
        f = theano.function([self.t]+self.fvars,self.f)

        # integrate the function from 0 to maximum firing rate nu_max
        nu_max = inputs[3]
        output_storage[0][0] = np.array(quad(f,0,nu_max,args=tuple(inputs))[0],dtype='float64')

    def grad(self,inputs,output_grads):
        nu_max = inputs[3]
        return [integrateOut(g,self.t,0,nu_max)(*inputs)*output_grads[0] \
            for g in self.gradF]

The code works smoothly for calculating the actual likelihood, but as I'd like to use NUTS sampling, I am also required to evaluate the grad / jacobian. And here comes the tricky part, or rather the part that I've been trying to wrap my head around for the past couple of days: Evaluating the jacobian for a scalar, defined as

p_N_AP = integrateOut(p_nu(nu,gamma,delta,nu_max)*poisson_spikes(nu,N,T_total),nu)(N,gamma,delta,nu_max)
pGrad = T.jacobian(p_N_AP,[gamma,delta,nu_max])
funcGrad = theano.function([N,gamma,delta,nu_max],pGrad)
grad_vals = funcGrad(0,1.2,4.8,30.)

with N being a single, scalar valued datapoint, works. Evaluating it for N_AP, a vector of datapoints, the functions are defined as

p_N_AP_arr, updates = theano.scan(
    fn = lambda N, gamma, delta, nu_max : integrateOut(p_nu(nu,gamma,delta,nu_max)*poisson_spikes(nu,N,T_total),nu)(N,gamma,delta,nu_max), 
    sequences=[N_AP],
    non_sequences=[gamma,delta,nu_max]
)
pGrad = T.jacobian(p_N_AP_arr,[gamma,delta,nu_max])
funcGrad = theano.function([N_AP,gamma,delta,nu_max],pGrad) ### somehow in here, copies are made, which dont fit ..
grad_vals = funcGrad([0,3,5],1.2,4.8,30.)

fails with the error:

[make node (fun)]: inputs: (N_AP[t], gamma, delta, nu_max), fvars:[N_AP[t], gamma, delta, nu_max]
[make node (grad)]: inputs: (N_AP[t], gamma_copy, delta_copy, nu_max_copy), fvars:[N_AP[t], gamma_copy, delta_copy, nu_max_copy]
... couple of same outputs here ...
[make node (grad)]: inputs: (N_AP[t], gamma_copy, delta_copy, nu_max_copy), fvars:[N_AP[t], gamma_copy, delta_copy, nu_max_copy]
[perform (fun)]: inputs: [array(0), array(1.2), array(4.8), array(30.)], fvars:[N_AP[t]012, gamma, delta, nu_max]
[perform (fun)]: inputs: [array(3), array(1.2), array(4.8), array(30.)], fvars:[N_AP[t]012, gamma, delta, nu_max]
[perform (fun)]: inputs: [array(5), array(1.2), array(4.8), array(30.)], fvars:[N_AP[t]012, gamma, delta, nu_max]
[perform (grad)]: inputs: [array(5), array(1.2), array(4.8), array(30.)], fvars:[N_AP[t]012, gamma_copy012, delta_copy012, nu_max_copy012]

---------------------------------------------------------------------------
UnusedInputError                          Traceback (most recent call last)
File scan_perform.pyx:399, in theano.scan.scan_perform.perform()

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/graph/op.py:476, in Op.make_py_thunk.<locals>.rval(p, i, o, n)
    475 def rval(p=p, i=node_input_storage, o=node_output_storage, n=node):
--> 476     r = p(n, [x[0] for x in i], o)
    477     for o in node.outputs:

Input In [29], in integrateOut.perform(self, node, inputs, output_storage)
     30     print(f'[perform (fun)]: inputs: {inputs}, fvars:{self.fvars}')
---> 32 f = theano.function([self.t]+self.fvars,self.f)
     34 ## integrate the function from 0 to maximum firing rate nu_max

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/compile/function/__init__.py:337, in function(inputs, outputs, mode, updates, givens, no_default_updates, accept_inplace, name, rebuild_strict, allow_input_downcast, profile, on_unused_input)
    334 else:
    335     # note: pfunc will also call orig_function -- orig_function is
    336     #      a choke point that all compilation must pass through
--> 337     fn = pfunc(
    338         params=inputs,
    339         outputs=outputs,
    340         mode=mode,
    341         updates=updates,
    342         givens=givens,
    343         no_default_updates=no_default_updates,
    344         accept_inplace=accept_inplace,
    345         name=name,
    346         rebuild_strict=rebuild_strict,
    347         allow_input_downcast=allow_input_downcast,
    348         on_unused_input=on_unused_input,
    349         profile=profile,
    350         output_keys=output_keys,
    351     )
    352 return fn

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/compile/function/pfunc.py:524, in pfunc(params, outputs, mode, updates, givens, no_default_updates, accept_inplace, name, rebuild_strict, allow_input_downcast, profile, on_unused_input, output_keys)
    522     inputs.append(si)
--> 524 return orig_function(
    525     inputs,
    526     cloned_outputs,
    527     mode,
    528     accept_inplace=accept_inplace,
    529     name=name,
    530     profile=profile,
    531     on_unused_input=on_unused_input,
    532     output_keys=output_keys,
    533 )

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/compile/function/types.py:1970, in orig_function(inputs, outputs, mode, accept_inplace, name, profile, on_unused_input, output_keys)
   1969 Maker = getattr(mode, "function_maker", FunctionMaker)
-> 1970 m = Maker(
   1971     inputs,
   1972     outputs,
   1973     mode,
   1974     accept_inplace=accept_inplace,
   1975     profile=profile,
   1976     on_unused_input=on_unused_input,
   1977     output_keys=output_keys,
   1978     name=name,
   1979 )
   1980 with config.change_flags(compute_test_value="off"):

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/compile/function/types.py:1573, in FunctionMaker.__init__(self, inputs, outputs, mode, accept_inplace, function_builder, profile, on_unused_input, fgraph, output_keys, name)
   1572 # Check if some input variables are unused
-> 1573 self._check_unused_inputs(inputs, outputs, on_unused_input)
   1575 # Make a list of (SymbolicInput|SymblicInputKits, indices,
   1576 # [SymbolicInput,...]), one tuple for each input. (See
   1577 # Function.indices for more details)

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/compile/function/types.py:1745, in FunctionMaker._check_unused_inputs(self, inputs, outputs, on_unused_input)
   1744 elif on_unused_input == "raise":
-> 1745     raise UnusedInputError(msg % (inputs.index(i), i.variable, err_msg))
   1746 else:

UnusedInputError: theano.function was asked to create a function computing outputs given certain inputs, but the provided input variable at index 2 is not part of the computational graph needed to compute the outputs: gamma_copy012.
To make this error into a warning, you can pass the parameter on_unused_input='warn' to theano.function. To disable it completely, use on_unused_input='ignore'.

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
File scan_perform.pyx:399, in theano.scan.scan_perform.perform()

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/scan/op.py:1086, in Scan.make_thunk.<locals>.rval(p, i, o, n, allow_gc)
   1083 def rval(
   1084     p=p, i=node_input_storage, o=node_output_storage, n=node, allow_gc=allow_gc
   1085 ):
-> 1086     r = p(n, [x[0] for x in i], o)
   1087     for o in node.outputs:

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/scan/op.py:1045, in Scan.make_thunk.<locals>.p(node, args, outs)
   1044 def p(node, args, outs):
-> 1045     return scan_perform_ext.perform(
   1046         self.n_shared_outs,
   1047         self.n_mit_mot_outs,
   1048         self.n_seqs,
   1049         self.n_mit_mot,
   1050         self.n_mit_sot,
   1051         self.n_sit_sot,
   1052         self.n_nit_sot,
   1053         args[0],
   1054         self.as_while,
   1055         cython_mintaps,
   1056         cython_tap_array,
   1057         cython_tap_array_len,
   1058         cython_vector_seqs,
   1059         cython_vector_outs,
   1060         cython_mit_mot_out_slices,
   1061         cython_mit_mot_out_nslices,
   1062         cython_mitmots_preallocated,
   1063         cython_inps_is_tensor,
   1064         cython_outs_is_tensor,
   1065         self.fn.fn,
   1066         self.fn,
   1067         cython_destroy_map,
   1068         args,
   1069         outs,
   1070         self,
   1071         node,
   1072     )

File scan_perform.pyx:407, in theano.scan.scan_perform.perform()

AttributeError: 'CVM' object has no attribute 'maker'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/compile/function/types.py:974, in Function.__call__(self, *args, **kwargs)
    972 try:
    973     outputs = (
--> 974         self.fn()
    975         if output_subset is None
    976         else self.fn(output_subset=output_subset)
    977     )
    978 except Exception:

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/scan/op.py:1086, in Scan.make_thunk.<locals>.rval(p, i, o, n, allow_gc)
   1083 def rval(
   1084     p=p, i=node_input_storage, o=node_output_storage, n=node, allow_gc=allow_gc
   1085 ):
-> 1086     r = p(n, [x[0] for x in i], o)
   1087     for o in node.outputs:

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/scan/op.py:1045, in Scan.make_thunk.<locals>.p(node, args, outs)
   1044 def p(node, args, outs):
-> 1045     return scan_perform_ext.perform(
   1046         self.n_shared_outs,
   1047         self.n_mit_mot_outs,
   1048         self.n_seqs,
   1049         self.n_mit_mot,
   1050         self.n_mit_sot,
   1051         self.n_sit_sot,
   1052         self.n_nit_sot,
   1053         args[0],
   1054         self.as_while,
   1055         cython_mintaps,
   1056         cython_tap_array,
   1057         cython_tap_array_len,
   1058         cython_vector_seqs,
   1059         cython_vector_outs,
   1060         cython_mit_mot_out_slices,
   1061         cython_mit_mot_out_nslices,
   1062         cython_mitmots_preallocated,
   1063         cython_inps_is_tensor,
   1064         cython_outs_is_tensor,
   1065         self.fn.fn,
   1066         self.fn,
   1067         cython_destroy_map,
   1068         args,
   1069         outs,
   1070         self,
   1071         node,
   1072     )

File scan_perform.pyx:407, in theano.scan.scan_perform.perform()

AttributeError: 'CVM' object has no attribute 'maker'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
Input In [32], in <cell line: 11>()
      9 pGrad = T.jacobian(p_N_AP_arr,[gamma,delta,nu_max])
     10 funcGrad = theano.function([N_AP,gamma,delta,nu_max],pGrad) ### somehow in here, copies are made, which dont fit ..
---> 11 grad_vals = funcGrad([0,3,5],1.2,4.8,30.)

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/compile/function/types.py:987, in Function.__call__(self, *args, **kwargs)
    985     if hasattr(self.fn, "thunks"):
    986         thunk = self.fn.thunks[self.fn.position_of_error]
--> 987     raise_with_op(
    988         self.maker.fgraph,
    989         node=self.fn.nodes[self.fn.position_of_error],
    990         thunk=thunk,
    991         storage_map=getattr(self.fn, "storage_map", None),
    992     )
    993 else:
    994     # old-style linkers raise their own exceptions
    995     raise

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/link/utils.py:508, in raise_with_op(fgraph, node, thunk, exc_info, storage_map)
    505     warnings.warn(f"{exc_type} error does not allow us to add extra error message")
    506     # Some exception need extra parameter in inputs. So forget the
    507     # extra long error message in that case.
--> 508 raise exc_value.with_traceback(exc_trace)

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/compile/function/types.py:974, in Function.__call__(self, *args, **kwargs)
    971 t0_fn = time.time()
    972 try:
    973     outputs = (
--> 974         self.fn()
    975         if output_subset is None
    976         else self.fn(output_subset=output_subset)
    977     )
    978 except Exception:
    979     restore_defaults()

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/scan/op.py:1086, in Scan.make_thunk.<locals>.rval(p, i, o, n, allow_gc)
   1083 def rval(
   1084     p=p, i=node_input_storage, o=node_output_storage, n=node, allow_gc=allow_gc
   1085 ):
-> 1086     r = p(n, [x[0] for x in i], o)
   1087     for o in node.outputs:
   1088         compute_map[o][0] = True

File ~/Data/Programmes/anaconda3/envs/inference/lib/python3.9/site-packages/theano/scan/op.py:1045, in Scan.make_thunk.<locals>.p(node, args, outs)
   1044 def p(node, args, outs):
-> 1045     return scan_perform_ext.perform(
   1046         self.n_shared_outs,
   1047         self.n_mit_mot_outs,
   1048         self.n_seqs,
   1049         self.n_mit_mot,
   1050         self.n_mit_sot,
   1051         self.n_sit_sot,
   1052         self.n_nit_sot,
   1053         args[0],
   1054         self.as_while,
   1055         cython_mintaps,
   1056         cython_tap_array,
   1057         cython_tap_array_len,
   1058         cython_vector_seqs,
   1059         cython_vector_outs,
   1060         cython_mit_mot_out_slices,
   1061         cython_mit_mot_out_nslices,
   1062         cython_mitmots_preallocated,
   1063         cython_inps_is_tensor,
   1064         cython_outs_is_tensor,
   1065         self.fn.fn,
   1066         self.fn,
   1067         cython_destroy_map,
   1068         args,
   1069         outs,
   1070         self,
   1071         node,
   1072     )

File scan_perform.pyx:407, in theano.scan.scan_perform.perform()

AttributeError: 'CVM' object has no attribute 'maker'
Apply node that caused the error: for{cpu,scan_fn}(Shape_i{0}.0, Subtensor{int64:int64:int8}.0, Shape_i{0}.0, Shape_i{0}.0, Shape_i{0}.0, for{cpu,scan_fn}.0, gamma, delta, nu_max, Shape_i{0}.0, Alloc.0, Subtensor{int64:int64:int64}.0, ScalarFromTensor.0, Alloc.0, Subtensor{int64:int64:int64}.0)
Toposort index: 38
Inputs types: [TensorType(int64, scalar), TensorType(int64, vector), TensorType(int64, scalar), TensorType(int64, scalar), TensorType(int64, scalar), TensorType(float64, vector), TensorType(float64, scalar), TensorType(float64, scalar), TensorType(float64, scalar), TensorType(int64, scalar), TensorType(float64, vector), TensorType(float64, vector), Scalar(int64), TensorType(float64, vector), TensorType(int64, vector)]
Inputs shapes: [(), (3,), (), (), (), (3,), (), (), (), (), (4,), (3,), (), (3,), (3,)]
Inputs strides: [(), (8,), (), (), (), (8,), (), (), (), (), (8,), (-8,), (), (8,), (-8,)]
Inputs values: [array(3), array([0, 1, 2]), array(3), array(3), array(3), array([1.06234782e-09, 3.62980190e-02, 2.32467904e-02]), array(1.2), array(4.8), array(30.), array(3), array([0., 0., 0., 0.]), array([2.32467904e-02, 3.62980190e-02, 1.06234782e-09]), 3, array([0., 0., 0.]), array([5, 3, 0])]
Inputs type_num: [7, 7, 7, 7, 7, 12, 12, 12, 12, 7, 12, 12, 7, 12, 7]
Outputs clients: [['output'], ['output'], ['output']]

HINT: Re-running with most Theano optimization disabled could give you a back-trace of when this node was created. This can be done with by setting the Theano flag 'optimizer=fast_compile'. If that does not work, Theano optimizations can be disabled with 'optimizer=None'.
HINT: Use the Theano flag 'exception_verbosity=high' for a debugprint and storage map footprint of this apply node.

To my understanding, theanos scan creates named copies of the symbolic variables when calling make_node, and when evaluating the function, passes down the given inputs, which do not agree with the automatically named variables anymore, thus throwing an UnusedInputError. I'm somewhat at a loss as to how to fix that and would love to get some insight into how to fix this (and would take some info on the way into how theano decides to create copies of symbolic variables and how their names are given).

I did search stackoverflow / the internet for similar / same issues, but couldn't find anything that helped me get past the described issue

wollex
  • 3
  • 3

0 Answers0